JaQr#(jn>
zW{ob7s7{To|9;!$(KB!OLZEGMuqXM{(u>l6U;19sJavNf7I|H3Z02~s^U(D_B#d7}
zd`b=_A=_WRg@wb1`85XreKhni2LEsSuc5f&tN&j5``qH8N&IC<&^-1(CP{y8@VknB
z7;pTtT%?}^`9Fspf9~+R5PeYFzf1!<<^NNA|GD~i|7Xq$oA~
literal 0
HcmV?d00001
diff --git a/resource/excel/ExcelImport.xlsx b/resource/excel/ExcelImport.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..6a6156dd8d3446defea0922dd7e30f107f5d5c46
GIT binary patch
literal 9005
zcmeHNg;yJU(+&>Bg1buzuEpJN+ZS%v=r|oea5O)GL|0!_iZ$FOVL}IG@S;Gn2*Fri>8hJrl7Y|D*6-?@G6l~h
zWtkdT*YXiBCbq?U+6)j?K^~+QLf%icILKAz9yc^-^7831eoli>>2kh2ivtmsYSdO-
z(kO5Bju2*;l-iMr$kIsJ7b4_3^U`4PMPL9+c?iGL+6JR?G0fKW*6`kIpYrxM+g2p4
zd$(L%-Z)Xy|Z4tZv4mu`;h(hn44;M{giUJN&SZMNu5RhSodkXBao$q}Ki?uIwL<
zNJ0iTXw!KT4t*NK&>;~(`&qzT-7elSC8P*(1LN>mPmtt*0%-grOe=M{8IIxHdk8;^
z2@lgJU~4A`7w6CS|Aps&aZLX0)(ex=9<_60MeHm6h=iSu%_ab4RK28?>gjX>gOz7+
zE90^mDQ27RQv!9!pCTy+H3t6Zot_no+k(-ZF7X$};1h~6RCyIeBwxCFps_N8Un{s5
zE_UE~j-8C1zEM>1VexEy&RS5Do2l9}&!F)2P_7g>$oZHYgWw}&1hM#=5W_AtgE@=y
zVuUdn?cJh?l4`+>trvr7LF2D;HnAn22|wH!c|!z)SlCV$`@@{*PA%$`SH(r2Nk=SGERaFEBFul-uZM0Vzd{)4thdkXmaP^
z73<$Y!hYx>A_fnTQ)B=D7w#EvCoV5nu%m^mtK-i)_CZI}b&4Ap_`c#6cp=WHPAess
z+lx*<@_^36k%n!V)FN>r(U~@PD`;LANVW16u7u0Av!pq|ok6|71U)k|kPZ
zfymC5r($82|1G_kkm1b$8K3ncN)wF_(;^}jo`nZ)gmICOwYG9K6TupKmcBIB(75nt
zXpG*7--1fkgX!LnWrYBX9!wV;Y!a`c37+C8nEkBulGNf+7`&1p7q>&lPyacoottM!V}
zaKE2JYI!)CWamP^4ks`F$%l$JtO?pc=aE>7*G##%+O@XHC>kHD#;p1~B>f{xa#?bcoYVr7EHep4D>2UPE|s^wg0rMu3+bhkJ2yYU~MZcrFjf>t>0h(r};|n~Vr(mr!
z(f_8(h2$FHv;U>r^GpC|35npOl`S_KAK90{Xf%I=1sx`BU`o4L@wudrK7x{vuT7su$Ee{hhtp&xtgWFCu0IYuKa*6Uo(Vij5eC4Ml-Wh&3X+QIOO47Q&wv#EIOqK?
z_h(}rr%p)LUvI983DD`THRPp%Z{MEJ_&wim^z=W`A-3zRSkz(^+PEO)k+$ybq;HiB
znt5kpVa1LI0(~&UnB#u>x$C`Tr(+(C+CYkY3+=;Dh@bzB-sgFHdet&4cQP9`(~5)b;Fyz
z5c)Xw1YeqDrb1+lNGkc@`op59YI7|)0uO?eSEo-KV&ds5DNOLD_&A>sBdDw`(;qwH
ziKx#4&I;%oLew00tnsFf6sYhV1Vu2s=hF|P(KXb%V*sxjiuiK#w1==B$s)Z9yyrU8
zF$HA{V{e+U;R0
zPrSo6jLC_`s;^h+tPe8r@@z)4WP9bfMv|ZovCov>-vbMM&H
zPN?~C@SKJlCrkBCNUa9NFL9djMAmR$?hDBA2hHySBzv1XXa(McdXoKY
zME?j7sGYU5HP;{Sf5gU)L7xkSAmJ>@h9s8Fyi(aoNh~a+NO7i19nnZRkx4-I3-=Nh
z8l$yhJ-va4c7~K8R6C=dGdc@_JVRRWniL6R>X^Z|C(%YJDw?1hG(t~oW+$|6NO4V&WsaXRWxu{W$et2yd>d+W_L3mrGMF~pG+3N+{=$O%Y!8cpLhL#4C_{K
zSiUvG-O(_y!5p$O~v#dIzyri;7?{Ue32!@Pjb8+(1
z4xR*TU8cpOPj$gc^csSr^zF6$#qivcdg5=%yic%UN4A3l)Dq%;=hKtfinh2xZ_bKL
z3_Pz`;vUu~O9oz_ojvgmy1s1h>z-dR;aU!l3OYPW<_f%B*4;Djux3dQx?1V{dVjF_
zYHRR4wP?dWn4fo1Hpy&pw&T+ro~l_e&W(4+e7^-LgM~o`slTZ4nY9yw>j2PW@w9}h
zt)5b+3#CD#(bZi%{z3;|aZqrn1Cg3n;f&M^bs=J_@7aM{s>0sD#b^f=9dv1{K1PeJ
z_1YfwyNWSqJx`V0Tn@`CmNwAqbl+}fQc(r3=RmgNNXCVSC8fuEc&@m3wn`CJRzzC%
z6_4;?ef{!4E$7a)gW~G$T*pyye+ve4L)tA`$&S%`%O3o%SlnwVv%*plB&B#g6UEXG
zTRKj{!yYQ8@P4MTpfi3@EfhPhbcwA&>NSPC$zGahoD|i!j$qL*cRKNbBAXEcX0~^-
zkR&2t(Oj9e89|Y>I>pi)Oh9{CtafG+S_FO1h3q9|tfpcek}Iga2V3>%-?4a84tSz<+SQ!B;}X||tmY*eNFgx4+9CYuG@0L@YELqEMx0;G)iQ;>fNdOY
z@rHKh=GL@k{L5-z^0ro{bVFZb-CXT`N5MTyna$F-n*OxMb0#fygg6=Fh`B7z}1REDR%d{Y!In?IG1T?{pgPacYGkvC_
zCy0FthuEn1nqD`Coj4QVAg^b(ywd7fuq?QBAr7F-19oQiY6mivz=~A{KHB)2aOW8y
zl*H)kNWIytTB`H(c7XciH7ewA;49QlGRE@8YCc;Zvp0QfY*RdKt-|5UN*FV#F=FT3
z7-P(aXE1`AnyMnLygo=Jz^u}Jngvv!(^
z12oc$kRrFHu{9DjHq{5=P@3+=Ji#pOz_5%C@}ksxW=hD$x1-K_3sA`jRD~$rlsj5-|t4Nh~D;@O|6Y#
zewLnEbxKpc{G6FCXBKgRaotI&^h?8WQ#&fce8?*y+p&N+l-3MAPa93`*vHoT_NNVF
zIi67zhz$5&1l-xaq1!TY)_s3E3m(fyu_*mK8vZ%xoI5DZEc=R#|J!fc~^eyOrG(4IGfQ($QylvSXMPTg&IQsHY3Bu<@1a3k`W_y
zu5T|Lkbzmz2Zq~Vn`zy;9F)9xD`k|oD_TTy?RrH!`8qGHUb(Z@>evH{u;Qu4-+|RkF2Zqa!i^XBgH?C~NM?A+JZSpr1}hneCq=!y@TwLw3Erz@NuLl$we*1o*x$3iW>+B2rmZ*g$_CoC|8*
zQGt2U^C)Jxd9mRXoNc7KmDEhcx9foO5>PiYz;W^kp;dzF=(ehz{grPCV_l8PZLz8_
zCQM=+oRr7eg14O^XiWPxygue)kYL<>o
z`B$s=0cZOuM0YKE516xyEYHII$=vKRYR!t
zs5UKk%%gaQezpzCt`mL0V^=iD=52W(Pti&LV-b2|Q75D(=D5^<|FvXRhyeB)CfmWI
zwHSF`53%)?WwV9r?i$B6KJ=Z-^5}LA9g{MuiDk@^_nwbeAcm93@c$dn^jbOtIO5lstqL2~q^$hVGaH&s6s@qybf+WLZ>W^Jg3879qKme)+zc_5)&?#1
z4GUg0#baAU_RVc$_X=wXGv+>&=W6uG`7d|9=cu)f5t$M>0lRZ1D)$m>!#4`UiLnD1
zWNF&>G6%VjC(8^$Cs&1aL85CaKOXr~zF@xaN#*EGo?kBuY;Z2y0-HWj%kSy#8fx!6
z8~4{Vef)Kt*+{~9XFPitMy2Ey5$@()$XJu8_r>Njs3d1-Y9atqeQx|NmFb)BOHHo(
z0oW69MdmLTgvxz__w4l76(^^dZQ3zzf9qmVXI|A-_!$|viy8iO@y`zbucqzKmj74l
z_D91X_PjN^jhj&Z4CzMJ+waA}9L>_DLR&M07#v~==3hCRQjyfvRie5R^ocM_!!;SN
zN!tTECQIWn@)9K0CY7UnxO*;QD25929h|>EL95_WuiR%}33&WeL|n_@W+y&?M$9F<
zc|hs=V3@LSf|*2?k}ynu_j|B~^tRV&x?=zia*lI<-_a?&G5vSj8g%@4Pab~fGw}WX
zpB`|7vkPhs*0zR1f6|QW&SxG@3wsv}vkWZmGVbzO1oGCxL0Ai{rH4CaI*b7Ywq*Ov
z%mQ%)?_s~xNMdW4j9)?}pNueVD#kPE;~-kMGc1KtRQT`w`%30~b`+%G&p{Jj4)EYJ
z7b{mw4X~>lgv-(uZ2hwwz~|uq8@0e)=bxmh+RjZFK96!M+vAt^;p?W%bVzSHCAuEr
zybQRSD2<LE$X_-xP1*RW;G9~1
z0K(xjYX=)fpfn^NfBc5~AwRLHlV!{0J!3+pDIRlh#nBvh?7NAOB~A<+stz?vqO(PZ
z>It{Q&gx)onKk{8^(4P|Q8On6dJ+1Mq={?O-^8WvVF|Dd&6%fa{8&FRdsRO%#76Kb
z|3_<>Al0(^q!!lF#$3Gvf0c7uMY)6m?9pNOQa<<>BiJqjZ$Z(od<
zg4(0)l4UiN?~A}=Oaahk4HB{yAz}&Lp0dw-afw%KI#AKr9{QZ4B;dFAgDxuuK_qr8
zxyuVdQJ^@CxUcm|b7g5L44Yxzh*-6QlG2BmzoXsCLEUB=oOVP=008jsv@>^e`ycDz
zbo*<2o7Czu$_=kHN)56re&ds&tQBD6OyW_bXRjXY2DmX(V0Opv*jMzGyDbKHP`E8Q
zOD*xFFmAb2t#&xu+zjszHWzVv74`YX_Mkp_wn|Sz=1ai3T^(M-F`wRuZ$N)QRI^YD
z^n19ZB^i|SjR#8FW6?X&_DP0cZdRq&irk5gL;tjJl&(r@rmG0c!x`HY4Kkl~-zR;Y
z1m7#HscoxJlr*}>eeCOPs$MIW|7P)w7gnJz8M?8((P(W_w?gBdh%Gj7-n6-zIfBrk
zcWwtuglRp?VhcO<%FbTWyWgmiXW<|kcT}JzOobY43)LJQg8hKG$k*`1j-`Wra*%Sp
z7A7InhENU^m%~ga_0msEE%h|X$_=_bKiytWmVDC8)^o{E(RZ=cP{nluHeOhQx^t%p
z=?gKja|l1A*%FYuL^}ZEi19wGAsEP!{;=6EJnwBOv6*-#x-I!}x)^I*>Kr(`so_li
zOy7Mf(he8NqdiYvFvVD4p;}*jM~o;UvMDXF*3^{!JBe`fqEpVwq2%^2*?@q^0hb8B
zU-JLY1N_hNH-7-oQ2i&sKNlnaWBBv<2_7YXTbjIUcz1#3muWvd>EBtdxoiB-q5CgW
z06+j(eS4n0_p_h9*Lr5J{jT+U-?g;V(a^~Nm;h`50Kf>46AK)2LID8KF#rHk
z05+N=9T;-aQQ5NrsL1F4xBVAKphU0Vse>Qb@^C}uUaLVw
zPFPV9Hf8*Tkc>~>adhar>Ohmw?~ledlJWtSXy&y60i
zSmM2jDRtYIx>U(h4185$5c4)ZN+g?-V0Rbua}Ab8{Un&zj_I0-^_ECAuFz`x!Tojl
z;&2%S))vlhMX2AuZc6oc7@?|jxKo0~J%e<3sMTg4HaBYv3K_7JXTa$6d7queaFA7M
zHB_I2R5u1j$g<5Up;UJjK(e+aCSm&$70Eq+J@4#!99XFnLLLNwSd$Kf5O!49{C`(Bqu@C+D3w(g(eTzOJLe2$&D
zUma2e46QL_3dHYwwFG0sLV>n3fZ4`v;v-sEDe4-|{*i$g#U91QGs4(Waq+kplYkV&
zBZ`EwyT|zG0r*=AZqTnM(X`)-rwC_W=E{NQllKW}u8?_leT@dt`bV19=<_olA+e`{
zyo-lSQwukU1B{RN=kb5(`Cpuqe|z}}DnG*dPRC~Af%57IIh7_xJ-?@_
z(?m5fc`P(DZEUnaJ?bDdW&akx9|Kb}k}(^7j3Ih9`-3mNy2Nlgi~IKa1EW9%
zv&a?e-$^p%x@9GUOpp_F0DuS?8BYg3gtMDH*xA|s=Q{REZ_as=ALy4=ecgWGoh~l>
zG+b+V#7<>Xuh8Vekg6A0TuC>98CZ0+E5SxkvV!4m0PbxaM9f|8Y4X||@IbK%YNy
zBYxCH{<%SG6aV_VFHJOmRcz6VWaEAO-G=Uz&%1NCB^=F@joTOiTB4mHFn#LKWO7p-
z?;Nf&*{TsG9W0Z#gpLsL5032(Nf1h9Z4xz3GACjk7-6V+Df$xZgJ>excp#;@4%U$)
zBj;3(st4hVSKlDI7#f4P49U4Ij%Dq>$epgrEv;y7P^ZyxJVA|0c)T1#$nArpU`_qqa7%`L-2%{
z@J*Oos~EN=o!F_81IJY7%e}?PS7yPxFXi@HQm@guh~Rjl?QegyuuuTho(x5%J(FxB
zR9Y&&`eAbmKaU>gT`QZj?J{fC`w&-f*~YEM5+a5oFbXZTVi0wD^_uk3Z05^FG`;i@IPx`$$iBYN98D^L*1R)H^$NxOJ)nvTapuAN+s`
zfgye;S~+_p^Ku?+23Q&vN0I6)XG>ln3jW5EV?r5_B@M*Ael!Zlw)|3}B4XsbXBH&O
zbzo0w)d${iOjVq}NuQ92{qNpNbTrkwkn{oXDvTeI;XzJ30q0}d(%&S)?yu&Uy3Hu$
zcQi-C*=T0?MV$}LIn-2W((K_m(K6HBK(nOfvCrXY8z0VR`vX_l_DyLWS)<^1BP#!9LLqAnVg$_43p6
zVBM|K!0Ng>hI9^_mFotDsT0hsKrhhnAr+3QB!L%Tcx{xC>#SYz1pA=*hmEG!NBj1?
zbNm(BVs-i=5h)}+6?0GGcQ;AopV;2u2MwOQ`(9h*ab>3>5;
zuvhQR01_^%Najt6eB!V21cO2#a2VeoCxM@JswCMAS*OTCciFaO3|slWKOG@qEu5_K
z7!$TNuA8A*4K~yS4~c4tZny1<2LLCXirBFOlf=9}hQ-B^T1XnO8U>2Vf59QZlxEd}
zrA0i+{IomgbpIoVK=K$B7lZ)EEGUO!{Q|MCwt=M!FboWyWT~z&P|SSni9#-fR+z2C
zn$ifq_276~NDXZ;J*;R1&)0uTUt_eI_1?J%e%fkar`~}gMvWsQwVKVazb-bUwmPy6
z^P#|Hg%+Es0WPtSHg1zs_A@=n#*A^Dw?+}JGMLOg{dqmk;cW|FG+>u$uhm?5V%~29
z`pq{F5BJz+yR4d-rQ)vjQ!STm4ROT+B=kH})!S`*aS;^@0Tr%Tu=SB)d25`@ZD(n=
zGf;g{n}kD+jBt!lfY4Sh6ZJu`Ip(kU}n1NEHI67s|fJh4TuHh=4riflyjP}(!1
zogRtiW#$oKF>#h91MUUt0l}Osp!)lvo=BQJobtEBAm{BFVUbJ4Bz(78_U(ra=h$F#
zLt#nkXnuo-+}gF7feVJOp+5Xul39X-BZhE`2INl0sSqbK_?n9vKfjs8GM^L
zm8#>4=)Qnb<>{XLs3xijtRe~@_!sf9SRl$xOh)dy*|NrP-Rvgb$XpcaY&o$@N;I6w
zBjzXl2{tMbk)*vGBTTm}q2jCN^t0YxXP?IGiAXkHTJmD6OZaW8jh(tsSH!>4?rjsI
z^{HC%md_Qw_w053(on@-`L@rGVf|_zs~46;Te_7ocm22_wPuCkfb@j=4#5{4O#(|e
ziFzecu8i1WaG4WOA3y5bl
zYQZ9Ju60)gLN=EYmrsF%^pojK0bx7}K-&!#5wlphoMr=6CX8F^f=$7nRA$oO5Jo?3YNU!Hdk_RcMv@hyc!
z`0pR4@cCUY>F+%1g0N@$Uo3ZjW*cg|*ci&9muTK~6Bb-hNVHs->3TCutZw;~@XE7m
z?md{28ElkI=_6r!3UNSj{s?qmIH{oPXrk5Y!DyCladwr8{jNu%JS4W*g-S1|bV`Z9
zoDba?4BvA}SK9gbeYA^?5x%(56s5z}erbz&pk~V3Bv1>T&1Zkk-ocTa`K0$YrGye9
z4#-sk!_QF*jO>`X-bSS8fjnq0@W`TpPQw{0E3GUTM}+RHOGBN#H)C-~Y!@mSBwG*8U}OW|33
z=m=R;O*KeBftc=2--`SDt?%M_R>3s`Pftt|Gvkj;eJylOdfM`~oMKwhH4zeiG$L~=
zU3cD`qzL=a?Z^~NO9^W_TcvUrbB$vyTro^vU7OdBe^?nz+0@CAYaVQAoNZvU7u&It
zU$1g~sNEM&$|9u#^|A*M_-TR^m<14Y
zIDA&9Czp*zpz!@fXWu8Ox5K#K4;9y>-_7{8$QaK(RUDH`_mNsqSG%IK}~`*
z7aunJlo;b9xJ}Ymcs!Jcnh~#@DM#6ZgL{(jmdU^+lYSKUtsH=zqJiboa=STQ-S4;zmu8iI)U0&~M)|Tz!fe{VLsw4Yjdr@}TgY^cVQ13#aSgx*LaCR4U
zX*#Z@3cBg_=m9hoeMbn7k!#c0_EGv^nO9DMJ-f5AaMZAmqCmG~m_0Gi+S(f?%%0Ca
z?}4AjfU!(9?^=(GysvahnI4DRG}ssy#Fgs{_G1>_#)F_4x3MA&aAo;u
zNV)$Rzkh~h-US1_R6Rzf4hNibAq6Ye@^i`@GeuR7CgSS1nr)}&z0*e|DT>Dg00{nE
z6ku>K2MFvB#r9m&`7=Ld@2-ju*5D~OpQD-yYuZ|)N>XTWHKVIL>6@BwDFYtrxQjh8
z%%DJ;RkJfujYX?HanjIvX&D?w9jtJlq6HSgQE4=3AoKGSgZB(b~)$x*wV!r=#d
z>1`Dq&057`s+zt?Cvqn2eZ?){(YrntsS^cY)Qo)irR+_sb0j%=*NXy?=M_2}JmDdQ
z=I1u092_wcEcGCa_CAVW)oGIa+%r%(!@14d6I|9WT~!!M?{}zgDdnb`bH5(3ZkMy8
zBUBC8aCyb7x7m`^bN(*QGQ>bh{{+3hwTRQ;QPG?{J$Ao_~Nj(ttJ)*P;4;JQWN-s$r_B7fEizTc{W
zl=Zf9ATlK~kvrL6z=u9^gIMdr-E5s6|ABvp_;$4}elo?=z-wTY^JiB^CR>{vY^Sae
zKUt_14bAt%JjiOdmcqAx$kV=esWB04=C7uvFHeJf@3vVx%^}Tx#C<`vLNU-NT
z79~zNoSPA6f1y3LE+CRuiHjepBFB&m
z$!)P)x|DT_`ucuM?KICjmMRnHm5q-(TUyAw
zl@*tv714|f$wC?%!UXQoM%0jHD+*}Ep7$hAn_Q*%#xxlYk7WF#&%CYaX!q}{MfAoS
zlH*l|XtP{WcKUTqRL0^I#!G*jP>t~7@Bbb}^QolB&hfwktD5hA%`W${@57+GQaVOg
zTd}?%NvFKFk17X4!K!!SEv0i+?(`{cA3O!iZ6Z!G?R^Q+^Bvz09-iR*E&~2X4|f!i
zZ$6FO+5U`x3lcnVh?_114*!WFz8lv9ykJ`=uw^zre-;1R85HXFlA*qLeee4Dqo#tH
zG5QuChg*VCM~F4v%Z?<0O3i1fcM2r_g&iQ4L&{A3|>7}pB@-Wjht1r(+N1~O@}
z4anx?-Nwl?gjt52u4k)f?64I2)OYTjHnF8xZwA@rf-1_K++(5gTIj~rRbTXDX9qN!
zjvIIT
z)=%Qn@V0tlh_hNpkbCkrtkz@o*f8ai>O|t$wmJ#{UcLMD6m^>}AE(>joXDV|m>@_H
zRx}CH^!&`R^Ma?F{r*l+F`L!6yJ{UJIQR#dzeAQq*qff39Rw?YJdI4laK5!hF?2Ph
zqCQljjX+{{i~$rY*QS#M4wsOZRbm9tEM-1F?OxJtkW*~yOr7q98
zj}EQ@2zTXA
z6*m-T<>tAg0oo9`caF8^Mkpz$QBV3YU+&fV`#W=BSFZnebNr^Cn?2HBp6v1d@1OWr$MmL`o1Lb=c_I95-|41NcY5vdB{de;g+P|6qRmio}F@Ew606y|}8wulB(9f&?
E12%USdH?_b
literal 0
HcmV?d00001
diff --git a/resource/gorm_settings.yaml b/resource/gorm_settings.yaml
new file mode 100644
index 0000000..329cb43
--- /dev/null
+++ b/resource/gorm_settings.yaml
@@ -0,0 +1,25 @@
+gorm:
+ settings:
+ - dsn-name: dsn1
+ bind-tables:
+ - authority_menu
+ - casbin_rule
+ - exa_customers
+ - exa_file_chunks
+ - exa_file_upload_and_downloads
+ - exa_files
+ - exa_simple_uploaders
+ - jwt_blacklists
+ - sys_apis
+ - sys_authorities
+ - sys_authority_menus
+ - sys_base_menu_parameters
+ - sys_base_menus
+ - sys_data_authority_id
+ - sys_dictionaries
+ - sys_dictionary_details
+ - sys_operation_records
+ - sys_users
+ - dsn-name: dsn2
+ bind-tables:
+ - bs_channel
diff --git a/resource/rbac_model.conf b/resource/rbac_model.conf
new file mode 100644
index 0000000..08d47f6
--- /dev/null
+++ b/resource/rbac_model.conf
@@ -0,0 +1,14 @@
+[request_definition]
+r = type, app, sub, obj, act, model
+
+[policy_definition]
+p = type, app, sub, obj, act, model
+
+[role_definition]
+g = _, _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.sub, p.sub, r.app) && (r.type == p.type && r.app == p.app && ParamsMatch(r.obj,p.obj) && r.act == p.act && r.model == p.model) || r.sub == "1"
\ No newline at end of file
diff --git a/resource/template/readme.txt.tpl b/resource/template/readme.txt.tpl
new file mode 100644
index 0000000..517311f
--- /dev/null
+++ b/resource/template/readme.txt.tpl
@@ -0,0 +1,9 @@
+代码解压后把fe的api文件内容粘贴进前端api文件夹下并修改为自己想要的名字即可
+
+后端代码解压后同理,放到自己想要的 mvc对应路径 并且到 initRouter中注册自动生成的路由 到registerTable中注册自动生成的model
+
+项目github:"https://github.com/piexlmax/pure-admin"
+
+希望大家给个star多多鼓励
+
+暂时不保存大家生成的结构体 只为方便一次性使用
\ No newline at end of file
diff --git a/resource/template/server/api.go.tpl b/resource/template/server/api.go.tpl
new file mode 100644
index 0000000..086656d
--- /dev/null
+++ b/resource/template/server/api.go.tpl
@@ -0,0 +1,130 @@
+package v1
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "pure-admin/service"
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+// @Tags {{.StructName}}
+// @Summary 创建{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "创建{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /{{.Abbreviation}}/create{{.StructName}} [post]
+func Create{{.StructName}}(c *gin.Context) {
+ var {{.Abbreviation}} model.{{.StructName}}
+ _ = c.ShouldBindJSON(&{{.Abbreviation}})
+ if err := service.Create{{.StructName}}({{.Abbreviation}}); err != nil {
+ global.MG_LOG.Error("创建失败!", zap.Any("err", err))
+ response.FailWithMessage("创建失败", c)
+ } else {
+ response.OkWithMessage("创建成功", c)
+ }
+}
+
+// @Tags {{.StructName}}
+// @Summary 删除{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "删除{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
+func Delete{{.StructName}}(c *gin.Context) {
+ var {{.Abbreviation}} model.{{.StructName}}
+ _ = c.ShouldBindJSON(&{{.Abbreviation}})
+ if err := service.Delete{{.StructName}}({{.Abbreviation}}); err != nil {
+ global.MG_LOG.Error("删除失败!", zap.Any("err", err))
+ response.FailWithMessage("删除失败", c)
+ } else {
+ response.OkWithMessage("删除成功", c)
+ }
+}
+
+// @Tags {{.StructName}}
+// @Summary 批量删除{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.IdsReq true "批量删除{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"批量删除成功"}"
+// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete]
+func Delete{{.StructName}}ByIds(c *gin.Context) {
+ var IDS request.IdsReq
+ _ = c.ShouldBindJSON(&IDS)
+ if err := service.Delete{{.StructName}}ByIds(IDS); err != nil {
+ global.MG_LOG.Error("批量删除失败!", zap.Any("err", err))
+ response.FailWithMessage("批量删除失败", c)
+ } else {
+ response.OkWithMessage("批量删除成功", c)
+ }
+}
+
+// @Tags {{.StructName}}
+// @Summary 更新{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "更新{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /{{.Abbreviation}}/update{{.StructName}} [put]
+func Update{{.StructName}}(c *gin.Context) {
+ var {{.Abbreviation}} model.{{.StructName}}
+ _ = c.ShouldBindJSON(&{{.Abbreviation}})
+ if err := service.Update{{.StructName}}({{.Abbreviation}}); err != nil {
+ global.MG_LOG.Error("更新失败!", zap.Any("err", err))
+ response.FailWithMessage("更新失败", c)
+ } else {
+ response.OkWithMessage("更新成功", c)
+ }
+}
+
+// @Tags {{.StructName}}
+// @Summary 用id查询{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "用id查询{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
+// @Router /{{.Abbreviation}}/find{{.StructName}} [get]
+func Find{{.StructName}}(c *gin.Context) {
+ var {{.Abbreviation}} model.{{.StructName}}
+ _ = c.ShouldBindQuery(&{{.Abbreviation}})
+ if err, re{{.Abbreviation}} := service.Get{{.StructName}}({{.Abbreviation}}.ID); err != nil {
+ global.MG_LOG.Error("查询失败!", zap.Any("err", err))
+ response.FailWithMessage("查询失败", c)
+ } else {
+ response.OkWithData(gin.H{"re{{.Abbreviation}}": re{{.Abbreviation}}}, c)
+ }
+}
+
+// @Tags {{.StructName}}
+// @Summary 分页获取{{.StructName}}列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.{{.StructName}}Search true "分页获取{{.StructName}}列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
+func Get{{.StructName}}List(c *gin.Context) {
+ var pageInfo request.{{.StructName}}Search
+ _ = c.ShouldBindQuery(&pageInfo)
+ if err, list, total := service.Get{{.StructName}}InfoList(pageInfo); err != nil {
+ global.MG_LOG.Error("获取失败!", zap.Any("err", err))
+ response.FailWithMessage("获取失败", c)
+ } else {
+ response.OkWithDetailed(response.PageResult{
+ List: list,
+ Total: total,
+ Page: pageInfo.Page,
+ PageSize: pageInfo.PageSize,
+ }, "获取成功", c)
+ }
+}
diff --git a/resource/template/server/model.go.tpl b/resource/template/server/model.go.tpl
new file mode 100644
index 0000000..f4eaff4
--- /dev/null
+++ b/resource/template/server/model.go.tpl
@@ -0,0 +1,22 @@
+// 自动生成模板{{.StructName}}
+package model
+
+import (
+ "pure-admin/global"
+)
+
+// 如果含有time.Time 请自行import time包
+type {{.StructName}} struct {
+ global.GVA_MODEL {{- range .Fields}}
+ {{- if eq .FieldType "bool" }}
+ {{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"column:{{.ColumnName}};comment:{{.Comment}}{{- if .DataType -}};type:{{.DataType}}{{- end }}"`
+ {{- else }}
+ {{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"column:{{.ColumnName}};comment:{{.Comment}}{{- if .DataType -}};type:{{.DataType}}{{- if eq .FieldType "string" -}}{{- if .DataTypeLong -}}({{.DataTypeLong}}){{- end -}}{{- end -}};{{- if ne .FieldType "string" -}}{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}{{- end -}}{{- end -}}"`
+ {{- end }} {{- end }}
+}
+
+{{ if .TableName }}
+func ({{.StructName}}) TableName() string {
+ return "{{.TableName}}"
+}
+{{ end }}
diff --git a/resource/template/server/request.go.tpl b/resource/template/server/request.go.tpl
new file mode 100644
index 0000000..ed58881
--- /dev/null
+++ b/resource/template/server/request.go.tpl
@@ -0,0 +1,8 @@
+package request
+
+import "pure-admin/model"
+
+type {{.StructName}}Search struct{
+ model.{{.StructName}}
+ PageInfo
+}
\ No newline at end of file
diff --git a/resource/template/server/router.go.tpl b/resource/template/server/router.go.tpl
new file mode 100644
index 0000000..5a32cce
--- /dev/null
+++ b/resource/template/server/router.go.tpl
@@ -0,0 +1,19 @@
+package router
+
+import (
+ "pure-admin/api/v1"
+ "pure-admin/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func Init{{.StructName}}Router(Router *gin.RouterGroup) {
+ {{.StructName}}Router := Router.Group("{{.Abbreviation}}").Use(middleware.OperationRecord())
+ {
+ {{.StructName}}Router.POST("create{{.StructName}}", v1.Create{{.StructName}}) // 新建{{.StructName}}
+ {{.StructName}}Router.DELETE("delete{{.StructName}}", v1.Delete{{.StructName}}) // 删除{{.StructName}}
+ {{.StructName}}Router.DELETE("delete{{.StructName}}ByIds", v1.Delete{{.StructName}}ByIds) // 批量删除{{.StructName}}
+ {{.StructName}}Router.PUT("update{{.StructName}}", v1.Update{{.StructName}}) // 更新{{.StructName}}
+ {{.StructName}}Router.GET("find{{.StructName}}", v1.Find{{.StructName}}) // 根据ID获取{{.StructName}}
+ {{.StructName}}Router.GET("get{{.StructName}}List", v1.Get{{.StructName}}List) // 获取{{.StructName}}列表
+ }
+}
diff --git a/resource/template/server/service.go.tpl b/resource/template/server/service.go.tpl
new file mode 100644
index 0000000..2d6ebeb
--- /dev/null
+++ b/resource/template/server/service.go.tpl
@@ -0,0 +1,105 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: Create{{.StructName}}
+//@description: 创建{{.StructName}}记录
+//@param: {{.Abbreviation}} model.{{.StructName}}
+//@return: err error
+
+func Create{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
+ err = global.MG_DB.Create(&{{.Abbreviation}}).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: Delete{{.StructName}}
+//@description: 删除{{.StructName}}记录
+//@param: {{.Abbreviation}} model.{{.StructName}}
+//@return: err error
+
+func Delete{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
+ err = global.MG_DB.Delete(&{{.Abbreviation}}).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: Delete{{.StructName}}ByIds
+//@description: 批量删除{{.StructName}}记录
+//@param: ids request.IdsReq
+//@return: err error
+
+func Delete{{.StructName}}ByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.{{.StructName}}{},"id in (?)",ids.Ids).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: Update{{.StructName}}
+//@description: 更新{{.StructName}}记录
+//@param: {{.Abbreviation}} *model.{{.StructName}}
+//@return: err error
+
+func Update{{.StructName}}({{.Abbreviation}} model.{{.StructName}}) (err error) {
+ err = global.MG_DB.Updates(&{{.Abbreviation}}).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: Get{{.StructName}}
+//@description: 根据id获取{{.StructName}}记录
+//@param: id uint
+//@return: err error, {{.Abbreviation}} model.{{.StructName}}
+
+func Get{{.StructName}}(id uint) (err error, {{.Abbreviation}} model.{{.StructName}}) {
+ err = global.MG_DB.Where("id = ?", id).First(&{{.Abbreviation}}).Error
+ return
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: Get{{.StructName}}InfoList
+//@description: 分页获取{{.StructName}}记录
+//@param: info request.{{.StructName}}Search
+//@return: err error, list interface{}, total int64
+
+func Get{{.StructName}}InfoList(info request.{{.StructName}}Search) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.{{.StructName}}{})
+ var {{.Abbreviation}}s []model.{{.StructName}}
+ // 如果有条件搜索 下方会自动创建搜索语句
+ {{- range .Fields}}
+ {{- if .FieldSearchType}}
+ {{- if eq .FieldType "string" }}
+ if info.{{.FieldName}} != "" {
+ db = db.Where("`{{.ColumnName}}` {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
+ }
+ {{- else if eq .FieldType "bool" }}
+ if info.{{.FieldName}} != nil {
+ db = db.Where("`{{.ColumnName}}` {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
+ }
+ {{- else if eq .FieldType "int" }}
+ if info.{{.FieldName}} != 0 {
+ db = db.Where("`{{.ColumnName}}` {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
+ }
+ {{- else if eq .FieldType "float64" }}
+ if info.{{.FieldName}} != 0 {
+ db = db.Where("`{{.ColumnName}}` {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
+ }
+ {{- else if eq .FieldType "time.Time" }}
+ if !info.{{.FieldName}}.IsZero() {
+ db = db.Where("`{{.ColumnName}}` {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }})
+ }
+ {{- end }}
+ {{- end }}
+ {{- end }}
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&{{.Abbreviation}}s).Error
+ return err, {{.Abbreviation}}s, total
+}
\ No newline at end of file
diff --git a/resource/template/web/api.js.tpl b/resource/template/web/api.js.tpl
new file mode 100644
index 0000000..58ab2e0
--- /dev/null
+++ b/resource/template/web/api.js.tpl
@@ -0,0 +1,97 @@
+import service from '@/utils/request'
+
+// @Tags {{.StructName}}
+// @Summary 创建{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "创建{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /{{.Abbreviation}}/create{{.StructName}} [post]
+export const create{{.StructName}} = (data) => {
+ return service({
+ url: '/{{.Abbreviation}}/create{{.StructName}}',
+ method: 'post',
+ data
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 删除{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "删除{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
+export const delete{{.StructName}} = (data) => {
+ return service({
+ url: '/{{.Abbreviation}}/delete{{.StructName}}',
+ method: 'delete',
+ data
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 删除{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.IdsReq true "批量删除{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
+// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]
+export const delete{{.StructName}}ByIds = (data) => {
+ return service({
+ url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds',
+ method: 'delete',
+ data
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 更新{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "更新{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
+// @Router /{{.Abbreviation}}/update{{.StructName}} [put]
+export const update{{.StructName}} = (data) => {
+ return service({
+ url: '/{{.Abbreviation}}/update{{.StructName}}',
+ method: 'put',
+ data
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 用id查询{{.StructName}}
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body model.{{.StructName}} true "用id查询{{.StructName}}"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
+// @Router /{{.Abbreviation}}/find{{.StructName}} [get]
+export const find{{.StructName}} = (params) => {
+ return service({
+ url: '/{{.Abbreviation}}/find{{.StructName}}',
+ method: 'get',
+ params
+ })
+}
+
+// @Tags {{.StructName}}
+// @Summary 分页获取{{.StructName}}列表
+// @Security ApiKeyAuth
+// @accept application/json
+// @Produce application/json
+// @Param data body request.PageInfo true "分页获取{{.StructName}}列表"
+// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
+// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]
+export const get{{.StructName}}List = (params) => {
+ return service({
+ url: '/{{.Abbreviation}}/get{{.StructName}}List',
+ method: 'get',
+ params
+ })
+}
\ No newline at end of file
diff --git a/resource/template/web/form.vue.tpl b/resource/template/web/form.vue.tpl
new file mode 100644
index 0000000..e18285c
--- /dev/null
+++ b/resource/template/web/form.vue.tpl
@@ -0,0 +1,122 @@
+
+
+
+ {{- range .Fields}}
+
+ {{ if eq .FieldType "bool" -}}
+
+ {{ end -}}
+ {{ if eq .FieldType "string" -}}
+
+ {{ end -}}
+ {{ if eq .FieldType "int" -}}
+ {{ if .DictType -}}
+
+
+
+ {{ else -}}
+
+ {{ end -}}
+ {{ end -}}
+ {{ if eq .FieldType "time.Time" }}
+
+ {{ end -}}
+ {{ if eq .FieldType "float64" }}
+
+ {{ end -}}
+
+ {{ end -}}
+
+ 保存
+ 返回
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resource/template/web/table.vue.tpl b/resource/template/web/table.vue.tpl
new file mode 100644
index 0000000..7a371c3
--- /dev/null
+++ b/resource/template/web/table.vue.tpl
@@ -0,0 +1,316 @@
+
+
+
+
+ {{- range .Fields}} {{- if .FieldSearchType}} {{- if eq .FieldType "bool" }}
+
+
+
+
+
+
+
+
+ {{- else }}
+
+
+ {{ end }} {{ end }} {{ end }}
+
+ 查询
+
+
+ 新增{{.Description}}
+
+
+
+ 确定要删除吗?
+
+ 取消
+ 确定
+
+ 批量删除
+
+
+
+
+
+
+
+ {{ "{{ scope.row.CreatedAt|formatDate }}" }}
+
+ {{- range .Fields}}
+ {{- if .DictType}}
+
+
+ {{"{{"}}filterDict(scope.row.{{.FieldJson}},"{{.DictType}}"){{"}}"}}
+
+
+ {{- else if eq .FieldType "bool" }}
+
+ {{ "{{scope.row."}}{{.FieldJson}}{{"|formatBoolean}}" }}
+ {{- else }}
+ {{ end -}}
+ {{ end -}}
+
+
+ 变更
+ 删除
+
+
+
+
+
+
+ {{- range .Fields}}
+
+ {{ if eq .FieldType "bool" }}
+
+ {{ end -}}
+ {{ if eq .FieldType "string" }}
+
+ {{ end -}}
+ {{ if eq .FieldType "int" }}
+ {{- if .DictType}}
+
+
+
+ {{ else }}
+
+ {{ end -}}
+ {{ end -}}
+ {{ if eq .FieldType "time.Time" }}
+
+ {{ end -}}
+ {{- if eq .FieldType "float64" }}
+
+ {{ end -}}
+
+ {{- end }}
+
+
+
+
+
+
+
+
+
diff --git a/router/application.go b/router/application.go
new file mode 100755
index 0000000..d410291
--- /dev/null
+++ b/router/application.go
@@ -0,0 +1,59 @@
+package router
+
+import (
+ "fmt"
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+ "pure-admin/service"
+ "time"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitApplicationRouter(Router *gin.RouterGroup) {
+ ApplicationRouter := Router.Group("application").Use(middleware.OperationRecord())
+ {
+ ApplicationRouter.POST("createApplication", v1.CreateApplication) // 新建Application
+ ApplicationRouter.DELETE("deleteApplication", v1.DeleteApplication) // 删除Application
+ ApplicationRouter.DELETE("deleteApplicationByIds", v1.DeleteApplicationByIds) // 批量删除Application
+ ApplicationRouter.PUT("updateApplication", v1.UpdateApplication) // 更新Application
+ ApplicationRouter.GET("findApplication", v1.FindApplication) // 根据ID获取Application
+ ApplicationRouter.GET("getApplicationList", v1.GetApplicationList) // 获取Application列表
+ }
+ go func() {
+ time.Sleep(1 * time.Second)
+ e := service.Casbin()
+ // e.AddPolicy("api", "admin", "/api/v1/application/getApplicationList", "*", "*", "*")
+ // e.AddRoleForUser("user1", "admin")
+ // e.AddRoleForUserInDomain("18162715654", "1", "51")
+ //接口
+ // e.AddPolicy("api", "domain1", "admin", "/api/v1/application/getApplicationList", "GET", "*")
+ //路由
+ // e.AddPolicy("menu", "appid", "1", "/sys", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/sys/account", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/sys/role", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/sys/api", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/sys/menu", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/sys/sysLog", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/", "*", "*")
+ // e.AddPolicy("menu", "appid", "1", "/login", "*", "*")
+ // r, err := e.Enforce("api", "domain1", "user1", "/api/v1/application/getApplicationList", "GET", "*")
+ // if err != nil {
+ // fmt.Println(err)
+ // }
+ // fmt.Println(r)
+ // e.RemoveFilteredGroupingPolicy(0, "menu", "domain2", "admin")
+ e.RemoveFilteredPolicy(0, "menu", "*", "admin")
+ //获取应用内用户的角色
+ res := e.GetRolesForUserInDomain("user1", "domain1")
+ fmt.Println(res)
+ //获取应用内角色的权限
+ res1 := e.GetFilteredPolicy(0, "menu", "domain1")
+ fmt.Println(res1)
+ for _, v := range res1 {
+ fmt.Println(v)
+ }
+ // source.Api.Init()
+ // source.Casbin.InitData()
+ }()
+}
diff --git a/router/banner.go b/router/banner.go
new file mode 100644
index 0000000..6e86fd3
--- /dev/null
+++ b/router/banner.go
@@ -0,0 +1,20 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+)
+
+func InitBanner(Router *gin.RouterGroup) {
+ bannerRouter := Router.Group("banner").Use(middleware.OperationRecord())
+ {
+ bannerRouter.GET("list", v1.GetBannerList) // banner列表
+ bannerRouter.DELETE("batchDelBanner", v1.DelBannerByIds)
+ bannerRouter.POST("", v1.CreateBanner)
+ bannerRouter.PUT("", v1.UpdateBanner)
+ bannerRouter.PUT("up-data", v1.BannerUpData)
+ bannerRouter.PUT("down-data", v1.BannerDownData)
+ }
+}
diff --git a/router/bill.go b/router/bill.go
new file mode 100644
index 0000000..935410e
--- /dev/null
+++ b/router/bill.go
@@ -0,0 +1,45 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ v1 "pure-admin/api/admin"
+)
+
+func InitBillRouter(Router *gin.RouterGroup) {
+ InRouter := Router.Group("bill")
+ {
+ InRouter.GET("customer-data", v1.GetUserBillData) // 用户账单数据
+ InRouter.GET("customer-list", v1.GetUserBillList) // 用户账单列表
+ InRouter.GET("influence-data", v1.GetInfluenceBillData) // 网红账单数据
+ InRouter.GET("influence-list", v1.GetInfluenceBillList) // 网红账单列表
+ InRouter.GET("seller-data", v1.GetSellerBillData) // 商家账单数据
+ InRouter.GET("seller-list", v1.GetSellerBillList) // 商家账单列表
+
+ InRouter.GET("seller-fund-data", v1.GetSellerFundData) // 商家营销账户数据
+ InRouter.GET("seller-fund-list", v1.GetSellerFundList) // 商家营销账户列表
+
+ InRouter.GET("admin-data", v1.GetAdminBillData) // 平台账单数据
+ InRouter.GET("admin-list", v1.GetAdminBillList) // 平台账单列表
+ InRouter.GET("admin-fund-data", v1.GetAdminFundData) // 平台奖励账户数据
+ InRouter.GET("admin-fund-list", v1.GetAdminFundList) // 平台奖励账户列表
+ }
+}
+
+func InitWithdrawalRouter(Router *gin.RouterGroup) {
+ InRouter := Router.Group("withdrawal")
+ {
+ InRouter.GET("influence-data", v1.GetInfluenceWithdrawalData) // 网红提现数据
+ InRouter.GET("influence-list", v1.GetInfluenceWithdrawalList) // 网红提现列表
+ InRouter.POST("influence-examine", v1.ExamineInfluenceWithdrawal) // 网红提现审核
+
+ InRouter.PUT("status", v1.UpdateWithdrawalStatus) // 手动确认打款状态
+ InRouter.POST("retry", v1.TransferWithdrawalRetry) // 提现打款重试
+
+ InRouter.GET("seller-data", v1.GetSellerWithdrawalData) // 商家提现数据
+ InRouter.GET("seller-list", v1.GetSellerWithdrawalList) // 商家提现列表
+ InRouter.POST("seller-examine", v1.ExamineSellerWithdrawal) // 商家提现审核
+
+ InRouter.GET("admin-data", v1.GetAdminWithdrawalData) // 平台提现数据
+ InRouter.GET("admin-list", v1.GetAdminWithdrawalList) // 平台提现列表
+ }
+}
diff --git a/router/business.go b/router/business.go
new file mode 100755
index 0000000..75d6988
--- /dev/null
+++ b/router/business.go
@@ -0,0 +1,29 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+)
+
+func InitBusinessRouter(Router *gin.RouterGroup) {
+ BusinessRouter := Router.Group("business").Use(middleware.OperationRecord())
+ {
+ BusinessRouter.POST("", v1.CreateBusiness) // 新建Business
+ BusinessRouter.DELETE("", v1.DeleteBusiness) // 删除Business
+ // BusinessRouter.DELETE("", v1.DeleteBusinessByIds) // 批量删除Business
+ BusinessRouter.PUT("", v1.UpdateBusiness) // 更新Business
+ BusinessRouter.GET("", v1.GetBusiness) // 根据ID获取Business
+ BusinessRouter.GET("list", v1.ListBusiness) // 获取Business列表
+ BusinessRouter.GET("search", v1.SearchBusiness) // 获取Business列表
+ }
+}
+func InitGoodsRouter(Router *gin.RouterGroup) {
+ BusinessRouter := Router.Group("goods").Use(middleware.OperationRecord())
+ {
+
+ BusinessRouter.GET("list", v1.ListGoods) // 获取Business列表
+ BusinessRouter.GET("search", v1.SearchGoods) // 获取Business列表
+ }
+}
diff --git a/router/dict.go b/router/dict.go
new file mode 100644
index 0000000..840b107
--- /dev/null
+++ b/router/dict.go
@@ -0,0 +1,13 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ "pure-admin/api/admin"
+)
+
+func InitDictRouter(Router *gin.RouterGroup) {
+ DictRouter := Router.Group("/dict")
+ {
+ DictRouter.GET("getDictDataList", v1.GetSysDictDataList)
+ }
+}
diff --git a/router/mission.go b/router/mission.go
new file mode 100644
index 0000000..ea5ddb4
--- /dev/null
+++ b/router/mission.go
@@ -0,0 +1,51 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+)
+
+func InitMissionRouter(Router *gin.RouterGroup) { // 任务
+ missionRouter := Router.Group("mission").Use(middleware.OperationRecord())
+ {
+ missionRouter.GET("mission", v1.GetMissionDetail) // 商家任务详情
+ missionRouter.GET("mission-list", v1.GetMissionList) // 商家任务列表
+ missionRouter.GET("claim-list", v1.GetMissionClaimList) // 任务领取列表
+ missionRouter.GET("video-list", v1.GetMissionVideoList) // 视频任务列表
+ missionRouter.POST("add-video", v1.AddMissionVideo) // 添加任务视频
+ missionRouter.PUT("edit-video", v1.EditMissionVideo) // 编辑任务视频
+ missionRouter.GET("claim-video-detail", v1.GetMissionClaimVideoDetail) // 任务视频详情
+ // 任务推荐模块
+ missionRouter.GET("recommend/list", v1.GetMissionRecommendList) // 获取任务推荐列表
+ missionRouter.POST("recommend/add-data", v1.AddMissionRecommend) // 添加任务推荐
+ missionRouter.DELETE("recommend/batch-del-data", v1.BatchDelMissionRecommendByIds) // 批量删除任务推荐
+ missionRouter.PUT("recommend/up-data", v1.MissionRecommendUpData) // 任务推荐上移
+ missionRouter.PUT("recommend/down-data", v1.MissionRecommendDownData) // 任务推荐下移
+ missionRouter.PUT("recommend/update-sort", v1.UpdateMissionRecommendSort) // 任务推荐修改排序
+
+ missionRouter.GET("stop-detail", v1.GetMissionStopDetail) // 商家任务详情
+ missionRouter.GET("stop-list", v1.GetMissionStopList) // 商家任务详情
+ missionRouter.PUT("stop", v1.StopMission) // 商家任务详情
+ missionRouter.GET("sys-reward-list", v1.GetSysRewardList) // 获取平台奖励列表
+ missionRouter.POST("send-sys-reward", v1.SendSysReward) // 发送系统任务奖励
+ missionRouter.POST("tag-relation", v1.MissionTagRelation) // 打标签
+ missionRouter.GET("influencer-summary-list", v1.GetInfluencerMissionSummaryList) // 网红统计列表
+
+ missionRouter.GET("claim-order", v1.GetMissionClaimOrder) //任务订单详情
+ missionRouter.GET("claim-order-list", v1.GetMissionClaimOrderList) //任务订单列表
+ }
+}
+
+func InitCategory(Router *gin.RouterGroup) {
+ categoryRouter := Router.Group("category").Use(middleware.OperationRecord())
+ {
+ categoryRouter.GET("list", v1.ListCategoryPage) // 列表
+ categoryRouter.DELETE("", v1.DeleteCategory)
+ categoryRouter.POST("", v1.CreateCategory)
+ categoryRouter.PUT("", v1.UpdateCategory)
+ categoryRouter.GET("children", v1.ListCategoryChildren) // 查询下级
+ categoryRouter.GET("", v1.GetCategoryItem)
+ }
+}
diff --git a/router/order.go b/router/order.go
new file mode 100644
index 0000000..a8fc73f
--- /dev/null
+++ b/router/order.go
@@ -0,0 +1,15 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ v1 "pure-admin/api/admin"
+)
+
+func InitOrderRouter(Router *gin.RouterGroup) {
+ InRouter := Router.Group("order")
+ {
+ InRouter.GET("list", v1.GetOrderList) //订单列表
+ InRouter.GET("detail", v1.GetOrderDetail) //订单详情
+ InRouter.GET("statistic", v1.GetStatisticData) // 数据统计
+ }
+}
diff --git a/router/organization.go b/router/organization.go
new file mode 100755
index 0000000..d566063
--- /dev/null
+++ b/router/organization.go
@@ -0,0 +1,20 @@
+package router
+
+import (
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitOrganizationRouter(Router *gin.RouterGroup) {
+ OrganizationRouter := Router.Group("organization").Use(middleware.OperationRecord())
+ {
+ OrganizationRouter.POST("createOrganization", v1.CreateOrganization) // 新建Organization
+ OrganizationRouter.DELETE("deleteOrganization", v1.DeleteOrganization) // 删除Organization
+ OrganizationRouter.DELETE("deleteOrganizationByIds", v1.DeleteOrganizationByIds) // 批量删除Organization
+ OrganizationRouter.PUT("updateOrganization", v1.UpdateOrganization) // 更新Organization
+ OrganizationRouter.GET("findOrganization", v1.FindOrganization) // 根据ID获取Organization
+ OrganizationRouter.GET("getOrganizationList", v1.GetOrganizationList) // 获取Organization列表
+ }
+}
diff --git a/router/provider.go b/router/provider.go
new file mode 100755
index 0000000..9686b9b
--- /dev/null
+++ b/router/provider.go
@@ -0,0 +1,20 @@
+package router
+
+import (
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitProviderRouter(Router *gin.RouterGroup) {
+ ProviderRouter := Router.Group("provider").Use(middleware.OperationRecord())
+ {
+ ProviderRouter.POST("createProvider", v1.CreateProvider) // 新建Provider
+ ProviderRouter.DELETE("deleteProvider", v1.DeleteProvider) // 删除Provider
+ ProviderRouter.DELETE("deleteProviderByIds", v1.DeleteProviderByIds) // 批量删除Provider
+ ProviderRouter.PUT("updateProvider", v1.UpdateProvider) // 更新Provider
+ ProviderRouter.GET("findProvider", v1.FindProvider) // 根据ID获取Provider
+ ProviderRouter.GET("getProviderList", v1.GetProviderList) // 获取Provider列表
+ }
+}
diff --git a/router/sys_api.go b/router/sys_api.go
new file mode 100644
index 0000000..82a3392
--- /dev/null
+++ b/router/sys_api.go
@@ -0,0 +1,21 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitApiRouter(Router *gin.RouterGroup) {
+ ApiRouter := Router.Group("api").Use(middleware.OperationRecord())
+ {
+ ApiRouter.POST("createApi", sys.CreateApi) // 创建Api
+ ApiRouter.POST("deleteApi", sys.DeleteApi) // 删除Api
+ ApiRouter.POST("getApiList", sys.GetApiList) // 获取Api列表
+ ApiRouter.POST("getApiById", sys.GetApiById) // 获取单条Api消息
+ ApiRouter.POST("updateApi", sys.UpdateApi) // 更新api
+ ApiRouter.POST("getAllApis", sys.GetAllApis) // 获取所有api
+ ApiRouter.DELETE("deleteApisByIds", sys.DeleteApisByIds) // 删除选中api
+ }
+}
diff --git a/router/sys_authority.go b/router/sys_authority.go
new file mode 100644
index 0000000..16df085
--- /dev/null
+++ b/router/sys_authority.go
@@ -0,0 +1,19 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitAuthorityRouter(Router *gin.RouterGroup) {
+ AuthorityRouter := Router.Group("authority").Use(middleware.OperationRecord())
+ {
+ AuthorityRouter.POST("createAuthority", sys.CreateAuthority) // 创建角色
+ AuthorityRouter.POST("deleteAuthority", sys.DeleteAuthority) // 删除角色
+ AuthorityRouter.PUT("updateAuthority", sys.UpdateAuthority) // 更新角色
+ AuthorityRouter.POST("copyAuthority", sys.CopyAuthority) // 更新角色
+ AuthorityRouter.POST("getAuthorityList", sys.GetAuthorityList) // 获取角色列表
+ }
+}
diff --git a/router/sys_auto_code.go b/router/sys_auto_code.go
new file mode 100644
index 0000000..c51928b
--- /dev/null
+++ b/router/sys_auto_code.go
@@ -0,0 +1,18 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitAutoCodeRouter(Router *gin.RouterGroup) {
+ AutoCodeRouter := Router.Group("autoCode")
+ {
+ AutoCodeRouter.POST("preview", sys.PreviewTemp) // 获取自动创建代码预览
+ AutoCodeRouter.POST("createTemp", sys.CreateTemp) // 创建自动化代码
+ AutoCodeRouter.GET("getTables", sys.GetTables) // 获取对应数据库的表
+ AutoCodeRouter.GET("getDB", sys.GetDB) // 获取数据库
+ AutoCodeRouter.GET("getColumn", sys.GetColumn) // 获取指定表所有字段信息
+ }
+}
diff --git a/router/sys_base.go b/router/sys_base.go
new file mode 100644
index 0000000..322d831
--- /dev/null
+++ b/router/sys_base.go
@@ -0,0 +1,19 @@
+package router
+
+import (
+ v1 "pure-admin/api/admin"
+ "pure-admin/api/sys"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitBaseRouter(Router *gin.RouterGroup) (R gin.IRoutes) {
+ BaseRouter := Router.Group("base")
+ {
+ BaseRouter.POST("login", sys.Login)
+ BaseRouter.POST("captcha", sys.Captcha)
+ BaseRouter.GET("chain", v1.GetChainInfo) // 获取区块链信息
+ BaseRouter.POST("payment/payback", v1.PaymentPayback) // 支付结果回调接口
+ }
+ return BaseRouter
+}
diff --git a/router/sys_casbin.go b/router/sys_casbin.go
new file mode 100644
index 0000000..118edf5
--- /dev/null
+++ b/router/sys_casbin.go
@@ -0,0 +1,16 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitCasbinRouter(Router *gin.RouterGroup) {
+ CasbinRouter := Router.Group("casbin").Use(middleware.OperationRecord())
+ {
+ CasbinRouter.POST("updateCasbin", sys.UpdateCasbin)
+ CasbinRouter.POST("getPolicyPathByAuthorityId", sys.GetPolicyPathByAuthorityId)
+ }
+}
diff --git a/router/sys_dictionary.go b/router/sys_dictionary.go
new file mode 100644
index 0000000..720284b
--- /dev/null
+++ b/router/sys_dictionary.go
@@ -0,0 +1,19 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitSysDictionaryRouter(Router *gin.RouterGroup) {
+ SysDictionaryRouter := Router.Group("sysDictionary").Use(middleware.OperationRecord())
+ {
+ SysDictionaryRouter.POST("createSysDictionary", sys.CreateSysDictionary) // 新建SysDictionary
+ SysDictionaryRouter.DELETE("deleteSysDictionary", sys.DeleteSysDictionary) // 删除SysDictionary
+ SysDictionaryRouter.PUT("updateSysDictionary", sys.UpdateSysDictionary) // 更新SysDictionary
+ SysDictionaryRouter.GET("findSysDictionary", sys.FindSysDictionary) // 根据ID获取SysDictionary
+ SysDictionaryRouter.GET("getSysDictionaryList", sys.GetSysDictionaryList) // 获取SysDictionary列表
+ }
+}
diff --git a/router/sys_dictionary_detail.go b/router/sys_dictionary_detail.go
new file mode 100644
index 0000000..442bd0f
--- /dev/null
+++ b/router/sys_dictionary_detail.go
@@ -0,0 +1,19 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitSysDictionaryDetailRouter(Router *gin.RouterGroup) {
+ SysDictionaryDetailRouter := Router.Group("sysDictionaryDetail").Use(middleware.OperationRecord())
+ {
+ SysDictionaryDetailRouter.POST("createSysDictionaryDetail", sys.CreateSysDictionaryDetail) // 新建SysDictionaryDetail
+ SysDictionaryDetailRouter.DELETE("deleteSysDictionaryDetail", sys.DeleteSysDictionaryDetail) // 删除SysDictionaryDetail
+ SysDictionaryDetailRouter.PUT("updateSysDictionaryDetail", sys.UpdateSysDictionaryDetail) // 更新SysDictionaryDetail
+ SysDictionaryDetailRouter.GET("findSysDictionaryDetail", sys.FindSysDictionaryDetail) // 根据ID获取SysDictionaryDetail
+ SysDictionaryDetailRouter.GET("getSysDictionaryDetailList", sys.GetSysDictionaryDetailList) // 获取SysDictionaryDetail列表
+ }
+}
diff --git a/router/sys_email.go b/router/sys_email.go
new file mode 100644
index 0000000..89db973
--- /dev/null
+++ b/router/sys_email.go
@@ -0,0 +1,15 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitEmailRouter(Router *gin.RouterGroup) {
+ EmailRouter := Router.Group("email").Use(middleware.OperationRecord())
+ {
+ EmailRouter.POST("emailTest", sys.EmailTest) // 发送测试邮件
+ }
+}
diff --git a/router/sys_jwt.go b/router/sys_jwt.go
new file mode 100644
index 0000000..29763c9
--- /dev/null
+++ b/router/sys_jwt.go
@@ -0,0 +1,15 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitJwtRouter(Router *gin.RouterGroup) {
+ JwtRouter := Router.Group("jwt").Use(middleware.OperationRecord())
+ {
+ JwtRouter.POST("jsonInBlacklist", sys.JsonInBlacklist) // jwt加入黑名单
+ }
+}
diff --git a/router/sys_menu.go b/router/sys_menu.go
new file mode 100644
index 0000000..0609c8c
--- /dev/null
+++ b/router/sys_menu.go
@@ -0,0 +1,25 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitMenuRouter(Router *gin.RouterGroup) (R gin.IRoutes) {
+ MenuRouter := Router.Group("menu").Use(middleware.OperationRecord())
+ {
+ MenuRouter.POST("getMenu", sys.GetMenu) // 获取用户菜单树
+ MenuRouter.POST("getMenuForList", sys.GetMenuForList) // 获取用户菜单列表
+ MenuRouter.POST("getMenuList", sys.GetMenuList) // 分页获取基础menu列表
+ MenuRouter.POST("addBaseMenu", sys.AddBaseMenu) // 新增菜单
+ MenuRouter.POST("getBaseMenuTree", sys.GetBaseMenuTree) // 获取用户动态路由
+ MenuRouter.POST("addMenuAuthority", sys.AddMenuAuthority) // 增加menu和角色关联关系
+ MenuRouter.POST("getMenuAuthority", sys.GetMenuAuthority) // 获取指定角色menu
+ MenuRouter.POST("deleteBaseMenu", sys.DeleteBaseMenu) // 删除菜单
+ MenuRouter.POST("updateBaseMenu", sys.UpdateBaseMenu) // 更新菜单
+ MenuRouter.POST("getBaseMenuById", sys.GetBaseMenuById) // 根据id获取菜单
+ }
+ return MenuRouter
+}
diff --git a/router/sys_operation_record.go b/router/sys_operation_record.go
new file mode 100644
index 0000000..8783ed9
--- /dev/null
+++ b/router/sys_operation_record.go
@@ -0,0 +1,23 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitSysOperationRecordRouter(Router *gin.RouterGroup) {
+ SysOperationRecordRouter := Router.Group("sysOperationRecord")
+ {
+ SysOperationRecordRouter.GET("findSysOperationRecord", sys.FindSysOperationRecord) // 根据ID获取SysOperationRecord
+ SysOperationRecordRouter.GET("getSysOperationRecordList", sys.GetSysOperationRecordList) // 获取SysOperationRecord列表
+ }
+ SysOperationRecordRouter.Use(middleware.OperationRecord())
+ {
+ SysOperationRecordRouter.POST("createSysOperationRecord", sys.CreateSysOperationRecord) // 新建SysOperationRecord
+ SysOperationRecordRouter.DELETE("deleteSysOperationRecord", sys.DeleteSysOperationRecord) // 删除SysOperationRecord
+ SysOperationRecordRouter.DELETE("deleteSysOperationRecordByIds", sys.DeleteSysOperationRecordByIds) // 批量删除SysOperationRecord
+
+ }
+}
diff --git a/router/sys_system.go b/router/sys_system.go
new file mode 100644
index 0000000..d0ced83
--- /dev/null
+++ b/router/sys_system.go
@@ -0,0 +1,17 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitSystemRouter(Router *gin.RouterGroup) {
+ SystemRouter := Router.Group("system").Use(middleware.OperationRecord())
+ {
+ SystemRouter.POST("getSystemConfig", sys.GetSystemConfig) // 获取配置文件内容
+ SystemRouter.POST("setSystemConfig", sys.SetSystemConfig) // 设置配置文件内容
+ SystemRouter.POST("reloadSystem", sys.ReloadSystem) // 重启服务
+ }
+}
diff --git a/router/sys_user.go b/router/sys_user.go
new file mode 100644
index 0000000..bf86e85
--- /dev/null
+++ b/router/sys_user.go
@@ -0,0 +1,21 @@
+package router
+
+import (
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitUser1Router(Router *gin.RouterGroup) {
+ UserRouter := Router.Group("suser").Use(middleware.OperationRecord())
+ {
+ UserRouter.POST("getUserList", sys.GetUserList) // 分页获取用户列表
+ UserRouter.GET("getUserSelectList", sys.GetUserSelectList) // 获取用户选择器列表
+ UserRouter.POST("setUserAuthority", sys.SetUserAuthority) // 设置用户权限
+ UserRouter.POST("setUserAuthorities", sys.SetUserAuthorities) // 设置用户权限组
+ UserRouter.DELETE("deleteUser", sys.DeleteUser) // 删除用户
+ UserRouter.PUT("setUserInfo", sys.SetUserInfo) // 设置用户信息
+ UserRouter.GET("getUserInfo", sys.GetUserInfo) // 获取自身信息
+ }
+}
diff --git a/router/tags.go b/router/tags.go
new file mode 100644
index 0000000..f9b91a5
--- /dev/null
+++ b/router/tags.go
@@ -0,0 +1,20 @@
+package router
+
+import (
+ v1 "pure-admin/api/admin"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitTagRouter(Router *gin.RouterGroup) {
+ TagRouter := Router.Group("tags").Use(middleware.OperationRecord())
+ {
+ TagRouter.GET("list", v1.GetTagsList) // 标签列表
+ TagRouter.POST("", v1.CreateTags) // 创建Tag
+ TagRouter.PUT("", v1.UpdateTags) // 更新Tag
+ TagRouter.DELETE("batchDelTags", v1.DelTagsByIds) // 删除Tag
+ TagRouter.GET("relationTags", v1.GetRelationTags) // 获取关联tag
+ TagRouter.POST("relationTags", v1.TagRelation) // 关联tag
+ }
+}
diff --git a/router/user.go b/router/user.go
new file mode 100755
index 0000000..5b671d9
--- /dev/null
+++ b/router/user.go
@@ -0,0 +1,27 @@
+package router
+
+import (
+ v1 "pure-admin/api/admin"
+ "pure-admin/api/sys"
+ "pure-admin/middleware"
+
+ "github.com/gin-gonic/gin"
+)
+
+func InitUserRouter(Router *gin.RouterGroup) {
+ UserRouter := Router.Group("user").Use(middleware.OperationRecord())
+ {
+ UserRouter.POST("register", sys.Register) // 用户注册账号
+ UserRouter.POST("createUser", v1.CreateUser) // 新建User
+ UserRouter.DELETE("deleteUser", v1.DeleteUser) // 删除User
+ UserRouter.DELETE("deleteUserByIds", v1.DeleteUserByIds) // 批量删除User
+ UserRouter.PUT("updateUser", v1.UpdateUser) // 更新User
+ UserRouter.GET("findUser", v1.FindUser) // 根据ID获取User
+ UserRouter.GET("getUserList", v1.GetUserList) // 获取User列表
+ UserRouter.POST("getUserList", v1.GetSysUserList) // 获取系统User列表
+ UserRouter.GET("platformAuth", v1.PlatformAuth) // 获取用户平台认证
+ UserRouter.PUT("platformAuth", v1.CheckPlatformAuth) // 用户平台认证
+ UserRouter.PUT("userStatus", v1.UpdateUserStatus) // 用户禁用启用
+ UserRouter.PUT("changePassword", sys.ChangePassword) // 用户修改密码
+ }
+}
diff --git a/router/wallet.go b/router/wallet.go
new file mode 100644
index 0000000..d0ab01a
--- /dev/null
+++ b/router/wallet.go
@@ -0,0 +1,13 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ v1 "pure-admin/api/admin"
+)
+
+func InitWalletRouter(Router *gin.RouterGroup) {
+ InRouter := Router.Group("wallet")
+ {
+ InRouter.POST("/fund/recharge", v1.FundRecharge) // 营销账户充值
+ }
+}
diff --git a/service/application.go b/service/application.go
new file mode 100755
index 0000000..2d66b63
--- /dev/null
+++ b/service/application.go
@@ -0,0 +1,80 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: CreateApplication
+//@description: 创建Application记录
+//@param: application model.Application
+//@return: err error
+
+func CreateApplication(application model.Application) (err error) {
+ err = global.MG_DB.Create(&application).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteApplication
+//@description: 删除Application记录
+//@param: application model.Application
+//@return: err error
+
+func DeleteApplication(application model.Application) (err error) {
+ err = global.MG_DB.Delete(&application).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteApplicationByIds
+//@description: 批量删除Application记录
+//@param: ids request.IdsReq
+//@return: err error
+
+func DeleteApplicationByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.Application{},"id in (?)",ids.Ids).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: UpdateApplication
+//@description: 更新Application记录
+//@param: application *model.Application
+//@return: err error
+
+func UpdateApplication(application model.Application) (err error) {
+ err = global.MG_DB.Updates(&application).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetApplication
+//@description: 根据id获取Application记录
+//@param: id uint
+//@return: err error, application model.Application
+
+func GetApplication(id uint) (err error, application model.Application) {
+ err = global.MG_DB.Where("id = ?", id).First(&application).Error
+ return
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetApplicationInfoList
+//@description: 分页获取Application记录
+//@param: info request.ApplicationSearch
+//@return: err error, list interface{}, total int64
+
+func GetApplicationInfoList(info request.ApplicationSearch) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.Application{})
+ var applications []model.Application
+ // 如果有条件搜索 下方会自动创建搜索语句
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&applications).Error
+ return err, applications, total
+}
\ No newline at end of file
diff --git a/service/banner.go b/service/banner.go
new file mode 100644
index 0000000..b08d7f6
--- /dev/null
+++ b/service/banner.go
@@ -0,0 +1,244 @@
+package service
+
+import (
+ "errors"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "strconv"
+)
+
+func AddBanner(q request.CreateBanner, userId string) error {
+
+ var (
+ err error
+ data *model.Banner
+ total int64
+ )
+
+ //查询数量限制
+ global.MG_DB.Model(&model.Banner{}).Where("status = ?", "1").Count(&total)
+
+ if total >= model.MAX_BANNER_LIMIT {
+ return errors.New(fmt.Sprintf("最多可新建%d条banner", model.MAX_BANNER_LIMIT))
+ }
+
+ data = &model.Banner{
+ Title: q.Title,
+ CoverUrl: q.CoverUrl,
+ LinkType: q.LinkType,
+ Status: q.Status,
+ CreateBy: userId,
+ UpdateBy: userId,
+ }
+ if q.LinkType == "1" {
+ data.Link = q.Link
+ } else {
+ data.Type = q.Type
+ if q.Type == "0101" {
+ //查询任务
+ data.RelationId = q.RelationId
+ data.RelationType = q.RelationType
+ }
+ }
+ _, maxSort := GetBannerMaxSort()
+ data.Sort = int(maxSort) + 1
+ data.ID = 0
+
+ err = data.New()
+ if err != nil {
+ return errors.New("创建banner失败")
+ }
+
+ return nil
+}
+
+func UpdateBanner(q request.UpdateBanner, uuid string) error {
+ var (
+ err error
+ check model.Banner
+ updateMap map[string]interface{}
+ )
+ err = global.MG_DB.Model(&model.Banner{}).Where("id = ?", q.ID).First(&check).Error
+ if err != nil {
+ return errors.New("数据不存在")
+ }
+ updateMap = map[string]interface{}{
+ "title": q.Title,
+ "relation_id": q.RelationId,
+ "relation_type": q.RelationType,
+ "cover_url": q.CoverUrl,
+ "link_type": q.LinkType,
+ "link": q.Link,
+ "status": q.Status,
+ "type": q.Type,
+ "update_by": uuid,
+ }
+
+ err = global.MG_DB.Model(&model.Banner{}).Where("id = ?", q.ID).Updates(updateMap).Error
+ if err != nil {
+ return errors.New("修改失败")
+ }
+
+ return nil
+}
+
+func GetBannerMaxSort() (err error, total int64) {
+ db := global.MG_DB.Model(&model.Banner{}).Select("IFNULL(MAX(sort),0)")
+ err = db.Scan(&(total)).Error
+ return
+}
+
+func DeleteBannerByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Model(model.Banner{}).Where("id in (?)", ids.Ids).Unscoped().Delete(&model.Banner{}).Error
+ if err != nil {
+ return errors.New("删除失败")
+ }
+
+ return err
+}
+
+func BannerUpData(info request.IdReq, c *gin.Context) error {
+ var (
+ err error
+ idList []global.BASE_ID_SORT
+ )
+
+ err, idList = GetBannerSortList()
+ if err != nil {
+ return err
+ }
+ for i := 1; i < len(idList); i++ {
+ if idList[i].ID == info.ID {
+ sort := idList[i].Sort
+ idList[i].Sort = idList[i-1].Sort
+ var tmp = make([]global.BASE_ID_SORT, 0)
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i].ID, Sort: idList[i].Sort})
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i-1].ID, Sort: sort})
+ err = updateBannerSort(tmp)
+ if err != nil {
+ return err
+ }
+ break
+ }
+ }
+
+ return err
+}
+
+func BannerDownData(info request.IdReq, c *gin.Context) error {
+ var (
+ err error
+ idList []global.BASE_ID_SORT
+ )
+
+ err, idList = GetBannerSortList()
+ if err != nil {
+ return err
+ }
+ for i := 0; i < len(idList); i++ {
+ if idList[i].ID == info.ID && i < len(idList)-1 {
+ sort := idList[i].Sort
+ idList[i].Sort = idList[i+1].Sort
+ var tmp = make([]global.BASE_ID_SORT, 0)
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i].ID, Sort: idList[i].Sort})
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i+1].ID, Sort: sort})
+ err = updateBannerSort(tmp)
+ if err != nil {
+ return err
+ }
+ break
+ }
+ }
+ return err
+}
+
+func GetBannerSortList() (err error, list []global.BASE_ID_SORT) {
+ db := global.MG_DB.Model(&model.Banner{})
+
+ var idList []global.BASE_ID_SORT
+ err = db.Select("id,sort").Order("sort desc").Find(&idList).Error
+ return err, idList
+}
+
+func updateBannerSort(idList []global.BASE_ID_SORT) error {
+ var err error
+ for _, val := range idList {
+ _ = UpdateBannerSort(val)
+ //_ = global.MG_DB.Model(&model.MissionRecommend{}).Where("id=?", val.ID).Update("sort", val.Sort).Error
+ }
+ return err
+}
+
+func UpdateBannerSort(val global.BASE_ID_SORT) error {
+ var err error
+ _ = global.MG_DB.Model(&model.Banner{}).Where("id=?", val.ID).Update("sort", val.Sort).Error
+ return err
+}
+
+func GetBannerList(info request.SearchBanner) (err error, list []response.BannerListResponse, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.Banner{})
+
+ if info.Title != "" {
+ db = db.Where("title LIKE ?", "%"+info.Title+"%")
+ }
+ if info.Status != "" {
+ db = db.Where("status = ?", info.Status)
+ }
+
+ err = db.Count(&total).Error
+ var res []model.Banner
+ var relationIds []int
+ var relationMission []model.Mission
+
+ err = db.Select("id,title,cover_url,relation_id,relation_type,link_type,link,type,status,sort").
+ Order("sort DESC").Limit(limit).Offset(offset).Find(&res).Error
+
+ if err != nil {
+ return err, nil, 0
+ }
+ for i := 0; i < len(res); i++ {
+ if res[i].LinkType == "0" && res[i].Type == "0101" && res[i].RelationId != "" {
+ rid, _ := strconv.Atoi(res[i].RelationId)
+ relationIds = append(relationIds, rid)
+ }
+ }
+
+ if len(relationIds) > 0 {
+ global.MG_DB.Model(&model.Mission{}).Select("id,title").Where("id in (?)", relationIds).Find(&relationMission)
+ }
+
+ for i := 0; i < len(res); i++ {
+ //精简字段赋值
+ vRes := response.BannerListResponse{
+ RelationId: res[i].RelationId,
+ RelationType: res[i].RelationType,
+ Title: res[i].Title,
+ CoverUrl: res[i].CoverUrl,
+ LinkType: res[i].LinkType,
+ Link: res[i].Link,
+ Type: res[i].Type,
+ Status: res[i].Status,
+ }
+
+ if len(relationMission) > 0 {
+ for _, r := range relationMission {
+ mid := strconv.Itoa(int(r.ID))
+ if mid == res[i].RelationId {
+ vRes.RelationTitle = r.Title
+ }
+ }
+ }
+
+ vRes.ID = res[i].ID
+ vRes.SortIndex = offset + i + 1
+ list = append(list, vRes)
+ }
+
+ return err, list, total
+}
diff --git a/service/bill.go b/service/bill.go
new file mode 100644
index 0000000..59a7577
--- /dev/null
+++ b/service/bill.go
@@ -0,0 +1,564 @@
+package service
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "gorm.io/gorm"
+ "pure-admin/global"
+ "pure-admin/initialize/api"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "pure-admin/utils"
+ "time"
+)
+
+func GetBillData(platform string, info *request.BillSearch) (error, response.BillData) {
+ var (
+ err error
+ money float64
+ total int64
+ result response.BillData
+ )
+ info.Status = 1
+ _, money, total = getBillAmount(platform, info)
+ result.Income = money
+ result.IncomeCount = total
+
+ info.Status = 2
+ _, money, total = getBillAmount(platform, info)
+ result.Expend = money
+ result.ExpendCount = total
+ return err, result
+}
+
+func getBillAmount(platform string, info *request.BillSearch) (error, float64, int64) {
+ var (
+ err error
+ result float64
+ total int64
+ )
+ db := global.MG_DB.Model(&model.Bill{})
+ if platform != "" {
+ db = db.Where("platform = ?", platform)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ if len(info.EndTime) == 10 {
+ tmp, _ := time.ParseInLocation(utils.DateFormat, info.EndTime, time.Local)
+ info.EndTime = tmp.AddDate(0, 0, 1).Format(utils.DateFormat)
+ }
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ if info.Receipt != 0 {
+ db = db.Where("receipt = ?", info.Receipt)
+ }
+ _ = db.Count(&total).Error
+ err = db.Select("IFNULL(SUM(`price`),0)").Scan(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ return nil, result, total
+}
+
+func GetBillList(platform string, info *request.BillSearch) (error, []model.BillList, int64) {
+ var (
+ err error
+ total int64
+ result []model.BillList
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.Bill{})
+ if platform != "" {
+ db = db.Where("platform = ?", platform)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ if len(info.EndTime) == 10 {
+ tmp, _ := time.ParseInLocation(utils.DateFormat, info.EndTime, time.Local)
+ info.EndTime = tmp.AddDate(0, 0, 1).Format(utils.DateFormat)
+ }
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ if info.Receipt != 0 {
+ db = db.Where("receipt = ?", info.Receipt)
+ }
+ _ = db.Count(&total).Error
+ err = db.Select("id,user_id,title,order_id,price,`status`,remark,platform,transaction_id,created_at,updated_at").Order("id desc").Offset(offset).Limit(limit).Find(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ var (
+ uuids []string
+ userMap map[string]model.UserView
+ )
+ for _, bill := range result {
+ uuids = append(uuids, bill.UserID)
+ }
+ userMap, err = getUserViewMap(uuids...)
+ if err != nil {
+ return errors.New("get user info fail"), result, total
+ }
+ for i := 0; i < len(result); i++ {
+ if val, ok := userMap[result[i].UserID]; ok {
+ result[i].User = val
+ }
+ }
+ return err, result, total
+}
+
+func GetInfluenceWithdrawalData(info *request.WithdrawalSearch) (error, response.InfluenceWithdrawalData) {
+ var (
+ err error
+ money float64
+ total int64
+ result response.InfluenceWithdrawalData
+ )
+ // 网红账户总额
+ _ = global.MG_DB.Model(&model.Wallet{}).Where("platform = 'influencer'").Select("IFNULL(SUM(`balance`),0)").Scan(&result.Balance).Error
+ _, money, total = getWithdrawalAmount("1", info)
+ result.Unexamined = money
+ result.UnexaminedCount = total
+ return err, result
+}
+func getWithdrawalAmount(platform string, info *request.WithdrawalSearch) (error, float64, int64) {
+ var (
+ err error
+ result float64
+ total int64
+ )
+ db := global.MG_DB.Model(&model.Withdrawal{})
+ if platform != "" {
+ db = db.Where("platform = ?", platform)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ if len(info.EndTime) == 10 {
+ tmp, _ := time.ParseInLocation(utils.DateFormat, info.EndTime, time.Local)
+ info.EndTime = tmp.AddDate(0, 0, 1).Format(utils.DateFormat)
+ }
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.CheckStatus != "" {
+ db = db.Where("check_status = ?", info.CheckStatus)
+ }
+ _ = db.Count(&total).Error
+ err = db.Select("IFNULL(SUM(`amount`),0)").Scan(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ return nil, result, total
+}
+func GetWithdrawalList(platform string, info *request.WithdrawalSearch) (error, []model.Withdrawal, int64) {
+ var (
+ err error
+ total int64
+ result []model.Withdrawal
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.Withdrawal{})
+ if platform != "" {
+ db = db.Where("platform = ?", platform)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ if len(info.EndTime) == 10 {
+ tmp, _ := time.ParseInLocation(utils.DateFormat, info.EndTime, time.Local)
+ info.EndTime = tmp.AddDate(0, 0, 1).Format(utils.DateFormat)
+ }
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.CheckStatus != "" {
+ db = db.Where("check_status = ?", info.CheckStatus)
+ }
+ _ = db.Count(&total).Error
+ err = db.Order("id desc").Offset(offset).Limit(limit).Find(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ return err, result, total
+}
+func ExamineWithdrawal(platform string, info *request.ExamineWithdrawal) error {
+ var (
+ err error
+ withdrawal model.Withdrawal
+ notifyContent string
+ )
+ err = global.MG_DB.Model(&model.Withdrawal{}).Where("platform = ? AND flow_no = ?", platform, info.FlowNo).First(&withdrawal).Error
+ if err != nil {
+ return err
+ }
+ if withdrawal.CheckStatus != "0" || withdrawal.Status != "0" {
+ return errors.New("withdrawal reply status error")
+ }
+
+ tx := global.MG_DB.Begin()
+ if info.CheckStatus == "1" { // 审核通过
+ err = tx.Model(&model.Withdrawal{}).Where("id = ?", withdrawal.ID).Updates(map[string]interface{}{"check_status": info.CheckStatus, "check_time": time.Now()}).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ notifyContent = "您的提现申请已通过!"
+ } else if info.CheckStatus == "2" { // 审核不通过
+ err = tx.Model(&model.Withdrawal{}).Where("id = ?", withdrawal.ID).Updates(map[string]interface{}{"check_status": info.CheckStatus, "remark": info.Remark, "check_time": time.Now()}).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ notifyContent = "您的提现申请未通过!"
+ } else {
+ tx.Rollback()
+ return errors.New("check status error")
+ }
+ // 更改申请账号钱包状态
+ err = tx.Model(&model.Wallet{}).Where("user_id = ?", withdrawal.CreateBy).UpdateColumn("state", "0").Error
+ if err != nil {
+ tx.Rollback()
+ return errors.New("update wallet state error")
+ }
+ tx.Commit()
+ if platform == "influencer" {
+ // 发送消息
+ _ = createOrUpdateNotify(request.CreateNotify{
+ UserId: withdrawal.CreateBy,
+ RelationType: "1",
+ RelationId: withdrawal.FlowNo,
+ Title: notifyContent,
+ })
+ }
+ return err
+}
+
+func UpdateWithdrawalStatus(info *request.TransferResult) error {
+ // 开始交易
+ for {
+ ok, err := global.MG_REDIS.SetNX("withdrawal-"+info.FlowNo, "used", 10*time.Second).Result()
+ if ok && err == nil {
+ // 获取成功
+ break
+ }
+ }
+ defer func() {
+ // 释放锁
+ _, _ = utils.RedisDel("withdrawal-" + info.FlowNo)
+ }()
+ var (
+ err error
+ notifyContent string
+ withdrawal model.Withdrawal
+ )
+ err = global.MG_DB.Model(&model.Withdrawal{}).Where("flow_no = ?", info.FlowNo).First(&withdrawal).Error
+ if err != nil {
+ return err
+ }
+ if withdrawal.Status != "0" {
+ err = errors.New("repeat operation")
+ return err
+ }
+ tx := global.MG_DB.Begin()
+ err = tx.Model(&model.Withdrawal{}).Where("id = ?", withdrawal.ID).UpdateColumns(map[string]interface{}{"status": info.Status, "remark": info.Remark}).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ // 恢复网红用户钱包状态
+ if withdrawal.Platform == "1" {
+ err = tx.Model(&model.Wallet{}).Where("user_id = ?", withdrawal.CreateBy).UpdateColumn("state", "0").Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ }
+ if info.Status == "1" {
+ notifyContent = "您的提现申请已通过!"
+ } else { // 审核不通过,将金额返还至用户余额
+ notifyContent = "您的提现申请未通过!"
+ err = tx.Model(&model.Wallet{}).Where("user_id = ?", withdrawal.CreateBy).UpdateColumn("balance", gorm.Expr("balance + ?", withdrawal.Amount)).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ }
+ tx.Commit()
+ if withdrawal.Platform == "1" {
+ // 发送消息
+ _ = createOrUpdateNotify(request.CreateNotify{
+ UserId: withdrawal.CreateBy,
+ RelationType: "1",
+ RelationId: withdrawal.FlowNo,
+ Title: notifyContent,
+ })
+ }
+ return err
+}
+
+//func TransferWithdrawalRetry(platform string, info *request.RetryWithdrawal) error {
+// var (
+// err error
+// transfer request.TransferWithdrawal
+// withdrawal model.Withdrawal
+// )
+// err = global.MG_DB.Model(&model.Withdrawal{}).Where("platform = ? AND flow_no = ?", platform, info.FlowNo).First(&withdrawal).Error
+// if err != nil {
+// return err
+// }
+// if withdrawal.CheckStatus != "1" || withdrawal.Status != "2" {
+// return errors.New("withdrawal reply status error")
+// }
+// transfer.AccountType = withdrawal.AccountType
+// transfer.AccountName = withdrawal.Account
+// transfer.Account = withdrawal.Account
+// transfer.Amount = withdrawal.Amount
+// transfer.FlowNo = withdrawal.FlowNo
+// err, payout := transferWithdrawal(&transfer)
+// err = global.MG_DB.Model(&model.Withdrawal{}).Where("id = ?", withdrawal.ID).UpdateColumn("pay_id", payout.PayId).Error
+// if err != nil {
+// return err
+// }
+// return err
+//}
+
+func transferWithdrawal(info *request.TransferWithdrawal) (error, *api.PayUrlResponse) {
+ var err error
+ attach, _ := json.Marshal(map[string]string{"transactionId": info.FlowNo})
+ params := api.PayoutRequest{
+ Appid: "bkb5918273465092837",
+ Mchid: "11000001",
+ OutTradeNo: info.FlowNo,
+ Attach: string(attach),
+ NotifyUrl: global.MG_CONFIG.Paypal.NotifyUrl + "/base/payment/payback",
+ Amount: info.Amount,
+ Currency: "USD",
+ PayChannel: info.AccountType,
+ PaypalName: info.AccountName,
+ }
+ payoutOrder, err := global.PAY_CLIENT.PayoutsAppUrl(context.Background(), ¶ms)
+ if err != nil {
+ fmt.Println(err.Error())
+ return errors.New("open " + info.AccountType + " failed"), nil
+ }
+ return err, payoutOrder
+}
+
+func GetSellerWithdrawalData(info *request.WithdrawalSearch) (error, response.SellerWithdrawalData) {
+ var (
+ err error
+ money float64
+ total int64
+ result response.SellerWithdrawalData
+ )
+ _ = global.MG_DB.Model(&model.Wallet{}).Where("platform = 'seller'").Select("IFNULL(SUM(`balance`),0)").Scan(&result.Balance).Error
+ _ = global.MG_DB.Model(&model.Wallet{}).Where("platform = 'seller'").Select("IFNULL(SUM(`fund`),0)").Scan(&result.Fund).Error
+
+ _, money, total = getWithdrawalAmount("2", info)
+ result.Unexamined = money
+ result.UnexaminedCount = total
+ return err, result
+}
+
+func getBillFundAmount(platform string, info *request.BillFundSearch) (error, float64, int64) {
+ var (
+ err error
+ result float64
+ total int64
+ )
+ db := global.MG_DB.Model(&model.BillFund{})
+ if platform != "" {
+ db = db.Where("platform = ?", platform)
+ }
+ if info.UserID != "" {
+ db = db.Where("user_id = ?", info.UserID)
+ }
+ if info.TransactionType != 0 {
+ db = db.Where("transaction_type = ?", info.TransactionType)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ if len(info.EndTime) == 10 {
+ tmp, _ := time.ParseInLocation(utils.DateFormat, info.EndTime, time.Local)
+ info.EndTime = tmp.AddDate(0, 0, 1).Format(utils.DateFormat)
+ }
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ _ = db.Count(&total).Error
+ err = db.Select("SUM(`price`)").Scan(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ return nil, result, total
+}
+
+func GetSellerFundData(info *request.BillFundSearch) (error, response.SellerFundData) {
+ var (
+ err error
+ money float64
+ total int64
+ result response.SellerFundData
+ )
+ _, money, total = getBillFundAmount("seller", info)
+ result.Expend = money
+ result.ExpendCount = total
+
+ _, money, total = getMissionFundLockAmount(&request.SearchMissionFund{})
+ result.Lock = money
+ result.LockCount = total
+ return err, result
+}
+
+func GetSellerFundList(platform string, info *request.BillFundSearch) (error, []model.SellerBillFund, int64) {
+ var (
+ err error
+ total int64
+ billFundList []model.BillFund
+ result []model.SellerBillFund
+ )
+ err, billFundList, total = getBillFundList(platform, info)
+ if err != nil {
+ return err, result, 0
+ }
+ var (
+ sellerIds []string
+ claimNos []string
+ )
+ for i := 0; i < len(billFundList); i++ {
+ sellerIds = append(sellerIds, billFundList[i].UserID)
+ claimNos = append(claimNos, billFundList[i].RelatedId)
+ result = append(result, model.SellerBillFund{
+ UserID: billFundList[i].UserID,
+ TransactionType: billFundList[i].TransactionType,
+ TransactionId: billFundList[i].TransactionId,
+ Price: billFundList[i].Price,
+ Remark: billFundList[i].Remark,
+ RelatedId: billFundList[i].RelatedId,
+ Status: billFundList[i].Status,
+ MG_MODEL: billFundList[i].MG_MODEL,
+ })
+ }
+ storeMap, _ := getSellerStoreMap(sellerIds)
+ claimMap, _ := getMissionClaimViewMap(claimNos)
+ for i := 0; i < len(result); i++ {
+ if val, ok := storeMap[result[i].UserID]; ok {
+ result[i].Seller = val
+ }
+ if val, ok := claimMap[result[i].RelatedId]; ok {
+ result[i].Mission = val
+ }
+ }
+ return err, result, total
+}
+
+func getBillFundList(platform string, info *request.BillFundSearch) (error, []model.BillFund, int64) {
+ var (
+ err error
+ total int64
+ result []model.BillFund
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.BillFund{})
+ if platform != "" {
+ db = db.Where("platform = ?", platform)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ if len(info.EndTime) == 10 {
+ tmp, _ := time.ParseInLocation(utils.DateFormat, info.EndTime, time.Local)
+ info.EndTime = tmp.AddDate(0, 0, 1).Format(utils.DateFormat)
+ }
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ _ = db.Count(&total).Error
+ err = db.Order("id desc").Offset(offset).Limit(limit).Find(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ return err, result, total
+}
+
+func GetBkbWithdrawalData(info *request.WithdrawalSearch) (error, response.BkbData) {
+ var (
+ err error
+ money float64
+ total int64
+ result response.BkbData
+ )
+
+ _, money, total = getWithdrawalAmount("", info)
+ result.Unexamined = money
+ result.UnexaminedCount = total
+ return err, result
+}
+
+func GetBkbFundData(info *request.BillFundSearch) (error, response.AdminFundData) {
+ var (
+ err error
+ money float64
+ total int64
+ result response.AdminFundData
+ )
+ info.TransactionType = 1
+ _, money, total = getBillFundAmount("bkb", info)
+ result.Expend = money
+ result.ExpendCount = total
+
+ info.TransactionType = 2
+ _, money, total = getBillFundAmount("bkb", info)
+ result.Recharge = money
+ result.RechargeCount = total
+ return err, result
+}
+
+func GetBkbFundList(platform string, info *request.BillFundSearch) (error, []model.AdminBillFund, int64) {
+ var (
+ err error
+ total int64
+ billFundList []model.BillFund
+ result []model.AdminBillFund
+ )
+ err, billFundList, total = getBillFundList(platform, info)
+ if err != nil {
+ return err, result, 0
+ }
+ for i := 0; i < len(billFundList); i++ {
+ result = append(result, model.AdminBillFund{
+ UserID: billFundList[i].UserID,
+ TransactionType: billFundList[i].TransactionType,
+ TransactionId: billFundList[i].TransactionId,
+ Price: billFundList[i].Price,
+ Remark: billFundList[i].Remark,
+ RelatedId: billFundList[i].RelatedId,
+ Status: billFundList[i].Status,
+ MG_MODEL: billFundList[i].MG_MODEL,
+ })
+ }
+ return err, result, total
+}
diff --git a/service/business.go b/service/business.go
new file mode 100755
index 0000000..e1722fe
--- /dev/null
+++ b/service/business.go
@@ -0,0 +1,144 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+)
+
+// @function: CreateBusiness
+// @description: 创建Business记录
+// @param: business model.Business
+// @return: err error
+func CreateBusiness(business model.SellerStore) (err error) {
+ err = global.MG_DB.Create(&business).Error
+ return err
+}
+
+func DeleteBusiness(business model.SellerStore) (err error) {
+ err = global.MG_DB.Delete(&business).Error
+ return err
+}
+
+func DeleteBusinessByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.SellerStore{}, "id in (?)", ids.Ids).Error
+ return err
+}
+
+func UpdateBusiness(business model.SellerStore) (err error) {
+ err = global.MG_DB.Updates(&business).Error
+ return err
+}
+
+func GetBusiness(id uint) (v response.StoreInfoItem, err error) {
+ var business model.SellerStore
+ err = global.MG_DB.Where("id = ?", id).First(&business).Error
+ if err != nil {
+ return
+ }
+ var (
+ wallet model.Wallet
+ account model.Account
+ )
+ err = global.MG_DB.Model(&model.Wallet{}).Where("user_id=?", business.StoreNo).Find(&wallet).Error
+ if err != nil {
+ return v, err
+ }
+ err = global.MG_DB.Model(&model.Account{}).Where("user_id=?", business.StoreNo).Find(&account).Error
+ if err != nil {
+ return v, err
+ }
+ return response.StoreInfoItem{
+ ID: business.ID,
+ StoreNo: business.StoreNo,
+ Type: business.Type,
+ Email: business.Email,
+ Phone: account.Phone,
+ PapaylName: account.AccountName,
+ ValidMissionNum: 0,
+ GoodsNum: 0,
+ Balance: wallet.Balance,
+ TransitBalance: wallet.TransitBalance,
+ }, nil
+}
+
+func GetBusinessInfoList(info request.PageInfo) (list any, total int64, err error) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.SellerStore{})
+ var businesss []model.SellerStoreInfo
+ // 如果有条件搜索 下方会自动创建搜索语句
+ err = db.Count(&total).Error
+ if err != nil {
+ return
+ }
+ err = db.Limit(limit).Offset(offset).Find(&businesss).Error
+ if err != nil {
+ return
+ }
+ return businesss, total, nil
+}
+
+func SearchBusiness(info request.SearchStore) (list any, total int64, err error) {
+ var (
+ bIds = make([]string, 0)
+ cIds = make([]string, 0)
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.SellerStore{})
+ if info.Email != "" {
+ db = db.Where("email = ?", info.Email)
+ }
+ if info.StoreNo != "" {
+ db = db.Where("store_no = ?", info.StoreNo)
+ }
+ if info.StartAt != "" && info.EndAt != "" {
+ db = db.Where("created_at between ? and ?", info.StartAt, info.EndAt)
+ }
+ var businesss []model.SellerStoreInfo
+ // 如果有条件搜索 下方会自动创建搜索语句
+ err = db.Count(&total).Error
+ if err != nil {
+ return
+ }
+ err = db.Limit(limit).Offset(offset).Find(&businesss).Error
+ if err != nil {
+ return
+ }
+ for _, v := range businesss {
+ bIds = append(bIds, v.StoreNo)
+ cIds = append(cIds, v.CreateBy)
+ }
+
+ var users []model.User
+ err = global.MG_DB.Model(&model.User{}).Where("uuid in (?)", cIds).Find(&users).Error
+ if err != nil {
+ return
+ }
+
+ //上架商品数
+ missionCount, err := model.GetMissionCount(bIds)
+ if err != nil {
+ return
+ }
+ //发布任务数
+ goodsCount, err := model.GetGoodsCount(bIds)
+ if err != nil {
+ return
+ }
+ for k, v := range businesss {
+ // businesss[k].ReleaseMissionNum = missionCount[v.StoreNo]
+ businesss[k].ValidMissionNum = missionCount[v.StoreNo]
+ businesss[k].GoodsNum = goodsCount[v.StoreNo]
+ for i, _ := range users {
+ if businesss[k].CreateBy == users[i].UUID.String() {
+ businesss[k].Phone = users[i].Phone
+ }
+ }
+ }
+ return businesss, total, err
+}
diff --git a/service/category.go b/service/category.go
new file mode 100644
index 0000000..31e69e1
--- /dev/null
+++ b/service/category.go
@@ -0,0 +1,233 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+func UpdateCategory(id uint, name string) error {
+ err := global.MG_DB.Model(&model.TbCategory{}).Where("id = ?", id).Update("name", name).Error
+
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type CategoryValue struct {
+ Id int64 `json:"id"`
+ Name string `json:"name"`
+}
+
+type CategoryItem struct {
+ Id int64 `json:"id"`
+ One CategoryValue `json:"one"`
+ Two CategoryValue `json:"two"`
+ Three CategoryValue `json:"three"`
+ Four CategoryValue `json:"four"`
+ Five CategoryValue `json:"five"`
+}
+
+func ListCategoryMap() (m map[int64]*model.CategoryParentTree, err error) {
+ var all []model.TbCategory
+ m = make(map[int64]*model.CategoryParentTree)
+ err = global.MG_DB.Model(&model.TbCategory{}).Where("`status` = 1").Order("layer DESC").Find(&all).Error
+
+ if err != nil {
+ return nil, err
+ }
+
+ for _, v := range all {
+ m[int64(v.ID)] = &model.CategoryParentTree{v, nil}
+ }
+ return m, nil
+}
+
+func GetCategoryTree(id int64) (vos CategoryItem, err error) {
+ var v model.TbCategory
+ db := global.MG_DB.Model(&model.TbCategory{}).Where("`status` = 1").Order("layer DESC")
+
+ err = db.Order("layer DESC").First(&v).Error
+ if err != nil {
+ return
+ }
+ m, err := ListCategoryMap()
+ if err != nil {
+ return
+ }
+
+ tmps := make([]model.CategoryParentTree, 0)
+ var cp = model.CategoryParentTree{v, nil}
+ _ = getCategoryParent(&cp, m)
+ var item = CategoryItem{
+ Id: int64(v.ID),
+ }
+ if cp.Parent != nil {
+ transferCategoryParents(&cp, &tmps)
+ } else {
+ item.One = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ }
+ for _, v := range tmps {
+ switch v.Layer {
+ case 5:
+ item.Five = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 4:
+ item.Four = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 3:
+ item.Three = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 2:
+ item.Two = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 1:
+ item.One = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ }
+ }
+
+ return item, nil
+}
+
+func ListCategoryTree(req request.ListCategoryPage) (vos []CategoryItem, total int64, err error) {
+ offset := req.PageSize * (req.Page - 1)
+ var list []model.TbCategory
+ db := global.MG_DB.Model(&model.TbCategory{}).Where("`status` = 1")
+
+ if req.One != ""{
+ db = db.Where("name LIKE ? AND layer=1", "%"+req.One+"%")
+ }
+ if req.Two != ""{
+ db = db.Where("name LIKE ? AND layer=2", "%"+req.Two+"%")
+ }
+ if req.Three != ""{
+ db = db.Where("name LIKE ? AND layer=3", "%"+req.Three+"%")
+ }
+ if req.Four != ""{
+ db = db.Where("name LIKE ? AND layer=4", "%"+req.Four+"%")
+ }
+ if req.Five != ""{
+ db = db.Where("name LIKE ? AND layer=5", "%"+req.Five+"%")
+ }
+ err = db.Count(&total).Error
+ if err != nil {
+ return nil, 0, err
+ }
+ err = db.Offset(offset).Order("id DESC").Limit(req.PageSize).Find(&list).Error
+ if err != nil {
+ return nil, 0, err
+ }
+ m, err := ListCategoryMap()
+ if err != nil {
+ return nil, 0, err
+ }
+
+ vos = make([]CategoryItem, 0)
+
+ for _, v := range list {
+
+ tmps := make([]model.CategoryParentTree, 0)
+ var cp = model.CategoryParentTree{v, nil}
+ _ = getCategoryParent(&cp, m)
+ var item = CategoryItem{
+ Id: int64(v.ID),
+ }
+ if cp.Parent != nil {
+ transferCategoryParents(&cp, &tmps)
+ } else {
+ item.One = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ }
+ for _, v := range tmps {
+ switch v.Layer {
+ case 5:
+ item.Five = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 4:
+ item.Four = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 3:
+ item.Three = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 2:
+ item.Two = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ case 1:
+ item.One = CategoryValue{
+ Id: int64(v.ID),
+ Name: v.Name,
+ }
+ }
+ }
+ vos = append(vos, item)
+ }
+
+ return vos, total, nil
+}
+
+func getCategoryParent(v *model.CategoryParentTree, m map[int64]*model.CategoryParentTree) (err error) {
+ if v==nil || v.Pid == 0 {
+ return nil
+ }
+ v.Parent = m[int64(v.Pid)]
+
+ err = getCategoryParent(v.Parent, m)
+ return err
+}
+
+func transferCategoryParents(info *model.CategoryParentTree, list *[]model.CategoryParentTree) {
+ if info.ID != 0 {
+ *list = append(*list, *info)
+ if info.Parent != nil {
+ transferCategoryParents(info.Parent, list)
+ }
+ }
+}
+
+func DeleteCategory(ids []int) error {
+ var category model.TbCategory
+ err := global.MG_DB.Where(" id IN (?)", ids).Delete(&category).Error
+ return err
+}
+
+func CreateCategory(name string, pid, layer int) error {
+ global.MG_DB.Model(&model.TbCategory{}).Where("id = ?", pid).Update("is_leaf", false)
+ err := global.MG_DB.Create(&model.TbCategory{
+ Name: name,
+ Layer: layer,
+ Pid: uint(pid),
+ IsLeaf: true,
+ Status: 1,
+ }).Error
+ return err
+}
+
+func ListCategoryChildren(pid int) (list []model.TbCategory, err error) {
+ err = global.MG_DB.Model(&model.TbCategory{}).Where("pid = ?", pid).Find(&list).Error
+ return list, err
+}
diff --git a/service/chain.go b/service/chain.go
new file mode 100644
index 0000000..3f11d24
--- /dev/null
+++ b/service/chain.go
@@ -0,0 +1,28 @@
+package service
+
+import (
+ "math/rand"
+ "pure-admin/model/response"
+ "pure-admin/utils"
+ "time"
+)
+
+func GetChainInfo(address string) (error, []response.ChainResp) {
+ var (
+ err error
+ result []response.ChainResp
+ )
+ now := time.Now()
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ for i := 0; i < 3; i++ {
+ intn := r.Intn(1000)
+ tmp := response.ChainResp{
+ Time: &now,
+ BlockHeight: 28741283 + int64(intn),
+ BlockAddress: utils.GetInvitationLowerLen(64),
+ Hash: utils.GetInvitationLowerLen(64),
+ }
+ result = append(result, tmp)
+ }
+ return err, result
+}
diff --git a/service/dict.go b/service/dict.go
new file mode 100644
index 0000000..37d4441
--- /dev/null
+++ b/service/dict.go
@@ -0,0 +1,40 @@
+package service
+
+import (
+ "fmt"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+func GetSysDictDataLabel(typeCode, value string) string {
+ var res string
+
+ cacheKey := "bkb-admin_sys_dict_labels"
+ labelKey := fmt.Sprintf("%s_%s", typeCode, value)
+ res = RedisHashGet(cacheKey, labelKey)
+ if res == "" {
+ _ = global.MG_DB.Model(&model.SysDictData{}).Select("label").Where("type_code=? AND `value`=?", typeCode, value).Scan(&res).Error
+ go RedisHashSet(cacheKey, labelKey, res)
+ }
+ return res
+}
+
+func GetSysDictDataList(params request.SearchDictDataParams) (err error, list interface{}) {
+ var lists []model.SysDictDataView
+ err = global.MG_DB.Model(&model.SysDictData{}).Where("type_code=? AND `status`='1'", params.TypeCode).Order("sort asc").Find(&lists).Error
+ return err, lists
+}
+
+func GetSysDictDataListByTypeCode(typeCode string) (list []model.SysDictDataView, err error) {
+ var lists []model.SysDictDataView
+ err = global.MG_DB.Model(&model.SysDictData{}).Where("type_code=? AND `status`='1'", typeCode).Order("sort asc").Find(&lists).Error
+ return lists, err
+}
+
+func checkSysDictData(typeCode, value string) bool {
+ if GetSysDictDataLabel(typeCode, value) != "" {
+ return true
+ }
+ return false
+}
diff --git a/service/exa_breakpoint_continue.go b/service/exa_breakpoint_continue.go
new file mode 100644
index 0000000..59aeb6f
--- /dev/null
+++ b/service/exa_breakpoint_continue.go
@@ -0,0 +1,57 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+
+ "gorm.io/gorm"
+)
+
+//@function: FindOrCreateFile
+//@description: 上传文件时检测当前文件属性,如果没有文件则创建,有则返回文件的当前切片
+//@param: fileMd5 string, fileName string, chunkTotal int
+//@return: err error, file model.ExaFile
+
+func FindOrCreateFile(fileMd5 string, fileName string, chunkTotal int) (err error, file model.ExaFile) {
+ var cfile model.ExaFile
+ cfile.FileMd5 = fileMd5
+ cfile.FileName = fileName
+ cfile.ChunkTotal = chunkTotal
+
+ if errors.Is(global.MG_DB.Where("file_md5 = ? AND is_finish = ?", fileMd5, true).First(&file).Error, gorm.ErrRecordNotFound) {
+ err = global.MG_DB.Where("file_md5 = ? AND file_name = ?", fileMd5, fileName).Preload("ExaFileChunk").FirstOrCreate(&file, cfile).Error
+ return err, file
+ }
+ cfile.IsFinish = true
+ cfile.FilePath = file.FilePath
+ err = global.MG_DB.Create(&cfile).Error
+ return err, cfile
+}
+
+//@function: CreateFileChunk
+//@description: 创建文件切片记录
+//@param: id uint, fileChunkPath string, fileChunkNumber int
+//@return: error
+
+func CreateFileChunk(id uint, fileChunkPath string, fileChunkNumber int) error {
+ var chunk model.ExaFileChunk
+ chunk.FileChunkPath = fileChunkPath
+ chunk.ExaFileID = id
+ chunk.FileChunkNumber = fileChunkNumber
+ err := global.MG_DB.Create(&chunk).Error
+ return err
+}
+
+//@function: DeleteFileChunk
+//@description: 删除文件切片记录
+//@param: fileMd5 string, fileName string, filePath string
+//@return: error
+
+func DeleteFileChunk(fileMd5 string, fileName string, filePath string) error {
+ var chunks []model.ExaFileChunk
+ var file model.ExaFile
+ err := global.MG_DB.Where("file_md5 = ? AND file_name = ?", fileMd5, fileName).First(&file).Update("IsFinish", true).Update("file_path", filePath).Error
+ err = global.MG_DB.Where("exa_file_id = ?", file.ID).Delete(&chunks).Unscoped().Error
+ return err
+}
diff --git a/service/exa_excel_parse.go b/service/exa_excel_parse.go
new file mode 100644
index 0000000..de499bd
--- /dev/null
+++ b/service/exa_excel_parse.go
@@ -0,0 +1,92 @@
+package service
+
+import (
+ "errors"
+ "fmt"
+ "pure-admin/global"
+ "pure-admin/model"
+ "strconv"
+
+ "github.com/xuri/excelize/v2"
+)
+
+func ParseInfoList2Excel(infoList []model.SysBaseMenu, filePath string) error {
+ excel := excelize.NewFile()
+ excel.SetSheetRow("Sheet1", "A1", &[]string{"ID", "路由Name", "路由Path", "是否隐藏", "父节点", "排序", "文件名称"})
+ for i, menu := range infoList {
+ axis := fmt.Sprintf("A%d", i+2)
+ excel.SetSheetRow("Sheet1", axis, &[]interface{}{
+ menu.ID,
+ menu.Name,
+ menu.Path,
+ menu.Hidden,
+ menu.ParentId,
+ menu.Sort,
+ menu.Component,
+ })
+ }
+ err := excel.SaveAs(filePath)
+ return err
+}
+
+func ParseExcel2InfoList() ([]model.SysBaseMenu, error) {
+ skipHeader := true
+ fixedHeader := []string{"ID", "路由Name", "路由Path", "是否隐藏", "父节点", "排序", "文件名称"}
+ file, err := excelize.OpenFile(global.MG_CONFIG.Excel.Dir + "ExcelImport.xlsx")
+ if err != nil {
+ return nil, err
+ }
+ menus := make([]model.SysBaseMenu, 0)
+ rows, err := file.Rows("Sheet1")
+ if err != nil {
+ return nil, err
+ }
+ for rows.Next() {
+ row, err := rows.Columns()
+ if err != nil {
+ return nil, err
+ }
+ if skipHeader {
+ if compareStrSlice(row, fixedHeader) {
+ skipHeader = false
+ continue
+ } else {
+ return nil, errors.New("Excel格式错误")
+ }
+ }
+ if len(row) != len(fixedHeader) {
+ continue
+ }
+ id, _ := strconv.Atoi(row[0])
+ hidden, _ := strconv.ParseBool(row[3])
+ sort, _ := strconv.Atoi(row[5])
+ menu := model.SysBaseMenu{
+ MG_MODEL: global.MG_MODEL{
+ ID: uint(id),
+ },
+ Name: row[1],
+ Path: row[2],
+ Hidden: hidden,
+ ParentId: row[4],
+ Sort: sort,
+ Component: row[6],
+ }
+ menus = append(menus, menu)
+ }
+ return menus, nil
+}
+
+func compareStrSlice(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ if (b == nil) != (a == nil) {
+ return false
+ }
+ for key, value := range a {
+ if value != b[key] {
+ return false
+ }
+ }
+ return true
+}
diff --git a/service/goods.go b/service/goods.go
new file mode 100644
index 0000000..2ca7fdc
--- /dev/null
+++ b/service/goods.go
@@ -0,0 +1,111 @@
+package service
+
+import (
+ "time"
+
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+)
+
+func ListGoods(info request.PageInfo) (list any, total int64, err error) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.TbGoods{})
+
+ err = db.Count(&total).Error
+ if err != nil {
+ return nil, 0, err
+ }
+ var res []model.TbGoods4List
+ err = db.Select("id,title,tags,retail_price,price_min,price_max,stock,sales,online,created_at").Order("id DESC").Limit(limit).Offset(offset).Find(&res).Error
+
+ if err != nil {
+ return nil, 0, err
+ }
+ return res, total, nil
+}
+
+func SearchGoods(info request.SearchGoods) (res any, total int64, err error) {
+ var (
+ bIds = make([]string, 0)
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.TbGoods{})
+ if info.Id != 0 {
+ db = db.Where("id = ?", info.Id)
+ }
+ if info.Title != "" {
+ db = db.Where("title like ?", "%"+info.Title+"%")
+ }
+
+ //取消账号,使用邮箱搜索
+ if info.StoreNo != "" {
+ //db = db.Where("store_no = ?", info.StoreNo)
+ db = db.Joins("join seller_store on seller_store.store_no = tb_goods.store_no").Where("seller_store.email like ?", "%"+info.StoreNo+"%")
+
+ }
+ if info.Status == 1 { // on/off
+ db = db.Where("online = ?", "on")
+ }
+ if info.Status == 2 { // on/off
+ db = db.Where("stock = 0")
+ }
+ if info.Status == 3 { // on/off
+ db = db.Where("online = ?", "off")
+ }
+ err = db.Count(&total).Error
+ if err != nil {
+ return nil, 0, err
+ }
+ var list []model.TbGoods
+ err = db.Select("tb_goods.id,title,retail_price,cover,tags,tb_goods.stock,tb_goods.sales,tb_goods.store_no,tb_goods.created_at,tb_goods.price_min,tb_goods.price_max").Order("id DESC").Limit(limit).Offset(offset).Find(&list).Error
+
+ if err != nil {
+ return nil, 0, err
+ }
+ resp := make([]response.Goods, 0)
+ for _, v := range list {
+ bIds = append(bIds, v.StoreNo)
+ resp = append(resp, response.Goods{
+ Id: v.ID,
+ Cover: v.Cover,
+ Title: v.Title,
+ RetailPrice: v.RetailPrice,
+ Tags: v.Tags,
+ Stock: v.Stock,
+ Sales: v.Sales,
+ StoreNo: v.StoreNo,
+ CreatedAt: v.CreatedAt,
+ PriceMax: v.PriceMax,
+ PriceMin: v.PriceMin,
+ })
+ }
+ // 获取商家信息
+ business, err := model.GetBusinessEmail(bIds)
+ if err != nil {
+ return nil, 0, err
+ }
+ for k, v := range resp {
+ resp[k].StoreNo = business[v.StoreNo]
+ }
+ return resp, total, nil
+}
+
+func GetGoodsVisitCount() (total, yesterday int64) {
+ global.MG_DB.Model(&model.GoodsVisit{}).Count(&total)
+ day := time.Now().AddDate(0, 0, -1)
+ now := time.Now()
+ global.MG_DB.Model(&model.GoodsVisit{}).Where("created_at BETWEEN ? AND ?", day.Format("2006-01-02 00:00:00"), now.Format("2006-01-02 00:00:00")).Count(&yesterday)
+ return
+}
+
+func GetGoodsCount() (total, yesterday int64) {
+ global.MG_DB.Model(&model.TbGoods{}).Count(&total)
+ day := time.Now().AddDate(0, 0, -1)
+ now := time.Now()
+ global.MG_DB.Model(&model.TbGoods{}).Where("created_at BETWEEN ? AND ?", day.Format("2006-01-02 00:00:00"), now.Format("2006-01-02 00:00:00")).Count(&yesterday)
+ return
+}
diff --git a/service/influencer_user.go b/service/influencer_user.go
new file mode 100644
index 0000000..6d842c0
--- /dev/null
+++ b/service/influencer_user.go
@@ -0,0 +1,41 @@
+package service
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "pure-admin/global"
+ "pure-admin/model"
+)
+
+func GetInfluencerUserDetail(userID string) (err error, userInfo *model.InfluencerUserDesc) {
+ var (
+ user model.InfluencerUserDesc
+ platform []model.Platform
+ dictData []model.SysDictData
+ )
+ err = global.MG_DB.Model(&model.SysDictData{}).Where("type_code=?", "release_channel").Find(&dictData).Error
+ if err != nil {
+ return errors.New("获取用户失败"), nil
+ }
+ err = global.MG_DB.Model(&model.User{}).Where("uuid=?", userID).Find(&user).Error
+ if err != nil {
+ return errors.New("获取用户失败"), nil
+ }
+ err = json.Unmarshal([]byte(user.Platform), &platform)
+ if err != nil {
+ fmt.Println(err)
+ }
+ for j := 0; j < len(platform); j++ {
+ for k := 0; k < len(dictData); k++ {
+ if platform[j].Platform == dictData[k].Value {
+ platform[j].PlatformName = dictData[k].Label
+ }
+
+ }
+ }
+
+ user.PlatformStr = platform
+ //user.TagsStr = tags
+ return nil, &user
+}
diff --git a/service/jwt_black_list.go b/service/jwt_black_list.go
new file mode 100644
index 0000000..734d610
--- /dev/null
+++ b/service/jwt_black_list.go
@@ -0,0 +1,53 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "time"
+
+ "gorm.io/gorm"
+)
+
+//@function: JsonInBlacklist
+//@description: 拉黑jwt
+//@param: jwtList model.JwtBlacklist
+//@return: err error
+
+func JsonInBlacklist(jwtList model.JwtBlacklist) (err error) {
+ err = global.MG_DB.Create(&jwtList).Error
+ return
+}
+
+//@function: IsBlacklist
+//@description: 判断JWT是否在黑名单内部
+//@param: jwt string
+//@return: bool
+
+func IsBlacklist(jwt string) bool {
+ err := global.MG_DB.Where("jwt = ?", jwt).First(&model.JwtBlacklist{}).Error
+ isNotFound := errors.Is(err, gorm.ErrRecordNotFound)
+ return !isNotFound
+}
+
+//@function: GetRedisJWT
+//@description: 从redis取jwt
+//@param: userName string
+//@return: err error, redisJWT string
+
+func GetRedisJWT(userName string) (err error, redisJWT string) {
+ redisJWT, err = global.MG_REDIS.Get(userName).Result()
+ return err, redisJWT
+}
+
+//@function: SetRedisJWT
+//@description: jwt存入redis并设置过期时间
+//@param: jwt string, userName string
+//@return: err error
+
+func SetRedisJWT(jwt string, userName string) (err error) {
+ // 此处过期时间等于jwt过期时间
+ timer := time.Duration(global.MG_CONFIG.JWT.ExpiresTime) * time.Second
+ err = global.MG_REDIS.Set(userName, jwt, timer).Err()
+ return err
+}
diff --git a/service/mission.go b/service/mission.go
new file mode 100644
index 0000000..1c45d6d
--- /dev/null
+++ b/service/mission.go
@@ -0,0 +1,1309 @@
+package service
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "gorm.io/gorm/clause"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "pure-admin/utils"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "gorm.io/gorm"
+)
+
+func GetMissionDetail(id uint) (error, model.MissionDetail) {
+ var (
+ err error
+ result model.MissionDetail
+ )
+ //err = global.MG_DB.Model(&model.Mission{}).
+ err = global.MG_DB.Model(&model.MissionDetail{}).
+ Select("id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,claim_num,start_time,end_time,`status`").
+ Where("id = ?", id).
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,retail_price,price_min,price_max,stock,sales,online,created_at,updated_at")
+ }).Preload("Seller").First(&result).Error
+ if err != nil {
+ return err, result
+ }
+ switch result.HireType {
+ case 1:
+ result.HireMoneyExpect = fmt.Sprintf("%.f", result.HireMoney)
+ case 2:
+ result.HireMoneyExpect = fmt.Sprintf("%.f", result.HireRatio)
+ }
+ //视频素材
+ if result.HasVideo == 1 {
+ result.ReleaseCountry = GetSysDictDataLabel(model.ReleaseCountryCode, result.VideoCountryId)
+ var channelNames []string
+ for _, cid := range strings.Split(result.VideoChannelIds, ",") {
+ vLabel := GetSysDictDataLabel(model.ReleaseChannelCode, cid)
+ channelNames = append(channelNames, vLabel)
+ }
+ result.ReleaseChannels = strings.Join(channelNames, "/")
+ }
+ //任务数
+ missionCountParam := request.SearchMission{SellerId: result.CreateBy}
+ err, result.Seller.ReleaseMissionNum = GetMissionCount(missionCountParam)
+ missionCountParam.Status = 2
+ err, result.Seller.ValidMissionNum = GetMissionCount(missionCountParam)
+ //任务视频数量
+ err, result.VideoClaimNum = GetValidMissionVideoCount(result.ID)
+
+ //todo 结束任务
+
+ return nil, result
+}
+
+func GetMissionList(info request.SearchMission) (err error, list []model.MissionDetail, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.Mission{})
+ if info.SellerId != "" {
+ db = db.Where("create_by = ?", info.SellerId)
+ }
+
+ if info.SellerEmail != "" {
+ db = db.Joins("join seller_store on seller_store.create_by = mission.create_by").Where("seller_store.email like ?", "%"+info.SellerEmail+"%")
+ }
+
+ if info.HireType > 0 {
+ db = db.Where("mission.hire_type = ?", info.HireType)
+ }
+
+ if info.VideoChannelId != "" {
+ db = db.Where(fmt.Sprintf("find_in_set(%s,video_channel_ids)", info.VideoChannelId))
+ }
+
+ if info.Title != "" {
+ db = db.Where("mission.title LIKE ?", "%"+info.Title+"%")
+ }
+ if info.Status != 0 {
+ db = db.Where("mission.status = ?", info.Status)
+ }
+ if info.MissionTime != "" {
+ var tmpT time.Time
+ tmpT, err = time.ParseInLocation(utils.DateFormat, info.MissionTime, time.UTC)
+ if err != nil {
+ err = errors.New("search mission time format error")
+ return err, nil, 0
+ }
+ db = db.Where("DATE_FORMAT(start_time,'%Y-%m-%d') <= ? AND end_time >= ?", tmpT, tmpT)
+ }
+
+ //是否有视频
+ switch info.HasVideo {
+ case 1:
+ db = db.Where("has_video = 1")
+ case 2:
+ db = db.Where("has_video = 0")
+ }
+
+ err = db.Count(&total).Error
+ var res []model.MissionDetail
+ err = db.Select("mission.id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,claim_num,start_time,end_time,mission.`status`,has_video,video_channel_ids,video_country_id,claim_days,mission.create_by,claim_stock,video_url,description,has_sample,sample_num").
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,retail_price,price_min,price_max,stock,sales,online,created_at,updated_at")
+ }).Preload("Seller").Order("id DESC").Limit(limit).Offset(offset).Find(&res).Error
+ if err != nil {
+ return err, nil, 0
+ }
+ for i := 0; i < len(res); i++ {
+ //查询订单数
+ switch res[i].HireType {
+ case 1:
+ res[i].HireMoneyExpect = fmt.Sprintf("视频拍摄%.f美元/单,总共%d笔", res[i].HireMoney, res[i].ClaimStock)
+ case 2:
+ res[i].HireMoneyExpect = fmt.Sprintf("订单金额%.f%%", res[i].HireRatio)
+ }
+ //发布国家
+ if res[i].HasVideo == 1 {
+ res[i].ReleaseCountry = GetSysDictDataLabel(model.ReleaseCountryCode, res[i].VideoCountryId)
+ var channelNames []string
+ for _, cid := range strings.Split(res[i].VideoChannelIds, ",") {
+ vLabel := GetSysDictDataLabel(model.ReleaseChannelCode, cid)
+ channelNames = append(channelNames, vLabel)
+ }
+ res[i].ReleaseChannels = strings.Join(channelNames, "/")
+ }
+ //任务视频数量
+ _, res[i].VideoClaimNum = GetValidMissionVideoCount(res[i].ID)
+ res[i].Tags = GetTagNamesByRelation(strconv.Itoa(int(res[i].ID)), "01")
+ }
+ return err, res, total
+}
+
+func GetMissionCount(info request.SearchMission) (err error, total int64) {
+ db := global.MG_DB.Model(&model.Mission{})
+ if info.SellerId != "" {
+ db = db.Where("create_by = ?", info.SellerId)
+ }
+ if info.Status != 0 {
+ db = db.Where("status = ?", info.Status)
+ }
+ err = db.Count(&total).Error
+
+ return err, total
+}
+
+func GetValidMissionVideoCount(missionId uint) (err error, total int64) {
+ db := global.MG_DB.Model(&model.MissionClaimVideo{})
+ db = db.Where("mission_id = ?", missionId)
+ err = db.Count(&total).Error
+
+ return err, total
+}
+
+func GetMissionVideoList(info request.SearchMissionVideo) (err error, list []response.MissionVideoResponse, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.MissionClaimVideo{}).
+ Joins("inner join mission on mission.id = mission_claim_video.mission_id")
+ //Where("mission_claim_video.status = 2")//审核通过状态
+ if info.VideoStatus != 0 {
+ db = db.Where("mission_claim_video.status = ?", info.VideoStatus)
+ }
+
+ if info.SellerId != "" {
+ db = db.Where("mission.create_by = ?", info.SellerId)
+ }
+
+ if info.SellerEmail != "" {
+ db = db.Joins("join seller_store on seller_store.create_by = mission.create_by").Where("seller_store.email like ?", "%"+info.SellerEmail+"%")
+ }
+
+ if info.MissionClaimBy != "" {
+ db = db.Where("mission_claim_video.create_by = ?", info.MissionClaimBy)
+ }
+
+ if info.SourceType != 0 {
+ db = db.Where("mission_claim_video.source_type = ?", info.SourceType)
+ }
+
+ if info.HireType > 0 {
+ db = db.Where("mission.hire_type = ?", info.HireType)
+ }
+
+ if info.Title != "" {
+ db = db.Where("mission.title LIKE ?", "%"+info.Title+"%")
+ }
+ if info.Status != 0 {
+ db = db.Where("mission.status = ?", info.Status)
+ }
+ if info.MissionId != 0 {
+ db = db.Where("mission.id = ?", info.MissionId)
+ }
+
+ err = db.Count(&total).Error
+ var res []model.MissionClaimVideoDetail
+
+ err = db.Preload("Mission", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,claim_num,start_time,end_time,`status`,has_video,video_channel_ids,video_country_id,claim_days,create_by,claim_stock").
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,retail_price,price_min,price_max,stock,sales,online,created_at,updated_at")
+ }).Preload("Seller")
+ }).Preload("Influencer", func(db *gorm.DB) *gorm.DB {
+ return db.Select("uuid,nick_name,phone")
+ }).Order("id DESC").Limit(limit).Offset(offset).Find(&res).Error
+
+ if err != nil {
+ return err, nil, 0
+ }
+ for i := 0; i < len(res); i++ {
+ //查询订单数
+ switch res[i].Mission.HireType {
+ case 1:
+ res[i].Mission.HireMoneyExpect = fmt.Sprintf("视频拍摄%.f美元/单,总共%d笔", res[i].Mission.HireMoney, res[i].Mission.ClaimStock)
+ case 2:
+ res[i].Mission.HireMoneyExpect = fmt.Sprintf("订单金额%.f%%", res[i].Mission.HireRatio)
+ }
+ //发布国家
+ if res[i].Mission.HasVideo == 1 {
+ res[i].Mission.ReleaseCountry = GetSysDictDataLabel(model.ReleaseCountryCode, res[i].Mission.VideoCountryId)
+ var channelNames []string
+ for _, cid := range strings.Split(res[i].Mission.VideoChannelIds, ",") {
+ vLabel := GetSysDictDataLabel(model.ReleaseChannelCode, cid)
+ channelNames = append(channelNames, vLabel)
+ }
+ res[i].Mission.ReleaseChannels = strings.Join(channelNames, "/")
+ }
+
+ //精简字段赋值
+ vRes := response.MissionVideoResponse{
+ ID: res[i].ID,
+ SourceType: res[i].SourceType,
+ MissionVideoCommonData: response.MissionVideoCommonData{
+ MissionId: res[i].MissionId,
+ VideoUrl: res[i].VideoUrl,
+ Cover: res[i].Cover,
+ Title: res[i].Mission.Title,
+ GoodsTitle: res[i].Mission.Goods.Title,
+ Influencer: res[i].Influencer,
+ Tag: "",
+ MissionStatus: res[i].Mission.Status,
+ StartTime: res[i].Mission.StartTime,
+ EndTime: res[i].Mission.EndTime,
+ HireType: res[i].Mission.HireType,
+ HireMoney: res[i].Mission.HireMoney,
+ HireRatio: res[i].Mission.HireRatio,
+ HireMoneyExpect: res[i].Mission.HireMoneyExpect,
+ ReleaseCountry: res[i].Mission.ReleaseCountry,
+ ReleaseChannels: res[i].Mission.ReleaseChannels,
+ Seller: res[i].Mission.Seller,
+ },
+ }
+ vRes.MissionVideoCommonData.Tag = GetTagNamesByRelation(strconv.Itoa(int(vRes.ID)), "02")
+ list = append(list, vRes)
+ }
+ return err, list, total
+}
+
+func AddMissionVideo(uuid string, info request.AddMissionVideo) (err error) {
+ var (
+ mission model.Mission
+ claimVideo model.MissionClaimVideo
+ tagCheck model.Tags
+ tagRelation model.TagRelation
+ )
+ err = global.MG_DB.Model(&model.Mission{}).Where("id = ?", info.MissionId).First(&mission).Error
+ if err != nil {
+ err = errors.New("not found mission record")
+ return err
+ }
+
+ claimVideo.MissionClaimId = 0
+ claimVideo.VideoUrl = info.VideoUrl
+ claimVideo.Cover = info.Cover
+ claimVideo.Status = 2 //审核已通过
+ claimVideo.SourceType = 3 //后台上传
+ claimVideo.MissionId = info.MissionId //任务ID
+ claimVideo.CreateBy = uuid //创建者ID
+
+ tx := global.MG_DB.Begin()
+ err = tx.Model(&model.MissionClaimVideo{}).Create(&claimVideo).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ //打标签
+ if info.TagId != 0 {
+ err = global.MG_DB.Model(&model.Tags{}).Where("id = ?", info.TagId).First(&tagCheck).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ tx.Rollback()
+ return err
+ }
+
+ tagRelation = model.TagRelation{
+ TagId: info.TagId,
+ RelationId: strconv.Itoa(int(claimVideo.ID)),
+ RelationType: "02",
+ CreateBy: uuid,
+ UpdateBy: uuid,
+ }
+ err = tx.Model(&model.TagRelation{}).Create(&tagRelation).Error
+ if err != nil {
+ tx.Rollback()
+ return errors.New("绑定标签失败")
+ }
+
+ }
+ tx.Commit()
+
+ return err
+}
+
+func EditMissionVideo(uuid string, info request.EditMissionVideo) (err error) {
+ var (
+ check model.MissionClaimVideo
+ claimVideo model.MissionClaimVideo
+ tagCheck model.Tags
+ tagRelation model.TagRelation
+ )
+ err = global.MG_DB.Model(&model.MissionClaimVideo{}).Where("id = ?", info.ID).First(&check).Error
+ if err != nil {
+ err = errors.New("not found record")
+ return err
+ }
+
+ tx := global.MG_DB.Begin()
+ claimVideo.MissionClaimId = 0
+ claimVideo.VideoUrl = info.VideoUrl
+ claimVideo.Cover = info.Cover
+ claimVideo.Status = 2 //2
+ claimVideo.SourceType = 3 //后台上传
+ claimVideo.UpdateBy = uuid //创建者ID
+ err = tx.Model(&model.MissionClaimVideo{}).Where("id = ?", info.ID).Updates(claimVideo).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ //打标签
+ if info.TagId != 0 {
+ err = global.MG_DB.Model(&model.Tags{}).Where("id = ?", info.TagId).First(&tagCheck).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ tx.Rollback()
+ return err
+ }
+ err = tx.Model(&model.TagRelation{}).Where("relation_type= ? and relation_id = ?", "02", info.ID).Delete(&model.TagRelation{}).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ tagRelation = model.TagRelation{
+ TagId: info.TagId,
+ RelationId: strconv.Itoa(int(info.ID)),
+ RelationType: "02",
+ CreateBy: uuid,
+ UpdateBy: uuid,
+ }
+
+ err = tx.Model(&model.TagRelation{}).Create(&tagRelation).Error
+ if err != nil {
+ tx.Rollback()
+ return errors.New("绑定标签失败")
+ }
+
+ }
+ tx.Commit()
+
+ return err
+}
+
+func AddMissionRecommend(q *request.AddMissionRecommend, userID string) error {
+ //添加任务推荐,倒序排序
+ var (
+ err error
+ repeat model.MissionRecommend
+ maxSort int64
+ )
+
+ for _, y := range q.List {
+ repeat = model.MissionRecommend{}
+ global.MG_DB.Model(&model.MissionRecommend{}).Where("relation_id=?", y.RelationId).Find(&repeat)
+ if repeat.ID != 0 {
+ break
+ }
+ }
+ if repeat.ID != 0 {
+ return errors.New("已添加该任务")
+ }
+ err, maxSort = GetMissionRecommendMaxSort()
+
+ //global.MG_DB.Model(&model.BsXzPageTemp{}).Where("date_id=?", q.List[0].DateId).Order("sort desc").Limit(1).Find(&data)
+ for x, _ := range q.List {
+ q.List[x].ID = 0
+ q.List[x].Sort = int(maxSort) + len(q.List) - x
+ q.List[x].CreateBy = userID
+ q.List[x].UpdateBy = userID
+ }
+ tx := global.MG_DB.Begin()
+
+ err = tx.Model(&model.MissionRecommend{}).Create(&(q.List)).Error
+ if err != nil {
+ tx.Rollback()
+ return errors.New("添加关联关系失败")
+ }
+ tx.Commit()
+
+ return nil
+}
+
+func GetMissionRecommendMaxSort() (err error, total int64) {
+ db := global.MG_DB.Model(&model.MissionRecommend{}).Select("IFNULL(MAX(sort),0)")
+ err = db.Scan(&(total)).Error
+ return
+}
+
+func DeleteMissionRecommendByIds(ids request.IdsUReq) (err error) {
+ err = global.MG_DB.Model(model.MissionRecommend{}).Where("id in (?)", ids.Ids).Unscoped().Delete(&model.MissionRecommend{}).Error
+ if err != nil {
+ return errors.New("删除失败")
+ }
+
+ return err
+}
+
+func MissionRecommendUpData(info request.IdReq, c *gin.Context) error {
+ var (
+ err error
+ idList []global.BASE_ID_SORT
+ )
+
+ err, idList = GetMissionRecommendSortList()
+ if err != nil {
+ return err
+ }
+ for i := 1; i < len(idList); i++ {
+ if idList[i].ID == info.ID {
+ sort := idList[i].Sort
+ if idList[i-1].Sort == idList[i].Sort {
+ idList[i].Sort += 1
+ } else {
+ idList[i].Sort = idList[i-1].Sort
+ }
+
+ var tmp = make([]global.BASE_ID_SORT, 0)
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i].ID, Sort: idList[i].Sort})
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i-1].ID, Sort: sort})
+ err = updateMissionRecommendSort(tmp)
+ if err != nil {
+ return err
+ }
+ break
+ }
+ }
+
+ return err
+}
+
+func MissionRecommendDownData(info request.IdReq, c *gin.Context) error {
+ var (
+ err error
+ idList []global.BASE_ID_SORT
+ )
+
+ err, idList = GetMissionRecommendSortList()
+ if err != nil {
+ return err
+ }
+ for i := 0; i < len(idList); i++ {
+ if idList[i].ID == info.ID && i < len(idList)-1 {
+ sort := idList[i].Sort
+ if idList[i+1].Sort == idList[i].Sort {
+ idList[i].Sort -= 1
+ } else {
+ idList[i].Sort = idList[i+1].Sort
+ }
+
+ var tmp = make([]global.BASE_ID_SORT, 0)
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i].ID, Sort: idList[i].Sort})
+ tmp = append(tmp, global.BASE_ID_SORT{ID: idList[i+1].ID, Sort: sort})
+ err = updateMissionRecommendSort(tmp)
+ if err != nil {
+ return err
+ }
+ break
+ }
+ }
+ return err
+}
+
+func GetMissionRecommendSortList() (err error, list []global.BASE_ID_SORT) {
+ db := global.MG_DB.Model(&model.MissionRecommend{})
+
+ var idList []global.BASE_ID_SORT
+ err = db.Select("id,sort").Order("sort desc").Order("id desc").Find(&idList).Error
+ return err, idList
+}
+
+func updateMissionRecommendSort(idList []global.BASE_ID_SORT) error {
+ var err error
+ for _, val := range idList {
+ //_ = UpdateMissionRecommendSort(val)
+ _ = global.MG_DB.Model(&model.MissionRecommend{}).Where("id=?", val.ID).Update("sort", val.Sort).Error
+ }
+ return err
+}
+
+func UpdateMissionRecommendSort(val global.BASE_ID_SORT) error {
+ var err error
+ _ = global.MG_DB.Model(&model.MissionRecommend{}).Where("id=?", val.ID).Update("sort", val.Sort).Error
+ return err
+}
+
+func GetMissionRecommendList(info request.SearchMission) (err error, list []response.MissionRecommendResponse, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.MissionRecommendDetail{}).
+ Joins("inner join mission_claim_video on mission_recommend.relation_id = mission_claim_video.id").
+ Joins("inner join mission on mission.id = mission_claim_video.mission_id").
+ Where("mission_claim_video.status = 2"). //审核通过状态
+ Order("mission_recommend.sort desc")
+
+ if info.SellerId != "" {
+ db = db.Where("mission.create_by = ?", info.SellerId)
+ }
+
+ if info.SellerEmail != "" {
+ db = db.Joins("join seller_store on seller_store.create_by = mission.create_by").Where("seller_store.email like ?", "%"+info.SellerEmail+"%")
+ }
+
+ if info.HireType > 0 {
+ db = db.Where("mission.hire_type = ?", info.HireType)
+ }
+
+ if info.Title != "" {
+ db = db.Where("mission.title LIKE ?", "%"+info.Title+"%")
+ }
+ if info.Status != 0 {
+ db = db.Where("mission.status = ?", info.Status)
+ }
+ if info.MissionId != 0 {
+ db = db.Where("mission.id = ?", info.MissionId)
+ }
+
+ err = db.Count(&total).Error
+ var res []model.MissionRecommendDetail
+
+ err = db.Preload("MissionVideo", func(db *gorm.DB) *gorm.DB {
+ return db.Preload("Mission", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,claim_num,start_time,end_time,`status`,has_video,video_channel_ids,video_country_id,claim_days,create_by,claim_stock").
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,retail_price,price_min,price_max,stock,sales,online,created_at,updated_at")
+ }).Preload("Seller")
+ }).Preload("Influencer", func(db *gorm.DB) *gorm.DB {
+ return db.Select("uuid,nick_name,phone")
+ })
+ }).Order("id DESC").Limit(limit).Offset(offset).Find(&res).Error
+
+ if err != nil {
+ return err, nil, 0
+ }
+ for i := 0; i < len(res); i++ {
+ //查询订单数
+ switch res[i].MissionVideo.Mission.HireType {
+ case 1:
+ res[i].MissionVideo.Mission.HireMoneyExpect = fmt.Sprintf("视频拍摄%.f美元/单,总共%d笔", res[i].MissionVideo.Mission.HireMoney, res[i].MissionVideo.Mission.ClaimStock)
+ case 2:
+ res[i].MissionVideo.Mission.HireMoneyExpect = fmt.Sprintf("订单金额%.f%%", res[i].MissionVideo.Mission.HireRatio)
+ }
+ //发布国家
+ if res[i].MissionVideo.Mission.HasVideo == 1 {
+ res[i].MissionVideo.Mission.ReleaseCountry = GetSysDictDataLabel(model.ReleaseCountryCode, res[i].MissionVideo.Mission.VideoCountryId)
+ var channelNames []string
+ for _, cid := range strings.Split(res[i].MissionVideo.Mission.VideoChannelIds, ",") {
+ vLabel := GetSysDictDataLabel(model.ReleaseChannelCode, cid)
+ channelNames = append(channelNames, vLabel)
+ }
+ res[i].MissionVideo.Mission.ReleaseChannels = strings.Join(channelNames, "/")
+ }
+ //精简字段赋值
+ vRes := response.MissionRecommendResponse{
+ SortIndex: offset + 1 + i,
+ MissionRecommend: res[i].MissionRecommend,
+ MissionVideoCommonData: response.MissionVideoCommonData{
+ MissionId: res[i].MissionVideo.MissionId,
+ VideoUrl: res[i].MissionVideo.VideoUrl,
+ Cover: res[i].MissionVideo.Cover,
+ Title: res[i].MissionVideo.Mission.Title,
+ GoodsTitle: res[i].MissionVideo.Mission.Goods.Title,
+ Influencer: res[i].MissionVideo.Influencer,
+ Tag: "",
+ MissionStatus: res[i].MissionVideo.Mission.Status,
+ StartTime: res[i].MissionVideo.Mission.StartTime,
+ EndTime: res[i].MissionVideo.Mission.EndTime,
+ HireType: res[i].MissionVideo.Mission.HireType,
+ HireMoney: res[i].MissionVideo.Mission.HireMoney,
+ HireRatio: res[i].MissionVideo.Mission.HireRatio,
+ HireMoneyExpect: res[i].MissionVideo.Mission.HireMoneyExpect,
+ ReleaseCountry: res[i].MissionVideo.Mission.ReleaseCountry,
+ ReleaseChannels: res[i].MissionVideo.Mission.ReleaseChannels,
+ Seller: res[i].MissionVideo.Mission.Seller,
+ },
+ }
+ vRes.Status = res[i].MissionVideo.Mission.Status
+ vRes.MissionVideoCommonData.Tag = GetTagNamesByRelation(strconv.Itoa(int(vRes.MissionRecommend.RelationId)), "02")
+ list = append(list, vRes)
+ }
+
+ return err, list, total
+}
+
+// 任务视频审核详情
+func GetMissionClaimVideoDetail(id uint) (err error, data response.MissionClaimVideoDetail) {
+ var (
+ claimVideo model.MissionClaimVideo
+ missionClaimDetail model.MissionClaimDetail
+ influencerUser *model.InfluencerUserDesc
+ )
+
+ err = global.MG_DB.Model(&model.MissionClaimVideo{}).Where("id = ?", id).First(&claimVideo).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return gorm.ErrRecordNotFound, data
+ }
+
+ db := global.MG_DB.Model(&model.MissionClaim{}).
+ Joins("INNER JOIN mission ON mission.id = mission_claim.mission_id").
+ Where("mission_claim.id = ?", claimVideo.MissionClaimId)
+
+ err = db.Select("mission_claim.id,mission_id,mission_claim.claim_no,mission_claim.achieve_num,mission_claim.`status`,mission_claim.created_at,mission_claim.updated_at,mission_claim.expire_at,mission_claim.create_by").
+ Preload("Order", func(db *gorm.DB) *gorm.DB {
+ return db.Select("order_id,mission_claim_id,`status`,courier,courier_url,courier_number,send_time").
+ Preload("OrderGoods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("order_id,price,sku_no,title,image,specs,id")
+ })
+ }).
+ Preload("Mission", func(db *gorm.DB) *gorm.DB {
+ return db.
+ Select("id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,start_time,end_time,`status`,create_by,claim_days,claim_num,claim_stock,has_video,video_url,video_channel_ids,video_country_id,claim_days,create_by,claim_stock").
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,tags,retail_price,price_min,price_max,status").Unscoped()
+ })
+ }).First(&missionClaimDetail).Error
+
+ if err != nil {
+ return err, data
+ }
+
+ err, influencerUser = GetInfluencerUserDetail(missionClaimDetail.CreateBy)
+ data.InfluencerUser = *influencerUser
+ data.ClaimVideo = claimVideo
+ data.MissionClaim = response.MissionClaimInfo{
+ MissionId: missionClaimDetail.MissionId,
+ Title: missionClaimDetail.Mission.Title,
+ ClaimAt: missionClaimDetail.CreatedAt,
+ HireType: missionClaimDetail.Mission.HireType,
+ HireMoney: missionClaimDetail.Mission.HireMoney,
+ HireRatio: missionClaimDetail.Mission.HireRatio,
+ HireMoneyExpect: utils.FormatFloatToString(missionClaimDetail.Order.OrderGoods.Price),
+ StartTime: missionClaimDetail.Mission.StartTime,
+ EndTime: missionClaimDetail.Mission.EndTime,
+ Status: missionClaimDetail.Status,
+ ClaimNum: missionClaimDetail.Mission.ClaimNum,
+ ClaimStock: missionClaimDetail.Mission.ClaimStock,
+ OrderNum: missionClaimDetail.Mission.OrderNum,
+ ClaimDays: missionClaimDetail.Mission.ClaimDays,
+ ClaimDemands: missionClaimDetail.Mission.ClaimDemands,
+ Description: missionClaimDetail.Mission.Description,
+ Sample: missionClaimDetail.Mission.Sample,
+ VideoMaterial: missionClaimDetail.Mission.VideoMaterial,
+ ReleaseCountry: "",
+ ReleaseChannels: "",
+ }
+
+ //发布国家
+ if data.MissionClaim.HasVideo == 1 {
+ data.MissionClaim.ReleaseCountry = GetSysDictDataLabel(model.ReleaseCountryCode, data.MissionClaim.VideoCountryId)
+ var channelNames []string
+ for _, cid := range strings.Split(data.MissionClaim.VideoChannelIds, ",") {
+ vLabel := GetSysDictDataLabel(model.ReleaseChannelCode, cid)
+ channelNames = append(channelNames, vLabel)
+ }
+ data.MissionClaim.ReleaseChannels = strings.Join(channelNames, "/")
+ }
+
+ data.ClaimGoods = response.MissionClaimGoods{
+ GoodsId: missionClaimDetail.Mission.GoodsId,
+ OrderId: missionClaimDetail.Order.OrderID,
+ SpuNo: missionClaimDetail.Mission.Goods.SpuNo,
+ Title: missionClaimDetail.Mission.Goods.Title,
+ TitleEng: missionClaimDetail.Mission.Goods.TitleEng,
+ Sales: 0.0,
+ Sales30: 0.0,
+ Status: missionClaimDetail.Mission.Goods.Status,
+ Specs: missionClaimDetail.Order.OrderGoods.Specs,
+ SkuNo: missionClaimDetail.Order.OrderGoods.SkuNo,
+ Stock: missionClaimDetail.Mission.Goods.Stock,
+ Price: missionClaimDetail.Order.OrderGoods.Price,
+ Image: missionClaimDetail.Order.OrderGoods.Image,
+ Tags: missionClaimDetail.Mission.Goods.Tags,
+ }
+
+ //查询销量
+ totalSales := GetMissionClaimSales(missionClaimDetail.ClaimNo, nil, nil)
+
+ endTime, _ := time.Parse(utils.DateFormat, time.Now().Format(utils.DateFormat))
+ startTime := endTime.AddDate(0, 0, -29)
+ sales30 := GetMissionClaimSales(missionClaimDetail.ClaimNo, &startTime, &endTime)
+ data.ClaimGoods.Sales = totalSales.Sales
+ data.ClaimGoods.Sales30 = sales30.Sales
+
+ return err, data
+}
+
+func GetMissionClaimSales(claimNo string, startTime, endTime *time.Time) (result model.OrderSummary) {
+ var (
+ err error
+ )
+ db := global.MG_DB.Model(&model.Order{}).Joins("INNER JOIN mission_claim ON mission_claim.claim_no = `order`.code").
+ Where("`order`.`status` IN (2,3,4) ").Where("`order`.code = ?", claimNo)
+ if startTime != nil && !startTime.IsZero() {
+ db = db.Where("`order`.pay_time >= ?", startTime)
+ }
+ if endTime != nil && !endTime.IsZero() {
+ db = db.Where("`order`.pay_time >= ?", startTime)
+ }
+ err = db.Select("`order`.code,COUNT(1) as count,sum(paid_price) as sales").Find(&result).Error
+ fmt.Println(result)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func StopMission(uuid string, info request.IdReq) (err error) {
+ var (
+ checkStop, missionStop model.MissionStop
+ )
+
+ err = global.MG_DB.Model(&model.MissionStop{}).Where("id = ? and status = 1", info.ID).First(&checkStop).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+
+ tx := global.MG_DB.Begin()
+ err = global.MG_DB.Model(&model.Mission{}).Where("id = ? AND create_by = ?", checkStop.MissionId, checkStop.CreateBy).Update("status", 3).Update("end_time", time.Now()).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ missionStop = model.MissionStop{
+ Status: 2,
+ AuditBy: uuid,
+ }
+ err = global.MG_DB.Model(&model.MissionStop{}).
+ Where("id = ?", info.ID).Updates(missionStop).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+
+ //清除任务定时
+ _, _ = global.MG_REDIS.Del(fmt.Sprintf("mission_start_%v", info.ID), fmt.Sprintf("mission_stop_%v", info.ID)).Result()
+ tx.Commit()
+
+ return err
+}
+
+func GetMissionStopList(info request.SearchStopMission) (err error, list []response.MissionStopData, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.MissionStopDetail{}).
+ Joins("inner join mission on mission.id = mission_stop.mission_id")
+
+ if info.AuditStatus != 0 {
+ if info.AuditStatus == 4 {
+ //已审核=审核通过+审核不通过
+ db = db.Where("mission_stop.status in (2,3)")
+ } else {
+ db = db.Where("mission_stop.status = ?", info.AuditStatus)
+ }
+ }
+
+ if info.SellerId != "" {
+ db = db.Where("mission.create_by = ?", info.SellerId)
+ }
+
+ if info.SellerEmail != "" {
+ db = db.Joins("join seller_store on seller_store.create_by = mission.create_by").Where("seller_store.email like ?", "%"+info.SellerEmail+"%")
+ }
+
+ if info.HireType > 0 {
+ db = db.Where("mission.hire_type = ?", info.HireType)
+ }
+
+ if info.Title != "" {
+ db = db.Where("mission.title LIKE ?", "%"+info.Title+"%")
+ }
+ if info.Status != 0 {
+ db = db.Where("mission.status = ?", info.Status)
+ }
+ if info.MissionId != 0 {
+ db = db.Where("mission.id = ?", info.MissionId)
+ }
+
+ if info.VideoChannelId != "" {
+ db = db.Where("find_in_set(?,mission.video_channel_ids)", info.VideoChannelId)
+ }
+
+ err = db.Count(&total).Error
+ var res []model.MissionStopDetail
+
+ err = db.Preload("Mission", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,claim_num,start_time,end_time,`status`,has_video,video_channel_ids,video_country_id,claim_days,create_by,claim_stock,has_sample,description,sample_num").
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,retail_price,price_min,price_max,stock,sales,online,created_at,updated_at")
+ }).Preload("Seller")
+ }).Order("mission_stop.id DESC").Limit(limit).Offset(offset).Find(&res).Error
+
+ if err != nil {
+ return err, nil, 0
+ }
+ for i := 0; i < len(res); i++ {
+ //精简字段赋值
+ vCommon := formatMissionCommonData(res[i].Mission)
+ vRes := response.MissionStopData{
+ Remark: res[i].Remark,
+ ID: res[i].ID,
+ MissionCommonData: vCommon,
+ Goods: res[i].Mission.Goods,
+ }
+ list = append(list, vRes)
+ }
+
+ return err, list, total
+}
+
+func GetMissionStopDetail(info request.IdReq) (err error, data response.MissionStopData) {
+ var (
+ missionStop model.MissionStop
+ )
+ err = global.MG_DB.Model(&model.MissionStopDetail{}).
+ Where("id = ?", info.ID).First(&missionStop).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err, data
+ }
+
+ var res model.MissionDetail
+
+ err = global.MG_DB.Select("id,title,goods_id,goods_status,num,hire_type,hire_money,hire_ratio,claim_num,start_time,end_time,`status`,has_video,video_channel_ids,video_country_id,claim_days,create_by,claim_stock").
+ Preload("Goods", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,spu_no,title,title_eng,images,retail_price,price_min,price_max,stock,sales,online,created_at,updated_at")
+ }).Preload("Seller").First(&res).Error
+
+ if err != nil {
+ return err, data
+ }
+ //精简字段赋值
+ vCommon := formatMissionCommonData(res)
+ data = response.MissionStopData{
+ Remark: missionStop.Remark,
+ ID: missionStop.ID,
+ MissionCommonData: vCommon,
+ }
+
+ return err, data
+}
+
+func formatMissionCommonData(mission model.MissionDetail) response.MissionCommonData {
+ //查询订单数
+ switch mission.HireType {
+ case 1:
+ mission.HireMoneyExpect = fmt.Sprintf("视频拍摄%.f美元/单,总共%d笔", mission.HireMoney, mission.ClaimStock)
+ case 2:
+ mission.HireMoneyExpect = fmt.Sprintf("订单金额%.f%%", mission.HireRatio)
+ }
+ //发布国家
+ mission.ReleaseCountry = GetSysDictDataLabel(model.ReleaseCountryCode, mission.VideoCountryId)
+ var channelNames []string
+ for _, cid := range strings.Split(mission.VideoChannelIds, ",") {
+ vLabel := GetSysDictDataLabel(model.ReleaseChannelCode, cid)
+ channelNames = append(channelNames, vLabel)
+ }
+
+ mission.ReleaseChannels = strings.Join(channelNames, "/")
+ //精简字段赋值
+ vCommon := response.MissionCommonData{
+ MissionId: mission.ID,
+ Title: mission.Title,
+ GoodsTitle: mission.Goods.Title,
+ Tag: "",
+ MissionStatus: mission.Status,
+ StartTime: mission.StartTime,
+ EndTime: mission.EndTime,
+ HireType: mission.HireType,
+ HireMoney: mission.HireMoney,
+ HireRatio: mission.HireRatio,
+ HireMoneyExpect: mission.HireMoneyExpect,
+ ReleaseCountry: mission.ReleaseCountry,
+ ReleaseChannels: mission.ReleaseChannels,
+ ClaimNum: mission.ClaimNum,
+ Seller: mission.Seller,
+ HasVideo: mission.HasVideo,
+ HasSample: mission.HasSample,
+ Description: mission.Description,
+ SampleNum: mission.SampleNum,
+ }
+ return vCommon
+}
+
+func getMissionClaim(claimNo string) (model.MissionClaim, error) {
+ var (
+ err error
+ result model.MissionClaim
+ )
+ err = global.MG_DB.Model(&model.MissionClaim{}).Where("claim_no = ?", claimNo).First(&result).Error
+ return result, err
+}
+
+func GetMissionClaimList(info request.SearchMissionClaim) (err error, list []response.MissionClaimSimpleData, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.MissionClaim{}).
+ Joins("INNER JOIN mission ON mission.id = mission_claim.mission_id")
+ if info.MissionId != 0 {
+ db = db.Where("mission.id = ?", info.MissionId)
+ }
+ if info.Status != 0 {
+ db = db.Where("mission_claim.`status` = ?", info.Status)
+ }
+ if info.Uuid != "" {
+ db = db.Where("mission_claim.create_by = ?", info.Uuid)
+ }
+
+ if info.Title != "" {
+ db = db.Where("mission.title LIKE ?", "%"+info.Title+"%")
+ }
+
+ err = db.Count(&total).Error
+ var res []model.MissionClaimDetail
+ err = db.Select("mission_claim.id,mission_claim.mission_id,mission_claim.claim_no,mission_claim.achieve_num,mission_claim.`status`,mission_claim.create_by,mission_claim.created_at,mission_claim.updated_at").
+ Preload("Mission", func(db *gorm.DB) *gorm.DB {
+ return db.Select("title,id,video_url,has_video,hire_type,hire_ratio,hire_money,start_time,end_time")
+ }).Preload("Video").Limit(limit).Offset(offset).Find(&res).Error
+ if err != nil {
+ return err, nil, 0
+ }
+
+ for i := 0; i < len(res); i++ {
+ vClaim := response.MissionClaimSimpleData{
+ Video: res[i].Video,
+ MissionId: res[i].MissionId,
+ MissionTitle: res[i].Mission.Title,
+ ClaimAt: res[i].CreatedAt,
+ ClaimDays: res[i].Mission.ClaimDays,
+ HireType: res[i].Mission.HireType,
+ VideoUrl: res[i].Mission.VideoUrl,
+ HasVideo: res[i].Mission.HasVideo,
+ SignUrl: "",
+ }
+ vClaim.ClaimNo = res[i].ClaimNo
+ vClaim.MissionClaim.ID = res[i].ID
+ list = append(list, vClaim)
+ }
+
+ return err, list, total
+}
+
+func getMissionClaimList(info *request.SearchMissionClaim) (error, []model.MissionClaim) {
+ var (
+ err error
+ result []model.MissionClaim
+ )
+ db := global.MG_DB.Model(&model.MissionClaim{})
+ if info.CreateBy != "" {
+ db = db.Where("create_by = ?", info.CreateBy)
+ }
+ if info.MissionId != 0 {
+ db = db.Where("mission_id = ?", info.MissionId)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ err = db.Find(&result).Error
+ return err, result
+}
+
+func CreateMissionTagRelation(params request.CreateMissionTagRelation, uuid string) (err error) {
+ var (
+ data []model.TagRelation
+ check []model.Tags
+ )
+ err = global.MG_DB.Model(&model.Tags{}).Where("id in (?)", params.TagId).Find(&check).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ if len(check) != len(params.TagId) {
+ return errors.New("标签不存在")
+ }
+ tx := global.MG_DB.Begin()
+ err = tx.Model(&model.TagRelation{}).Where("tag_id in (?) and relation_id = ? and relation_type = ?", params.TagId, params.RelationId, params.RelationType).Delete(&model.TagRelation{}).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ for _, v := range params.TagId {
+ data = append(data, model.TagRelation{
+ TagId: v,
+ RelationId: params.RelationId,
+ RelationType: params.RelationType,
+ CreateBy: uuid,
+ UpdateBy: uuid,
+ })
+ }
+ err = tx.Model(&model.TagRelation{}).Create(&data).Error
+ if err != nil {
+ return errors.New("提交失败")
+ }
+ tx.Commit()
+ return err
+}
+
+func GetInfluencerSummaryList(params request.InfluencerSummaryListParam) (err error, list []model.MissionClaimSummary, total int64) {
+ var (
+ res []model.MissionClaimSummary
+ uids []string
+ influencerMap map[string]model.UserSimple
+ summary model.Sum
+ )
+ limit := params.PageSize
+ offset := params.PageSize * (params.Page - 1)
+ db := global.MG_DB.Model(&model.MissionClaim{})
+
+ if params.Uuid != "" {
+ db = db.Where("create_by = ?", params.Uuid)
+ }
+ err = db.Select("count(distinct create_by) as total_count").First(&summary).Error
+ total = summary.TotalCount
+
+ db = db.Group("create_by")
+ err = db.Select("count(1) as count,min(created_at) as first_claim_at,create_by").Limit(limit).Offset(offset).Find(&res).Error
+
+ for i := 0; i < len(res); i++ {
+ uids = append(uids, res[i].CreateBy)
+ }
+ if len(uids) > 0 {
+ var influencerList []model.UserSimple
+ influencerMap = map[string]model.UserSimple{}
+ influencerList, err = getUserSimpleList("influencer", uids)
+ if err != nil {
+ return errors.New("获取网红信息失败"), list, total
+ }
+ for i, user := range influencerList {
+ if user.Platform != "" {
+ var vPlatform []model.Platform
+ json.Unmarshal([]byte(user.Platform), &vPlatform)
+ influencerList[i].Platforms = vPlatform
+ }
+
+ influencerMap[user.UUID.String()] = influencerList[i]
+ }
+ }
+ for i := 0; i < len(res); i++ {
+ if _, ok := influencerMap[res[i].CreateBy]; ok {
+ res[i].Influencer = model.InfluencerUserView{
+ UUID: influencerMap[res[i].CreateBy].UUID.String(),
+ NickName: influencerMap[res[i].CreateBy].NickName,
+ Phone: influencerMap[res[i].CreateBy].Phone,
+ }
+ var vPlatforms []string
+ for _, vp := range influencerMap[res[i].CreateBy].Platforms {
+ vPlatforms = append(vPlatforms, vp.Platform)
+ }
+ res[i].Platform = strings.Join(vPlatforms, "/")
+ }
+ _, claims := getMissionClaimList(&request.SearchMissionClaim{CreateBy: res[i].CreateBy})
+ if len(claims) != 0 {
+ var claimNos = make([]string, 0)
+ for _, claim := range claims {
+ claimNos = append(claimNos, claim.ClaimNo)
+ res[i].Bonus += claim.RewardFinished
+ res[i].TransitBonus += 0
+ }
+ statistic, _ := getStatisticsOrderByValuesRelationIds([]string{}, "", "2", claimNos)
+ res[i].OrderNum = statistic.OrderNum
+ }
+ }
+ //todo 订单、佣金、在途佣金统计
+
+ list = res
+ return
+}
+
+func GetMissionClaimOrder(orderId string) (error, model.MissionClaimOrderDetail) {
+ var (
+ err error
+ result model.MissionClaimOrderDetail
+ )
+ err = global.MG_DB.Model(&model.MissionClaimOrder{}).
+ Joins("INNER JOIN mission_claim ON mission_claim.id = mission_claim_order.mission_claim_id").
+ Joins("INNER JOIN mission ON mission.id = mission_claim.mission_id").
+ Where("mission_claim_order.order_id = ?", orderId).
+ Select("mission_claim_order.*").Preload("Goods").Preload("Address").
+ Preload("MissionClaim", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,mission_id,claim_no,achieve_num,`status`,create_by,created_at,updated_at")
+ }).Preload("MissionClaim.Influencer", func(db *gorm.DB) *gorm.DB {
+ return db.Select("uuid,nick_name,phone")
+ }).First(&result).Error
+ if err != nil {
+ return err, result
+ }
+ return nil, result
+}
+
+func GetMissionClaimOrderList(info request.SearchMissionClaimOrder) (err error, list []model.MissionClaimOrderDetail, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.MissionClaimOrder{}).Joins("INNER JOIN mission_claim ON mission_claim.id = mission_claim_order.mission_claim_id").
+ Joins("INNER JOIN mission ON mission.id = mission_claim.mission_id")
+ if info.SellerId != "" {
+ db = db.Where("mission.create_by = ?", info.SellerId)
+ }
+ if info.Uuid != "" {
+ db = db.Where("mission_claim.create_by = ?", info.Uuid)
+ }
+ if info.OrderID != "" {
+ db = db.Where("mission_claim_order.order_id = ?", info.OrderID)
+ }
+ if info.Status != 0 {
+ db = db.Where("mission_claim_order.`status` = ?", info.Status)
+ }
+ if info.SpuNo != "" {
+ db = db.Where("mission_claim_order.spu_no = ?", info.SpuNo)
+ }
+ if info.MissionTitle != "" {
+ db = db.Where("`mission`.`title` like ?", "%"+info.MissionTitle+"%")
+ }
+ if info.AddressName != "" || info.AddressPhone != "" {
+ db = db.Joins("INNER JOIN mission_claim_address ON mission_claim_address.mission_claim_id = mission_claim.id")
+ if info.AddressName != "" {
+ name := "%" + info.AddressName + "%"
+ db = db.Where("CONCAT(mission_claim_address.first_name,mission_claim_address.last_name) LIKE ? OR CONCAT(mission_claim_address.first_name,' ',mission_claim_address.last_name) LIKE ?", name, name)
+ }
+ if info.AddressPhone != "" {
+ db = db.Where("mission_claim_address.phone LIKE ?", "%"+info.AddressPhone+"%")
+ }
+ }
+ if info.OrderTimeStart != "" {
+ var tmpT time.Time
+ tmpT, err = time.ParseInLocation(utils.DateTimeFormat, info.OrderTimeStart, time.UTC)
+ if err != nil {
+ err = errors.New("search mission time format error")
+ return err, nil, 0
+ }
+ db = db.Where("mission_claim_order.created_at >= ?", tmpT)
+ }
+ if info.OrderTimeEnd != "" {
+ var tmpT time.Time
+ tmpT, err = time.ParseInLocation(utils.DateTimeFormat, info.OrderTimeEnd, time.UTC)
+ if err != nil {
+ err = errors.New("search mission time format error")
+ return err, nil, 0
+ }
+ //tmpT = tmpT.AddDate(0, 0, 1)
+ db = db.Where("mission_claim_order.created_at <= ?", tmpT)
+ }
+ err = db.Count(&total).Error
+ var res []model.MissionClaimOrderDetail
+ err = db.Select("mission_claim_order.*").Preload("Goods").Preload("Address").
+ Preload("MissionClaim", func(db *gorm.DB) *gorm.DB {
+ return db.Select("id,mission_id,claim_no,achieve_num,`status`,create_by,created_at,updated_at").Preload("Influencer", func(db *gorm.DB) *gorm.DB {
+ return db.Select("uuid,nick_name,phone")
+ })
+ }).Order("mission_claim_order.id DESC").Limit(limit).Offset(offset).Find(&res).Error
+ if err != nil {
+ return err, nil, 0
+ }
+ return err, res, total
+}
+
+func getMissionFundLockAmount(info *request.SearchMissionFund) (error, float64, int64) {
+ var (
+ err error
+ result float64
+ total int64
+ )
+ db := global.MG_DB.Model(&model.Mission{}).Where("hire_type = 1")
+ if info.CreateBy != "" {
+ db = db.Where("create_by = ?", info.CreateBy)
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ _ = db.Count(&total).Error
+ err = db.Select("SUM(`fund_lock`)").Scan(&result).Error
+ if err != nil {
+ return errors.New("获取失败"), result, total
+ }
+ return nil, result, total
+}
+
+func getMissionClaimViewMap(claimNos []string) (map[string]model.MissionClaimView, error) {
+ var (
+ err error
+ list []model.MissionClaimView
+ result = make(map[string]model.MissionClaimView)
+ )
+ err = global.MG_DB.Model(&model.MissionClaim{}).Joins("INNER JOIN mission ON mission.id = mission_claim.mission_id").
+ Where("mission_claim.claim_no IN (?)", claimNos).Select("mission_claim.mission_id,mission_claim.claim_no,mission_claim.create_by,mission.title as mission_title").
+ Find(&list).Error
+ if err != nil {
+ return nil, err
+ }
+ var uuids []string
+ for _, claim := range list {
+ uuids = append(uuids, claim.CreateBy)
+ }
+ userMap, _ := getUserViewMap(uuids...)
+ for i := 0; i < len(list); i++ {
+ if val, ok := userMap[list[i].CreateBy]; ok {
+ list[i].User = val
+ }
+ }
+ for _, claim := range list {
+ result[claim.ClaimNo] = claim
+ }
+ return result, nil
+}
+
+// date eg:2023-12-21
+func MissionStatisticCountDaily(date string) error {
+ var (
+ dailySum model.StatisticMissionDaily
+ claimNum, releaseNum, worksNum int64
+ err error
+ )
+ global.MG_DB.Model(&model.Mission{}).
+ Where("created_at between ? and ?", fmt.Sprintf("%s 00:00:00", date), fmt.Sprintf("%s 23:59:59", date)).Count(&releaseNum)
+ global.MG_DB.Model(&model.MissionClaim{}).
+ Where("created_at between ? and ?", fmt.Sprintf("%s 00:00:00", date), fmt.Sprintf("%s 23:59:59", date)).Count(&claimNum)
+ global.MG_DB.Model(&model.MissionClaimWorks{}).
+ Where("created_at between ? and ?", fmt.Sprintf("%s 00:00:00", date), fmt.Sprintf("%s 23:59:59", date)).Count(&worksNum)
+ dailySum = model.StatisticMissionDaily{
+ Type: "daily",
+ Date: date,
+ ReleaseNum: releaseNum,
+ ClaimNum: claimNum,
+ WorksNum: worksNum,
+ }
+ err = global.MG_DB.Model(&model.StatisticMissionDaily{}).Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "type"}, {Name: "daily"}},
+ UpdateAll: true, // 主键或唯一索引冲突时, 更新除主键的所有字段
+ }).Create(&dailySum).Error
+ return err
+}
+
+// date eg:2023-12-21
+func MissionStatisticCountTotal() error {
+ var (
+ dailySum model.StatisticMissionDaily
+ claimNum, releaseNum, worksNum int64
+ err error
+ )
+
+ global.MG_DB.Model(&model.Mission{}).Count(&releaseNum)
+ global.MG_DB.Model(&model.MissionClaim{}).Count(&claimNum)
+ global.MG_DB.Model(&model.MissionClaimWorks{}).Count(&worksNum)
+ dailySum = model.StatisticMissionDaily{
+ Type: "total",
+ Date: time.Now().Format(utils.DateFormat),
+ ReleaseNum: releaseNum,
+ ClaimNum: claimNum,
+ WorksNum: worksNum,
+ }
+ err = global.MG_DB.Model(&model.StatisticMissionDaily{}).Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "type"}, {Name: "daily"}},
+ UpdateAll: true, // 主键或唯一索引冲突时, 更新除主键的所有字段
+ }).Create(&dailySum).Error
+ return err
+}
+
+// @date 日期 eg:2023-10-20
+// @sumType 类型 total:累计数据 daily:单日数据
+func GetMissionStatisticData(date string, sumType string) (error, model.StatisticMissionDaily) {
+ var (
+ err error
+ data model.StatisticMissionDaily
+ )
+ db := global.MG_DB.Model(&model.StatisticMissionDaily{}).Where("type = ?", sumType)
+ if sumType == "daily" && date != "" {
+ db = db.Where("date = ?", date)
+ }
+ err = db.Order("date desc").First(&data).Error
+ if err != nil {
+ return err, data
+ }
+ return err, data
+
+}
diff --git a/service/notify.go b/service/notify.go
new file mode 100644
index 0000000..d5113f0
--- /dev/null
+++ b/service/notify.go
@@ -0,0 +1,34 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+func createOrUpdateNotify(info request.CreateNotify) error {
+ var (
+ err error
+ notify model.Notify
+ )
+ err = global.MG_DB.Model(&model.Notify{}).Where("user_id = ? AND relation_type = ? AND relation_id = ?", info.UserId, info.RelationType, info.RelationId).Find(¬ify).Error
+ if err != nil {
+ return err
+ }
+ if notify.ID != 0 {
+ err = global.MG_DB.Model(&model.Notify{}).Where("id = ?", notify.ID).Update("title", info.Title).Error
+ if err != nil {
+ return err
+ }
+ } else {
+ notify.UserId = info.UserId
+ notify.RelationType = info.RelationType
+ notify.RelationId = info.RelationId
+ notify.Title = info.Title
+ err = global.MG_DB.Model(&model.Notify{}).Create(¬ify).Error
+ if err != nil {
+ return err
+ }
+ }
+ return err
+}
diff --git a/service/order.go b/service/order.go
new file mode 100644
index 0000000..bf9e3f3
--- /dev/null
+++ b/service/order.go
@@ -0,0 +1,451 @@
+package service
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "pure-admin/utils"
+ "sync/atomic"
+ "time"
+)
+
+func GetOrderList(info *request.SearchOrderList) (error, interface{}, int64) {
+ var (
+ err error
+ result []model.OrderList
+ total int64
+ orderIds []string
+ customers []string
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.Order{})
+ if info.Status != 0 {
+ db = db.Where("`order`.status = ?", info.Status)
+ }
+ if info.OrderID != "" {
+ db = db.Where("`order`.order_id = ?", info.OrderID)
+ }
+ if info.GoodsID != 0 {
+ db = db.Where("`order`.commod_id=?", info.GoodsID)
+ }
+ if info.SpuNo != "" {
+ db = db.Joins("LEFT JOIN order_goods ON order_goods.order_id = `order`.order_id").Where("order_goods.spu_no = ?", info.SpuNo)
+ }
+ if info.Name != "" {
+ db = db.Joins("INNER JOIN order_address on order_address.order_id=`order`.order_id").Where("CONCAT(order_address.first_name,order_address.last_name) LIKE ? OR CONCAT(order_address.first_name,' ',order_address.last_name) LIKE ?", "%"+info.Name+"%", "%"+info.Name+"%")
+ }
+ if info.Phone != "" {
+ db = db.Joins("inner join order_address b on b.phone=? and b.order_id=`order`.order_id", info.Phone)
+ }
+ if info.CreatedAtStart != "" {
+ db.Where("`order`.created_at >= ?", info.CreatedAtStart)
+ }
+ if info.CreatedAtEnd != "" {
+ db.Where("`order`.created_at < ?", info.CreatedAtEnd)
+ }
+ if info.Code != "" {
+ db = db.Where("`order`.code=?", info.Code)
+ }
+ if info.CreatedAt != "" {
+ db = db.Where("`order`.created_at>=?", info.CreatedAt)
+ }
+ if info.UserId != "" {
+ db = db.Where("`order`.user_id = ?", info.UserId)
+ }
+ if info.InfluenceId != "" || info.MissionTitle != "" {
+ db = db.Joins("INNER JOIN mission_claim ON mission_claim.claim_no = `order`.`code`")
+ if info.InfluenceId != "" {
+ db = db.Where("mission_claim.create_by = ?", info.InfluenceId)
+ }
+ if info.MissionTitle != "" {
+ db = db.Joins("INNER JOIN mission ON mission.id = mission_claim.mission_id").Where("mission.title LIKE ?", "%"+info.MissionTitle+"%")
+ }
+ }
+ if info.StoreNo != "" {
+ db = db.Where("`order`.store_no = ?", info.StoreNo)
+ }
+ _ = db.Count(&total).Error
+ err = db.Order("`order`.id desc").Offset(offset).Limit(limit).Find(&result).Error
+ if err != nil {
+ return errors.New("获取订单失败"), result, total
+ }
+ if len(result) == 0 {
+ return nil, result, total
+ }
+ for v := range result {
+ orderIds = append(orderIds, result[v].OrderID)
+ }
+ var (
+ orderGoodsList []model.OrderGoodsDetail
+ goodsMap = make(map[string]model.OrderGoodsDetail)
+ )
+ err = global.MG_DB.Model(&model.OrderGoods{}).Where("order_id IN (?)", orderIds).Find(&orderGoodsList).Error
+ if err != nil {
+ return errors.New("获取订单商品信息失败"), result, total
+ }
+ for _, goods := range orderGoodsList {
+ goodsMap[goods.OrderID] = goods
+ }
+ var (
+ addressList []model.OrderAddress
+ addressMap = make(map[string]model.OrderAddress)
+ )
+ err = global.MG_DB.Model(&model.OrderAddress{}).Where("order_id in (?)", orderIds).Find(&addressList).Error
+ if err != nil {
+ return errors.New("获取订单地址信息失败"), result, total
+ }
+ for _, address := range addressList {
+ addressMap[address.OrderID] = address
+ }
+ var (
+ delivers []model.OrderDeliver
+ deliverMap = make(map[string]model.OrderDeliver)
+ )
+ err = global.MG_DB.Model(&model.OrderDeliver{}).Where("order_id in (?)", orderIds).Find(&delivers).Error
+ if err != nil {
+ return errors.New("获取订单发货信息失败"), result, total
+ }
+ for _, deliver := range delivers {
+ deliverMap[deliver.OrderID] = deliver
+ }
+ var codes []string
+ for v := range result {
+ if val, ok := goodsMap[result[v].OrderID]; ok {
+ result[v].Goods = val
+ }
+ if val, ok := addressMap[result[v].OrderID]; ok {
+ result[v].Address = val
+ }
+
+ if val, ok := deliverMap[result[v].OrderID]; ok {
+ result[v].Deliver = val
+ }
+ if result[v].Code != "" {
+ codes = append(codes, result[v].Code)
+ }
+ customers = append(customers, result[v].UserID)
+ }
+ var (
+ missionMap = make(map[string]model.MissionClaimInfo)
+ influencerMap = make(map[string]model.InfluencerUserClaimView)
+ )
+ if len(codes) > 0 {
+ var (
+ missions []model.MissionClaimInfo
+ influencerList []model.InfluencerUserClaimView
+ )
+ err = global.MG_DB.Select("mission_claim.claim_no,mission_claim.mission_id,b.hire_type,b.hire_money,b.hire_ratio").Model(&model.MissionClaim{}).Joins("inner join mission b on mission_claim.mission_id=b.id").Where("claim_no in (?)", codes).Scan(&missions).Error
+ if err != nil {
+ return errors.New("获取任务详情失败"), result, total
+ }
+ for _, mission := range missions {
+ missionMap[mission.ClaimNo] = mission
+ }
+ err = global.MG_DB.Select("`user`.uuid,`user`.nick_name,`user`.phone,mission_claim.claim_no").Model(&model.MissionClaim{}).Joins("inner join `user` on mission_claim.create_by=`user`.uuid").Where("mission_claim.claim_no in (?)", codes).Scan(&influencerList).Error
+ if err != nil {
+ return errors.New("获取任务网红失败"), result, total
+ }
+ for _, influencer := range influencerList {
+ influencerMap[influencer.ClaimNo] = influencer
+ }
+ }
+ var customerMap = make(map[string]model.UserSimple)
+ if len(customers) > 0 {
+ var customerList []model.UserSimple
+ customerList, err = getUserSimpleList("customer", customers)
+ if err != nil {
+ return errors.New("获取买家详情失败"), result, total
+ }
+ for _, user := range customerList {
+ customerMap[user.UUID.String()] = user
+ }
+ }
+ for v := range result {
+ if result[v].Code != "" {
+ if val, ok := missionMap[result[v].Code]; ok {
+ if val.HireType == 1 {
+ amount := val.HireMoney * float64(result[v].Number)
+ result[v].InfluencerAmount = utils.FormatFloatToString(amount)
+ result[v].SellerAmount = utils.FormatFloatToString(result[v].PaidPrice - amount)
+ } else {
+ amount := result[v].PaidPrice * val.HireRatio / 100
+ result[v].InfluencerAmount = utils.FormatFloatToString(amount)
+ result[v].SellerAmount = utils.FormatFloatToString(result[v].PaidPrice - amount)
+ }
+ }
+ if val, ok := influencerMap[result[v].Code]; ok {
+ result[v].InfluencerUser = val
+ }
+ } else {
+ result[v].SellerAmount = utils.FormatFloatToString(result[v].PaidPrice)
+ }
+ if val, ok := customerMap[result[v].UserID]; ok {
+ result[v].CustomerPhone = utils.HideStar(val.Phone)
+ }
+ }
+ return nil, result, total
+}
+
+func GetOrderDetail(info *request.SearchOrderDetail) (error, interface{}) {
+ var (
+ err error
+ result model.OrderDetail
+ missions model.MissionClaimInfo
+ )
+ db := global.MG_DB.Model(&model.Order{}).Where("`order`.order_id = ?", info.OrderID)
+ err = db.Find(&result).Error
+ if err != nil {
+ return errors.New("获取订单失败"), result
+ }
+ err = global.MG_DB.Model(&model.OrderAddress{}).Where("order_id = ?", info.OrderID).Find(&result.Address).Error
+ if err != nil {
+ return errors.New("获取订单地址失败"), result
+ }
+ err = global.MG_DB.Model(&model.OrderGoods{}).Where("order_id = ?", info.OrderID).Find(&result.Goods).Error
+ if err != nil {
+ return errors.New("获取订单商品信息失败"), result
+ }
+ err = global.MG_DB.Model(&model.OrderDeliver{}).Where("order_id = ?", info.OrderID).Find(&result.Deliver).Error
+ if err != nil {
+ return errors.New("获取发货信息失败"), result
+ }
+ if result.Code != "" {
+ err = global.MG_DB.Select("mission_claim.claim_no,mission_claim.mission_id,b.hire_type,b.hire_money,b.hire_ratio").Model(&model.MissionClaim{}).Joins("inner join mission b on mission_claim.mission_id=b.id").Where("claim_no=?", result.Code).Scan(&missions).Error
+ if err != nil {
+ fmt.Println(err)
+ return errors.New("获取任务详情失败"), result
+ }
+ if missions.HireType == 1 {
+ amount := missions.HireMoney * float64(result.Number)
+ result.InfluencerAmount = utils.FormatFloatToString(amount)
+ result.SellerAmount = utils.FormatFloatToString((result.PaidPrice - amount))
+ } else {
+ amount := result.PaidPrice * missions.HireRatio / 100
+ result.InfluencerAmount = utils.FormatFloatToString(amount)
+ result.SellerAmount = utils.FormatFloatToString(result.PaidPrice - amount)
+ }
+ }
+ result.Chain = model.Chain{Address: "b714e5508531a7b50d0696abdc83d2333bd896db24f68eb1470bb9529390ef3e"}
+ return nil, result
+}
+
+func getStatisticsOrderList(values []string, unit, relationId string) ([]model.DtStatisticOrder, error) {
+ var (
+ err error
+ result []model.DtStatisticOrder
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("`value` IN (?) AND `unit` = ?", values, unit)
+ if relationId != "" {
+ db = db.Where("`relation_id` = ?", relationId)
+ }
+ err = db.Find(&result).Error
+ return result, err
+}
+
+func getStatisticsOrderByValues(values []string, unit, t, relationId string) (model.DtStatisticOrder, error) {
+ var (
+ err error
+ result model.DtStatisticOrder
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("`unit` = ?", unit).
+ Select("SUM(order_num) as order_num,SUM(order_done_num) as order_done_num,SUM(order_money) as order_money,SUM(sale_volume) as sale_volume,SUM(settle_reward) as settle_reward,SUM(order_cancel_num) as order_cancel_num,SUM(order_cancel_money) as order_cancel_money")
+ if len(values) != 0 {
+ db = db.Where("`value` IN (?) ", values)
+ }
+ if t != "" {
+ db = db.Where("`type` = ?", t)
+ }
+ if relationId != "" {
+ db = db.Where("`relation_id` = ?", relationId)
+ }
+ err = db.First(&result).Error
+ return result, err
+}
+
+func getStatisticsOrderByValuesRelationIds(values []string, unit, t string, relationIds []string) (model.DtStatisticOrder, error) {
+ var (
+ err error
+ result model.DtStatisticOrder
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("`unit` = ?", unit).
+ Select("SUM(order_num) as order_num,SUM(order_done_num) as order_done_num,SUM(order_money) as order_money,SUM(sale_volume) as sale_volume,SUM(settle_reward) as settle_reward,SUM(order_cancel_num) as order_cancel_num,SUM(order_cancel_money) as order_cancel_money")
+ if len(values) != 0 {
+ db = db.Where("`value` IN (?) ", values)
+ }
+ if t != "" {
+ db = db.Where("`type` = ?", t)
+ }
+ if len(relationIds) != 0 {
+ db = db.Where("`relation_id` IN (?)", relationIds)
+ }
+ err = db.First(&result).Error
+ return result, err
+}
+
+// 获取退款数据
+func getOrderPostSaleCount(info *request.SearchOrderPostSale) (int64, error) {
+ var (
+ err error
+ result int64
+ )
+ db := global.MG_DB.Model(&model.OrderPostSale{}).Where("`status` != 4")
+ if info.OrderId != "" {
+ db = db.Where("order_id = ?", info.OrderId)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ if info.UserId != "" {
+ db = db.Where("user_id = ?", info.UserId)
+ }
+ if info.CreatedAtStart != "" {
+ db = db.Where("created_at >= ?", info.CreatedAtStart)
+ }
+ if info.CreatedAtEnd != "" {
+ db = db.Where("created_at < ?", info.CreatedAtEnd)
+ }
+ err = db.Count(&result).Error
+ return result, err
+}
+
+func getOrderPostSaleCountList(info *request.SearchOrderPostSale) (map[string]int64, error) {
+ var (
+ err error
+ list []model.CountMap
+ result = make(map[string]int64)
+ )
+ db := global.MG_DB.Model(&model.OrderPostSale{}).Where("`status` != 4")
+ if info.OrderId != "" {
+ db = db.Where("order_id = ?", info.OrderId)
+ }
+ if info.Status != 0 {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ if len(info.UserIds) != 0 {
+ db = db.Where("user_id IN (?)", info.UserIds)
+ }
+ if info.CreatedAtStart != "" {
+ db = db.Where("created_at >= ?", info.CreatedAtStart)
+ }
+ if info.CreatedAtEnd != "" {
+ db = db.Where("created_at < ?", info.CreatedAtEnd)
+ }
+ err = db.Group("user_id").Select("user_id as `key`,COUNT(1) as value").Scan(&list).Error
+ if err != nil {
+ return result, err
+ }
+ for _, countMap := range list {
+ result[countMap.Key] = countMap.Value
+ }
+ return result, err
+}
+
+func GetStatisticData() (response.DataStatistics, error) {
+ var (
+ err error
+ result response.DataStatistics
+ )
+ now := time.Now()
+ {
+ // 用户数据
+ statistic, _ := getStatisticOrderSum(&request.Statistic{T: 0, RelationId: "bkb"}, "order_num", "order_money", "new_order_num")
+ // 总销售额
+ result.User.Money.Value1 = fmt.Sprintf("%.2f", statistic.OrderMoney)
+ // 人均支付金额
+ result.User.Money.Value2 = fmt.Sprintf("%.2f", statistic.OrderMoney/float64(statistic.OrderNum))
+ // 访问量
+ goods, yesterdayGoods := GetGoodsVisitCount()
+ result.User.Visit.Value1 = fmt.Sprintf("%v", goods)
+ // 昨天访问量
+ result.User.Visit.Value2 = fmt.Sprintf("%v", yesterdayGoods)
+ // 支付笔数
+ result.User.PayNum.Value1 = fmt.Sprintf("%v", statistic.OrderNum)
+ // 昨日支付笔数
+ yesterday, _ := getStatisticOrderSum(&request.Statistic{Values: []string{now.AddDate(0, 0, -1).Format("060102") + "0000"}, Unit: "", T: 0, RelationId: "bkb"}, "order_num", "new_order_num")
+ result.User.PayNum.Value2 = fmt.Sprintf("%v", yesterday.OrderNum)
+ // 订单数
+ result.User.OrderNum.Value1 = fmt.Sprintf("%v", statistic.NewOrderNum)
+ // 昨日订单数
+ result.User.OrderNum.Value2 = fmt.Sprintf("%v", yesterday.NewOrderNum)
+ }
+ _, missionData := GetMissionStatisticData("", "total")
+ _, yesterdayMissionData := GetMissionStatisticData(now.AddDate(0, 0, -1).Format(utils.DateFormat), "daily")
+ {
+ // 网红数据
+ // 接任务数
+ result.Influence.MissionNum.Value1 = fmt.Sprintf("%v", missionData.ClaimNum)
+ // 昨日接任务数
+ result.Influence.MissionNum.Value2 = fmt.Sprintf("%v", yesterdayMissionData.ClaimNum)
+ // 发布第三方平台数
+ result.Influence.PlatformNum.Value1 = fmt.Sprintf("%v", missionData.WorksNum)
+ // 昨日发布数
+ result.Influence.PlatformNum.Value2 = fmt.Sprintf("%v", yesterdayMissionData.WorksNum)
+ // 在途佣金额
+ statistic, _ := getStatisticOrderSum(&request.Statistic{T: 2}, "transit_reward")
+ result.Influence.TransitReward.Value1 = fmt.Sprintf("%.2f", statistic.TransitReward)
+ // 昨日在途佣金
+ yesterday, _ := getStatisticOrderSum(&request.Statistic{Values: []string{now.AddDate(0, 0, -1).Format("060102") + "0000"}, Unit: "", T: 2}, "transit_reward")
+ result.Influence.TransitReward.Value2 = fmt.Sprintf("%.2f", yesterday.TransitReward)
+ // 可用佣金额
+ result.Influence.UsedReward.Value1 = ""
+ // 昨天可用佣金数
+ result.Influence.UsedReward.Value2 = ""
+ }
+ {
+ // 商家数据
+
+ // 发布任务数
+ result.Store.MissionNum.Value1 = fmt.Sprintf("%v", missionData.ReleaseNum)
+ // 昨日发布任务数
+ result.Store.MissionNum.Value2 = fmt.Sprintf("%v", yesterdayMissionData.ReleaseNum)
+
+ goods, yesterdayGoods := GetGoodsCount()
+ // 发布商品数
+ result.Store.GoodsNum.Value1 = fmt.Sprintf("%v", goods)
+ // 昨日发布商品数
+ result.Store.GoodsNum.Value2 = fmt.Sprintf("%v", yesterdayGoods)
+ // 在途金额
+ statistic, _ := getStatisticOrderSum(&request.Statistic{T: 3}, "transit_reward")
+ result.Store.TransitReward.Value1 = fmt.Sprintf("%.2f", statistic.TransitReward)
+ // 昨日在订单金额
+ yesterday, _ := getStatisticOrderSum(&request.Statistic{Values: []string{now.AddDate(0, 0, -1).Format("060102") + "0000"}, Unit: "", T: 3}, "transit_reward")
+ result.Store.TransitReward.Value2 = fmt.Sprintf("%.2f", yesterday.TransitReward)
+ // 可提金额
+ result.Store.UsedCash.Value1 = ""
+ // 昨天可用佣金额
+ result.Store.UsedCash.Value2 = ""
+ }
+ return result, err
+}
+
+// 生成24位订单号
+// 前面17位代表时间精确到毫秒,中间3位代表进程id,最后4位代表序号
+var num int64
+
+func generate() string {
+ t := time.Now()
+ s := t.Format(utils.Continuity)
+ m := t.UnixNano()/1e6 - t.UnixNano()/1e9*1e3
+ ms := sup(m, 3)
+ p := os.Getpid() % 1000
+ ps := sup(int64(p), 3)
+ i := atomic.AddInt64(&num, 1)
+ r := i % 10000
+ rs := sup(r, 4)
+ n := fmt.Sprintf("%s%s%s%s", s, ms, ps, rs)
+ return n
+}
+
+// 对长度不足n的数字前面补0
+func sup(i int64, n int) string {
+ m := fmt.Sprintf("%d", i)
+ for len(m) < n {
+ m = fmt.Sprintf("0%s", m)
+ }
+ return m
+}
diff --git a/service/organization.go b/service/organization.go
new file mode 100755
index 0000000..d0c3e7c
--- /dev/null
+++ b/service/organization.go
@@ -0,0 +1,80 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: CreateOrganization
+//@description: 创建Organization记录
+//@param: organization model.Organization
+//@return: err error
+
+func CreateOrganization(organization model.Organization) (err error) {
+ err = global.MG_DB.Create(&organization).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteOrganization
+//@description: 删除Organization记录
+//@param: organization model.Organization
+//@return: err error
+
+func DeleteOrganization(organization model.Organization) (err error) {
+ err = global.MG_DB.Delete(&organization).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteOrganizationByIds
+//@description: 批量删除Organization记录
+//@param: ids request.IdsReq
+//@return: err error
+
+func DeleteOrganizationByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.Organization{},"id in (?)",ids.Ids).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: UpdateOrganization
+//@description: 更新Organization记录
+//@param: organization *model.Organization
+//@return: err error
+
+func UpdateOrganization(organization model.Organization) (err error) {
+ err = global.MG_DB.Updates(&organization).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetOrganization
+//@description: 根据id获取Organization记录
+//@param: id uint
+//@return: err error, organization model.Organization
+
+func GetOrganization(id uint) (err error, organization model.Organization) {
+ err = global.MG_DB.Where("id = ?", id).First(&organization).Error
+ return
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetOrganizationInfoList
+//@description: 分页获取Organization记录
+//@param: info request.OrganizationSearch
+//@return: err error, list interface{}, total int64
+
+func GetOrganizationInfoList(info request.OrganizationSearch) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.Organization{})
+ var organizations []model.Organization
+ // 如果有条件搜索 下方会自动创建搜索语句
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&organizations).Error
+ return err, organizations, total
+}
\ No newline at end of file
diff --git a/service/provider.go b/service/provider.go
new file mode 100755
index 0000000..bdf69d7
--- /dev/null
+++ b/service/provider.go
@@ -0,0 +1,80 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: CreateProvider
+//@description: 创建Provider记录
+//@param: provider model.Provider
+//@return: err error
+
+func CreateProvider(provider model.Provider) (err error) {
+ err = global.MG_DB.Create(&provider).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteProvider
+//@description: 删除Provider记录
+//@param: provider model.Provider
+//@return: err error
+
+func DeleteProvider(provider model.Provider) (err error) {
+ err = global.MG_DB.Delete(&provider).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteProviderByIds
+//@description: 批量删除Provider记录
+//@param: ids request.IdsReq
+//@return: err error
+
+func DeleteProviderByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.Provider{},"id in (?)",ids.Ids).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: UpdateProvider
+//@description: 更新Provider记录
+//@param: provider *model.Provider
+//@return: err error
+
+func UpdateProvider(provider model.Provider) (err error) {
+ err = global.MG_DB.Updates(&provider).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetProvider
+//@description: 根据id获取Provider记录
+//@param: id uint
+//@return: err error, provider model.Provider
+
+func GetProvider(id uint) (err error, provider model.Provider) {
+ err = global.MG_DB.Where("id = ?", id).First(&provider).Error
+ return
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetProviderInfoList
+//@description: 分页获取Provider记录
+//@param: info request.ProviderSearch
+//@return: err error, list interface{}, total int64
+
+func GetProviderInfoList(info request.ProviderSearch) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.Provider{})
+ var providers []model.Provider
+ // 如果有条件搜索 下方会自动创建搜索语句
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&providers).Error
+ return err, providers, total
+}
\ No newline at end of file
diff --git a/service/redis_tools.go b/service/redis_tools.go
new file mode 100644
index 0000000..c1d4f6a
--- /dev/null
+++ b/service/redis_tools.go
@@ -0,0 +1,255 @@
+package service
+
+import (
+ "fmt"
+ "pure-admin/global"
+ "time"
+
+ "github.com/go-redis/redis"
+ "go.uber.org/zap"
+)
+
+// RedisGet ...
+func RedisGet(key string) string {
+ result, err := global.MG_REDIS.Get(key).Result()
+ if err != nil {
+ return ""
+ }
+ return result
+}
+
+func RedisSet(key string, value interface{}, expiration time.Duration) string {
+ result, err := global.MG_REDIS.Set(key, value, expiration).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis Set Error:", zap.Any("err", err))
+ return ""
+ }
+ return result
+}
+
+func RedisDel(key ...string) (int64, error) {
+ result, err := global.MG_REDIS.Del(key...).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis Del Error:", zap.Any("err", err))
+ return -1, err
+ }
+ return result, err
+}
+
+// RedisHashSet 向key的hash中添加元素field的值
+func RedisHashSet(key, field string, data interface{}) error {
+ err := global.MG_REDIS.HSet(key, field, data).Err()
+ if err != nil {
+ global.MG_LOG.Error("Redis HSet Error:", zap.Any("err", err))
+ return err
+ }
+ return nil
+}
+
+// RedisBatchHashSet 批量向key的hash添加对应元素field的值
+func RedisBatchHashSet(key string, fields map[string]interface{}) (error, string) {
+ val, err := global.MG_REDIS.HMSet(key, fields).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis HMSet Error:", zap.Any("err", err))
+ return err, ""
+ }
+ return nil, val
+}
+
+func RedisHDel(key string, fields ...string) (error, int64) {
+ result, err := global.MG_REDIS.HDel(key, fields...).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis HMSet Error:", zap.Any("err", err))
+ return err, 0
+ }
+ return nil, result
+}
+
+func RedisHSet(key, field string, value interface{}) (error, bool) {
+ val, err := global.MG_REDIS.HSet(key, field, value).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis HSet Error:", zap.Any("err", err))
+ return err, false
+ }
+ return nil, val
+}
+
+// RedisHashGet 通过key获取hash的元素值
+func RedisHashGet(key, field string) string {
+ val, err := global.MG_REDIS.HGet(key, field).Result()
+ if err != nil {
+ return ""
+ }
+ return val
+}
+
+func RedisHashMGet(key string, fields ...string) []interface{} {
+ val, err := global.MG_REDIS.HMGet(key, fields...).Result()
+ if err != nil {
+ return nil
+ }
+ return val
+}
+
+// HGetAll
+func RedisHashGetAll(key string) map[string]string {
+ val, err := global.MG_REDIS.HGetAll(key).Result()
+ if err != nil {
+ return nil
+ }
+ return val
+}
+
+// RedisBatchHashGet 批量获取key的hash中对应多元素值
+func RedisBatchHashGet(key string, fields ...string) map[string]interface{} {
+ resMap := make(map[string]interface{})
+ for _, field := range fields {
+ val, err := global.MG_REDIS.HGet(key, fmt.Sprintf("%s", field)).Result()
+ if err == nil && val != "" {
+ resMap[field] = val
+ }
+ }
+ return resMap
+}
+
+func RedisLPush(key string, values ...interface{}) (int64, error) {
+ result, err := global.MG_REDIS.LPush(key, values...).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis LPush Error:", zap.Any("err", err))
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisLPop(key string) (error, string) {
+ result, err := global.MG_REDIS.LPop(key).Result()
+ if err != nil {
+ return err, ""
+ }
+ return err, result
+}
+
+// RedisSAdd 无序集合添加
+func RedisSAdd(key string, values ...interface{}) (int64, error) {
+ result, err := global.MG_REDIS.SAdd(key, values...).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis LPush Error:", zap.Any("err", err))
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisSIsMember(key string, value interface{}) (bool, error) {
+ result, err := global.MG_REDIS.SIsMember(key, value).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis LPush Error:", zap.Any("err", err))
+ return false, err
+ }
+ return result, nil
+}
+
+func RedisSRem(key string, values ...interface{}) (int64, error) {
+ result, err := global.MG_REDIS.SRem(key, values...).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, err
+}
+
+// RedisSMembers 从无序集合中获取数据
+func RedisSMembers(key string) ([]string, error) {
+ val, err := global.MG_REDIS.SMembers(key).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis SMembers Error:", zap.Any("err", err))
+ return nil, err
+ }
+ return val, nil
+}
+
+// RedisZAdd 有序集合添加
+func RedisZAdd(key string, values map[float64]interface{}) (int64, error) {
+ var members []redis.Z
+ for scale, value := range values {
+ members = append(members, redis.Z{
+ Score: scale,
+ Member: value,
+ })
+ }
+ result, err := global.MG_REDIS.ZAdd(key, members...).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis ZAdd Error:", zap.Any("err", err))
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisZRem(key string, values ...interface{}) (int64, error) {
+ result, err := global.MG_REDIS.ZRem(key, values...).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, err
+}
+
+func RedisZCard(key string) (int64, error) {
+ result, err := global.MG_REDIS.ZCard(key).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis ZAdd Error:", zap.Any("err", err))
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisZRange(key string, start, stop int64, sort string) ([]interface{}, error) {
+ var val = make([]interface{}, 0)
+ if sort == "asc" { //scale 正序
+ result, err := global.MG_REDIS.ZRangeWithScores(key, start, stop).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis ZPopMax Error:", zap.Any("err", err))
+ return val, err
+ }
+ for _, z := range result {
+ val = append(val, z.Member)
+ }
+ } else { //scale 倒序
+ result, err := global.MG_REDIS.ZRevRange(key, start, stop).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis ZPopMax Error:", zap.Any("err", err))
+ return val, err
+ }
+ for _, z := range result {
+ val = append(val, z)
+ }
+ return val, nil
+ }
+
+ return val, nil
+}
+
+// RedisIncr 获取自增唯一ID
+func RedisIncr(key string) int {
+ val, err := global.MG_REDIS.Incr(key).Result()
+ if err != nil {
+ global.MG_LOG.Error("Redis RedisIncr Error:", zap.Any("err", err))
+ }
+ return int(val)
+}
+
+func RedisTest(key string, values ...interface{}) {
+ result, err := global.MG_REDIS.SRem(key, values...).Result()
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(result)
+}
+
+func RedisSetNX(key string, value interface{}, expiration time.Duration) (error, bool) {
+ val, err := global.MG_REDIS.SetNX(key, value, expiration).Result()
+ if err != nil {
+ if err != nil {
+ fmt.Println("Redis SetNX Error:", err.Error())
+ }
+ return err, false
+ }
+ return nil, val
+}
diff --git a/service/seller.go b/service/seller.go
new file mode 100644
index 0000000..fc2b4e2
--- /dev/null
+++ b/service/seller.go
@@ -0,0 +1,22 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+)
+
+func getSellerStoreMap(stores []string) (map[string]model.StoreInfo, error) {
+ var (
+ err error
+ list []model.StoreInfo
+ result = make(map[string]model.StoreInfo)
+ )
+ err = global.MG_DB.Model(&model.BillFund{}).Where("store_no IN (?)").Select("store_no,email").Find(&list).Error
+ if err != nil {
+ return nil, err
+ }
+ for _, store := range list {
+ result[store.StoreNo] = store
+ }
+ return result, nil
+}
diff --git a/service/statistic.go b/service/statistic.go
new file mode 100644
index 0000000..d2dd10e
--- /dev/null
+++ b/service/statistic.go
@@ -0,0 +1,181 @@
+package service
+
+import (
+ "errors"
+ "gorm.io/gorm"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "strings"
+)
+
+func getDtStatisticsOrderDB(info *request.Statistic) *gorm.DB {
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("`type` = ?", info.T)
+ if len(info.Values) != 0 {
+ db = db.Where("`value` IN (?)", info.Values)
+ }
+ if info.Unit != "" {
+ db = db.Where("`unit` = ?", info.Unit)
+ }
+ if info.RelationId != "" {
+ db = db.Where("`relation_id` = ?", info.RelationId)
+ }
+ return db
+}
+
+func getStatisticsOrder(info request.Statistic) (model.DtStatisticOrder, error) {
+ var (
+ err error
+ result model.DtStatisticOrder
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("`type` = ?", info.T)
+ if len(info.Values) != 0 {
+ db = db.Where("`value` IN (?)", info.Values)
+ }
+ if info.Unit != "" {
+ db = db.Where("`unit` = ?", info.Unit)
+ }
+ if info.RelationId != "" {
+ db = db.Where("`relation_id` = ?", info.RelationId)
+ }
+ err = db.First(&result).Error
+ return result, err
+}
+
+// 获取订单统计数据-买家维度
+func getStatisticsOrderUser(uuid, unit string, timeArea []string) (model.DtStatisticOrder, error) {
+ var (
+ err error
+ result model.DtStatisticOrder
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("dt_statistic_order.`type` = 1 AND dt_statistic_order.relation_id = ? AND dt_statistic_order.`unit` = ?", uuid, unit)
+ if len(timeArea) == 2 {
+ db = db.Where("dt_statistic_order.created_at >= ? AND dt_statistic_order.created_at < ?", timeArea[0], timeArea[1])
+ }
+ err = db.Select("SUM(order_num) as order_num,SUM(order_done_num) as order_done_num,SUM(order_money) as order_money,SUM(sale_volume) as sale_volume,SUM(settle_reward) as settle_reward,SUM(order_cancel_num) as order_cancel_num,SUM(order_cancel_money) as order_cancel_money").First(&result).Error
+ return result, err
+}
+
+func getStatisticsOrderUsers(uuids []string, unit string, timeArea []string) (map[string]model.DtStatisticOrder, error) {
+ var (
+ err error
+ list []model.DtStatisticOrder
+ result = make(map[string]model.DtStatisticOrder)
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("dt_statistic_order.`type` = 1 AND dt_statistic_order.relation_id IN (?) AND dt_statistic_order.`unit` = ?", uuids, unit)
+ if len(timeArea) == 2 {
+ db = db.Where("dt_statistic_order.created_at >= ? AND dt_statistic_order.created_at < ?", timeArea[0], timeArea[1])
+ }
+ err = db.Select("dt_statistic_order.relation_id,SUM(order_num) as order_num,SUM(order_done_num) as order_done_num,SUM(order_money) as order_money,SUM(sale_volume) as sale_volume,SUM(settle_reward) as settle_reward,SUM(order_cancel_num) as order_cancel_num,SUM(order_cancel_money) as order_cancel_money").Group("dt_statistic_order.relation_id").Find(&list).Error
+ if err != nil {
+ return result, err
+ }
+ for _, statisticOrder := range list {
+ result[statisticOrder.RelationId] = statisticOrder
+ }
+ return result, nil
+}
+
+func getStatisticOrderSum(info *request.Statistic, fields ...string) (model.Statistic, error) {
+ var (
+ err error
+ selectSql string
+ result model.Statistic
+ )
+ db := getDtStatisticsOrderDB(info)
+ for _, field := range fields {
+ switch field {
+ case "value":
+ if len(info.Values) == 1 {
+ selectSql += "`value`,"
+ }
+ case "relation_id":
+ selectSql += "relation_id,"
+ case "new_order_num":
+ selectSql += "SUM(new_order_num) as new_order_num,"
+ case "new_order_money":
+ selectSql += "SUM(new_order_money) as new_order_money,"
+ case "order_num":
+ selectSql += "SUM(order_num) as order_num,"
+ case "order_done_num":
+ selectSql += "SUM(order_done_num) as order_done_num,"
+ case "order_money":
+ selectSql += "SUM(order_money) as order_money,"
+ case "settle_reward":
+ selectSql += "SUM(settle_reward) as settle_reward,"
+ case "transit_reward":
+ selectSql += "SUM(transit_reward) as transit_reward,"
+ case "order_cancel_num":
+ selectSql += "SUM(order_cancel_num) as order_cancel_num,"
+ case "order_cancel_money":
+ selectSql += "SUM(order_cancel_money) as order_cancel_money,"
+ case "sale_volume":
+ selectSql += "SUM(sale_volume) as sale_volume,"
+ default:
+ selectSql = ""
+ break
+ }
+ }
+ if selectSql == "" {
+ return result, errors.New("fields is error")
+ } else {
+ if strings.HasSuffix(selectSql, ",") {
+ selectSql = strings.TrimRight(selectSql, ",")
+ }
+ }
+ err = db.Select(selectSql).Scan(&result).Error
+ if err != nil {
+ return result, err
+ }
+ //for _, field := range fields {
+ // switch field {
+ // case "value":
+ // if len(info.Values) == 1 {
+ // result["value"] = statistic.Value
+ // }
+ // case "relation_id":
+ // result["relation_id"] = statistic.RelationId
+ // case "new_order_num":
+ // result["new_order_num"] = fmt.Sprintf("%v", statistic.NewOrderNum)
+ // case "new_order_money":
+ // result["new_order_money"] = fmt.Sprintf("%.2f", statistic.NewOrderMoney)
+ // case "order_num":
+ // result["order_num"] = fmt.Sprintf("%v", statistic.OrderNum)
+ // case "order_done_num":
+ // result["order_done_num"] = fmt.Sprintf("%v", statistic.OrderDoneNum)
+ // case "order_money":
+ // result["order_money"] = fmt.Sprintf("%.2f", statistic.OrderMoney)
+ // case "settle_reward":
+ // result["settle_reward"] = fmt.Sprintf("%.2f", statistic.SettleReward)
+ // case "transit_reward":
+ // result["transit_reward"] = fmt.Sprintf("%.2f", statistic.TransitReward)
+ // case "order_cancel_num":
+ // result["order_cancel_num"] = fmt.Sprintf("%v", statistic.NewOrderNum)
+ // case "order_cancel_money":
+ // result["order_cancel_money"] = fmt.Sprintf("%.2f", statistic.OrderCancelMoney)
+ // case "sale_volume":
+ // result["sale_volume"] = fmt.Sprintf("%v", statistic.SaleVolume)
+ // }
+ //}
+ return result, err
+}
+
+func getStatisticsOrderSkus(skuNos []string, unit string, timeArea []string) (map[string]model.DtStatisticOrder, error) {
+ var (
+ err error
+ list []model.DtStatisticOrder
+ result = make(map[string]model.DtStatisticOrder)
+ )
+ db := global.MG_DB.Model(&model.DtStatisticOrder{}).Where("dt_statistic_order.`type` = 4 AND dt_statistic_order.relation_id IN (?) AND dt_statistic_order.`unit` = ?", skuNos, unit)
+ if len(timeArea) == 2 {
+ db = db.Where("dt_statistic_order.created_at >= ? AND dt_statistic_order.created_at < ?", timeArea[0], timeArea[1])
+ }
+ err = db.Select("dt_statistic_order.relation_id,SUM(order_num) as order_num,SUM(order_done_num) as order_done_num,SUM(order_money) as order_money,SUM(sale_volume) as sale_volume,SUM(settle_reward) as settle_reward,SUM(order_cancel_num) as order_cancel_num,SUM(order_cancel_money) as order_cancel_money").Group("dt_statistic_order.relation_id").Find(&list).Error
+ if err != nil {
+ return result, err
+ }
+ for _, statisticOrder := range list {
+ result[statisticOrder.RelationId] = statisticOrder
+ }
+ return result, nil
+}
diff --git a/service/sys_api.go b/service/sys_api.go
new file mode 100644
index 0000000..5f749d2
--- /dev/null
+++ b/service/sys_api.go
@@ -0,0 +1,148 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+
+ "gorm.io/gorm"
+)
+
+//@function: CreateApi
+//@description: 新增基础api
+//@param: api model.SysApi
+//@return: err error
+
+func CreateApi(api model.SysApi) (err error) {
+ if !errors.Is(global.MG_DB.Where("path = ? AND method = ? and appid=?", api.Path, api.Method, api.Appid).First(&model.SysApi{}).Error, gorm.ErrRecordNotFound) {
+ return errors.New("存在相同api")
+ }
+ return global.MG_DB.Create(&api).Error
+}
+
+//@function: DeleteApi
+//@description: 删除基础api
+//@param: api model.SysApi
+//@return: err error
+
+func DeleteApi(api model.SysApi, appid string) (err error) {
+ var e model.SysApi
+ if errors.Is(global.MG_DB.Where("id = ? and appid=? and type=?", api.ID, appid, "admin").First(&e).Error, gorm.ErrRecordNotFound) {
+ return errors.New("未找到api")
+ }
+ err = global.MG_DB.Delete(&api).Error
+ if err != nil {
+ return err
+ }
+ err = global.MG_DB.Table("casbin_rule").Model(&model.CasbinModel{}).Where("v1=? and v3 = ? AND v4 = ? AND v5=? ", appid, e.Path, e.Method, "admin").Delete(&model.CasbinModel{}).Error
+ return err
+}
+
+//@function: GetAPIInfoList
+//@description: 分页获取数据,
+//@param: api model.SysApi, info request.PageInfo, order string, desc bool
+//@return: err error
+
+func GetAPIInfoList(api model.SysApi, info request.PageInfo, order string, desc bool) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.SysApi{})
+ var apiList []model.SysApi
+ db = db.Where("appid=? and type=?", api.Appid, "admin")
+ if api.Path != "" {
+ db = db.Where("path LIKE ?", "%"+api.Path+"%")
+ }
+
+ if api.Description != "" {
+ db = db.Where("description LIKE ?", "%"+api.Description+"%")
+ }
+
+ if api.Method != "" {
+ db = db.Where("method = ?", api.Method)
+ }
+
+ if api.ApiGroup != "" {
+ db = db.Where("api_group = ?", api.ApiGroup)
+ }
+
+ err = db.Count(&total).Error
+
+ if err != nil {
+ return err, apiList, total
+ } else {
+ db = db.Limit(limit).Offset(offset)
+ if order != "" {
+ var OrderStr string
+ if desc {
+ OrderStr = order + " desc"
+ } else {
+ OrderStr = order
+ }
+ err = db.Order(OrderStr).Find(&apiList).Error
+ } else {
+ err = db.Order("api_group").Find(&apiList).Error
+ }
+ }
+ return err, apiList, total
+}
+
+//@function: GetAllApis
+//@description: 获取所有的api
+//@return: err error, apis []model.SysApi
+
+func GetAllApis(appid string) (err error, apis []model.SysApi) {
+ err = global.MG_DB.Where("appid=? and type=?", appid, "admin").Find(&apis).Error
+ return
+}
+
+//@function: GetApiById
+//@description: 根据id获取api
+//@param: id float64
+//@return: err error, api model.SysApi
+
+func GetApiById(id float64) (err error, api model.SysApi) {
+ err = global.MG_DB.Where("id = ?", id).First(&api).Error
+ return
+}
+
+func GetApiByPathMethod(path, method, appid string) (err error, api model.SysApi) {
+ err = global.MG_DB.Where("appid=? and path = ? AND method=? and type=?", appid, path, method, "admin").First(&api).Error
+ return
+}
+
+//@function: UpdateApi
+//@description: 根据id更新api
+//@param: api model.SysApi
+//@return: err error
+
+func UpdateApi(api model.SysApi) (err error) {
+ var oldA model.SysApi
+ err = global.MG_DB.Where("id = ?", api.ID).First(&oldA).Error
+ if oldA.Path != api.Path || oldA.Method != api.Method {
+ if !errors.Is(global.MG_DB.Where("path = ? AND method = ? and appid=? and type=?", api.Path, api.Method, api.Appid, "admin").First(&model.SysApi{}).Error, gorm.ErrRecordNotFound) {
+ return errors.New("存在相同api路径")
+ }
+ }
+ if err != nil {
+ return err
+ } else {
+ err = UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method, api.Appid, "admin")
+ if err != nil {
+ return err
+ } else {
+ err = global.MG_DB.Save(&api).Error
+ }
+ }
+ return err
+}
+
+//@function: DeleteApis
+//@description: 删除选中API
+//@param: apis []model.SysApi
+//@return: err error
+
+func DeleteApisByIds(ids request.IdsReq, appid string) (err error) {
+ err = global.MG_DB.Delete(&[]model.SysApi{}, "id in (?) and appid=?", ids.Ids, appid).Error
+ return err
+}
diff --git a/service/sys_authority.go b/service/sys_authority.go
new file mode 100644
index 0000000..0394dfd
--- /dev/null
+++ b/service/sys_authority.go
@@ -0,0 +1,152 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "strconv"
+
+ "gorm.io/gorm"
+)
+
+//@function: CreateAuthority
+//@description: 创建一个角色
+//@param: auth model.SysAuthority
+//@return: err error, authority model.SysAuthority
+
+func CreateAuthority(auth model.SysAuthority) (err error, authority model.SysAuthority) {
+ err = global.MG_DB.Create(&auth).Error
+ return err, auth
+}
+
+//@function: CopyAuthority
+//@description: 复制一个角色
+//@param: copyInfo response.SysAuthorityCopyResponse
+//@return: err error, authority model.SysAuthority
+
+func CopyAuthority(copyInfo response.SysAuthorityCopyResponse) (err error, authority model.SysAuthority) {
+ var authorityBox model.SysAuthority
+ if !errors.Is(global.MG_DB.Where("authority_id = ?", copyInfo.Authority.AuthorityId).First(&authorityBox).Error, gorm.ErrRecordNotFound) {
+ return errors.New("存在相同角色id"), authority
+ }
+ copyInfo.Authority.Children = []model.SysAuthority{}
+ err, menus := GetMenuAuthority(copyInfo.OldAuthorityId, copyInfo.Appid)
+ var baseMenu []model.SysBaseMenu
+ for _, v := range menus {
+ //intNum, _ := strconv.Atoi(v.ID)
+ intNum := int(v.ID)
+ v.SysBaseMenu.ID = uint(intNum)
+ baseMenu = append(baseMenu, v.SysBaseMenu)
+ }
+ copyInfo.Authority.SysBaseMenus = baseMenu
+ err = global.MG_DB.Create(©Info.Authority).Error
+
+ paths := GetPolicyPathByAuthorityId(copyInfo.OldAuthorityId, copyInfo.Appid)
+ err = UpdateCasbin(copyInfo.Authority.AuthorityId, copyInfo.Appid, paths)
+ if err != nil {
+ _ = DeleteAuthority(©Info.Authority)
+ }
+ return err, copyInfo.Authority
+}
+
+//@function: UpdateAuthority
+//@description: 更改一个角色
+//@param: auth model.SysAuthority
+//@return: err error, authority model.SysAuthority
+
+func UpdateAuthority(auth model.SysAuthority) (err error, authority model.SysAuthority) {
+ err = global.MG_DB.Where("authority_id = ?", auth.AuthorityId).First(&model.SysAuthority{}).Updates(&auth).Error
+ return err, auth
+}
+
+//@function: DeleteAuthority
+//@description: 删除角色
+//@param: auth *model.SysAuthority
+//@return: err error
+
+func DeleteAuthority(auth *model.SysAuthority) (err error) {
+ // if !errors.Is(global.MG_DB.Where("sys_authority_id = ?", auth.AuthorityId).First(&model.SysUserAuthority{}).Error, gorm.ErrRecordNotFound) {
+ // return errors.New("此角色有用户正在使用禁止删除")
+ // }
+ if !errors.Is(global.MG_DB.Where("parent_id = ?", auth.AuthorityId).First(&model.SysAuthority{}).Error, gorm.ErrRecordNotFound) {
+ return errors.New("此角色存在子角色不允许删除")
+ }
+ db := global.MG_DB.Preload("SysBaseMenus").Where("authority_id = ?", auth.AuthorityId).First(auth)
+ err = db.Unscoped().Delete(auth).Error
+ if len(auth.SysBaseMenus) > 0 {
+ err = global.MG_DB.Model(auth).Association("SysBaseMenus").Delete(auth.SysBaseMenus)
+ //err = db.Association("SysBaseMenus").Delete(&auth)
+ } else {
+ err = db.Error
+ }
+ ClearCasbin(1, auth.Appid, strconv.Itoa(int(auth.AuthorityId)))
+ return err
+}
+
+//@function: GetAuthorityInfoList
+//@description: 分页获取数据
+//@param: info request.PageInfo
+//@return: err error, list interface{}, total int64
+
+func GetAuthorityInfoList(info request.PageInfo, appid string) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB
+ var authority []model.SysAuthority
+ err = db.Limit(limit).Offset(offset).Where("parent_id = 0 and appid=? and type=?", appid, "admin").Find(&authority).Error
+ if len(authority) > 0 {
+ for k := range authority {
+ err = findChildrenAuthority(&authority[k])
+ }
+ }
+ return err, authority, total
+}
+
+//@function: GetAuthorityInfo
+//@description: 获取所有角色信息
+//@param: auth model.SysAuthority
+//@return: err error, sa model.SysAuthority
+
+func GetAuthorityInfo(auth model.SysAuthority) (err error, sa model.SysAuthority) {
+ err = global.MG_DB.Preload("DataAuthorityId").Where("authority_id = ?", auth.AuthorityId).First(&sa).Error
+ return err, sa
+}
+
+//@function: SetMenuAuthority
+//@description: 菜单与角色绑定
+//@param: auth *model.SysAuthority
+//@return: error
+
+func SetMenuAuthority(auth *model.SysAuthority) error {
+ //清除原有角色与菜单
+ //ClearCasbin(0, "menu", auth.Appid, strconv.Itoa(int(auth.AuthorityId)))
+ global.MG_DB.Table("casbin_rule").Model(&model.CasbinModel{}).Where("ptype=? and v0=? and v1=? and v2 = ? AND v5=? ", "p", "menu", auth.Appid, strconv.Itoa(int(auth.AuthorityId)), "admin").Delete(&model.CasbinModel{})
+ //更新casbin
+ rules := [][]string{}
+ for _, v := range auth.SysBaseMenus {
+ rules = append(rules, []string{"menu", auth.Appid, strconv.Itoa(int(auth.AuthorityId)), v.Path, "*", "admin"})
+ }
+ e := Casbin()
+ success, _ := e.AddPolicies(rules)
+ if success == false {
+ return errors.New("存在相同menu,添加失败,请联系管理员")
+ }
+ return nil
+}
+
+//@function: findChildrenAuthority
+//@description: 查询子角色
+//@param: authority *model.SysAuthority
+//@return: err error
+
+func findChildrenAuthority(authority *model.SysAuthority) (err error) {
+ err = global.MG_DB.Where("parent_id = ?", authority.AuthorityId).Find(&authority.Children).Error
+ if len(authority.Children) > 0 {
+ for k := range authority.Children {
+ err = findChildrenAuthority(&authority.Children[k])
+ }
+ }
+ return err
+}
diff --git a/service/sys_auto_code.go b/service/sys_auto_code.go
new file mode 100644
index 0000000..3e889b4
--- /dev/null
+++ b/service/sys_auto_code.go
@@ -0,0 +1,373 @@
+package service
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/utils"
+ "strings"
+ "text/template"
+
+ "gorm.io/gorm"
+)
+
+const (
+ autoPath = "autoCode/"
+ basePath = "resource/template"
+)
+
+type tplData struct {
+ template *template.Template
+ locationPath string
+ autoCodePath string
+ autoMoveFilePath string
+}
+
+//@function: PreviewTemp
+//@description: 预览创建代码
+//@param: model.AutoCodeStruct
+//@return: map[string]string, error
+
+func PreviewTemp(autoCode model.AutoCodeStruct) (map[string]string, error) {
+ dataList, _, needMkdir, err := getNeedList(&autoCode)
+ if err != nil {
+ return nil, err
+ }
+
+ // 写入文件前,先创建文件夹
+ if err = utils.CreateDir(needMkdir...); err != nil {
+ return nil, err
+ }
+
+ // 创建map
+ ret := make(map[string]string)
+
+ // 生成map
+ for _, value := range dataList {
+ ext := ""
+ if ext = filepath.Ext(value.autoCodePath); ext == ".txt" {
+ continue
+ }
+ f, err := os.OpenFile(value.autoCodePath, os.O_CREATE|os.O_WRONLY, 0755)
+ if err != nil {
+ return nil, err
+ }
+ if err = value.template.Execute(f, autoCode); err != nil {
+ return nil, err
+ }
+ _ = f.Close()
+ f, err = os.OpenFile(value.autoCodePath, os.O_CREATE|os.O_RDONLY, 0755)
+ if err != nil {
+ return nil, err
+ }
+ builder := strings.Builder{}
+ builder.WriteString("```")
+
+ if ext != "" && strings.Contains(ext, ".") {
+ builder.WriteString(strings.Replace(ext, ".", "", -1))
+ }
+ builder.WriteString("\n\n")
+ data, err := ioutil.ReadAll(f)
+ if err != nil {
+ return nil, err
+ }
+ builder.Write(data)
+ builder.WriteString("\n\n```")
+
+ pathArr := strings.Split(value.autoCodePath, string(os.PathSeparator))
+ ret[pathArr[1]+"-"+pathArr[3]] = builder.String()
+ _ = f.Close()
+
+ }
+ defer func() { // 移除中间文件
+ if err := os.RemoveAll(autoPath); err != nil {
+ return
+ }
+ }()
+ return ret, nil
+}
+
+//@function: CreateTemp
+//@description: 创建代码
+//@param: model.AutoCodeStruct
+//@return: err error
+
+func CreateTemp(autoCode model.AutoCodeStruct) (err error) {
+ dataList, fileList, needMkdir, err := getNeedList(&autoCode)
+ if err != nil {
+ return err
+ }
+ // 写入文件前,先创建文件夹
+ if err = utils.CreateDir(needMkdir...); err != nil {
+ return err
+ }
+
+ // 生成文件
+ for _, value := range dataList {
+ f, err := os.OpenFile(value.autoCodePath, os.O_CREATE|os.O_WRONLY, 0755)
+ if err != nil {
+ return err
+ }
+ if err = value.template.Execute(f, autoCode); err != nil {
+ return err
+ }
+ _ = f.Close()
+ }
+
+ defer func() { // 移除中间文件
+ if err := os.RemoveAll(autoPath); err != nil {
+ return
+ }
+ }()
+ if autoCode.AutoMoveFile { // 判断是否需要自动转移
+ for index, _ := range dataList {
+ addAutoMoveFile(&dataList[index])
+ }
+ for _, value := range dataList { // 移动文件
+ if err := utils.FileMove(value.autoCodePath, value.autoMoveFilePath); err != nil {
+ return err
+ }
+ }
+ initializeGormFilePath := filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Server, global.MG_CONFIG.AutoCode.SInitialize, "gorm.go")
+ initializeRouterFilePath := filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Server, global.MG_CONFIG.AutoCode.SInitialize, "router.go")
+ err = utils.AutoInjectionCode(initializeGormFilePath, "MysqlTables", "model."+autoCode.StructName+"{},")
+ if err != nil {
+ return err
+ }
+ err = utils.AutoInjectionCode(initializeRouterFilePath, "Routers", "router.Init"+autoCode.StructName+"Router(PrivateGroup)")
+ if err != nil {
+ return err
+ }
+ if global.MG_CONFIG.AutoCode.TransferRestart {
+ go func() {
+ _ = utils.Reload()
+ }()
+ }
+ return errors.New("创建代码成功并移动文件成功")
+ } else { // 打包
+ if err := utils.ZipFiles("./ginvueadmin.zip", fileList, ".", "."); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+//@function: GetAllTplFile
+//@description: 获取 pathName 文件夹下所有 tpl 文件
+//@param: pathName string, fileList []string
+//@return: []string, error
+
+func GetAllTplFile(pathName string, fileList []string) ([]string, error) {
+ files, err := ioutil.ReadDir(pathName)
+ for _, fi := range files {
+ if fi.IsDir() {
+ fileList, err = GetAllTplFile(pathName+"/"+fi.Name(), fileList)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ if strings.HasSuffix(fi.Name(), ".tpl") {
+ fileList = append(fileList, pathName+"/"+fi.Name())
+ }
+ }
+ }
+ return fileList, err
+}
+
+//@function: GetTables
+//@description: 获取数据库的所有表名
+//@param: dbName string
+//@return: err error, TableNames []request.TableReq
+
+func GetTables(dbName string) (err error, TableNames []request.TableReq) {
+ err = global.MG_DB.Raw("select table_name as table_name from information_schema.tables where table_schema = ?", dbName).Scan(&TableNames).Error
+ return err, TableNames
+}
+
+//@function: GetDB
+//@description: 获取数据库的所有数据库名
+//@return: err error, DBNames []request.DBReq
+
+func GetDB() (err error, DBNames []request.DBReq) {
+ err = global.MG_DB.Raw("SELECT SCHEMA_NAME AS `database` FROM INFORMATION_SCHEMA.SCHEMATA;").Scan(&DBNames).Error
+ return err, DBNames
+}
+
+//@function: GetDB
+//@description: 获取指定数据库和指定数据表的所有字段名,类型值等
+//@param: tableName string, dbName string
+//@return: err error, Columns []request.ColumnReq
+
+func GetColumn(tableName string, dbName string) (err error, Columns []request.ColumnReq) {
+ err = global.MG_DB.Raw("SELECT COLUMN_NAME column_name,DATA_TYPE data_type,CASE DATA_TYPE WHEN 'longtext' THEN c.CHARACTER_MAXIMUM_LENGTH WHEN 'varchar' THEN c.CHARACTER_MAXIMUM_LENGTH WHEN 'double' THEN CONCAT_WS( ',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE ) WHEN 'decimal' THEN CONCAT_WS( ',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE ) WHEN 'int' THEN c.NUMERIC_PRECISION WHEN 'bigint' THEN c.NUMERIC_PRECISION ELSE '' END AS data_type_long,COLUMN_COMMENT column_comment FROM INFORMATION_SCHEMA.COLUMNS c WHERE table_name = ? AND table_schema = ?", tableName, dbName).Scan(&Columns).Error
+ return err, Columns
+}
+
+//@function: addAutoMoveFile
+//@description: 生成对应的迁移文件路径
+//@param: *tplData
+//@return: null
+
+func addAutoMoveFile(data *tplData) {
+ base := filepath.Base(data.autoCodePath)
+ fileSlice := strings.Split(data.autoCodePath, string(os.PathSeparator))
+ n := len(fileSlice)
+ if n <= 2 {
+ return
+ }
+ if strings.Contains(fileSlice[1], "server") {
+ if strings.Contains(fileSlice[n-2], "router") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root, global.MG_CONFIG.AutoCode.Server,
+ global.MG_CONFIG.AutoCode.SRouter, base)
+ } else if strings.Contains(fileSlice[n-2], "api") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Server, global.MG_CONFIG.AutoCode.SApi, base)
+ } else if strings.Contains(fileSlice[n-2], "service") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Server, global.MG_CONFIG.AutoCode.SService, base)
+ } else if strings.Contains(fileSlice[n-2], "model") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Server, global.MG_CONFIG.AutoCode.SModel, base)
+ } else if strings.Contains(fileSlice[n-2], "request") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Server, global.MG_CONFIG.AutoCode.SRequest, base)
+ }
+ } else if strings.Contains(fileSlice[1], "web") {
+ if strings.Contains(fileSlice[n-1], "js") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Web, global.MG_CONFIG.AutoCode.WApi, base)
+ } else if strings.Contains(fileSlice[n-2], "form") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Web, global.MG_CONFIG.AutoCode.WForm, filepath.Base(filepath.Dir(filepath.Dir(data.autoCodePath))), strings.TrimSuffix(base, filepath.Ext(base))+"Form.vue")
+ } else if strings.Contains(fileSlice[n-2], "table") {
+ data.autoMoveFilePath = filepath.Join(global.MG_CONFIG.AutoCode.Root,
+ global.MG_CONFIG.AutoCode.Web, global.MG_CONFIG.AutoCode.WTable, filepath.Base(filepath.Dir(filepath.Dir(data.autoCodePath))), base)
+ }
+ }
+}
+
+//@function: CreateApi
+//@description: 自动创建api数据,
+//@param: a *model.AutoCodeStruct
+//@return: err error
+
+func AutoCreateApi(a *model.AutoCodeStruct) (err error) {
+ var apiList = []model.SysApi{
+ {
+ Path: "/" + a.Abbreviation + "/" + "create" + a.StructName,
+ Description: "新增" + a.Description,
+ ApiGroup: a.Abbreviation,
+ Method: "POST",
+ },
+ {
+ Path: "/" + a.Abbreviation + "/" + "delete" + a.StructName,
+ Description: "删除" + a.Description,
+ ApiGroup: a.Abbreviation,
+ Method: "DELETE",
+ },
+ {
+ Path: "/" + a.Abbreviation + "/" + "delete" + a.StructName + "ByIds",
+ Description: "批量删除" + a.Description,
+ ApiGroup: a.Abbreviation,
+ Method: "DELETE",
+ },
+ {
+ Path: "/" + a.Abbreviation + "/" + "update" + a.StructName,
+ Description: "更新" + a.Description,
+ ApiGroup: a.Abbreviation,
+ Method: "PUT",
+ },
+ {
+ Path: "/" + a.Abbreviation + "/" + "find" + a.StructName,
+ Description: "根据ID获取" + a.Description,
+ ApiGroup: a.Abbreviation,
+ Method: "GET",
+ },
+ {
+ Path: "/" + a.Abbreviation + "/" + "get" + a.StructName + "List",
+ Description: "获取" + a.Description + "列表",
+ ApiGroup: a.Abbreviation,
+ Method: "GET",
+ },
+ }
+ err = global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ for _, v := range apiList {
+ var api model.SysApi
+ if errors.Is(tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error, gorm.ErrRecordNotFound) {
+ if err := tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ }
+ }
+ return nil
+ })
+ return err
+}
+
+func getNeedList(autoCode *model.AutoCodeStruct) (dataList []tplData, fileList []string, needMkdir []string, err error) {
+ // 去除所有空格
+ utils.TrimSpace(autoCode)
+ for _, field := range autoCode.Fields {
+ utils.TrimSpace(field)
+ }
+ // 获取 basePath 文件夹下所有tpl文件
+ tplFileList, err := GetAllTplFile(basePath, nil)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ dataList = make([]tplData, 0, len(tplFileList))
+ fileList = make([]string, 0, len(tplFileList))
+ needMkdir = make([]string, 0, len(tplFileList)) // 当文件夹下存在多个tpl文件时,改为map更合理
+ // 根据文件路径生成 tplData 结构体,待填充数据
+ for _, value := range tplFileList {
+ dataList = append(dataList, tplData{locationPath: value})
+ }
+ // 生成 *Template, 填充 template 字段
+ for index, value := range dataList {
+ dataList[index].template, err = template.ParseFiles(value.locationPath)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ }
+ // 生成文件路径,填充 autoCodePath 字段,readme.txt.tpl不符合规则,需要特殊处理
+ // resource/template/web/api.js.tpl -> autoCode/web/autoCode.PackageName/api/autoCode.PackageName.js
+ // resource/template/readme.txt.tpl -> autoCode/readme.txt
+ autoPath := "autoCode/"
+ for index, value := range dataList {
+ trimBase := strings.TrimPrefix(value.locationPath, basePath+"/")
+ if trimBase == "readme.txt.tpl" {
+ dataList[index].autoCodePath = autoPath + "readme.txt"
+ continue
+ }
+
+ if lastSeparator := strings.LastIndex(trimBase, "/"); lastSeparator != -1 {
+ origFileName := strings.TrimSuffix(trimBase[lastSeparator+1:], ".tpl")
+ firstDot := strings.Index(origFileName, ".")
+ if firstDot != -1 {
+ var fileName string
+ if origFileName[firstDot:] != ".go" {
+ fileName = autoCode.PackageName + origFileName[firstDot:]
+ } else {
+ fileName = autoCode.HumpPackageName + origFileName[firstDot:]
+ }
+
+ dataList[index].autoCodePath = filepath.Join(autoPath, trimBase[:lastSeparator], autoCode.PackageName,
+ origFileName[:firstDot], fileName)
+ }
+ }
+
+ if lastSeparator := strings.LastIndex(dataList[index].autoCodePath, string(os.PathSeparator)); lastSeparator != -1 {
+ needMkdir = append(needMkdir, dataList[index].autoCodePath[:lastSeparator])
+ }
+ }
+ for _, value := range dataList {
+ fileList = append(fileList, value.autoCodePath)
+ }
+ return dataList, fileList, needMkdir, err
+}
diff --git a/service/sys_base_menu.go b/service/sys_base_menu.go
new file mode 100644
index 0000000..731b957
--- /dev/null
+++ b/service/sys_base_menu.go
@@ -0,0 +1,90 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+
+ "gorm.io/gorm"
+)
+
+//@function: DeleteBaseMenu
+//@description: 删除基础路由
+//@param: id float64
+//@return: err error
+
+func DeleteBaseMenu(id float64, appid string) (err error) {
+ err = global.MG_DB.Where("parent_id = ? and appid=?", id, appid).First(&model.SysBaseMenu{}).Error
+ if err != nil {
+ var menu model.SysBaseMenu
+ db := global.MG_DB.Where("id = ? and appid=?", id, appid).First(&menu).Delete(&menu)
+ //删除路由权限
+ err = db.Error
+ } else {
+ return errors.New("此菜单存在子菜单不可删除")
+ }
+ return err
+}
+
+//@function: UpdateBaseMenu
+//@description: 更新路由
+//@param: menu model.SysBaseMenu
+//@return: err error
+
+func UpdateBaseMenu(menu model.SysBaseMenu) (err error) {
+ var oldMenu model.SysBaseMenu
+ upDateMap := make(map[string]interface{})
+ upDateMap["keep_alive"] = menu.KeepAlive
+ upDateMap["default_menu"] = menu.DefaultMenu
+ upDateMap["parent_id"] = menu.ParentId
+ upDateMap["path"] = menu.Path
+ upDateMap["name"] = menu.Name
+ upDateMap["hidden"] = menu.Hidden
+ upDateMap["component"] = menu.Component
+ upDateMap["title"] = menu.Title
+ upDateMap["icon"] = menu.Icon
+ upDateMap["sort"] = menu.Sort
+
+ err = global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ db := tx.Where("id = ?", menu.ID).Find(&oldMenu)
+ if oldMenu.Name != menu.Name {
+ if !errors.Is(tx.Where("id <> ? AND name = ?", menu.ID, menu.Name).First(&model.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
+ global.MG_LOG.Debug("存在相同name修改失败")
+ return errors.New("存在相同name修改失败")
+ }
+ }
+ txErr := tx.Unscoped().Delete(&model.SysBaseMenuParameter{}, "sys_base_menu_id = ?", menu.ID).Error
+ if txErr != nil {
+ global.MG_LOG.Debug(txErr.Error())
+ return txErr
+ }
+ if len(menu.Parameters) > 0 {
+ for k, _ := range menu.Parameters {
+ menu.Parameters[k].SysBaseMenuID = menu.ID
+ }
+ txErr = tx.Create(&menu.Parameters).Error
+ if txErr != nil {
+ global.MG_LOG.Debug(txErr.Error())
+ return txErr
+ }
+ }
+
+ txErr = db.Updates(upDateMap).Error
+ if txErr != nil {
+ global.MG_LOG.Debug(txErr.Error())
+ return txErr
+ }
+ return nil
+ })
+ return err
+}
+
+//@function: GetBaseMenuById
+//@description: 返回当前选中menu
+//@param: id float64
+//@return: err error, menu model.SysBaseMenu
+
+func GetBaseMenuById(id float64) (err error, menu model.SysBaseMenu) {
+ err = global.MG_DB.Preload("Parameters").Where("id = ?", id).First(&menu).Error
+ return
+}
diff --git a/service/sys_casbin.go b/service/sys_casbin.go
new file mode 100644
index 0000000..35f2273
--- /dev/null
+++ b/service/sys_casbin.go
@@ -0,0 +1,137 @@
+package service
+
+import (
+ "errors"
+ "fmt"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/casbin/casbin/v2"
+ "github.com/casbin/casbin/v2/util"
+ gormadapter "github.com/casbin/gorm-adapter/v3"
+ _ "github.com/go-sql-driver/mysql"
+)
+
+//@function: UpdateCasbin
+//@description: 更新casbin权限
+//@param: authorityId string, casbinInfos []request.CasbinInfo
+//@return: error
+
+func UpdateCasbin(authorityId uint, appid string, casbinInfos []request.CasbinInfo) error {
+ //ClearCasbin(0, "api", appid, strconv.Itoa(int(authorityId)))
+ global.MG_DB.Table("casbin_rule").Model(&model.CasbinModel{}).Where("ptype=? and v0=? and v1=? and v2 = ? AND v5=? ", "p", "api", appid, strconv.Itoa(int(authorityId)), "admin").Delete(&model.CasbinModel{})
+ rules := [][]string{}
+ for _, v := range casbinInfos {
+ cm := model.CasbinModel{
+ Ptype: "p",
+ AuthorityId: authorityId,
+ Path: v.Path,
+ Method: v.Method,
+ }
+ rules = append(rules, []string{"api", appid, strconv.Itoa(int(cm.AuthorityId)), cm.Path, cm.Method, "admin"})
+ }
+ e := Casbin()
+ success, _ := e.AddPolicies(rules)
+ if success == false {
+ return errors.New("存在相同api,添加失败,请联系管理员")
+ }
+ return nil
+}
+
+//@function: UpdateCasbinApi
+//@description: API更新随动
+//@param: oldPath string, newPath string, oldMethod string, newMethod string
+//@return: error
+
+func UpdateCasbinApi(oldPath string, newPath string, oldMethod string, newMethod, appid, types string) error {
+ err := global.MG_DB.Table("casbin_rule").Model(&model.CasbinModel{}).Where("v1=? and v3 = ? AND v4 = ? and v5=?", appid, oldPath, oldMethod, types).Updates(map[string]interface{}{
+ "v3": newPath,
+ "v4": newMethod,
+ }).Error
+ return err
+}
+
+//@function: GetPolicyPathByAuthorityId
+//@description: 获取权限列表
+//@param: authorityId string
+//@return: pathMaps []request.CasbinInfo
+
+func GetPolicyPathByAuthorityId(authorityId uint, appid string) (pathMaps []request.CasbinInfo) {
+ e := Casbin()
+ list := e.GetFilteredPolicy(0, "api", appid, strconv.Itoa(int(authorityId)))
+ for _, v := range list {
+ pathMaps = append(pathMaps, request.CasbinInfo{
+ Path: v[3],
+ Method: v[4],
+ })
+ }
+ return pathMaps
+}
+
+//@function: ClearCasbin
+//@description: 清除匹配的权限
+//@param: v int, p ...string
+//@return: bool
+
+func ClearCasbin(v int, p ...string) bool {
+ e := Casbin()
+ success, _ := e.RemoveFilteredPolicy(v, p...)
+ return success
+
+}
+
+//@function: Casbin
+//@description: 持久化到数据库 引入自定义规则
+//@return: *casbin.Enforcer
+
+var (
+ syncedEnforcer *casbin.SyncedEnforcer
+ once sync.Once
+)
+
+func Casbin() *casbin.SyncedEnforcer {
+ once.Do(func() {
+ var err error
+ a, _ := gormadapter.NewAdapterByDB(global.MG_DB)
+ syncedEnforcer, err = casbin.NewSyncedEnforcer(global.MG_CONFIG.Casbin.ModelPath, a)
+ if err != nil {
+ fmt.Println(err)
+ }
+ syncedEnforcer.AddFunction("ParamsMatch", ParamsMatchFunc)
+ })
+ _ = syncedEnforcer.LoadPolicy()
+ return syncedEnforcer
+}
+
+//@function: ParamsMatch
+//@description: 自定义规则函数
+//@param: fullNameKey1 string, key2 string
+//@return: bool
+
+func ParamsMatch(fullNameKey1 string, key2 string) bool {
+ key1 := strings.Split(fullNameKey1, "?")[0]
+ // 剥离路径后再使用casbin的keyMatch2
+ return util.KeyMatch2(key1, key2)
+}
+
+//@function: ParamsMatchFunc
+//@description: 自定义规则函数
+//@param: args ...interface{}
+//@return: interface{}, error
+
+func ParamsMatchFunc(args ...interface{}) (interface{}, error) {
+ name1 := args[0].(string)
+ name2 := args[1].(string)
+
+ return ParamsMatch(name1, name2), nil
+}
+
+// 初始化角色
+func InitRole(userID, roleID, Appid string) {
+ e := Casbin()
+ e.AddRoleForUserInDomain(userID, roleID, Appid)
+}
diff --git a/service/sys_dictionary.go b/service/sys_dictionary.go
new file mode 100644
index 0000000..481e956
--- /dev/null
+++ b/service/sys_dictionary.go
@@ -0,0 +1,98 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+
+ "gorm.io/gorm"
+)
+
+//@function: DeleteSysDictionary
+//@description: 创建字典数据
+//@param: sysDictionary model.SysDictionary
+//@return: err error
+
+func CreateSysDictionary(sysDictionary model.SysDictionary) (err error) {
+ if (!errors.Is(global.MG_DB.First(&model.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) {
+ return errors.New("存在相同的type,不允许创建")
+ }
+ err = global.MG_DB.Create(&sysDictionary).Error
+ return err
+}
+
+//@function: DeleteSysDictionary
+//@description: 删除字典数据
+//@param: sysDictionary model.SysDictionary
+//@return: err error
+
+func DeleteSysDictionary(sysDictionary model.SysDictionary) (err error) {
+ err = global.MG_DB.Delete(&sysDictionary).Delete(&sysDictionary.SysDictionaryDetails).Error
+ return err
+}
+
+//@function: UpdateSysDictionary
+//@description: 更新字典数据
+//@param: sysDictionary *model.SysDictionary
+//@return: err error
+
+func UpdateSysDictionary(sysDictionary *model.SysDictionary) (err error) {
+ var dict model.SysDictionary
+ sysDictionaryMap := map[string]interface{}{
+ "Name": sysDictionary.Name,
+ "Type": sysDictionary.Type,
+ "Status": sysDictionary.Status,
+ "Desc": sysDictionary.Desc,
+ }
+ db := global.MG_DB.Where("id = ?", sysDictionary.ID).First(&dict)
+ if dict.Type == sysDictionary.Type {
+ err = db.Updates(sysDictionaryMap).Error
+ } else {
+ if (!errors.Is(global.MG_DB.First(&model.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) {
+ return errors.New("存在相同的type,不允许创建")
+ }
+ err = db.Updates(sysDictionaryMap).Error
+
+ }
+ return err
+}
+
+//@function: GetSysDictionary
+//@description: 根据id或者type获取字典单条数据
+//@param: Type string, Id uint
+//@return: err error, sysDictionary model.SysDictionary
+
+func GetSysDictionary(Type string, Id uint) (err error, sysDictionary model.SysDictionary) {
+ err = global.MG_DB.Where("type = ? OR id = ?", Type, Id).Preload("SysDictionaryDetails").First(&sysDictionary).Error
+ return
+}
+
+//@function: GetSysDictionaryInfoList
+//@description: 分页获取字典列表
+//@param: info request.SysDictionarySearch
+//@return: err error, list interface{}, total int64
+
+func GetSysDictionaryInfoList(info request.SysDictionarySearch) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.SysDictionary{})
+ var sysDictionarys []model.SysDictionary
+ // 如果有条件搜索 下方会自动创建搜索语句
+ if info.Name != "" {
+ db = db.Where("`name` LIKE ?", "%"+info.Name+"%")
+ }
+ if info.Type != "" {
+ db = db.Where("`type` LIKE ?", "%"+info.Type+"%")
+ }
+ if info.Status != nil {
+ db = db.Where("`status` = ?", info.Status)
+ }
+ if info.Desc != "" {
+ db = db.Where("`desc` LIKE ?", "%"+info.Desc+"%")
+ }
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&sysDictionarys).Error
+ return err, sysDictionarys, total
+}
diff --git a/service/sys_dictionary_detail.go b/service/sys_dictionary_detail.go
new file mode 100644
index 0000000..f69ab23
--- /dev/null
+++ b/service/sys_dictionary_detail.go
@@ -0,0 +1,76 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+//@function: CreateSysDictionaryDetail
+//@description: 创建字典详情数据
+//@param: sysDictionaryDetail model.SysDictionaryDetail
+//@return: err error
+
+func CreateSysDictionaryDetail(sysDictionaryDetail model.SysDictionaryDetail) (err error) {
+ err = global.MG_DB.Create(&sysDictionaryDetail).Error
+ return err
+}
+
+//@function: DeleteSysDictionaryDetail
+//@description: 删除字典详情数据
+//@param: sysDictionaryDetail model.SysDictionaryDetail
+//@return: err error
+
+func DeleteSysDictionaryDetail(sysDictionaryDetail model.SysDictionaryDetail) (err error) {
+ err = global.MG_DB.Delete(&sysDictionaryDetail).Error
+ return err
+}
+
+//@function: UpdateSysDictionaryDetail
+//@description: 更新字典详情数据
+//@param: sysDictionaryDetail *model.SysDictionaryDetail
+//@return: err error
+
+func UpdateSysDictionaryDetail(sysDictionaryDetail *model.SysDictionaryDetail) (err error) {
+ err = global.MG_DB.Save(sysDictionaryDetail).Error
+ return err
+}
+
+//@function: GetSysDictionaryDetail
+//@description: 根据id获取字典详情单条数据
+//@param: id uint
+//@return: err error, sysDictionaryDetail model.SysDictionaryDetail
+
+func GetSysDictionaryDetail(id uint) (err error, sysDictionaryDetail model.SysDictionaryDetail) {
+ err = global.MG_DB.Where("id = ?", id).First(&sysDictionaryDetail).Error
+ return
+}
+
+//@function: GetSysDictionaryDetailInfoList
+//@description: 分页获取字典详情列表
+//@param: info request.SysDictionaryDetailSearch
+//@return: err error, list interface{}, total int64
+
+func GetSysDictionaryDetailInfoList(info request.SysDictionaryDetailSearch) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.SysDictionaryDetail{})
+ var sysDictionaryDetails []model.SysDictionaryDetail
+ // 如果有条件搜索 下方会自动创建搜索语句
+ if info.Label != "" {
+ db = db.Where("label LIKE ?", "%"+info.Label+"%")
+ }
+ if info.Value != 0 {
+ db = db.Where("value = ?", info.Value)
+ }
+ if info.Status != nil {
+ db = db.Where("status = ?", info.Status)
+ }
+ if info.SysDictionaryID != 0 {
+ db = db.Where("sys_dictionary_id = ?", info.SysDictionaryID)
+ }
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&sysDictionaryDetails).Error
+ return err, sysDictionaryDetails, total
+}
diff --git a/service/sys_email.go b/service/sys_email.go
new file mode 100644
index 0000000..6e8db45
--- /dev/null
+++ b/service/sys_email.go
@@ -0,0 +1,16 @@
+package service
+
+import (
+ "pure-admin/utils"
+)
+
+//@function: EmailTest
+//@description: 发送邮件测试
+//@return: err error
+
+func EmailTest() (err error) {
+ subject := "test"
+ body := "test"
+ err = utils.EmailTest(subject, body)
+ return err
+}
diff --git a/service/sys_menu.go b/service/sys_menu.go
new file mode 100644
index 0000000..7d3da57
--- /dev/null
+++ b/service/sys_menu.go
@@ -0,0 +1,168 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "strconv"
+
+ "gorm.io/gorm"
+)
+
+//@function: getMenuTreeMap
+//@description: 获取路由总树map
+//@param: authorityId string
+//@return: err error, treeMap map[string][]model.SysMenu
+
+func getMenuTreeMap(authorityId uint, appid string) (err error, treeMap map[string][]model.SysMenu) {
+ var (
+ allMenus []model.SysMenu
+ paths []string
+ )
+ e := Casbin()
+ res := e.GetFilteredPolicy(0, "menu", appid, strconv.Itoa(int(authorityId)))
+ for _, v := range res {
+ if len(v) > 3 {
+ paths = append(paths, v[3])
+ }
+ }
+ treeMap = make(map[string][]model.SysMenu)
+ err = global.MG_DB.Model(&model.SysMenu{}).
+ Where("path in (?) and appid=?", paths, appid).Order("sys_base_menus.sort,sys_base_menus.id").Find(&allMenus).Error
+ for _, v := range allMenus {
+ treeMap[v.ParentId] = append(treeMap[v.ParentId], v)
+ }
+ return err, treeMap
+}
+
+//@function: GetMenuTree
+//@description: 获取动态菜单树
+//@param: authorityId string
+//@return: err error, menus []model.SysMenu
+
+func GetMenuTree(authorityId uint, appid string) (err error, menus []model.SysMenu) {
+ err, menuTree := getMenuTreeMap(authorityId, appid)
+ menus = menuTree["0"]
+ for i := 0; i < len(menus); i++ {
+ err = getChildrenList(&menus[i], menuTree)
+ }
+ return err, menus
+}
+
+//@function: getChildrenList
+//@description: 获取子菜单
+//@param: menu *model.SysMenu, treeMap map[string][]model.SysMenu
+//@return: err error
+
+func getChildrenList(menu *model.SysMenu, treeMap map[string][]model.SysMenu) (err error) {
+ menu.Children = treeMap[strconv.Itoa(int(menu.ID))]
+ for i := 0; i < len(menu.Children); i++ {
+ err = getChildrenList(&menu.Children[i], treeMap)
+ }
+ return err
+}
+
+//@function: GetInfoList
+//@description: 获取路由分页
+//@return: err error, list interface{}, total int64
+
+func GetInfoList() (err error, list interface{}, total int64) {
+ var menuList []model.SysBaseMenu
+ err, treeMap := getBaseMenuTreeMap()
+ menuList = treeMap["0"]
+ for i := 0; i < len(menuList); i++ {
+ err = getBaseChildrenList(&menuList[i], treeMap)
+ }
+ return err, menuList, total
+}
+
+//@function: getBaseChildrenList
+//@description: 获取菜单的子菜单
+//@param: menu *model.SysBaseMenu, treeMap map[string][]model.SysBaseMenu
+//@return: err error
+
+func getBaseChildrenList(menu *model.SysBaseMenu, treeMap map[string][]model.SysBaseMenu) (err error) {
+ menu.Children = treeMap[strconv.Itoa(int(menu.ID))]
+ for i := 0; i < len(menu.Children); i++ {
+ err = getBaseChildrenList(&menu.Children[i], treeMap)
+ }
+ return err
+}
+
+//@function: AddBaseMenu
+//@description: 添加基础路由
+//@param: menu model.SysBaseMenu
+//@return: error
+
+func AddBaseMenu(menu model.SysBaseMenu, appid string) error {
+ if !errors.Is(global.MG_DB.Where("name = ? and appid=?", menu.Name, appid).First(&model.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
+ return errors.New("存在重复name,请修改name")
+ }
+ menu.Appid = appid
+ menu.Type = "admin"
+ return global.MG_DB.Create(&menu).Error
+}
+
+//@function: getBaseMenuTreeMap
+//@description: 获取路由总树map
+//@return: err error, treeMap map[string][]model.SysBaseMenu
+
+func getBaseMenuTreeMap() (err error, treeMap map[string][]model.SysBaseMenu) {
+ var allMenus []model.SysBaseMenu
+ treeMap = make(map[string][]model.SysBaseMenu)
+ err = global.MG_DB.Order("sort").Preload("Parameters").Find(&allMenus).Error
+ for _, v := range allMenus {
+ treeMap[v.ParentId] = append(treeMap[v.ParentId], v)
+ }
+ return err, treeMap
+}
+
+//@function: GetBaseMenuTree
+//@description: 获取基础路由树
+//@return: err error, menus []model.SysBaseMenu
+
+func GetBaseMenuTree() (err error, menus []model.SysBaseMenu) {
+ err, treeMap := getBaseMenuTreeMap()
+ menus = treeMap["0"]
+ for i := 0; i < len(menus); i++ {
+ err = getBaseChildrenList(&menus[i], treeMap)
+ }
+ return err, menus
+}
+
+//@function: AddMenuAuthority
+//@description: 为角色增加menu树
+//@param: menus []model.SysBaseMenu, authorityId string
+//@return: err error
+
+func AddMenuAuthority(menus []model.SysBaseMenu, authorityId uint, appid string) (err error) {
+ var auth model.SysAuthority
+ auth.AuthorityId = authorityId
+ auth.SysBaseMenus = menus
+ auth.Appid = appid
+ err = SetMenuAuthority(&auth)
+ return err
+}
+
+//@function: GetMenuAuthority
+//@description: 查看当前角色树
+//@param: info *request.GetAuthorityId
+//@return: err error, menus []model.SysMenu
+
+func GetMenuAuthority(authorityId uint, appid string) (err error, menus []model.SysMenu) {
+ var (
+ paths []string
+ )
+ e := Casbin()
+ res := e.GetFilteredPolicy(0, "menu", appid, strconv.Itoa(int(authorityId)))
+ for _, v := range res {
+ if len(v) > 3 {
+ paths = append(paths, v[3])
+ }
+ }
+ err = global.MG_DB.Model(&model.SysMenu{}).
+ Where("path in (?) and appid=? and type=?", paths, appid, "admin").Order("sys_base_menus.sort").Find(&menus).Error
+ //sql := "SELECT authority_menu.keep_alive,authority_menu.default_menu,authority_menu.created_at,authority_menu.updated_at,authority_menu.deleted_at,authority_menu.menu_level,authority_menu.parent_id,authority_menu.path,authority_menu.`name`,authority_menu.hidden,authority_menu.component,authority_menu.title,authority_menu.icon,authority_menu.sort,authority_menu.menu_id,authority_menu.authority_id FROM authority_menu WHERE authority_menu.authority_id = ? ORDER BY authority_menu.sort ASC"
+ //err = global.MG_DB.Raw(sql, authorityId).Scan(&menus).Error
+ return err, menus
+}
diff --git a/service/sys_mission_reward.go b/service/sys_mission_reward.go
new file mode 100644
index 0000000..b97b226
--- /dev/null
+++ b/service/sys_mission_reward.go
@@ -0,0 +1,107 @@
+package service
+
+import (
+ "errors"
+ "gorm.io/gorm"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "strconv"
+)
+
+func GetSysMissionRewardList(params request.SearchSysReward) (err error, list []model.SysMissionReward, total int64) {
+ limit := params.PageSize
+ offset := params.PageSize * (params.Page - 1)
+ db := global.MG_DB.Model(&model.SysMissionReward{})
+ db.Count(&total)
+ var res []model.SysMissionReward
+ err = db.Order("id desc").Offset(offset).Limit(limit).Find(&res).Error
+ if err != nil {
+ return err, nil, 0
+ }
+
+ return err, res, total
+}
+
+func SendSysMissionReward(uuid string, info request.IdReq) (err error) {
+ var (
+ checkReward model.SysMissionReward
+ checkMission model.MissionClaim
+ checkVideo model.MissionClaimVideo
+ )
+ err = global.MG_DB.Model(&model.SysMissionReward{}).Where("id = ?", info.ID).First(&checkReward).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ err = global.MG_DB.Model(&model.MissionClaimVideo{}).Where("id = ?", checkReward.RelationId).First(&checkVideo).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ err = global.MG_DB.Model(&model.MissionClaim{}).Where("id = ?", checkVideo.MissionClaimId).First(&checkMission).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ var bkbWallet model.Wallet
+ err = global.MG_DB.Model(&model.Wallet{}).Where("platform = 'bkb' AND user_id = 'bkb'").First(&bkbWallet).Error
+ if err != nil {
+ return
+ }
+ tx := global.MG_DB.Begin()
+ //修改任务状态
+ err = global.MG_DB.Model(&model.SysMissionReward{}).Where("id = ?", info.ID).Update("status", 3).Error
+ if err != nil {
+ tx.Rollback()
+ err = errors.New("update mission reward status failed")
+ return
+ }
+
+ // 扣除商家锁定营销账户
+ err = tx.Model(&model.Wallet{}).Where("id = ?", bkbWallet.ID).UpdateColumn("fund", gorm.Expr("fund - ?", checkReward.Bonus)).Error
+ if err != nil {
+ tx.Rollback()
+ err = errors.New("settle seller wallet failed")
+ return
+ }
+ var billFund model.BillFund
+ billFund.UserID = "bkb"
+ billFund.TransactionType = 1
+ billFund.TransactionId = "MB" + generate() // mission-bill
+ billFund.Title = ""
+ billFund.Price = checkReward.Bonus
+ billFund.Balance = bkbWallet.Fund
+ billFund.Platform = "seller"
+ billFund.RelatedId = strconv.Itoa(int(checkReward.ID))
+ billFund.Status = 2
+ err = tx.Model(&model.BillFund{}).Create(&billFund).Error
+ if err != nil {
+ tx.Rollback()
+ return
+ }
+ // 结算至网红钱包-余额
+ var influenceWallet model.Wallet
+ err = tx.Model(&model.Wallet{}).Where("platform = 'influencer' AND user_id = ?", checkVideo.CreateBy).
+ Updates(map[string]interface{}{"balance": gorm.Expr("balance + ?", checkReward.Bonus), "transit_balance": gorm.Expr("transit_balance - ?", checkReward.Bonus)}).First(&influenceWallet).Error
+ if err != nil {
+ tx.Rollback()
+ err = errors.New("settle influencer wallet failed")
+ return
+ }
+ // 创建任务结算账单
+ var bill model.Bill
+ bill.UserID = checkVideo.CreateBy
+ bill.Type = "1"
+ bill.Title = "平台奖励发放-" + checkReward.Title
+ bill.Price = checkReward.Bonus
+ bill.Balance = influenceWallet.Balance
+ bill.Status = 2
+ bill.Receipt = 1
+ bill.Platform = "influencer"
+ err = tx.Model(&model.Bill{}).Create(&bill).Error
+ if err != nil {
+ tx.Rollback()
+ err = errors.New("create influencer mission settle bill failed")
+ return
+ }
+ tx.Commit()
+ return err
+}
diff --git a/service/sys_operation_record.go b/service/sys_operation_record.go
new file mode 100644
index 0000000..fddf92b
--- /dev/null
+++ b/service/sys_operation_record.go
@@ -0,0 +1,91 @@
+package service
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+)
+
+//@function: CreateSysOperationRecord
+//@description: 创建记录
+//@param: sysOperationRecord model.SysOperationRecord
+//@return: err error
+
+func CreateSysOperationRecord(sysOperationRecord model.SysOperationRecord) (err error) {
+ err = global.MG_DB.Create(&sysOperationRecord).Error
+ return err
+}
+
+//@function: DeleteSysOperationRecordByIds
+//@description: 批量删除记录
+//@param: ids request.IdsReq
+//@return: err error
+
+func DeleteSysOperationRecordByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.SysOperationRecord{}, "id in (?)", ids.Ids).Error
+ return err
+}
+
+//@function: DeleteSysOperationRecord
+//@description: 删除操作记录
+//@param: sysOperationRecord model.SysOperationRecord
+//@return: err error
+
+func DeleteSysOperationRecord(sysOperationRecord model.SysOperationRecord) (err error) {
+ err = global.MG_DB.Delete(&sysOperationRecord).Error
+ return err
+}
+
+//@function: DeleteSysOperationRecord
+//@description: 根据id获取单条操作记录
+//@param: id uint
+//@return: err error, sysOperationRecord model.SysOperationRecord
+
+func GetSysOperationRecord(id uint) (err error, sysOperationRecord model.SysOperationRecord) {
+ err = global.MG_DB.Where("id = ?", id).First(&sysOperationRecord).Error
+ return
+}
+
+//@function: GetSysOperationRecordInfoList
+//@description: 分页获取操作记录列表
+//@param: info request.SysOperationRecordSearch
+//@return: err error, list interface{}, total int64
+
+func GetSysOperationRecordInfoList(info request.SysOperationRecordSearch, appid string) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.SysOperationRecord{}).Where("sys_operation_records.appid=?", appid).
+ Joins("LEFT JOIN user as u ON u.id=sys_operation_records.user_id")
+ var sysOperationRecords []model.SysOperationRecord
+ // 如果有条件搜索 下方会自动创建搜索语句
+ if info.Method != "" {
+ db = db.Where("sys_operation_records.method = ?", info.Method)
+ }
+ if info.Path != "" {
+ db = db.Where("sys_operation_records.path LIKE ?", "%"+info.Path+"%")
+ }
+ if info.UserID != 0 {
+ db = db.Where("sys_operation_records.user_id = ?", info.UserID)
+ }
+ if info.NickName != "" {
+ db = db.Where("u.nick_name = ?", info.NickName)
+ }
+ if info.CreatedAtStart != "" {
+ db = db.Where("sys_operation_records.created_at >= ?", info.CreatedAtStart)
+ }
+ if info.CreatedAtEnd != "" {
+ db = db.Where("sys_operation_records.created_at < ?", info.CreatedAtEnd)
+ }
+ if info.Status != 0 {
+ db = db.Where("sys_operation_records.status = ?", info.Status)
+ }
+ err = db.Count(&total).Error
+ err = db.Select("sys_operation_records.id,sys_operation_records.ip,sys_operation_records.method,sys_operation_records.path,sys_operation_records.status,sys_operation_records.agent,sys_operation_records.user_id,sys_operation_records.created_at,sys_operation_records.updated_at").Order("sys_operation_records.id desc").Limit(limit).Offset(offset).Preload("User").Find(&sysOperationRecords).Error
+ for i := 0; i < len(sysOperationRecords); i++ {
+ var api model.SysApi
+ _, api = GetApiByPathMethod(sysOperationRecords[i].Path, sysOperationRecords[i].Method, appid)
+ sysOperationRecords[i].ApiDescription = api.Description
+ }
+ return err, sysOperationRecords, total
+}
diff --git a/service/sys_system.go b/service/sys_system.go
new file mode 100644
index 0000000..8c33782
--- /dev/null
+++ b/service/sys_system.go
@@ -0,0 +1,30 @@
+package service
+
+import (
+ "pure-admin/config"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/utils"
+)
+
+// @function: GetSystemConfig
+// @description: 读取配置文件
+// @return: err error, conf config.Server
+func GetSystemConfig() (err error, conf config.Server) {
+ return nil, global.MG_CONFIG
+}
+
+// @description set system config,
+
+// @function: SetSystemConfig
+// @description: 设置配置文件
+// @param: system model.System
+// @return: err error
+func SetSystemConfig(system model.System) (err error) {
+ cs := utils.StructToMap(system.Config)
+ for k, v := range cs {
+ global.MG_VP.Set(k, v)
+ }
+ err = global.MG_VP.WriteConfig()
+ return err
+}
diff --git a/service/sys_user.go b/service/sys_user.go
new file mode 100644
index 0000000..dc86d9b
--- /dev/null
+++ b/service/sys_user.go
@@ -0,0 +1,284 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/utils"
+ "strconv"
+
+ uuid "github.com/satori/go.uuid"
+ "gorm.io/gorm"
+)
+
+//@function: Register
+//@description: 用户注册
+//@param: u model.SysUser
+//@return: err error, userInter model.SysUser
+
+func Register(u model.User) (err error, userInter model.User) {
+ var user model.SysUser
+ if !errors.Is(global.MG_DB.Where("username = ?", u.Username).First(&user).Error, gorm.ErrRecordNotFound) { // 判断用户名是否注册
+ return errors.New("用户名已注册"), userInter
+ }
+ // 否则 附加uuid 密码md5简单加密 注册
+ u.Password = utils.MD5V([]byte(u.Password))
+ u.UUID = uuid.NewV4()
+ u.Appid = "appid"
+ u.Avatar = "http://qmplusimg.henrongyi.top/head.png"
+ u.Type = "admin"
+ err = global.MG_DB.Create(&u).Error
+ if err != nil {
+ err = errors.New("注册失败")
+ }
+ go InitRole(u.UUID.String(), strconv.Itoa(int(u.AuthorityID)), u.Appid)
+ return err, u
+}
+
+//@function: Login
+//@description: 用户登录
+//@param: u *model.SysUser
+//@return: err error, userInter *model.SysUser
+
+func Login(u *model.User) (err error, userInter *model.User) {
+ var (
+ user model.User
+ authorities []model.SysAuthority
+ )
+ u.Password = utils.MD5V([]byte(u.Password))
+ err = global.MG_DB.Where("username = ? AND password = ? and type=?", u.Username, u.Password, "admin").First(&user).Error
+ if err != nil {
+ return errors.New("用户名不存在或者密码错误"), nil
+ }
+ e := Casbin()
+ res := e.GetRolesForUserInDomain(user.UUID.String(), u.Appid)
+ if len(res) > 0 {
+ err = global.MG_DB.Where("authority_id IN ?", res).Find(&authorities).Error
+ } else {
+ err = errors.New("该用户未分配角色")
+ }
+ for _, v := range authorities {
+ if user.AuthorityID == v.AuthorityId {
+ user.Authority = v
+ break
+ }
+ }
+ user.Authorities = authorities
+ return err, &user
+}
+
+//@function: ChangePassword
+//@description: 修改用户密码
+//@param: u *model.SysUser, newPassword string
+//@return: err error, userInter *model.SysUser
+
+func ChangePassword(u *model.SysUser, newPassword string) (err error, userInter *model.SysUser) {
+ var user model.SysUser
+ u.Password = utils.MD5V([]byte(u.Password))
+ err = global.MG_DB.Where("username = ? AND password = ?", u.Username, u.Password).First(&user).Update("password", utils.MD5V([]byte(newPassword))).Error
+ return err, u
+}
+
+//@function: GetUserInfoList
+//@description: 分页获取数据
+//@param: info request.PageInfo
+//@return: err error, list interface{}, total int64
+
+func GetUserInfoList1(info request.SearchSysUserParams, appid string) (err error, list interface{}, total int64) {
+ var (
+ authorityIds = make([]string, 0)
+ authorities []model.SysAuthority
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.User{})
+ db = db.Where("appid=? and type=?", appid, "admin")
+ if info.SysUser.NickName != "" {
+ db = db.Where("nick_name LIKE ?", "%"+info.SysUser.NickName+"%")
+ }
+ if info.SysUser.Username != "" {
+ db = db.Where("username LIKE ?", "%"+info.SysUser.Username+"%")
+ }
+ if info.SysUser.Phone != "" {
+ db = db.Where("phone LIKE ?", "%"+info.SysUser.Phone+"%")
+ }
+ if info.SysUser.Status != "" {
+ db = db.Where("status = ?", info.SysUser.Status)
+ }
+ var userList []model.User
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&userList).Error
+ if err != nil {
+ return err, nil, 0
+ }
+ for k, _ := range userList {
+ e := Casbin()
+ res := e.GetRolesForUserInDomain(userList[k].UUID.String(), appid)
+ if len(res) > 0 {
+ authorityIds = append(authorityIds, res...)
+ }
+ }
+ if len(authorityIds) > 0 {
+ err = global.MG_DB.Where("authority_id IN ?", authorityIds).Find(&authorities).Error
+ }
+ for k, _ := range userList {
+ authorities1 := make([]model.SysAuthority, 0)
+ for _, v := range authorities {
+ if userList[k].AuthorityID == v.AuthorityId {
+ authorities1 = append(authorities1, v)
+ }
+ }
+ userList[k].Authorities = authorities1
+ }
+ return err, userList, total
+}
+
+func GetUserSelectList(nickName, appid string) (list []model.SysUserSimple, err error) {
+ db := global.MG_DB.Model(&model.User{})
+ if nickName != "" {
+ db = db.Where("nick_name LIKE ?", "%"+nickName+"%")
+ }
+ err = db.Select("uuid,nick_name").Where("appid=?", appid).Find(&list).Error
+ return
+}
+
+//@function: SetUserAuthority
+//@description: 设置一个用户的权限
+//@param: uuid uuid.UUID, authorityId string
+//@return: err error
+
+func SetUserAuthority(uuid string, authorityId string) (err error) {
+ err = global.MG_DB.Where("uuid = ?", uuid).First(&model.User{}).Update("authority_id", authorityId).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: SetUserAuthorities
+//@description: 设置一个用户的权限
+//@param: id uint, authorityIds []string
+//@return: err error
+
+func SetUserAuthorities(uuid string, authorityIds []string) (err error) {
+ if len(authorityIds) != 0 {
+ err = global.MG_DB.Model(&model.SysUser{}).Where("uuid = ?", uuid).Update("authority_id", authorityIds[0]).Error
+ if err != nil {
+ return err
+ }
+ }
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ // TxErr := tx.Delete(&[]model.SysUserAuthority{}, "sys_user_id = ?", uuid).Error
+ // if TxErr != nil {
+ // return TxErr
+ // }
+ // useAuthority := []model.SysUserAuthority{}
+ // for _, v := range authorityIds {
+ // useAuthority = append(useAuthority, model.SysUserAuthority{
+ // uuid, v,
+ // })
+ // }
+ // TxErr = tx.Create(&useAuthority).Error
+ // if TxErr != nil {
+ // return TxErr
+ // }
+ // 返回 nil 提交事务
+ return nil
+ })
+}
+
+//@function: DeleteUser
+//@description: 删除用户
+//@param: id float64
+//@return: err error
+
+func DeleteUser1(id float64) (err error) {
+ var user model.User
+ err = global.MG_DB.Where("id = ?", id).Delete(&user).Error
+ // err = global.MG_DB.Delete(&[]model.SysUserAuthority{}, "sys_user_id = ?", id).Error
+ return err
+}
+
+//@function: SetUserInfo
+//@description: 设置用户信息
+//@param: reqUser model.SysUser
+//@return: err error, user model.SysUser
+
+func SetUserInfo(reqUser model.SysUser) (err error, user model.SysUser) {
+ err = global.MG_DB.Model(&model.SysUser{}).Where("uuid = ?", reqUser.UUID).Updates(&reqUser).Scan(&user).Error
+ return err, user
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetUserInfo
+//@description: 获取用户信息
+//@param: uuid string
+//@return: err error, user system.SysUser
+
+func GetUserInfo1(uuid string) (err error, user model.SysUser) {
+ var reqUser model.SysUser
+ err = global.MG_DB.Preload("Authority").Preload("Authorities").Preload("SysUserAuthors").First(&reqUser, "uuid = ?", uuid).Error
+ if err != nil {
+ return err, reqUser
+ }
+ return err, reqUser
+}
+
+//@function: FindUserById
+//@description: 通过id获取用户信息
+//@param: id int
+//@return: err error, user *model.SysUser
+
+func FindUserById(id int) (err error, user *model.SysUser) {
+ var u model.SysUser
+ err = global.MG_DB.Where("`id` = ?", id).First(&u).Error
+ return err, &u
+}
+
+//@function: FindUserByUuid
+//@description: 通过uuid获取用户信息
+//@param: uuid string
+//@return: err error, user *model.SysUser
+
+func FindUserByUuid(uuid string) (err error, user *model.User) {
+ var u model.User
+ if err = global.MG_DB.Where("`uuid` = ?", uuid).First(&u).Error; err != nil {
+ return errors.New("用户不存在"), &u
+ }
+ return nil, &u
+}
+
+func FindUserByAuthorityID(authority_id string) (err error, user *model.SysUser) {
+ var u model.SysUser
+ if err = global.MG_DB.Where("`authority_id` = ?", authority_id).First(&u).Error; err != nil {
+ return errors.New("用户不存在"), &u
+ }
+ return nil, &u
+}
+
+func FindUserSimpleByUuid(uuid string) (err error, user *model.SysUserSimple) {
+ var u model.SysUserSimple
+ if err = global.MG_DB.Select("id,uuid,nick_name,username").Where("`uuid` = ?", uuid).First(&u).Error; err != nil {
+ return errors.New("用户不存在"), &u
+ }
+ return nil, &u
+}
+
+func FindAuthorityByAuthorityID(id string) (err error, user *model.SysAuthoritySimple) {
+ var u model.SysAuthoritySimple
+ if err = global.MG_DB.Where("`authority_id` = ?", id).First(&u).Error; err != nil {
+ return errors.New("角色不存在"), &u
+ }
+ return nil, &u
+}
+
+func GetSysUserAuthority(userId, authorityId string) (err error, list []global.BASE_ID_STR) {
+ db := global.MG_DB.Table("sys_user_authority").Select("sys_user_id as id")
+ if userId != "" {
+ db = db.Where("sys_user_id = ?", userId)
+ }
+ if authorityId != "" {
+ db = db.Where("sys_authority_id = ?", authorityId)
+ }
+ err = db.Find(&list).Error
+ return err, list
+}
diff --git a/service/tags.go b/service/tags.go
new file mode 100644
index 0000000..4a93c1e
--- /dev/null
+++ b/service/tags.go
@@ -0,0 +1,171 @@
+package service
+
+import (
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "strings"
+
+ "gorm.io/gorm"
+)
+
+func GetTagsList(info request.SearchTags) (err error, list []model.Tags, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.Tags{})
+
+ //词库类型
+ if info.Value != "" {
+ db = db.Where("value = ?", info.Value)
+ }
+
+ //敏感词类型
+ if info.Type != "" {
+ db = db.Where("type = ?", info.Type)
+ }
+
+ var Tags []model.Tags
+
+ err = db.Count(&total).Error
+ err = db.Preload("User").Order("created_at desc").Limit(limit).Offset(offset).Find(&Tags).Error
+ if err != nil {
+ return
+ }
+
+ return err, Tags, total
+}
+
+func CreateTags(tags request.TagsCommon, uuid string) (model.Tags, error) {
+ var doc, data model.Tags
+ if !errors.Is(global.MG_DB.Model(&model.Tags{}).Where("value = ? and type = ?", tags.Value, tags.Type).First(&model.Tags{}).Error, gorm.ErrRecordNotFound) {
+ return doc, errors.New("该标签已存在")
+ }
+ data = model.Tags{
+ Value: tags.Value,
+ CreateBy: uuid,
+ UpdateBy: uuid,
+ Type: tags.Type,
+ }
+ if err := global.MG_DB.Model(&model.Tags{}).Create(&data).Error; err != nil {
+ return doc, err
+ }
+ doc = data
+ return doc, nil
+}
+
+func UpdateTags(tags request.TagsCommon, uuid string) error {
+ var err error
+ if tags.Value != "" {
+ if !errors.Is(global.MG_DB.Model(&model.Tags{}).Where("value = ? and type = ? AND id!=?", tags.Value, tags.Type, tags.ID).First(&model.Tags{}).Error, gorm.ErrRecordNotFound) {
+ return errors.New(",存在相同标签")
+ }
+ }
+
+ err = global.MG_DB.Model(&model.Tags{}).Where("id = ?", tags.ID).Updates(model.Tags{Type: tags.Type, Value: tags.Value, UpdateBy: uuid}).Error
+
+ return err
+}
+
+func DeleteTagsByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Model(&model.Tags{}).Where("id in (?)", ids.Ids).Unscoped().Delete(&model.Tags{}).Error
+
+ return err
+}
+
+func GetTagsNameByIds(ids []uint) (tags string) {
+ var (
+ tagList []model.Tags
+ names []string
+ )
+ global.MG_DB.Model(&model.Tags{}).Where("id in (?)", ids).Find(&tagList)
+ for _, vTag := range tagList {
+ names = append(names, vTag.Value)
+ }
+
+ return strings.Join(names, ",")
+}
+
+func GetTagsByIds(ids []uint) (tags []model.TagsDesc) {
+ var (
+ tagList []model.TagsDesc
+ )
+ global.MG_DB.Model(&model.Tags{}).Where("id in (?)", ids).Find(&tagList)
+
+ return tagList
+}
+
+func GetTagNamesByRelation(relationId, relationType string) string {
+ //params:relationType 标签类型 01-任务 02-任务视频 03-网红
+ var (
+ relationList []model.TagRelation
+ tagIds []uint
+ tagNames string
+ )
+ global.MG_DB.Model(&model.TagRelation{}).
+ Where("relation_type = ? and relation_id = ?", relationType, relationId).Find(&relationList)
+ if len(relationList) == 0 {
+ return tagNames
+ }
+ for _, v := range relationList {
+ tagIds = append(tagIds, v.TagId)
+ }
+ tagNames = GetTagsNameByIds(tagIds)
+
+ return tagNames
+}
+
+func GetTagsByRelation(info *request.RaletionTags) (interface{}, error) {
+ //params:relationType 标签类型 01-任务 02-任务视频 03-网红
+ var (
+ relationList []model.TagRelation
+ tagIds []uint
+ tags []model.TagsDesc
+ )
+ global.MG_DB.Model(&model.TagRelation{}).
+ Where("relation_type = ? and relation_id = ?", info.RelationType, info.RelationID).Find(&relationList)
+ if len(relationList) == 0 {
+ return tags, errors.New("标签不存在")
+ }
+ for _, v := range relationList {
+ tagIds = append(tagIds, v.TagId)
+ }
+ tags = GetTagsByIds(tagIds)
+
+ return tags, nil
+}
+
+func CreateTagRelation(params request.CreateTagRelation, uuid string) (err error) {
+ var (
+ data []model.TagRelation
+ check []model.Tags
+ )
+ err = global.MG_DB.Model(&model.Tags{}).Where("id in (?)", params.TagId).Find(&check).Error
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ if len(check) != len(params.TagId) {
+ return errors.New("标签不存在")
+ }
+ tx := global.MG_DB.Begin()
+ err = tx.Model(&model.TagRelation{}).Where("tag_id in (?) and relation_id = ? and relation_type = ?", params.TagId, params.RelationId, params.RelationType).Delete(&model.TagRelation{}).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ for _, v := range params.TagId {
+ data = append(data, model.TagRelation{
+ TagId: v,
+ RelationId: params.RelationId,
+ RelationType: params.RelationType,
+ CreateBy: uuid,
+ UpdateBy: uuid,
+ })
+ }
+ err = tx.Model(&model.TagRelation{}).Create(&data).Error
+ if err != nil {
+ return errors.New("提交失败")
+ }
+ tx.Commit()
+ return err
+}
diff --git a/service/task.go b/service/task.go
new file mode 100644
index 0000000..582ccfa
--- /dev/null
+++ b/service/task.go
@@ -0,0 +1,61 @@
+package service
+
+import (
+ "fmt"
+ "pure-admin/global"
+ "pure-admin/utils"
+ "time"
+
+ "github.com/robfig/cron/v3"
+)
+
+func newWithSecond() *cron.Cron {
+ secondParser := cron.NewParser(cron.Second | cron.Minute |
+ cron.Hour | cron.Dom | cron.Month | cron.DowOptional | cron.Descriptor)
+ return cron.New(cron.WithParser(secondParser), cron.WithChain())
+}
+
+type MissionStatisticTask struct {
+}
+
+func (t MissionStatisticTask) Run() {
+ err, result := RedisSetNX("mission_statistic_task", "used", 10*time.Minute)
+ result = true
+ if err == nil && result {
+ fmt.Println("任务执行")
+ _ = MissionStatisticCountTotal()
+ fmt.Println("任务执行完成")
+ }
+}
+
+type MissionStatisticTaskDaily struct {
+}
+
+func (t MissionStatisticTaskDaily) Run() {
+
+ err, result := RedisSetNX("mission_statistic_task_daily", "used", 10*time.Minute)
+ result = true
+ if err == nil && result {
+ fmt.Println("任务执行")
+ _ = MissionStatisticCountDaily(time.Now().AddDate(0, 0, -1).Format(utils.DateFormat))
+ fmt.Println("任务执行完成")
+ }
+
+}
+
+func TimingTask() {
+ c := newWithSecond()
+ var err error
+ _, err = c.AddJob("1 */30 * * * *", MissionStatisticTask{})
+ if err != nil {
+ global.MG_LOG.Error("MissionStatisticCron " + err.Error())
+ return
+ }
+ _, err = c.AddJob("1 0 * * * *", MissionStatisticTaskDaily{})
+ if err != nil {
+ global.MG_LOG.Error("MissionStatisticDailyCron " + err.Error())
+ return
+ }
+ c.Start()
+ fmt.Println("定时任务开启成功")
+}
diff --git a/service/ueditor.go b/service/ueditor.go
new file mode 100644
index 0000000..0d1d091
--- /dev/null
+++ b/service/ueditor.go
@@ -0,0 +1,132 @@
+package service
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "pure-admin/global"
+ "pure-admin/utils/upload"
+ "regexp"
+ "time"
+
+ "github.com/gin-gonic/gin"
+)
+
+func UeditorAction(c *gin.Context) {
+ action := c.Query("action")
+
+ switch action {
+ //自动读入配置文件,只要初始化UEditor即会发生
+ case "config":
+ callback := c.Query("callback")
+ jsonByte, _ := ioutil.ReadFile("ueditor-config.json")
+ re, _ := regexp.Compile("\\/\\*[\\S\\s]+?\\*\\/")
+ jsonByte = re.ReplaceAll(jsonByte, []byte(""))
+ c.Writer.Write([]byte(callback + "(" + string(jsonByte) + ")"))
+
+ case "uploadimage":
+ {
+ var qiniu upload.Qiniu
+ //保存上传的图片
+ //获取上传的文件,直接可以获取表单名称对应的文件名,不用另外提取
+ _, fileHeader, err := c.Request.FormFile("upfile")
+ if err != nil {
+ data, _ := json.Marshal(map[string]string{
+ "state": fmt.Sprintf("获取文件错误: %s", err.Error()),
+ })
+ c.Writer.Write(data)
+ return
+ }
+ fileKey := "ue-img/" + time.Now().Format("20060102") + "/" + fileHeader.Filename
+ //go func(fileKey string, fileHeader *multipart.FileHeader) {
+ _, _, err = qiniu.UploadFile2(fileKey, fileHeader)
+ if err != nil {
+ data, _ := json.Marshal(map[string]string{
+ "state": fmt.Sprintf("上传文件错误: %s", err.Error()),
+ })
+ c.Writer.Write(data)
+ return
+ }
+ //}(fileKey, fileHeader)
+ data, err := json.Marshal(map[string]string{
+ "url": fmt.Sprintf("%s", global.MG_CONFIG.Qiniu.ImgPath+"/"+fileKey), //保存后的文件路径
+ "title": "", //文件描述,对图片来说在前端会添加到title属性上
+ "original": fileHeader.Filename, //原始文件名
+ "state": "SUCCESS", //上传状态,成功时返回SUCCESS,其他任何值将原样返回至图片上传框中
+ })
+ if err != nil {
+ panic(err)
+ }
+ c.Writer.Write(data)
+ return
+ }
+
+ case "uploadvideo":
+ var qiniu upload.Qiniu
+ _, fileHeader, err := c.Request.FormFile("upfile")
+ if err != nil {
+ data, _ := json.Marshal(map[string]string{
+ "state": fmt.Sprintf("获取文件错误: %s", err.Error()),
+ })
+ c.Writer.Write(data)
+ return
+ }
+
+ fileKey := "ue-video/" + time.Now().Format("20060102") + "/" + fileHeader.Filename
+ //go func(fileKey string, fileHeader *multipart.FileHeader) {
+ _, _, err = qiniu.UploadFile2(fileKey, fileHeader)
+ if err != nil {
+ data, _ := json.Marshal(map[string]string{
+ "state": fmt.Sprintf("上传文件错误: %s", err.Error()),
+ })
+ c.Writer.Write(data)
+ return
+ }
+ //}(fileKey, fileHeader)
+
+ data, err := json.Marshal(map[string]string{
+ "url": fmt.Sprintf("%s", global.MG_CONFIG.Qiniu.ImgPath+"/"+fileKey), //保存后的文件路径
+ "title": "", //文件描述,对图片来说在前端会添加到title属性上
+ "original": fileHeader.Filename, //原始文件名
+ "state": "SUCCESS", //上传状态,成功时返回SUCCESS,其他任何值将原样返回至图片上传框中
+ })
+ if err != nil {
+ panic(err)
+ }
+ _, err = c.Writer.Write(data)
+ case "uploadfile":
+ {
+ var qiniu upload.Qiniu
+ //保存上传的文件
+ //获取上传的文件,直接可以获取表单名称对应的文件名,不用另外提取
+ _, fileHeader, err := c.Request.FormFile("upfile")
+ if err != nil {
+ data, _ := json.Marshal(map[string]string{
+ "state": fmt.Sprintf("获取文件错误: %s", err.Error()),
+ })
+ c.Writer.Write(data)
+ return
+ }
+ fileKey := "ue-file/" + time.Now().Format("20060102") + "/" + fileHeader.Filename
+ //go func(fileKey string, fileHeader *multipart.FileHeader) {
+ _, _, err = qiniu.UploadFile2(fileKey, fileHeader)
+ if err != nil {
+ data, _ := json.Marshal(map[string]string{
+ "state": fmt.Sprintf("上传文件错误: %s", err.Error()),
+ })
+ c.Writer.Write(data)
+ return
+ }
+ //}(fileKey, fileHeader)
+
+ data, err := json.Marshal(map[string]string{
+ "url": fmt.Sprintf("%s", global.MG_CONFIG.Qiniu.ImgPath+"/"+fileKey), //保存后的文件路径
+ "title": "", //文件描述,对图片来说在前端会添加到title属性上
+ "original": fileHeader.Filename, //原始文件名
+ "state": "SUCCESS", //上传状态,成功时返回SUCCESS,其他任何值将原样返回至图片上传框中
+ })
+ c.Writer.Write(data)
+ return
+ }
+ }
+}
diff --git a/service/user.go b/service/user.go
new file mode 100755
index 0000000..bb80719
--- /dev/null
+++ b/service/user.go
@@ -0,0 +1,519 @@
+package service
+
+import (
+ "encoding/json"
+ "errors"
+ "pure-admin/global"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/utils"
+ "time"
+)
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: CreateUser
+//@description: 创建User记录
+//@param: user model.User
+//@return: err error
+
+func CreateUser(user model.User) (err error) {
+ err = global.MG_DB.Create(&user).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteUser
+//@description: 删除User记录
+//@param: user model.User
+//@return: err error
+
+func DeleteUser(user model.User) (err error) {
+ err = global.MG_DB.Delete(&user).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: DeleteUserByIds
+//@description: 批量删除User记录
+//@param: ids request.IdsReq
+//@return: err error
+
+func DeleteUserByIds(ids request.IdsReq) (err error) {
+ err = global.MG_DB.Delete(&[]model.User{}, "id in (?)", ids.Ids).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: UpdateUser
+//@description: 更新User记录
+//@param: user *model.User
+//@return: err error
+
+func UpdateUser(user model.User) (err error) {
+ if user.Password != "" {
+ user.Password = utils.MD5V([]byte(user.Password))
+ }
+ err = global.MG_DB.Updates(&user).Error
+ return err
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetUser
+//@description: 根据id获取User记录
+//@param: id uint
+//@return: err error, user model.User
+
+func GetUser(uuid string) (err error, user model.UserSimple) {
+ var (
+ dictData []model.SysDictData
+ )
+ err = global.MG_DB.Where("uuid = ?", uuid).First(&user).Error
+ if err != nil {
+ return
+ }
+ err = global.MG_DB.Model(&model.SysDictData{}).Where("dict_type=?", "release_channel").Find(&dictData).Error
+ if err != nil {
+ return errors.New("获取用户失败"), user
+ }
+ platformsC := make([]model.Platform, 0)
+ for i := 0; i < len(dictData); i++ {
+ platformsC = append(platformsC, model.Platform{
+ Platform: dictData[i].Value,
+ })
+ }
+ if user.Platform != "" {
+ var platforms []model.Platform
+ json.Unmarshal([]byte(user.Platform), &platforms)
+ for i := 0; i < len(platforms); i++ {
+ for j := 0; j < len(platformsC); j++ {
+ if platforms[i].Platform == platformsC[j].Platform {
+ platformsC[j].IsAuth = platforms[i].IsAuth
+ platformsC[j].Url = platforms[i].Url
+ platformsC[j].Image = platforms[i].Image
+ }
+ }
+ }
+ user.Platforms = platformsC
+ } else {
+ user.Platforms = platformsC
+ }
+ return
+}
+
+//@author: [piexlmax](https://github.com/piexlmax)
+//@function: GetUserInfoList
+//@description: 分页获取User记录
+//@param: info request.UserSearch
+//@return: err error, list interface{}, total int64
+
+func GetUserInfoList(info request.UserSimpleSearch, appid string) (err error, list interface{}, total int64) {
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.User{})
+ db = db.Where("appid = ?", appid)
+ var users []model.UserSimple
+ // 如果有条件搜索 下方会自动创建搜索语句
+ if info.Type != "" {
+ db = db.Where("type = ?", info.Type)
+ } else {
+ db = db.Where("type=?", "customer")
+ }
+ if info.StartTime != "" {
+ db = db.Where("created_at >= ?", info.StartTime)
+ }
+ if info.EndTime != "" {
+ db = db.Where("created_at < ?", info.EndTime)
+ }
+ if info.Phone != "" {
+ db = db.Where("phone = ?", info.Phone)
+ }
+ if info.Email != "" {
+ db = db.Where("email = ?", info.Email)
+ }
+ if info.NickName != "" {
+ db = db.Where("nick_name like ?", "%"+info.NickName+"%")
+ }
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&users).Error
+ if err != nil {
+ return err, nil, 0
+ }
+ var userIds []string
+ for i := 0; i < len(users); i++ {
+ users[i].CreatedStr = users[i].CreatedAt.Format(utils.DateTimeFormat)
+ userIds = append(userIds, users[i].UUID.String())
+ }
+ if info.Type == "influencer" {
+ var (
+ dictData []model.SysDictData
+ )
+ err = global.MG_DB.Model(&model.SysDictData{}).Where("type_code=?", "release_channel").Find(&dictData).Error
+ if err != nil {
+ return errors.New("获取用户失败"), users, total
+ }
+ for i := 0; i < len(users); i++ {
+ var platforms []model.Platform
+ err = json.Unmarshal([]byte(users[i].Platform), &platforms)
+ if err != nil {
+ for i := 0; i < len(dictData); i++ {
+ platforms = append(platforms, model.Platform{
+ Platform: dictData[i].Value,
+ PlatformName: dictData[i].Label,
+ })
+ }
+ // platforms = append(platforms, u.Platform)
+ }
+ for j := 0; j < len(platforms); j++ {
+ for k := 0; k < len(dictData); k++ {
+ if platforms[j].Platform == dictData[k].Value {
+ platforms[j].PlatformName = dictData[k].Label
+ }
+ }
+ }
+ err = nil
+ users[i].Platforms = platforms
+ }
+ }
+ // 累计订单数
+ var orderNumMap map[string]model.DtStatisticOrder
+ orderNumMap, err = getStatisticsOrderUsers(userIds, "", nil)
+ for i := 0; i < len(users); i++ {
+ if val, ok := orderNumMap[users[i].UUID.String()]; ok {
+ users[i].OrderCount = val.OrderNum
+ }
+ }
+ // 退款数
+ var postSaleNumMap map[string]int64
+ now := time.Now()
+ start := now.AddDate(0, 0, -6).Format("20060102")
+ end := now.AddDate(0, 0, 1).Format("20060102")
+ postSaleNumMap, err = getOrderPostSaleCountList(&request.SearchOrderPostSale{UserIds: userIds, CreatedAtStart: start, CreatedAtEnd: end})
+ for i := 0; i < len(users); i++ {
+ if val, ok := postSaleNumMap[users[i].UUID.String()]; ok {
+ users[i].RefundCount = val
+ }
+ }
+ return err, users, total
+}
+
+func GetSysUserInfoList(info request.UserSimpleSearch, appid string) (err error, list interface{}, total int64) {
+ var (
+ authorityIds = make([]string, 0)
+ authorities []model.SysAuthority
+ )
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ db := global.MG_DB.Model(&model.User{})
+ db = db.Where("appid=? and type=?", appid, "admin")
+ if info.NickName != "" {
+ db = db.Where("nick_name LIKE ?", "%"+info.NickName+"%")
+ }
+ if info.Username != "" {
+ db = db.Where("username LIKE ?", "%"+info.Username+"%")
+ }
+ if info.Phone != "" {
+ db = db.Where("phone LIKE ?", "%"+info.Phone+"%")
+ }
+ if info.IDForbidden != "" {
+ db = db.Where("id_forbidden = ?", info.IDForbidden)
+ }
+ var userList []model.UserSimple
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Find(&userList).Error
+ if err != nil {
+ return err, nil, 0
+ }
+ for k, _ := range userList {
+ e := Casbin()
+ res := e.GetRolesForUserInDomain(userList[k].UUID.String(), appid)
+ if len(res) > 0 {
+ authorityIds = append(authorityIds, res...)
+ }
+ }
+ if len(authorityIds) > 0 {
+ err = global.MG_DB.Where("authority_id IN ?", authorityIds).Find(&authorities).Error
+ }
+ for k, _ := range userList {
+ authorities1 := make([]model.SysAuthority, 0)
+ for _, v := range authorities {
+ if userList[k].AuthorityID == v.AuthorityId {
+ authorities1 = append(authorities1, v)
+ }
+ }
+ userList[k].Authorities = authorities1
+ }
+ return err, userList, total
+}
+
+func GetUserInfo(uuid, appid string) (err error, user model.User) {
+ var (
+ reqUser model.User
+ authorities []model.SysAuthority
+ )
+ err = global.MG_DB.First(&reqUser, "uuid = ?", uuid).Error
+ if err != nil {
+ return err, reqUser
+ }
+ e := Casbin()
+ res := e.GetRolesForUserInDomain(uuid, appid)
+ if len(res) > 0 {
+ err = global.MG_DB.Where("authority_id IN ?", res).Find(&authorities).Error
+ } else {
+ err = errors.New("该用户未分配角色")
+ }
+ for _, v := range authorities {
+ if reqUser.AuthorityID == v.AuthorityId {
+ reqUser.Authority = v
+ break
+ }
+ }
+ reqUser.Authorities = authorities
+ return err, reqUser
+}
+
+func GetPlatformAuthList(info request.PlatformAuthSearch) (err error, list interface{}, total int64) {
+ var (
+ platformAuths []model.PlatformAuthSimple
+ userIds []string
+ checkUserIds []string
+ checkUsers []model.UserSimple
+ users []model.UserSimple
+ wallet []model.Wallet
+ account []model.Account
+ dictData []model.SysDictData
+ tag_relation []model.TagRelation
+ tags []model.Tags
+ userPlatformAuths []model.PlatformAuthSimple
+ )
+ err = global.MG_DB.Model(&model.SysDictData{}).Where("type_code=?", "release_channel").Find(&dictData).Error
+ if err != nil {
+ return errors.New("获取用户失败"), platformAuths, total
+ }
+ limit := info.PageSize
+ offset := info.PageSize * (info.Page - 1)
+ // 创建db
+ db := global.MG_DB.Model(&model.PlatformAuth{})
+ if info.Platform != "" {
+ db = db.Where("platform = ?", info.Platform)
+ }
+ if info.Status != "" {
+ db = db.Where("status = ?", info.Status)
+ }
+ if info.StartTime != "" && info.EndTime != "" {
+ db = db.Where("created_at BETWEEN ? AND ?", info.StartTime, info.EndTime)
+ }
+ if info.Phone != "" {
+ db = db.Joins("inner join user on user.uuid=platform_auth.user_id and user.phone = ?", info.Phone)
+ }
+ if info.Email != "" {
+ db = db.Joins("inner join user on user.uuid=platform_auth.user_id and user.email = ?", info.Email)
+ }
+ if info.NickName != "" {
+ db = db.Joins("inner join user on user.uuid=platform_auth.user_id and user.nick_name like ?", "%"+info.NickName+"%")
+ }
+ // 如果有条件搜索 下方会自动创建搜索语句
+ err = db.Count(&total).Error
+ err = db.Limit(limit).Offset(offset).Order("id desc").Find(&platformAuths).Error
+ for _, v := range platformAuths {
+ userIds = append(userIds, v.UserID)
+ }
+ err = global.MG_DB.Where("uuid IN ?", userIds).Find(&users).Error
+ err = global.MG_DB.Where("user_id IN ?", userIds).Find(&userPlatformAuths).Error
+ err = global.MG_DB.Raw("SELECT * FROM (SELECT *,ROW_NUMBER() OVER (PARTITION BY user_id,platform ORDER BY updated_at DESC) AS rn FROM platform_auth WHERE user_id in (?) AND `status` !=0) AS subquery WHERE rn=1", userIds).Scan(&userPlatformAuths).Error
+ for _, v := range userPlatformAuths {
+ checkUserIds = append(checkUserIds, v.CheckUser)
+ }
+ err = global.MG_DB.Where("uuid IN ?", checkUserIds).Find(&checkUsers).Error
+ err = global.MG_DB.Where("user_id IN ?", userIds).Find(&wallet).Error
+ err = global.MG_DB.Where("user_id IN ?", userIds).Find(&account).Error
+ err = global.MG_DB.Where("relation_id IN ?", userIds).Find(&tag_relation).Error
+ var tagIds []uint
+ for _, v := range tag_relation {
+ tagIds = append(tagIds, v.TagId)
+ }
+ err = global.MG_DB.Where("id IN ?", tagIds).Find(&tags).Error
+ for i, v := range platformAuths {
+ for k := 0; k < len(dictData); k++ {
+ if platformAuths[i].Platform.Platform == dictData[k].Value {
+ platformAuths[i].PlatformName = dictData[k].Label
+ }
+ }
+ var tagDesc = make([]model.TagRelationDesc, 0)
+ for j := 0; j < len(tag_relation); j++ {
+ if v.UserID == tag_relation[j].RelationId {
+ for k := 0; k < len(tags); k++ {
+ if tag_relation[j].TagId == tags[k].ID {
+ tagDesc = append(tagDesc, model.TagRelationDesc{
+ ID: tags[k].ID,
+ Name: tags[k].Value,
+ })
+ }
+ }
+ }
+ }
+ platformAuths[i].TagRelationDesc = tagDesc
+ for _, u := range users {
+ if v.UserID == u.UUID.String() {
+ var platforms []model.Platform
+ platformAuths[i].User = u
+ err = json.Unmarshal([]byte(u.Platform), &platforms)
+ if err != nil {
+ for i := 0; i < len(dictData); i++ {
+ platforms = append(platforms, model.Platform{
+ Platform: dictData[i].Value,
+ PlatformName: dictData[i].Label,
+ })
+ }
+ // platforms = append(platforms, u.Platform)
+ }
+ for j := 0; j < len(platforms); j++ {
+ for k := 0; k < len(dictData); k++ {
+ if platforms[j].Platform == dictData[k].Value {
+ platforms[j].PlatformName = dictData[k].Label
+ }
+ }
+ for k := 0; k < len(userPlatformAuths); k++ {
+ if userPlatformAuths[k].UserID == u.UUID.String() && userPlatformAuths[k].Platform.Platform == platforms[j].Platform {
+ if platforms[j].AuthMsg == "" {
+ platforms[j].AuthMsg = userPlatformAuths[k].AuthMsg
+ }
+ for l := 0; l < len(checkUsers); l++ {
+ if userPlatformAuths[k].CheckUser == checkUsers[l].UUID.String() {
+ platforms[j].CheckUser = checkUsers[l].NickName
+ }
+ }
+ }
+ }
+ }
+ err = nil
+ platformAuths[i].User.Platforms = platforms
+ break
+ }
+ }
+ for _, w := range wallet {
+ if v.UserID == w.UserID {
+ platformAuths[i].User.Wallet = w
+ break
+ }
+ }
+ for _, a := range account {
+ if v.UserID == a.UserID {
+ platformAuths[i].User.Account = append(platformAuths[i].User.Account, a)
+ }
+ }
+ }
+ return err, platformAuths, total
+}
+
+func CheckPlatformAuth(info *request.PlatformAuthCheck, userID string) (err error) {
+ var (
+ platformAuth model.PlatformAuth
+ user model.User
+ platforms []model.Platform
+ uMap = make(map[string]interface{})
+ )
+ err = global.MG_DB.First(&platformAuth, "id = ?", info.ID).Error
+ if err != nil {
+ return err
+ }
+ if platformAuth.Status != "0" {
+ return errors.New("该认证已审核")
+ }
+ err = global.MG_DB.First(&user, "uuid = ?", platformAuth.UserID).Error
+ if err != nil {
+ return err
+ }
+ json.Unmarshal([]byte(user.Platform), &platforms)
+ err = global.MG_DB.Model(&platformAuth).Updates(map[string]interface{}{"status": info.Status, "check_user": userID, "auth_msg": info.AuthMsg}).Error
+ if err != nil {
+ return err
+ }
+ for i, v := range platforms {
+ if v.Platform == platformAuth.Platform.Platform {
+ if info.Status == "1" {
+ platforms[i].IsAuth = true
+ platforms[i].AuthTime = time.Now().Format(utils.DateTimeFormat)
+ } else {
+ platforms[i].IsAuth = false
+ platforms[i].Url = ""
+ platforms[i].Image = ""
+ }
+ break
+ }
+ }
+ uMap["is_auth"] = false
+ for _, v := range platforms {
+ if v.IsAuth {
+ uMap["is_auth"] = true
+ break
+ } else {
+ uMap["is_auth"] = false
+ }
+ }
+ platformsByte, _ := json.Marshal(platforms)
+ uMap["platform"] = string(platformsByte)
+ err = global.MG_DB.Model(&user).Updates(uMap).Error
+ return
+}
+
+func UpdateUserStatus(info *request.UserStatus, appid, userID string) (err error) {
+ var (
+ user model.User
+ uMap = make(map[string]interface{})
+ )
+ err = global.MG_DB.First(&user, "uuid = ? and appid=?", info.UserID, appid).Error
+ if err != nil {
+ return err
+ }
+ if user.IDForbidden == info.IDForbidden {
+ return errors.New("该用户已是该状态")
+ }
+ if info.IDForbidden == true {
+ uMap["id_forbidden"] = 1
+ uMap["forbidden_reason"] = info.ForbiddenReason
+ uMap["forbidden_time"] = time.Now()
+ uMap["forbidden_operation"] = userID
+ _, jwtStr := GetRedisJWT(user.Username)
+ if jwtStr != "" {
+ JsonInBlacklist(model.JwtBlacklist{Jwt: jwtStr})
+ }
+ } else {
+ uMap["id_forbidden"] = 0
+ uMap["forbidden_reason"] = ""
+ uMap["forbidden_time"] = nil
+ uMap["forbidden_operation"] = userID
+ }
+ err = global.MG_DB.Model(&user).Where("uuid = ? and appid=?", info.UserID, appid).Updates(uMap).Error
+ if err != nil {
+ return err
+ }
+ return
+}
+
+func getUserSimpleList(ut string, uuids []string) ([]model.UserSimple, error) {
+ var (
+ err error
+ result []model.UserSimple
+ )
+ err = global.MG_DB.Model(&model.User{}).Select("uuid,nick_name,avatar,phone,platform,tags").Where("`type` = ? AND uuid IN (?)", ut, uuids).Find(&result).Error
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+}
+func getUserViewMap(uuids ...string) (map[string]model.UserView, error) {
+ var (
+ err error
+ list []model.UserView
+ result = make(map[string]model.UserView)
+ )
+ err = global.MG_DB.Model(&model.User{}).Select("uuid,email").Where("uuid IN (?)", uuids).Find(&list).Error
+ if err != nil {
+ return nil, err
+ }
+ for _, userView := range list {
+ result[userView.UUID.String()] = userView
+ }
+ return result, nil
+}
diff --git a/service/wallet.go b/service/wallet.go
new file mode 100644
index 0000000..f5bf904
--- /dev/null
+++ b/service/wallet.go
@@ -0,0 +1,158 @@
+package service
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "gorm.io/gorm"
+ "pure-admin/dto"
+ "pure-admin/global"
+ "pure-admin/initialize/api"
+ "pure-admin/model"
+ "pure-admin/model/request"
+ "pure-admin/model/response"
+ "pure-admin/utils"
+ "time"
+)
+
+func getWallet(platform, uuid string) (error, model.Wallet) {
+ var (
+ err error
+ result model.Wallet
+ )
+ err = global.MG_DB.Model(&model.Wallet{}).Where("platform = ? AND user_id = ?", platform, uuid).First(&result).Error
+ return err, result
+}
+
+func FundRecharge(info *request.FundRecharge) (error, response.PayResult) {
+ var (
+ err error
+ wallet model.Wallet
+ billFund model.BillFund
+ result response.PayResult
+ )
+ err = global.MG_DB.Model(&model.Wallet{}).Where("platform = 'bkb' AND user_id = 'bkb'").First(&wallet).Error
+ if err != nil {
+ return err, result
+ }
+ billFund.UserID = "bkb"
+ billFund.TransactionType = 2
+ billFund.TransactionId = "RC" + generate()
+ billFund.Title = "recharge"
+ billFund.Price = info.Amount
+ billFund.Balance = wallet.Fund + info.Amount
+ billFund.Platform = "bkb"
+ billFund.Status = 1
+ tx := global.MG_DB.Begin()
+ err = tx.Model(&model.BillFund{}).Create(&billFund).Error
+ if err != nil {
+ tx.Rollback()
+ return err, result
+ }
+ var payChannel string
+ if info.PayMode == 1 {
+ payChannel = "paypal"
+ }
+ attach, _ := json.Marshal(map[string]string{"transactionId": billFund.TransactionId})
+ params := api.PayTransRequest{
+ Appid: "bkb5918273465092837",
+ Mchid: "11000001",
+ OutTradeNo: billFund.TransactionId,
+ Attach: string(attach),
+ NotifyUrl: global.MG_CONFIG.Paypal.NotifyUrl + "/base/payment/payback",
+ Amount: info.Amount,
+ Currency: "USD",
+ PayChannel: payChannel,
+ ReturnUrl: global.MG_CONFIG.Paypal.ReturnUrl,
+ CancelUrl: global.MG_CONFIG.Paypal.CancelUrl,
+ }
+ payOrder, err := global.PAY_CLIENT.PayTransactionAppUrl(context.Background(), ¶ms)
+ if err != nil {
+ fmt.Println(err.Error())
+ tx.Rollback()
+ return errors.New("open " + payChannel + " failed"), result
+ }
+ err = tx.Model(&model.BillFund{}).Where("id = ?", billFund.ID).UpdateColumn("pay_id", payOrder.PayId).Error
+ if err != nil {
+ tx.Rollback()
+ return err, result
+ }
+ tx.Commit()
+ result.PayChannel = payOrder.PayChannel
+ result.PayId = payOrder.PayId
+ result.PayReturn = payOrder.PayReturn
+ result.PayStatus = payOrder.PayStatus
+ return err, result
+}
+
+func PaypalCallback(info *dto.PaybackBody) error {
+ // 开始交易
+ for {
+ ok, err := global.MG_REDIS.SetNX("payback-"+info.TransactionId, "used", 10*time.Second).Result()
+ if ok && err == nil {
+ // 获取成功
+ break
+ }
+ }
+ defer func() {
+ // 释放锁
+ _, _ = utils.RedisDel("payback-" + info.TransactionId)
+ }()
+ switch info.ResourceType {
+ case "pay": // 支付结果
+ if info.Status != "SUCCESS" {
+ return nil
+ } else {
+ var (
+ err error
+ billFund model.BillFund
+ )
+ err = global.MG_DB.Model(&model.BillFund{}).Where("transaction_id = ?", info.OutTradeNo).First(&billFund).Error
+ if err != nil {
+ return err
+ }
+ tx := global.MG_DB.Begin()
+ err = tx.Model(&model.BillFund{}).Where("id = ?", billFund.ID).UpdateColumn("status", 2).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ err = tx.Model(&model.Wallet{}).Where("platform = 'seller' AND user_id = ?", billFund.UserID).UpdateColumn("fund", gorm.Expr("fund + ?", billFund.Price)).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ tx.Commit()
+ }
+ case "payout": // 提现结果
+ var (
+ err error
+ status string
+ withdrawal model.Withdrawal
+ )
+ err = global.MG_DB.Model(&model.Withdrawal{}).Where("flow_no = ?", info.OutTradeNo).First(&withdrawal).Error
+ if err != nil {
+ return err
+ }
+ tx := global.MG_DB.Begin()
+ if info.Status != "Success" {
+ status = "2"
+ } else {
+ status = "1"
+ // 将对应钱包提现状态恢复
+ err = tx.Model(&model.Wallet{}).Where("id = ?", withdrawal.CreateBy).UpdateColumn("state", 0).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ }
+ err = tx.Model(&model.Withdrawal{}).Where("id = ?", withdrawal.ID).UpdateColumn("status", status).Error
+ if err != nil {
+ tx.Rollback()
+ return err
+ }
+ tx.Commit()
+ }
+ return nil
+}
diff --git a/source/admin.go b/source/admin.go
new file mode 100644
index 0000000..04f11ac
--- /dev/null
+++ b/source/admin.go
@@ -0,0 +1,36 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "time"
+
+ "github.com/gookit/color"
+
+ uuid "github.com/satori/go.uuid"
+ "gorm.io/gorm"
+)
+
+var Admin = new(admin)
+
+type admin struct{}
+
+var admins = []model.SysUser{
+ {UUID: uuid.NewV4(), Username: "admin", Password: "e10adc3949ba59abbe56e057f20f883e", NickName: "超级管理员", HeaderImg: "http://qmplusimg.henrongyi.top/MG_header.jpg", MG_MODEL: global.MG_MODEL{ID: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {UUID: uuid.NewV4(), Username: "a303176530", Password: "3ec063004a6f31642261936a379fde3d", NickName: "QMPlusUser", HeaderImg: "http://qmplusimg.henrongyi.top/1572075907logo.png", MG_MODEL: global.MG_MODEL{ID: 2, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+}
+
+//@description: sys_users 表数据初始化
+func (a *admin) Init() error {
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Where("id in (?)", []int{1, 2}).Find(&[]model.SysUser{}).RowsAffected == 2 {
+ color.Danger.Println("\n[Mysql] --> sys_users 表的初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&admins).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_users 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/api.go b/source/api.go
new file mode 100644
index 0000000..ab6a382
--- /dev/null
+++ b/source/api.go
@@ -0,0 +1,104 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "time"
+
+ "github.com/gookit/color"
+ "gorm.io/gorm"
+)
+
+var Api = new(api)
+
+type api struct{}
+
+var apis = []model.SysApi{
+ {"d", "appid", "admin", "/base/login", "用户登录", "base", "POST", global.MG_MODEL{ID: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/user/register", "用户注册", "user", "POST", global.MG_MODEL{ID: 2, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/createApi", "创建api", "api", "POST", global.MG_MODEL{ID: 3, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/getApiList", "获取api列表", "api", "POST", global.MG_MODEL{ID: 4, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/getApiById", "获取api详细信息", "api", "POST", global.MG_MODEL{ID: 5, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/deleteApi", "删除Api", "api", "POST", global.MG_MODEL{ID: 6, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/updateApi", "更新Api", "api", "POST", global.MG_MODEL{ID: 7, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/getAllApis", "获取所有api", "api", "POST", global.MG_MODEL{ID: 8, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/authority/createAuthority", "创建角色", "authority", "POST", global.MG_MODEL{ID: 9, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/authority/deleteAuthority", "删除角色", "authority", "POST", global.MG_MODEL{ID: 10, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/authority/getAuthorityList", "获取角色列表", "authority", "POST", global.MG_MODEL{ID: 11, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/getMenu", "获取菜单树", "menu", "POST", global.MG_MODEL{ID: 12, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/getMenuList", "分页获取基础menu列表", "menu", "POST", global.MG_MODEL{ID: 13, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/addBaseMenu", "新增菜单", "menu", "POST", global.MG_MODEL{ID: 14, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/getBaseMenuTree", "获取用户动态路由", "menu", "POST", global.MG_MODEL{ID: 15, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/addMenuAuthority", "增加menu和角色关联关系", "menu", "POST", global.MG_MODEL{ID: 16, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/getMenuAuthority", "获取指定角色menu", "menu", "POST", global.MG_MODEL{ID: 17, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/deleteBaseMenu", "删除菜单", "menu", "POST", global.MG_MODEL{ID: 18, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/updateBaseMenu", "更新菜单", "menu", "POST", global.MG_MODEL{ID: 19, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/menu/getBaseMenuById", "根据id获取菜单", "menu", "POST", global.MG_MODEL{ID: 20, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/user/changePassword", "修改密码", "user", "POST", global.MG_MODEL{ID: 21, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/user/getUserList", "获取用户列表", "user", "POST", global.MG_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/user/setUserAuthority", "修改用户角色", "user", "POST", global.MG_MODEL{ID: 23, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/fileUploadAndDownload/upload", "文件上传示例", "fileUploadAndDownload", "POST", global.MG_MODEL{ID: 24, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/fileUploadAndDownload/getFileList", "获取上传文件列表", "fileUploadAndDownload", "POST", global.MG_MODEL{ID: 25, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/casbin/updateCasbin", "更改角色api权限", "casbin", "POST", global.MG_MODEL{ID: 26, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/casbin/getPolicyPathByAuthorityId", "获取权限列表", "casbin", "POST", global.MG_MODEL{ID: 27, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/fileUploadAndDownload/deleteFile", "删除文件", "fileUploadAndDownload", "POST", global.MG_MODEL{ID: 28, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/jwt/jsonInBlacklist", "jwt加入黑名单(退出)", "jwt", "POST", global.MG_MODEL{ID: 29, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/authority/setDataAuthority", "设置角色资源权限", "authority", "POST", global.MG_MODEL{ID: 30, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/system/getSystemConfig", "获取配置文件内容", "system", "POST", global.MG_MODEL{ID: 31, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/system/setSystemConfig", "设置配置文件内容", "system", "POST", global.MG_MODEL{ID: 32, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/customer/customer", "创建客户", "customer", "POST", global.MG_MODEL{ID: 33, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/customer/customer", "更新客户", "customer", "PUT", global.MG_MODEL{ID: 34, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/customer/customer", "删除客户", "customer", "DELETE", global.MG_MODEL{ID: 35, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/customer/customer", "获取单一客户", "customer", "GET", global.MG_MODEL{ID: 36, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/customer/customerList", "获取客户列表", "customer", "GET", global.MG_MODEL{ID: 37, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/casbin/casbinTest/:pathParam", "RESTFUL模式测试", "casbin", "GET", global.MG_MODEL{ID: 38, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/autoCode/createTemp", "自动化代码", "autoCode", "POST", global.MG_MODEL{ID: 39, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/authority/copyAuthority", "拷贝角色", "authority", "POST", global.MG_MODEL{ID: 41, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/authority/updateAuthority", "更新角色信息", "authority", "PUT", global.MG_MODEL{ID: 40, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/user/deleteUser", "删除用户", "user", "DELETE", global.MG_MODEL{ID: 42, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionaryDetail/createSysDictionaryDetail", "新增字典内容", "sysDictionaryDetail", "POST", global.MG_MODEL{ID: 43, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionaryDetail/deleteSysDictionaryDetail", "删除字典内容", "sysDictionaryDetail", "DELETE", global.MG_MODEL{ID: 44, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionaryDetail/updateSysDictionaryDetail", "更新字典内容", "sysDictionaryDetail", "PUT", global.MG_MODEL{ID: 45, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionaryDetail/findSysDictionaryDetail", "根据ID获取字典内容", "sysDictionaryDetail", "GET", global.MG_MODEL{ID: 46, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionaryDetail/getSysDictionaryDetailList", "获取字典内容列表", "sysDictionaryDetail", "GET", global.MG_MODEL{ID: 47, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionary/createSysDictionary", "新增字典", "sysDictionary", "POST", global.MG_MODEL{ID: 48, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionary/deleteSysDictionary", "删除字典", "sysDictionary", "DELETE", global.MG_MODEL{ID: 49, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionary/updateSysDictionary", "更新字典", "sysDictionary", "PUT", global.MG_MODEL{ID: 50, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionary/findSysDictionary", "根据ID获取字典", "sysDictionary", "GET", global.MG_MODEL{ID: 51, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysDictionary/getSysDictionaryList", "获取字典列表", "sysDictionary", "GET", global.MG_MODEL{ID: 52, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysOperationRecord/createSysOperationRecord", "新增操作记录", "sysOperationRecord", "POST", global.MG_MODEL{ID: 53, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysOperationRecord/deleteSysOperationRecord", "删除操作记录", "sysOperationRecord", "DELETE", global.MG_MODEL{ID: 54, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysOperationRecord/findSysOperationRecord", "根据ID获取操作记录", "sysOperationRecord", "GET", global.MG_MODEL{ID: 55, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysOperationRecord/getSysOperationRecordList", "获取操作记录列表", "sysOperationRecord", "GET", global.MG_MODEL{ID: 56, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/autoCode/getTables", "获取数据库表", "autoCode", "GET", global.MG_MODEL{ID: 57, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/autoCode/getDB", "获取所有数据库", "autoCode", "GET", global.MG_MODEL{ID: 58, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/autoCode/getColumn", "获取所选table的所有字段", "autoCode", "GET", global.MG_MODEL{ID: 59, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/sysOperationRecord/deleteSysOperationRecordByIds", "批量删除操作历史", "sysOperationRecord", "DELETE", global.MG_MODEL{ID: 60, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/simpleUploader/upload", "插件版分片上传", "simpleUploader", "POST", global.MG_MODEL{ID: 61, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/simpleUploader/checkFileMd5", "文件完整度验证", "simpleUploader", "GET", global.MG_MODEL{ID: 62, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/simpleUploader/mergeFileMd5", "上传完成合并文件", "simpleUploader", "GET", global.MG_MODEL{ID: 63, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/user/setUserInfo", "设置用户信息", "user", "PUT", global.MG_MODEL{ID: 64, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/system/getServerInfo", "获取服务器信息", "system", "POST", global.MG_MODEL{ID: 65, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/email/emailTest", "发送测试邮件", "email", "POST", global.MG_MODEL{ID: 66, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/autoCode/preview", "预览自动化代码", "autoCode", "POST", global.MG_MODEL{ID: 67, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/excel/importExcel", "导入excel", "excel", "POST", global.MG_MODEL{ID: 68, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/excel/loadExcel", "下载excel", "excel", "GET", global.MG_MODEL{ID: 69, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/excel/exportExcel", "导出excel", "excel", "POST", global.MG_MODEL{ID: 70, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/excel/downloadTemplate", "下载excel模板", "excel", "GET", global.MG_MODEL{ID: 71, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"d", "appid", "admin", "/api/deleteApisByIds", "批量删除api", "api", "DELETE", global.MG_MODEL{ID: 72, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+}
+
+// @description: sys_apis 表数据初始化
+func (a *api) Init() error {
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Where("id in (?)", []int{1, 67}).Find(&[]model.SysApi{}).RowsAffected == 2 {
+ color.Danger.Println("\n[Mysql] --> sys_apis 表的初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&apis).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_apis 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/authorities_menus.go b/source/authorities_menus.go
new file mode 100644
index 0000000..62a379c
--- /dev/null
+++ b/source/authorities_menus.go
@@ -0,0 +1,77 @@
+package source
+
+import (
+ "pure-admin/global"
+
+ "github.com/gookit/color"
+ "gorm.io/gorm"
+)
+
+var AuthoritiesMenus = new(authoritiesMenus)
+
+type authoritiesMenus struct{}
+
+type AuthorityMenus struct {
+ AuthorityId string `gorm:"column:sys_authority_authority_id"`
+ BaseMenuId uint `gorm:"column:sys_base_menu_id"`
+}
+
+var authorityMenus = []AuthorityMenus{
+ {"888", 1},
+ {"888", 2},
+ {"888", 3},
+ {"888", 4},
+ {"888", 5},
+ {"888", 6},
+ {"888", 7},
+ {"888", 8},
+ {"888", 9},
+ {"888", 10},
+ {"888", 11},
+ {"888", 12},
+ {"888", 13},
+ {"888", 14},
+ {"888", 15},
+ {"888", 16},
+ {"888", 17},
+ {"888", 18},
+ {"888", 19},
+ {"888", 20},
+ {"888", 21},
+ {"888", 22},
+ {"888", 23},
+ {"8881", 1},
+ {"8881", 2},
+ {"8881", 8},
+ {"9528", 1},
+ {"9528", 2},
+ {"9528", 3},
+ {"9528", 4},
+ {"9528", 5},
+ {"9528", 6},
+ {"9528", 7},
+ {"9528", 8},
+ {"9528", 9},
+ {"9528", 10},
+ {"9528", 11},
+ {"9528", 12},
+ {"9528", 14},
+ {"9528", 15},
+ {"9528", 16},
+ {"9528", 17},
+}
+
+//@description: sys_authority_menus 表数据初始化
+func (a *authoritiesMenus) Init() error {
+ return global.MG_DB.Table("sys_authority_menus").Transaction(func(tx *gorm.DB) error {
+ if tx.Where("sys_authority_authority_id IN ('888', '8881', '9528')").Find(&[]AuthorityMenus{}).RowsAffected == 48 {
+ color.Danger.Println("\n[Mysql] --> sys_authority_menus 表的初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&authorityMenus).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_authority_menus 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/authority.go b/source/authority.go
new file mode 100644
index 0000000..1aac1a7
--- /dev/null
+++ b/source/authority.go
@@ -0,0 +1,35 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+
+ "github.com/gookit/color"
+
+ "gorm.io/gorm"
+)
+
+var Authority = new(authority)
+
+type authority struct{}
+
+var authorities = []model.SysAuthority{
+ {AuthorityId: 888, AuthorityName: "普通用户", ParentId: "0", DefaultRouter: "dashboard"},
+ {AuthorityId: 8881, AuthorityName: "普通用户子角色", ParentId: "888", DefaultRouter: "dashboard"},
+ {AuthorityId: 9528, AuthorityName: "测试角色", ParentId: "0", DefaultRouter: "dashboard"},
+}
+
+// @description: sys_authorities 表数据初始化
+func (a *authority) Init() error {
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Where("authority_id in (?) ", []string{"888", "9528"}).Find(&[]model.SysAuthority{}).RowsAffected == 2 {
+ color.Danger.Println("\n[Mysql] --> sys_authorities 表的初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&authorities).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_authorities 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/authority_menu.go b/source/authority_menu.go
new file mode 100644
index 0000000..608d6aa
--- /dev/null
+++ b/source/authority_menu.go
@@ -0,0 +1,25 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+
+ "github.com/gookit/color"
+)
+
+var AuthorityMenu = new(authorityMenu)
+
+type authorityMenu struct{}
+
+//@description: authority_menu 视图数据初始化
+func (a *authorityMenu) Init() error {
+ if global.MG_DB.Find(&[]model.SysMenu{}).RowsAffected > 0 {
+ color.Danger.Println("\n[Mysql] --> authority_menu 视图已存在!")
+ return nil
+ }
+ if err := global.MG_DB.Exec("CREATE ALGORITHM = UNDEFINED SQL SECURITY DEFINER VIEW `authority_menu` AS select `sys_base_menus`.`id` AS `id`,`sys_base_menus`.`created_at` AS `created_at`, `sys_base_menus`.`updated_at` AS `updated_at`, `sys_base_menus`.`deleted_at` AS `deleted_at`, `sys_base_menus`.`menu_level` AS `menu_level`,`sys_base_menus`.`parent_id` AS `parent_id`,`sys_base_menus`.`path` AS `path`,`sys_base_menus`.`name` AS `name`,`sys_base_menus`.`hidden` AS `hidden`,`sys_base_menus`.`component` AS `component`, `sys_base_menus`.`title` AS `title`,`sys_base_menus`.`icon` AS `icon`,`sys_base_menus`.`sort` AS `sort`,`sys_authority_menus`.`sys_authority_authority_id` AS `authority_id`,`sys_authority_menus`.`sys_base_menu_id` AS `menu_id`,`sys_base_menus`.`keep_alive` AS `keep_alive`,`sys_base_menus`.`default_menu` AS `default_menu` from (`sys_authority_menus` join `sys_base_menus` on ((`sys_authority_menus`.`sys_base_menu_id` = `sys_base_menus`.`id`)))").Error; err != nil {
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> authority_menu 视图创建成功!")
+ return nil
+}
diff --git a/source/casbin.go b/source/casbin.go
new file mode 100644
index 0000000..a8e4ac4
--- /dev/null
+++ b/source/casbin.go
@@ -0,0 +1,130 @@
+package source
+
+import (
+ "pure-admin/global"
+
+ gormadapter "github.com/casbin/gorm-adapter/v3"
+ "github.com/gookit/color"
+ "gorm.io/gorm"
+)
+
+var Casbin = new(CasbinS)
+
+type CasbinS struct{}
+
+var carbines = []gormadapter.CasbinRule{
+ {Ptype: "p", V2: "888", V3: "/base/login", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/register", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/api/createApi", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/api/getApiList", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/api/getApiById", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/api/deleteApi", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/api/updateApi", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/api/getAllApis", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/authority/createAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/authority/deleteAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/authority/getAuthorityList", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/authority/setDataAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/authority/updateAuthority", V4: "PUT"},
+ {Ptype: "p", V2: "888", V3: "/authority/copyAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/getMenu", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/getMenuList", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/addBaseMenu", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/getBaseMenuTree", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/addMenuAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/getMenuAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/deleteBaseMenu", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/updateBaseMenu", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/menu/getBaseMenuById", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/changePassword", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/getUserList", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/setUserAuthority", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/deleteUser", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/fileUploadAndDownload/upload", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/fileUploadAndDownload/getFileList", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/fileUploadAndDownload/deleteFile", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/casbin/updateCasbin", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/casbin/getPolicyPathByAuthorityId", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/casbin/casbinTest/:pathParam", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/jwt/jsonInBlacklist", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/system/getSystemConfig", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/system/setSystemConfig", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/system/getServerInfo", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/customer/customer", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/customer/customer", V4: "PUT"},
+ {Ptype: "p", V2: "888", V3: "/customer/customer", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/customer/customer", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/customer/customerList", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/createTemp", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/preview", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/getTables", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/getDB", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/getColumn", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionaryDetail/createSysDictionaryDetail", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionaryDetail/deleteSysDictionaryDetail", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionaryDetail/updateSysDictionaryDetail", V4: "PUT"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionaryDetail/findSysDictionaryDetail", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionaryDetail/getSysDictionaryDetailList", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionary/createSysDictionary", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionary/deleteSysDictionary", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionary/updateSysDictionary", V4: "PUT"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionary/findSysDictionary", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysDictionary/getSysDictionaryList", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysOperationRecord/createSysOperationRecord", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/sysOperationRecord/deleteSysOperationRecord", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/sysOperationRecord/updateSysOperationRecord", V4: "PUT"},
+ {Ptype: "p", V2: "888", V3: "/sysOperationRecord/findSysOperationRecord", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysOperationRecord/getSysOperationRecordList", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/sysOperationRecord/deleteSysOperationRecordByIds", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/user/setUserInfo", V4: "PUT"},
+ {Ptype: "p", V2: "888", V3: "/email/emailTest", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/simpleUploader/upload", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/simpleUploader/checkFileMd5", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/simpleUploader/mergeFileMd5", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/excel/importExcel", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/excel/loadExcel", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/excel/exportExcel", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/excel/downloadTemplate", V4: "GET"},
+ {Ptype: "p", V2: "888", V3: "/api/deleteApisByIds", V4: "DELETE"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/getSysHistory", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/rollback", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/getMeta", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/autoCode/delSysHistory", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/setUserAuthorities", V4: "POST"},
+ {Ptype: "p", V2: "888", V3: "/user/getUserInfo", V4: "GET"},
+}
+
+// @description: casbin_rule 表数据初始化
+func (c *CasbinS) Init() error {
+ global.MG_DB.AutoMigrate(gormadapter.CasbinRule{})
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Find(&[]gormadapter.CasbinRule{}).RowsAffected == 154 {
+ color.Danger.Println("\n[Mysql] --> casbin_rule 表的初始数据已存在!")
+ return nil
+ }
+ for k, _ := range carbines {
+ carbines[k].V0 = "api"
+ carbines[k].V1 = "appid"
+ }
+ if err := tx.Create(&carbines).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> casbin_rule 表初始数据成功!")
+ return nil
+ })
+}
+
+// @description: casbin_rule 表数据初始化
+func (c *CasbinS) InitData() error {
+ tx := global.MG_DB.Begin()
+ for k, _ := range carbines {
+ carbines[k].V0 = "api"
+ carbines[k].V1 = "appid"
+ }
+ if err := tx.Create(&carbines).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ tx.Commit()
+ color.Info.Println("\n[Mysql] --> casbin_rule 表初始数据成功!")
+ return nil
+}
diff --git a/source/data_authorities.go b/source/data_authorities.go
new file mode 100644
index 0000000..345f0de
--- /dev/null
+++ b/source/data_authorities.go
@@ -0,0 +1,40 @@
+package source
+
+import (
+ "pure-admin/global"
+
+ "github.com/gookit/color"
+ "gorm.io/gorm"
+)
+
+var DataAuthorities = new(dataAuthorities)
+
+type dataAuthorities struct{}
+
+type DataAuthority struct {
+ AuthorityId string `gorm:"column:sys_authority_authority_id"`
+ DataAuthority string `gorm:"column:data_authority_id_authority_id"`
+}
+
+var infos = []DataAuthority{
+ {"888", "888"},
+ {"888", "8881"},
+ {"888", "9528"},
+ {"9528", "8881"},
+ {"9528", "9528"},
+}
+
+//@description: sys_data_authority_id 表数据初始化
+func (d *dataAuthorities) Init() error {
+ return global.MG_DB.Table("sys_data_authority_id").Transaction(func(tx *gorm.DB) error {
+ if tx.Where("sys_authority_authority_id IN ('888', '9528') ").Find(&[]DataAuthority{}).RowsAffected == 5 {
+ color.Danger.Println("\n[Mysql] --> sys_data_authority_id 表初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&infos).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_data_authority_id 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/dictionary.go b/source/dictionary.go
new file mode 100644
index 0000000..6b9d442
--- /dev/null
+++ b/source/dictionary.go
@@ -0,0 +1,41 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "time"
+
+ "github.com/gookit/color"
+
+ "gorm.io/gorm"
+)
+
+var Dictionary = new(dictionary)
+
+type dictionary struct{}
+
+var status = new(bool)
+
+//@description: sys_dictionaries 表数据初始化
+func (d *dictionary) Init() error {
+ *status = true
+ var dictionaries = []model.SysDictionary{
+ {Name: "性别", Type: "sex", Status: status, Desc: "性别字典", MG_MODEL: global.MG_MODEL{ID: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {Name: "数据库int类型", Type: "int", Status: status, Desc: "int类型对应的数据库类型", MG_MODEL: global.MG_MODEL{ID: 2, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {Name: "数据库时间日期类型", Type: "time.Time", Status: status, Desc: "数据库时间日期类型", MG_MODEL: global.MG_MODEL{ID: 3, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {Name: "数据库浮点型", Type: "float64", Status: status, Desc: "数据库浮点型", MG_MODEL: global.MG_MODEL{ID: 4, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {Name: "数据库字符串", Type: "string", Status: status, Desc: "数据库字符串", MG_MODEL: global.MG_MODEL{ID: 5, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {Name: "数据库bool类型", Type: "bool", Status: status, Desc: "数据库bool类型", MG_MODEL: global.MG_MODEL{ID: 6, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ }
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Where("id in (?)", []int{1, 6}).Find(&[]model.SysDictionary{}).RowsAffected == 2 {
+ color.Danger.Println("\n[Mysql] --> sys_dictionaries 表初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&dictionaries).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_dictionaries 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/dictionary_details.go b/source/dictionary_details.go
new file mode 100644
index 0000000..21f5f5d
--- /dev/null
+++ b/source/dictionary_details.go
@@ -0,0 +1,55 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "time"
+
+ "github.com/gookit/color"
+
+ "gorm.io/gorm"
+)
+
+var DictionaryDetail = new(dictionaryDetail)
+
+type dictionaryDetail struct{}
+
+//@description: dictionary_details 表数据初始化
+func (d *dictionaryDetail) Init() error {
+ var details = []model.SysDictionaryDetail{
+ {"smallint", 1, status, 1, 2, global.MG_MODEL{ID: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"mediumint", 2, status, 2, 2, global.MG_MODEL{ID: 2, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"int", 3, status, 3, 2, global.MG_MODEL{ID: 3, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"bigint", 4, status, 4, 2, global.MG_MODEL{ID: 4, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"date", 0, status, 0, 3, global.MG_MODEL{ID: 5, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"time", 1, status, 1, 3, global.MG_MODEL{ID: 6, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"year", 2, status, 2, 3, global.MG_MODEL{ID: 7, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"datetime", 3, status, 3, 3, global.MG_MODEL{ID: 8, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"timestamp", 5, status, 5, 3, global.MG_MODEL{ID: 9, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"float", 0, status, 0, 4, global.MG_MODEL{ID: 10, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"double", 1, status, 1, 4, global.MG_MODEL{ID: 11, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"decimal", 2, status, 2, 4, global.MG_MODEL{ID: 12, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"char", 0, status, 0, 5, global.MG_MODEL{ID: 13, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"varchar", 1, status, 1, 5, global.MG_MODEL{ID: 14, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"tinyblob", 2, status, 2, 5, global.MG_MODEL{ID: 15, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"tinytext", 3, status, 3, 5, global.MG_MODEL{ID: 16, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"text", 4, status, 4, 5, global.MG_MODEL{ID: 17, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"blob", 5, status, 5, 5, global.MG_MODEL{ID: 18, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"mediumblob", 6, status, 6, 5, global.MG_MODEL{ID: 19, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"mediumtext", 7, status, 7, 5, global.MG_MODEL{ID: 20, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"longblob", 8, status, 8, 5, global.MG_MODEL{ID: 21, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"longtext", 9, status, 9, 5, global.MG_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ {"tinyint", 0, status, 0, 6, global.MG_MODEL{ID: 23, CreatedAt: time.Now(), UpdatedAt: time.Now()}},
+ }
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Where("id in (?)", []int{1, 23}).Find(&[]model.SysDictionaryDetail{}).RowsAffected == 2 {
+ color.Danger.Println("\n[Mysql] --> sys_dictionary_details 表的初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&details).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_dictionary_details 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/source/file.go b/source/file.go
new file mode 100644
index 0000000..d150341
--- /dev/null
+++ b/source/file.go
@@ -0,0 +1 @@
+package source
diff --git a/source/menu.go b/source/menu.go
new file mode 100644
index 0000000..8b4ab8c
--- /dev/null
+++ b/source/menu.go
@@ -0,0 +1,56 @@
+package source
+
+import (
+ "pure-admin/global"
+ "pure-admin/model"
+ "time"
+
+ "github.com/gookit/color"
+
+ "gorm.io/gorm"
+)
+
+var BaseMenu = new(menu)
+
+type menu struct{}
+
+var menus = []model.SysBaseMenu{
+ {MG_MODEL: global.MG_MODEL{ID: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "dashboard", Name: "dashboard", Hidden: false, Component: "view/dashboard/index.vue", Sort: 1, Meta: model.Meta{Title: "仪表盘", Icon: "setting"}},
+ {MG_MODEL: global.MG_MODEL{ID: 2, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "0", Path: "about", Name: "about", Component: "view/about/index.vue", Sort: 7, Meta: model.Meta{Title: "关于我们", Icon: "info"}},
+ {MG_MODEL: global.MG_MODEL{ID: 3, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "0", Path: "admin", Name: "superAdmin", Component: "view/superAdmin/index.vue", Sort: 3, Meta: model.Meta{Title: "超级管理员", Icon: "user-solid"}},
+ {MG_MODEL: global.MG_MODEL{ID: 4, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "authority", Name: "authority", Component: "view/superAdmin/authority/authority.vue", Sort: 1, Meta: model.Meta{Title: "角色管理", Icon: "s-custom"}},
+ {MG_MODEL: global.MG_MODEL{ID: 5, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "menu", Name: "menu", Component: "view/superAdmin/menu/menu.vue", Sort: 2, Meta: model.Meta{Title: "菜单管理", Icon: "s-order", KeepAlive: true}},
+ {MG_MODEL: global.MG_MODEL{ID: 6, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "api", Name: "api", Component: "view/superAdmin/api/api.vue", Sort: 3, Meta: model.Meta{Title: "api管理", Icon: "s-platform", KeepAlive: true}},
+ {MG_MODEL: global.MG_MODEL{ID: 7, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "user", Name: "user", Component: "view/superAdmin/user/user.vue", Sort: 4, Meta: model.Meta{Title: "用户管理", Icon: "coordinate"}},
+ {MG_MODEL: global.MG_MODEL{ID: 8, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: true, ParentId: "0", Path: "person", Name: "person", Component: "view/person/person.vue", Sort: 4, Meta: model.Meta{Title: "个人信息", Icon: "message-solid"}},
+ {MG_MODEL: global.MG_MODEL{ID: 9, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "0", Path: "example", Name: "example", Component: "view/example/index.vue", Sort: 6, Meta: model.Meta{Title: "示例文件", Icon: "s-management"}},
+ {MG_MODEL: global.MG_MODEL{ID: 10, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "excel", Name: "excel", Component: "view/example/excel/excel.vue", Sort: 4, Meta: model.Meta{Title: "excel导入导出", Icon: "s-marketing"}},
+ {MG_MODEL: global.MG_MODEL{ID: 11, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "upload", Name: "upload", Component: "view/example/upload/upload.vue", Sort: 5, Meta: model.Meta{Title: "媒体库(上传下载)", Icon: "upload"}},
+ {MG_MODEL: global.MG_MODEL{ID: 12, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "breakpoint", Name: "breakpoint", Component: "view/example/breakpoint/breakpoint.vue", Sort: 6, Meta: model.Meta{Title: "断点续传", Icon: "upload"}},
+ {MG_MODEL: global.MG_MODEL{ID: 13, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "customer", Name: "customer", Component: "view/example/customer/customer.vue", Sort: 7, Meta: model.Meta{Title: "客户列表(资源示例)", Icon: "s-custom"}},
+ {MG_MODEL: global.MG_MODEL{ID: 14, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "0", Path: "systemTools", Name: "systemTools", Component: "view/systemTools/index.vue", Sort: 5, Meta: model.Meta{Title: "系统工具", Icon: "s-cooperation"}},
+ {MG_MODEL: global.MG_MODEL{ID: 15, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "14", Path: "autoCode", Name: "autoCode", Component: "view/systemTools/autoCode/index.vue", Sort: 1, Meta: model.Meta{Title: "代码生成器", Icon: "cpu", KeepAlive: true}},
+ {MG_MODEL: global.MG_MODEL{ID: 16, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "14", Path: "formCreate", Name: "formCreate", Component: "view/systemTools/formCreate/index.vue", Sort: 2, Meta: model.Meta{Title: "表单生成器", Icon: "magic-stick", KeepAlive: true}},
+ {MG_MODEL: global.MG_MODEL{ID: 17, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "14", Path: "system", Name: "system", Component: "view/systemTools/system/system.vue", Sort: 3, Meta: model.Meta{Title: "系统配置", Icon: "s-operation"}},
+ {MG_MODEL: global.MG_MODEL{ID: 18, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "dictionary", Name: "dictionary", Component: "view/superAdmin/dictionary/sysDictionary.vue", Sort: 5, Meta: model.Meta{Title: "字典管理", Icon: "notebook-2"}},
+ {MG_MODEL: global.MG_MODEL{ID: 19, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: true, ParentId: "3", Path: "dictionaryDetail/:id", Name: "dictionaryDetail", Component: "view/superAdmin/dictionary/sysDictionaryDetail.vue", Sort: 1, Meta: model.Meta{Title: "字典详情", Icon: "s-order"}},
+ {MG_MODEL: global.MG_MODEL{ID: 20, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "3", Path: "operation", Name: "operation", Component: "view/superAdmin/operation/sysOperationRecord.vue", Sort: 6, Meta: model.Meta{Title: "操作历史", Icon: "time"}},
+ {MG_MODEL: global.MG_MODEL{ID: 21, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, Hidden: false, ParentId: "9", Path: "simpleUploader", Name: "simpleUploader", Component: "view/example/simpleUploader/simpleUploader", Sort: 6, Meta: model.Meta{Title: "断点续传(插件版)", Icon: "upload"}},
+ {MG_MODEL: global.MG_MODEL{ID: 22, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "https://www.pure-admin.com", Name: "https://www.pure-admin.com", Hidden: false, Component: "/", Sort: 0, Meta: model.Meta{Title: "官方网站", Icon: "s-home"}},
+ {MG_MODEL: global.MG_MODEL{ID: 23, CreatedAt: time.Now(), UpdatedAt: time.Now()}, MenuLevel: 0, ParentId: "0", Path: "state", Name: "state", Hidden: false, Component: "view/system/state.vue", Sort: 6, Meta: model.Meta{Title: "服务器状态", Icon: "cloudy"}},
+}
+
+//@description: sys_base_menus 表数据初始化
+func (m *menu) Init() error {
+ return global.MG_DB.Transaction(func(tx *gorm.DB) error {
+ if tx.Where("id in (?)", []int{1, 29}).Find(&[]model.SysBaseMenu{}).RowsAffected == 2 {
+ color.Danger.Println("\n[Mysql] --> sys_base_menus 表的初始数据已存在!")
+ return nil
+ }
+ if err := tx.Create(&menus).Error; err != nil { // 遇到错误时回滚事务
+ return err
+ }
+ color.Info.Println("\n[Mysql] --> sys_base_menus 表初始数据成功!")
+ return nil
+ })
+}
diff --git a/third_party/google/api/annotations.proto b/third_party/google/api/annotations.proto
new file mode 100644
index 0000000..85c361b
--- /dev/null
+++ b/third_party/google/api/annotations.proto
@@ -0,0 +1,31 @@
+// Copyright (c) 2015, Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.api;
+
+import "google/api/http.proto";
+import "google/protobuf/descriptor.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "AnnotationsProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+extend google.protobuf.MethodOptions {
+ // See `HttpRule`.
+ HttpRule http = 72295728;
+}
diff --git a/third_party/google/api/client.proto b/third_party/google/api/client.proto
new file mode 100644
index 0000000..d3d1ead
--- /dev/null
+++ b/third_party/google/api/client.proto
@@ -0,0 +1,101 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.api;
+
+import "google/protobuf/descriptor.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "ClientProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+
+extend google.protobuf.ServiceOptions {
+ // The hostname for this service.
+ // This should be specified with no prefix or protocol.
+ //
+ // Example:
+ //
+ // service Foo {
+ // option (google.api.default_host) = "foo.googleapi.com";
+ // ...
+ // }
+ string default_host = 1049;
+
+ // OAuth scopes needed for the client.
+ //
+ // Example:
+ //
+ // service Foo {
+ // option (google.api.oauth_scopes) = \
+ // "https://www.googleapis.com/auth/cloud-platform";
+ // ...
+ // }
+ //
+ // If there is more than one scope, use a comma-separated string:
+ //
+ // Example:
+ //
+ // service Foo {
+ // option (google.api.oauth_scopes) = \
+ // "https://www.googleapis.com/auth/cloud-platform,"
+ // "https://www.googleapis.com/auth/monitoring";
+ // ...
+ // }
+ string oauth_scopes = 1050;
+}
+
+
+extend google.protobuf.MethodOptions {
+ // A definition of a client library method signature.
+ //
+ // In client libraries, each proto RPC corresponds to one or more methods
+ // which the end user is able to call, and calls the underlying RPC.
+ // Normally, this method receives a single argument (a struct or instance
+ // corresponding to the RPC request object). Defining this field will
+ // add one or more overloads providing flattened or simpler method signatures
+ // in some languages.
+ //
+ // The fields on the method signature are provided as a comma-separated
+ // string.
+ //
+ // For example, the proto RPC and annotation:
+ //
+ // rpc CreateSubscription(CreateSubscriptionRequest)
+ // returns (Subscription) {
+ // option (google.api.method_signature) = "name,topic";
+ // }
+ //
+ // Would add the following Java overload (in addition to the method accepting
+ // the request object):
+ //
+ // public final Subscription createSubscription(String name, String topic)
+ //
+ // The following backwards-compatibility guidelines apply:
+ //
+ // * Adding this annotation to an unannotated method is backwards
+ // compatible.
+ // * Adding this annotation to a method which already has existing
+ // method signature annotations is backwards compatible if and only if
+ // the new method signature annotation is last in the sequence.
+ // * Modifying or removing an existing method signature annotation is
+ // a breaking change.
+ // * Re-ordering existing method signature annotations is a breaking
+ // change.
+ repeated string method_signature = 1051;
+}
\ No newline at end of file
diff --git a/third_party/google/api/field_behavior.proto b/third_party/google/api/field_behavior.proto
new file mode 100644
index 0000000..e9716a2
--- /dev/null
+++ b/third_party/google/api/field_behavior.proto
@@ -0,0 +1,80 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.api;
+
+import "google/protobuf/descriptor.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "FieldBehaviorProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+
+// An indicator of the behavior of a given field (for example, that a field
+// is required in requests, or given as output but ignored as input).
+// This **does not** change the behavior in protocol buffers itself; it only
+// denotes the behavior and may affect how API tooling handles the field.
+//
+// Note: This enum **may** receive new values in the future.
+enum FieldBehavior {
+ // Conventional default for enums. Do not use this.
+ FIELD_BEHAVIOR_UNSPECIFIED = 0;
+
+ // Specifically denotes a field as optional.
+ // While all fields in protocol buffers are optional, this may be specified
+ // for emphasis if appropriate.
+ OPTIONAL = 1;
+
+ // Denotes a field as required.
+ // This indicates that the field **must** be provided as part of the request,
+ // and failure to do so will cause an error (usually `INVALID_ARGUMENT`).
+ REQUIRED = 2;
+
+ // Denotes a field as output only.
+ // This indicates that the field is provided in responses, but including the
+ // field in a request does nothing (the server *must* ignore it and
+ // *must not* throw an error as a result of the field's presence).
+ OUTPUT_ONLY = 3;
+
+ // Denotes a field as input only.
+ // This indicates that the field is provided in requests, and the
+ // corresponding field is not included in output.
+ INPUT_ONLY = 4;
+
+ // Denotes a field as immutable.
+ // This indicates that the field may be set once in a request to create a
+ // resource, but may not be changed thereafter.
+ IMMUTABLE = 5;
+}
+
+
+extend google.protobuf.FieldOptions {
+ // A designation of a specific field behavior (required, output only, etc.)
+ // in protobuf messages.
+ //
+ // Examples:
+ //
+ // string name = 1 [(google.api.field_behavior) = REQUIRED];
+ // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
+ // google.protobuf.Duration ttl = 1
+ // [(google.api.field_behavior) = INPUT_ONLY];
+ // google.protobuf.Timestamp expire_time = 1
+ // [(google.api.field_behavior) = OUTPUT_ONLY,
+ // (google.api.field_behavior) = IMMUTABLE];
+ repeated FieldBehavior field_behavior = 1052;
+}
\ No newline at end of file
diff --git a/third_party/google/api/http.proto b/third_party/google/api/http.proto
new file mode 100644
index 0000000..69460cf
--- /dev/null
+++ b/third_party/google/api/http.proto
@@ -0,0 +1,375 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.api;
+
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
+option java_multiple_files = true;
+option java_outer_classname = "HttpProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+// Defines the HTTP configuration for an API service. It contains a list of
+// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
+// to one or more HTTP REST API methods.
+message Http {
+ // A list of HTTP configuration rules that apply to individual API methods.
+ //
+ // **NOTE:** All service configuration rules follow "last one wins" order.
+ repeated HttpRule rules = 1;
+
+ // When set to true, URL path parameters will be fully URI-decoded except in
+ // cases of single segment matches in reserved expansion, where "%2F" will be
+ // left encoded.
+ //
+ // The default behavior is to not decode RFC 6570 reserved characters in multi
+ // segment matches.
+ bool fully_decode_reserved_expansion = 2;
+}
+
+// # gRPC Transcoding
+//
+// gRPC Transcoding is a feature for mapping between a gRPC method and one or
+// more HTTP REST endpoints. It allows developers to build a single API service
+// that supports both gRPC APIs and REST APIs. Many systems, including [Google
+// APIs](https://github.com/googleapis/googleapis),
+// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC
+// Gateway](https://github.com/grpc-ecosystem/grpc-gateway),
+// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature
+// and use it for large scale production services.
+//
+// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies
+// how different portions of the gRPC request message are mapped to the URL
+// path, URL query parameters, and HTTP request body. It also controls how the
+// gRPC response message is mapped to the HTTP response body. `HttpRule` is
+// typically specified as an `google.api.http` annotation on the gRPC method.
+//
+// Each mapping specifies a URL path template and an HTTP method. The path
+// template may refer to one or more fields in the gRPC request message, as long
+// as each field is a non-repeated field with a primitive (non-message) type.
+// The path template controls how fields of the request message are mapped to
+// the URL path.
+//
+// Example:
+//
+// service Messaging {
+// rpc GetMessage(GetMessageRequest) returns (Message) {
+// option (google.api.http) = {
+// get: "/v1/{name=messages/*}"
+// };
+// }
+// }
+// message GetMessageRequest {
+// string name = 1; // Mapped to URL path.
+// }
+// message Message {
+// string text = 1; // The resource content.
+// }
+//
+// This enables an HTTP REST to gRPC mapping as below:
+//
+// HTTP | gRPC
+// -----|-----
+// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")`
+//
+// Any fields in the request message which are not bound by the path template
+// automatically become HTTP query parameters if there is no HTTP request body.
+// For example:
+//
+// service Messaging {
+// rpc GetMessage(GetMessageRequest) returns (Message) {
+// option (google.api.http) = {
+// get:"/v1/messages/{message_id}"
+// };
+// }
+// }
+// message GetMessageRequest {
+// message SubMessage {
+// string subfield = 1;
+// }
+// string message_id = 1; // Mapped to URL path.
+// int64 revision = 2; // Mapped to URL query parameter `revision`.
+// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`.
+// }
+//
+// This enables a HTTP JSON to RPC mapping as below:
+//
+// HTTP | gRPC
+// -----|-----
+// `GET /v1/messages/123456?revision=2&sub.subfield=foo` |
+// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield:
+// "foo"))`
+//
+// Note that fields which are mapped to URL query parameters must have a
+// primitive type or a repeated primitive type or a non-repeated message type.
+// In the case of a repeated type, the parameter can be repeated in the URL
+// as `...?param=A¶m=B`. In the case of a message type, each field of the
+// message is mapped to a separate parameter, such as
+// `...?foo.a=A&foo.b=B&foo.c=C`.
+//
+// For HTTP methods that allow a request body, the `body` field
+// specifies the mapping. Consider a REST update method on the
+// message resource collection:
+//
+// service Messaging {
+// rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
+// option (google.api.http) = {
+// patch: "/v1/messages/{message_id}"
+// body: "message"
+// };
+// }
+// }
+// message UpdateMessageRequest {
+// string message_id = 1; // mapped to the URL
+// Message message = 2; // mapped to the body
+// }
+//
+// The following HTTP JSON to RPC mapping is enabled, where the
+// representation of the JSON in the request body is determined by
+// protos JSON encoding:
+//
+// HTTP | gRPC
+// -----|-----
+// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id:
+// "123456" message { text: "Hi!" })`
+//
+// The special name `*` can be used in the body mapping to define that
+// every field not bound by the path template should be mapped to the
+// request body. This enables the following alternative definition of
+// the update method:
+//
+// service Messaging {
+// rpc UpdateMessage(Message) returns (Message) {
+// option (google.api.http) = {
+// patch: "/v1/messages/{message_id}"
+// body: "*"
+// };
+// }
+// }
+// message Message {
+// string message_id = 1;
+// string text = 2;
+// }
+//
+//
+// The following HTTP JSON to RPC mapping is enabled:
+//
+// HTTP | gRPC
+// -----|-----
+// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id:
+// "123456" text: "Hi!")`
+//
+// Note that when using `*` in the body mapping, it is not possible to
+// have HTTP parameters, as all fields not bound by the path end in
+// the body. This makes this option more rarely used in practice when
+// defining REST APIs. The common usage of `*` is in custom methods
+// which don't use the URL at all for transferring data.
+//
+// It is possible to define multiple HTTP methods for one RPC by using
+// the `additional_bindings` option. Example:
+//
+// service Messaging {
+// rpc GetMessage(GetMessageRequest) returns (Message) {
+// option (google.api.http) = {
+// get: "/v1/messages/{message_id}"
+// additional_bindings {
+// get: "/v1/users/{user_id}/messages/{message_id}"
+// }
+// };
+// }
+// }
+// message GetMessageRequest {
+// string message_id = 1;
+// string user_id = 2;
+// }
+//
+// This enables the following two alternative HTTP JSON to RPC mappings:
+//
+// HTTP | gRPC
+// -----|-----
+// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")`
+// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id:
+// "123456")`
+//
+// ## Rules for HTTP mapping
+//
+// 1. Leaf request fields (recursive expansion nested messages in the request
+// message) are classified into three categories:
+// - Fields referred by the path template. They are passed via the URL path.
+// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP
+// request body.
+// - All other fields are passed via the URL query parameters, and the
+// parameter name is the field path in the request message. A repeated
+// field can be represented as multiple query parameters under the same
+// name.
+// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields
+// are passed via URL path and HTTP request body.
+// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all
+// fields are passed via URL path and URL query parameters.
+//
+// ### Path template syntax
+//
+// Template = "/" Segments [ Verb ] ;
+// Segments = Segment { "/" Segment } ;
+// Segment = "*" | "**" | LITERAL | Variable ;
+// Variable = "{" FieldPath [ "=" Segments ] "}" ;
+// FieldPath = IDENT { "." IDENT } ;
+// Verb = ":" LITERAL ;
+//
+// The syntax `*` matches a single URL path segment. The syntax `**` matches
+// zero or more URL path segments, which must be the last part of the URL path
+// except the `Verb`.
+//
+// The syntax `Variable` matches part of the URL path as specified by its
+// template. A variable template must not contain other variables. If a variable
+// matches a single path segment, its template may be omitted, e.g. `{var}`
+// is equivalent to `{var=*}`.
+//
+// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL`
+// contains any reserved character, such characters should be percent-encoded
+// before the matching.
+//
+// If a variable contains exactly one path segment, such as `"{var}"` or
+// `"{var=*}"`, when such a variable is expanded into a URL path on the client
+// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The
+// server side does the reverse decoding. Such variables show up in the
+// [Discovery
+// Document](https://developers.google.com/discovery/v1/reference/apis) as
+// `{var}`.
+//
+// If a variable contains multiple path segments, such as `"{var=foo/*}"`
+// or `"{var=**}"`, when such a variable is expanded into a URL path on the
+// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded.
+// The server side does the reverse decoding, except "%2F" and "%2f" are left
+// unchanged. Such variables show up in the
+// [Discovery
+// Document](https://developers.google.com/discovery/v1/reference/apis) as
+// `{+var}`.
+//
+// ## Using gRPC API Service Configuration
+//
+// gRPC API Service Configuration (service config) is a configuration language
+// for configuring a gRPC service to become a user-facing product. The
+// service config is simply the YAML representation of the `google.api.Service`
+// proto message.
+//
+// As an alternative to annotating your proto file, you can configure gRPC
+// transcoding in your service config YAML files. You do this by specifying a
+// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same
+// effect as the proto annotation. This can be particularly useful if you
+// have a proto that is reused in multiple services. Note that any transcoding
+// specified in the service config will override any matching transcoding
+// configuration in the proto.
+//
+// Example:
+//
+// http:
+// rules:
+// # Selects a gRPC method and applies HttpRule to it.
+// - selector: example.v1.Messaging.GetMessage
+// get: /v1/messages/{message_id}/{sub.subfield}
+//
+// ## Special notes
+//
+// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the
+// proto to JSON conversion must follow the [proto3
+// specification](https://developers.google.com/protocol-buffers/docs/proto3#json).
+//
+// While the single segment variable follows the semantics of
+// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String
+// Expansion, the multi segment variable **does not** follow RFC 6570 Section
+// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion
+// does not expand special characters like `?` and `#`, which would lead
+// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding
+// for multi segment variables.
+//
+// The path variables **must not** refer to any repeated or mapped field,
+// because client libraries are not capable of handling such variable expansion.
+//
+// The path variables **must not** capture the leading "/" character. The reason
+// is that the most common use case "{var}" does not capture the leading "/"
+// character. For consistency, all path variables must share the same behavior.
+//
+// Repeated message fields must not be mapped to URL query parameters, because
+// no client library can support such complicated mapping.
+//
+// If an API needs to use a JSON array for request or response body, it can map
+// the request or response body to a repeated field. However, some gRPC
+// Transcoding implementations may not support this feature.
+message HttpRule {
+ // Selects a method to which this rule applies.
+ //
+ // Refer to [selector][google.api.DocumentationRule.selector] for syntax details.
+ string selector = 1;
+
+ // Determines the URL pattern is matched by this rules. This pattern can be
+ // used with any of the {get|put|post|delete|patch} methods. A custom method
+ // can be defined using the 'custom' field.
+ oneof pattern {
+ // Maps to HTTP GET. Used for listing and getting information about
+ // resources.
+ string get = 2;
+
+ // Maps to HTTP PUT. Used for replacing a resource.
+ string put = 3;
+
+ // Maps to HTTP POST. Used for creating a resource or performing an action.
+ string post = 4;
+
+ // Maps to HTTP DELETE. Used for deleting a resource.
+ string delete = 5;
+
+ // Maps to HTTP PATCH. Used for updating a resource.
+ string patch = 6;
+
+ // The custom pattern is used for specifying an HTTP method that is not
+ // included in the `pattern` field, such as HEAD, or "*" to leave the
+ // HTTP method unspecified for this rule. The wild-card rule is useful
+ // for services that provide content to Web (HTML) clients.
+ CustomHttpPattern custom = 8;
+ }
+
+ // The name of the request field whose value is mapped to the HTTP request
+ // body, or `*` for mapping all request fields not captured by the path
+ // pattern to the HTTP body, or omitted for not having any HTTP request body.
+ //
+ // NOTE: the referred field must be present at the top-level of the request
+ // message type.
+ string body = 7;
+
+ // Optional. The name of the response field whose value is mapped to the HTTP
+ // response body. When omitted, the entire response message will be used
+ // as the HTTP response body.
+ //
+ // NOTE: The referred field must be present at the top-level of the response
+ // message type.
+ string response_body = 12;
+
+ // Additional HTTP bindings for the selector. Nested bindings must
+ // not contain an `additional_bindings` field themselves (that is,
+ // the nesting may only be one level deep).
+ repeated HttpRule additional_bindings = 11;
+}
+
+// A custom pattern is used for defining custom HTTP verb.
+message CustomHttpPattern {
+ // The name of this custom HTTP verb.
+ string kind = 1;
+
+ // The path matched by this custom verb.
+ string path = 2;
+}
diff --git a/third_party/google/api/httpbody.proto b/third_party/google/api/httpbody.proto
new file mode 100644
index 0000000..1a5bb78
--- /dev/null
+++ b/third_party/google/api/httpbody.proto
@@ -0,0 +1,77 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.api;
+
+import "google/protobuf/any.proto";
+
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody";
+option java_multiple_files = true;
+option java_outer_classname = "HttpBodyProto";
+option java_package = "com.google.api";
+option objc_class_prefix = "GAPI";
+
+// Message that represents an arbitrary HTTP body. It should only be used for
+// payload formats that can't be represented as JSON, such as raw binary or
+// an HTML page.
+//
+//
+// This message can be used both in streaming and non-streaming API methods in
+// the request as well as the response.
+//
+// It can be used as a top-level request field, which is convenient if one
+// wants to extract parameters from either the URL or HTTP template into the
+// request fields and also want access to the raw HTTP body.
+//
+// Example:
+//
+// message GetResourceRequest {
+// // A unique request id.
+// string request_id = 1;
+//
+// // The raw HTTP body is bound to this field.
+// google.api.HttpBody http_body = 2;
+// }
+//
+// service ResourceService {
+// rpc GetResource(GetResourceRequest) returns (google.api.HttpBody);
+// rpc UpdateResource(google.api.HttpBody) returns
+// (google.protobuf.Empty);
+// }
+//
+// Example with streaming methods:
+//
+// service CaldavService {
+// rpc GetCalendar(stream google.api.HttpBody)
+// returns (stream google.api.HttpBody);
+// rpc UpdateCalendar(stream google.api.HttpBody)
+// returns (stream google.api.HttpBody);
+// }
+//
+// Use of this type only changes how the request and response bodies are
+// handled, all other features will continue to work unchanged.
+message HttpBody {
+ // The HTTP Content-Type header value specifying the content type of the body.
+ string content_type = 1;
+
+ // The HTTP request/response body as raw binary.
+ bytes data = 2;
+
+ // Application specific response metadata. Must be set in the first response
+ // for streaming APIs.
+ repeated google.protobuf.Any extensions = 3;
+}
diff --git a/third_party/google/protobuf/any.proto b/third_party/google/protobuf/any.proto
new file mode 100644
index 0000000..e2c2042
--- /dev/null
+++ b/third_party/google/protobuf/any.proto
@@ -0,0 +1,158 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "google.golang.org/protobuf/types/known/anypb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "AnyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// `Any` contains an arbitrary serialized protocol buffer message along with a
+// URL that describes the type of the serialized message.
+//
+// Protobuf library provides support to pack/unpack Any values in the form
+// of utility functions or additional generated methods of the Any type.
+//
+// Example 1: Pack and unpack a message in C++.
+//
+// Foo foo = ...;
+// Any any;
+// any.PackFrom(foo);
+// ...
+// if (any.UnpackTo(&foo)) {
+// ...
+// }
+//
+// Example 2: Pack and unpack a message in Java.
+//
+// Foo foo = ...;
+// Any any = Any.pack(foo);
+// ...
+// if (any.is(Foo.class)) {
+// foo = any.unpack(Foo.class);
+// }
+//
+// Example 3: Pack and unpack a message in Python.
+//
+// foo = Foo(...)
+// any = Any()
+// any.Pack(foo)
+// ...
+// if any.Is(Foo.DESCRIPTOR):
+// any.Unpack(foo)
+// ...
+//
+// Example 4: Pack and unpack a message in Go
+//
+// foo := &pb.Foo{...}
+// any, err := anypb.New(foo)
+// if err != nil {
+// ...
+// }
+// ...
+// foo := &pb.Foo{}
+// if err := any.UnmarshalTo(foo); err != nil {
+// ...
+// }
+//
+// The pack methods provided by protobuf library will by default use
+// 'type.googleapis.com/full.type.name' as the type URL and the unpack
+// methods only use the fully qualified type name after the last '/'
+// in the type URL, for example "foo.bar.com/x/y.z" will yield type
+// name "y.z".
+//
+//
+// JSON
+//
+// The JSON representation of an `Any` value uses the regular
+// representation of the deserialized, embedded message, with an
+// additional field `@type` which contains the type URL. Example:
+//
+// package google.profile;
+// message Person {
+// string first_name = 1;
+// string last_name = 2;
+// }
+//
+// {
+// "@type": "type.googleapis.com/google.profile.Person",
+// "firstName": ,
+// "lastName":
+// }
+//
+// If the embedded message type is well-known and has a custom JSON
+// representation, that representation will be embedded adding a field
+// `value` which holds the custom JSON in addition to the `@type`
+// field. Example (for message [google.protobuf.Duration][]):
+//
+// {
+// "@type": "type.googleapis.com/google.protobuf.Duration",
+// "value": "1.212s"
+// }
+//
+message Any {
+ // A URL/resource name that uniquely identifies the type of the serialized
+ // protocol buffer message. This string must contain at least
+ // one "/" character. The last segment of the URL's path must represent
+ // the fully qualified name of the type (as in
+ // `path/google.protobuf.Duration`). The name should be in a canonical form
+ // (e.g., leading "." is not accepted).
+ //
+ // In practice, teams usually precompile into the binary all types that they
+ // expect it to use in the context of Any. However, for URLs which use the
+ // scheme `http`, `https`, or no scheme, one can optionally set up a type
+ // server that maps type URLs to message definitions as follows:
+ //
+ // * If no scheme is provided, `https` is assumed.
+ // * An HTTP GET on the URL must yield a [google.protobuf.Type][]
+ // value in binary format, or produce an error.
+ // * Applications are allowed to cache lookup results based on the
+ // URL, or have them precompiled into a binary to avoid any
+ // lookup. Therefore, binary compatibility needs to be preserved
+ // on changes to types. (Use versioned type names to manage
+ // breaking changes.)
+ //
+ // Note: this functionality is not currently available in the official
+ // protobuf release, and it is not used for type URLs beginning with
+ // type.googleapis.com.
+ //
+ // Schemes other than `http`, `https` (or the empty scheme) might be
+ // used with implementation specific semantics.
+ //
+ string type_url = 1;
+
+ // Must be a valid serialized protocol buffer of the above specified type.
+ bytes value = 2;
+}
diff --git a/third_party/google/protobuf/api.proto b/third_party/google/protobuf/api.proto
new file mode 100644
index 0000000..3d598fc
--- /dev/null
+++ b/third_party/google/protobuf/api.proto
@@ -0,0 +1,208 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/source_context.proto";
+import "google/protobuf/type.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "ApiProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/apipb";
+
+// Api is a light-weight descriptor for an API Interface.
+//
+// Interfaces are also described as "protocol buffer services" in some contexts,
+// such as by the "service" keyword in a .proto file, but they are different
+// from API Services, which represent a concrete implementation of an interface
+// as opposed to simply a description of methods and bindings. They are also
+// sometimes simply referred to as "APIs" in other contexts, such as the name of
+// this message itself. See https://cloud.google.com/apis/design/glossary for
+// detailed terminology.
+message Api {
+ // The fully qualified name of this interface, including package name
+ // followed by the interface's simple name.
+ string name = 1;
+
+ // The methods of this interface, in unspecified order.
+ repeated Method methods = 2;
+
+ // Any metadata attached to the interface.
+ repeated Option options = 3;
+
+ // A version string for this interface. If specified, must have the form
+ // `major-version.minor-version`, as in `1.10`. If the minor version is
+ // omitted, it defaults to zero. If the entire version field is empty, the
+ // major version is derived from the package name, as outlined below. If the
+ // field is not empty, the version in the package name will be verified to be
+ // consistent with what is provided here.
+ //
+ // The versioning schema uses [semantic
+ // versioning](http://semver.org) where the major version number
+ // indicates a breaking change and the minor version an additive,
+ // non-breaking change. Both version numbers are signals to users
+ // what to expect from different versions, and should be carefully
+ // chosen based on the product plan.
+ //
+ // The major version is also reflected in the package name of the
+ // interface, which must end in `v`, as in
+ // `google.feature.v1`. For major versions 0 and 1, the suffix can
+ // be omitted. Zero major versions must only be used for
+ // experimental, non-GA interfaces.
+ //
+ //
+ string version = 4;
+
+ // Source context for the protocol buffer service represented by this
+ // message.
+ SourceContext source_context = 5;
+
+ // Included interfaces. See [Mixin][].
+ repeated Mixin mixins = 6;
+
+ // The source syntax of the service.
+ Syntax syntax = 7;
+}
+
+// Method represents a method of an API interface.
+message Method {
+ // The simple name of this method.
+ string name = 1;
+
+ // A URL of the input message type.
+ string request_type_url = 2;
+
+ // If true, the request is streamed.
+ bool request_streaming = 3;
+
+ // The URL of the output message type.
+ string response_type_url = 4;
+
+ // If true, the response is streamed.
+ bool response_streaming = 5;
+
+ // Any metadata attached to the method.
+ repeated Option options = 6;
+
+ // The source syntax of this method.
+ Syntax syntax = 7;
+}
+
+// Declares an API Interface to be included in this interface. The including
+// interface must redeclare all the methods from the included interface, but
+// documentation and options are inherited as follows:
+//
+// - If after comment and whitespace stripping, the documentation
+// string of the redeclared method is empty, it will be inherited
+// from the original method.
+//
+// - Each annotation belonging to the service config (http,
+// visibility) which is not set in the redeclared method will be
+// inherited.
+//
+// - If an http annotation is inherited, the path pattern will be
+// modified as follows. Any version prefix will be replaced by the
+// version of the including interface plus the [root][] path if
+// specified.
+//
+// Example of a simple mixin:
+//
+// package google.acl.v1;
+// service AccessControl {
+// // Get the underlying ACL object.
+// rpc GetAcl(GetAclRequest) returns (Acl) {
+// option (google.api.http).get = "/v1/{resource=**}:getAcl";
+// }
+// }
+//
+// package google.storage.v2;
+// service Storage {
+// rpc GetAcl(GetAclRequest) returns (Acl);
+//
+// // Get a data record.
+// rpc GetData(GetDataRequest) returns (Data) {
+// option (google.api.http).get = "/v2/{resource=**}";
+// }
+// }
+//
+// Example of a mixin configuration:
+//
+// apis:
+// - name: google.storage.v2.Storage
+// mixins:
+// - name: google.acl.v1.AccessControl
+//
+// The mixin construct implies that all methods in `AccessControl` are
+// also declared with same name and request/response types in
+// `Storage`. A documentation generator or annotation processor will
+// see the effective `Storage.GetAcl` method after inheriting
+// documentation and annotations as follows:
+//
+// service Storage {
+// // Get the underlying ACL object.
+// rpc GetAcl(GetAclRequest) returns (Acl) {
+// option (google.api.http).get = "/v2/{resource=**}:getAcl";
+// }
+// ...
+// }
+//
+// Note how the version in the path pattern changed from `v1` to `v2`.
+//
+// If the `root` field in the mixin is specified, it should be a
+// relative path under which inherited HTTP paths are placed. Example:
+//
+// apis:
+// - name: google.storage.v2.Storage
+// mixins:
+// - name: google.acl.v1.AccessControl
+// root: acls
+//
+// This implies the following inherited HTTP annotation:
+//
+// service Storage {
+// // Get the underlying ACL object.
+// rpc GetAcl(GetAclRequest) returns (Acl) {
+// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
+// }
+// ...
+// }
+message Mixin {
+ // The fully qualified name of the interface which is included.
+ string name = 1;
+
+ // If non-empty specifies a path under which inherited HTTP paths
+ // are rooted.
+ string root = 2;
+}
diff --git a/third_party/google/protobuf/compiler/plugin.proto b/third_party/google/protobuf/compiler/plugin.proto
new file mode 100644
index 0000000..9242aac
--- /dev/null
+++ b/third_party/google/protobuf/compiler/plugin.proto
@@ -0,0 +1,183 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: kenton@google.com (Kenton Varda)
+//
+// WARNING: The plugin interface is currently EXPERIMENTAL and is subject to
+// change.
+//
+// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is
+// just a program that reads a CodeGeneratorRequest from stdin and writes a
+// CodeGeneratorResponse to stdout.
+//
+// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead
+// of dealing with the raw protocol defined here.
+//
+// A plugin executable needs only to be placed somewhere in the path. The
+// plugin should be named "protoc-gen-$NAME", and will then be used when the
+// flag "--${NAME}_out" is passed to protoc.
+
+syntax = "proto2";
+
+package google.protobuf.compiler;
+option java_package = "com.google.protobuf.compiler";
+option java_outer_classname = "PluginProtos";
+
+option go_package = "google.golang.org/protobuf/types/pluginpb";
+
+import "google/protobuf/descriptor.proto";
+
+// The version number of protocol compiler.
+message Version {
+ optional int32 major = 1;
+ optional int32 minor = 2;
+ optional int32 patch = 3;
+ // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should
+ // be empty for mainline stable releases.
+ optional string suffix = 4;
+}
+
+// An encoded CodeGeneratorRequest is written to the plugin's stdin.
+message CodeGeneratorRequest {
+ // The .proto files that were explicitly listed on the command-line. The
+ // code generator should generate code only for these files. Each file's
+ // descriptor will be included in proto_file, below.
+ repeated string file_to_generate = 1;
+
+ // The generator parameter passed on the command-line.
+ optional string parameter = 2;
+
+ // FileDescriptorProtos for all files in files_to_generate and everything
+ // they import. The files will appear in topological order, so each file
+ // appears before any file that imports it.
+ //
+ // protoc guarantees that all proto_files will be written after
+ // the fields above, even though this is not technically guaranteed by the
+ // protobuf wire format. This theoretically could allow a plugin to stream
+ // in the FileDescriptorProtos and handle them one by one rather than read
+ // the entire set into memory at once. However, as of this writing, this
+ // is not similarly optimized on protoc's end -- it will store all fields in
+ // memory at once before sending them to the plugin.
+ //
+ // Type names of fields and extensions in the FileDescriptorProto are always
+ // fully qualified.
+ repeated FileDescriptorProto proto_file = 15;
+
+ // The version number of protocol compiler.
+ optional Version compiler_version = 3;
+
+}
+
+// The plugin writes an encoded CodeGeneratorResponse to stdout.
+message CodeGeneratorResponse {
+ // Error message. If non-empty, code generation failed. The plugin process
+ // should exit with status code zero even if it reports an error in this way.
+ //
+ // This should be used to indicate errors in .proto files which prevent the
+ // code generator from generating correct code. Errors which indicate a
+ // problem in protoc itself -- such as the input CodeGeneratorRequest being
+ // unparseable -- should be reported by writing a message to stderr and
+ // exiting with a non-zero status code.
+ optional string error = 1;
+
+ // A bitmask of supported features that the code generator supports.
+ // This is a bitwise "or" of values from the Feature enum.
+ optional uint64 supported_features = 2;
+
+ // Sync with code_generator.h.
+ enum Feature {
+ FEATURE_NONE = 0;
+ FEATURE_PROTO3_OPTIONAL = 1;
+ }
+
+ // Represents a single generated file.
+ message File {
+ // The file name, relative to the output directory. The name must not
+ // contain "." or ".." components and must be relative, not be absolute (so,
+ // the file cannot lie outside the output directory). "/" must be used as
+ // the path separator, not "\".
+ //
+ // If the name is omitted, the content will be appended to the previous
+ // file. This allows the generator to break large files into small chunks,
+ // and allows the generated text to be streamed back to protoc so that large
+ // files need not reside completely in memory at one time. Note that as of
+ // this writing protoc does not optimize for this -- it will read the entire
+ // CodeGeneratorResponse before writing files to disk.
+ optional string name = 1;
+
+ // If non-empty, indicates that the named file should already exist, and the
+ // content here is to be inserted into that file at a defined insertion
+ // point. This feature allows a code generator to extend the output
+ // produced by another code generator. The original generator may provide
+ // insertion points by placing special annotations in the file that look
+ // like:
+ // @@protoc_insertion_point(NAME)
+ // The annotation can have arbitrary text before and after it on the line,
+ // which allows it to be placed in a comment. NAME should be replaced with
+ // an identifier naming the point -- this is what other generators will use
+ // as the insertion_point. Code inserted at this point will be placed
+ // immediately above the line containing the insertion point (thus multiple
+ // insertions to the same point will come out in the order they were added).
+ // The double-@ is intended to make it unlikely that the generated code
+ // could contain things that look like insertion points by accident.
+ //
+ // For example, the C++ code generator places the following line in the
+ // .pb.h files that it generates:
+ // // @@protoc_insertion_point(namespace_scope)
+ // This line appears within the scope of the file's package namespace, but
+ // outside of any particular class. Another plugin can then specify the
+ // insertion_point "namespace_scope" to generate additional classes or
+ // other declarations that should be placed in this scope.
+ //
+ // Note that if the line containing the insertion point begins with
+ // whitespace, the same whitespace will be added to every line of the
+ // inserted text. This is useful for languages like Python, where
+ // indentation matters. In these languages, the insertion point comment
+ // should be indented the same amount as any inserted code will need to be
+ // in order to work correctly in that context.
+ //
+ // The code generator that generates the initial file and the one which
+ // inserts into it must both run as part of a single invocation of protoc.
+ // Code generators are executed in the order in which they appear on the
+ // command line.
+ //
+ // If |insertion_point| is present, |name| must also be present.
+ optional string insertion_point = 2;
+
+ // The file contents.
+ optional string content = 15;
+
+ // Information describing the file content being inserted. If an insertion
+ // point is used, this information will be appropriately offset and inserted
+ // into the code generation metadata for the generated files.
+ optional GeneratedCodeInfo generated_code_info = 16;
+ }
+ repeated File file = 15;
+}
diff --git a/third_party/google/protobuf/descriptor.proto b/third_party/google/protobuf/descriptor.proto
new file mode 100644
index 0000000..49ec653
--- /dev/null
+++ b/third_party/google/protobuf/descriptor.proto
@@ -0,0 +1,921 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: kenton@google.com (Kenton Varda)
+// Based on original Protocol Buffers design by
+// Sanjay Ghemawat, Jeff Dean, and others.
+//
+// The messages in this file describe the definitions found in .proto files.
+// A valid .proto file can be translated directly to a FileDescriptorProto
+// without any other information (e.g. without reading its imports).
+
+
+syntax = "proto2";
+
+package google.protobuf;
+
+option go_package = "google.golang.org/protobuf/types/descriptorpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DescriptorProtos";
+option csharp_namespace = "Google.Protobuf.Reflection";
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// descriptor.proto must be optimized for speed because reflection-based
+// algorithms don't work during bootstrapping.
+option optimize_for = SPEED;
+
+// The protocol compiler can output a FileDescriptorSet containing the .proto
+// files it parses.
+message FileDescriptorSet {
+ repeated FileDescriptorProto file = 1;
+}
+
+// Describes a complete .proto file.
+message FileDescriptorProto {
+ optional string name = 1; // file name, relative to root of source tree
+ optional string package = 2; // e.g. "foo", "foo.bar", etc.
+
+ // Names of files imported by this file.
+ repeated string dependency = 3;
+ // Indexes of the public imported files in the dependency list above.
+ repeated int32 public_dependency = 10;
+ // Indexes of the weak imported files in the dependency list.
+ // For Google-internal migration only. Do not use.
+ repeated int32 weak_dependency = 11;
+
+ // All top-level definitions in this file.
+ repeated DescriptorProto message_type = 4;
+ repeated EnumDescriptorProto enum_type = 5;
+ repeated ServiceDescriptorProto service = 6;
+ repeated FieldDescriptorProto extension = 7;
+
+ optional FileOptions options = 8;
+
+ // This field contains optional information about the original source code.
+ // You may safely remove this entire field without harming runtime
+ // functionality of the descriptors -- the information is needed only by
+ // development tools.
+ optional SourceCodeInfo source_code_info = 9;
+
+ // The syntax of the proto file.
+ // The supported values are "proto2" and "proto3".
+ optional string syntax = 12;
+}
+
+// Describes a message type.
+message DescriptorProto {
+ optional string name = 1;
+
+ repeated FieldDescriptorProto field = 2;
+ repeated FieldDescriptorProto extension = 6;
+
+ repeated DescriptorProto nested_type = 3;
+ repeated EnumDescriptorProto enum_type = 4;
+
+ message ExtensionRange {
+ optional int32 start = 1; // Inclusive.
+ optional int32 end = 2; // Exclusive.
+
+ optional ExtensionRangeOptions options = 3;
+ }
+ repeated ExtensionRange extension_range = 5;
+
+ repeated OneofDescriptorProto oneof_decl = 8;
+
+ optional MessageOptions options = 7;
+
+ // Range of reserved tag numbers. Reserved tag numbers may not be used by
+ // fields or extension ranges in the same message. Reserved ranges may
+ // not overlap.
+ message ReservedRange {
+ optional int32 start = 1; // Inclusive.
+ optional int32 end = 2; // Exclusive.
+ }
+ repeated ReservedRange reserved_range = 9;
+ // Reserved field names, which may not be used by fields in the same message.
+ // A given name may only be reserved once.
+ repeated string reserved_name = 10;
+}
+
+message ExtensionRangeOptions {
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+// Describes a field within a message.
+message FieldDescriptorProto {
+ enum Type {
+ // 0 is reserved for errors.
+ // Order is weird for historical reasons.
+ TYPE_DOUBLE = 1;
+ TYPE_FLOAT = 2;
+ // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
+ // negative values are likely.
+ TYPE_INT64 = 3;
+ TYPE_UINT64 = 4;
+ // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
+ // negative values are likely.
+ TYPE_INT32 = 5;
+ TYPE_FIXED64 = 6;
+ TYPE_FIXED32 = 7;
+ TYPE_BOOL = 8;
+ TYPE_STRING = 9;
+ // Tag-delimited aggregate.
+ // Group type is deprecated and not supported in proto3. However, Proto3
+ // implementations should still be able to parse the group wire format and
+ // treat group fields as unknown fields.
+ TYPE_GROUP = 10;
+ TYPE_MESSAGE = 11; // Length-delimited aggregate.
+
+ // New in version 2.
+ TYPE_BYTES = 12;
+ TYPE_UINT32 = 13;
+ TYPE_ENUM = 14;
+ TYPE_SFIXED32 = 15;
+ TYPE_SFIXED64 = 16;
+ TYPE_SINT32 = 17; // Uses ZigZag encoding.
+ TYPE_SINT64 = 18; // Uses ZigZag encoding.
+ }
+
+ enum Label {
+ // 0 is reserved for errors
+ LABEL_OPTIONAL = 1;
+ LABEL_REQUIRED = 2;
+ LABEL_REPEATED = 3;
+ }
+
+ optional string name = 1;
+ optional int32 number = 3;
+ optional Label label = 4;
+
+ // If type_name is set, this need not be set. If both this and type_name
+ // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
+ optional Type type = 5;
+
+ // For message and enum types, this is the name of the type. If the name
+ // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping
+ // rules are used to find the type (i.e. first the nested types within this
+ // message are searched, then within the parent, on up to the root
+ // namespace).
+ optional string type_name = 6;
+
+ // For extensions, this is the name of the type being extended. It is
+ // resolved in the same manner as type_name.
+ optional string extendee = 2;
+
+ // For numeric types, contains the original text representation of the value.
+ // For booleans, "true" or "false".
+ // For strings, contains the default text contents (not escaped in any way).
+ // For bytes, contains the C escaped value. All bytes >= 128 are escaped.
+ optional string default_value = 7;
+
+ // If set, gives the index of a oneof in the containing type's oneof_decl
+ // list. This field is a member of that oneof.
+ optional int32 oneof_index = 9;
+
+ // JSON name of this field. The value is set by protocol compiler. If the
+ // user has set a "json_name" option on this field, that option's value
+ // will be used. Otherwise, it's deduced from the field's name by converting
+ // it to camelCase.
+ optional string json_name = 10;
+
+ optional FieldOptions options = 8;
+
+ // If true, this is a proto3 "optional". When a proto3 field is optional, it
+ // tracks presence regardless of field type.
+ //
+ // When proto3_optional is true, this field must be belong to a oneof to
+ // signal to old proto3 clients that presence is tracked for this field. This
+ // oneof is known as a "synthetic" oneof, and this field must be its sole
+ // member (each proto3 optional field gets its own synthetic oneof). Synthetic
+ // oneofs exist in the descriptor only, and do not generate any API. Synthetic
+ // oneofs must be ordered after all "real" oneofs.
+ //
+ // For message fields, proto3_optional doesn't create any semantic change,
+ // since non-repeated message fields always track presence. However it still
+ // indicates the semantic detail of whether the user wrote "optional" or not.
+ // This can be useful for round-tripping the .proto file. For consistency we
+ // give message fields a synthetic oneof also, even though it is not required
+ // to track presence. This is especially important because the parser can't
+ // tell if a field is a message or an enum, so it must always create a
+ // synthetic oneof.
+ //
+ // Proto2 optional fields do not set this flag, because they already indicate
+ // optional with `LABEL_OPTIONAL`.
+ optional bool proto3_optional = 17;
+}
+
+// Describes a oneof.
+message OneofDescriptorProto {
+ optional string name = 1;
+ optional OneofOptions options = 2;
+}
+
+// Describes an enum type.
+message EnumDescriptorProto {
+ optional string name = 1;
+
+ repeated EnumValueDescriptorProto value = 2;
+
+ optional EnumOptions options = 3;
+
+ // Range of reserved numeric values. Reserved values may not be used by
+ // entries in the same enum. Reserved ranges may not overlap.
+ //
+ // Note that this is distinct from DescriptorProto.ReservedRange in that it
+ // is inclusive such that it can appropriately represent the entire int32
+ // domain.
+ message EnumReservedRange {
+ optional int32 start = 1; // Inclusive.
+ optional int32 end = 2; // Inclusive.
+ }
+
+ // Range of reserved numeric values. Reserved numeric values may not be used
+ // by enum values in the same enum declaration. Reserved ranges may not
+ // overlap.
+ repeated EnumReservedRange reserved_range = 4;
+
+ // Reserved enum value names, which may not be reused. A given name may only
+ // be reserved once.
+ repeated string reserved_name = 5;
+}
+
+// Describes a value within an enum.
+message EnumValueDescriptorProto {
+ optional string name = 1;
+ optional int32 number = 2;
+
+ optional EnumValueOptions options = 3;
+}
+
+// Describes a service.
+message ServiceDescriptorProto {
+ optional string name = 1;
+ repeated MethodDescriptorProto method = 2;
+
+ optional ServiceOptions options = 3;
+}
+
+// Describes a method of a service.
+message MethodDescriptorProto {
+ optional string name = 1;
+
+ // Input and output type names. These are resolved in the same way as
+ // FieldDescriptorProto.type_name, but must refer to a message type.
+ optional string input_type = 2;
+ optional string output_type = 3;
+
+ optional MethodOptions options = 4;
+
+ // Identifies if client streams multiple client messages
+ optional bool client_streaming = 5 [default = false];
+ // Identifies if server streams multiple server messages
+ optional bool server_streaming = 6 [default = false];
+}
+
+
+// ===================================================================
+// Options
+
+// Each of the definitions above may have "options" attached. These are
+// just annotations which may cause code to be generated slightly differently
+// or may contain hints for code that manipulates protocol messages.
+//
+// Clients may define custom options as extensions of the *Options messages.
+// These extensions may not yet be known at parsing time, so the parser cannot
+// store the values in them. Instead it stores them in a field in the *Options
+// message called uninterpreted_option. This field must have the same name
+// across all *Options messages. We then use this field to populate the
+// extensions when we build a descriptor, at which point all protos have been
+// parsed and so all extensions are known.
+//
+// Extension numbers for custom options may be chosen as follows:
+// * For options which will only be used within a single application or
+// organization, or for experimental options, use field numbers 50000
+// through 99999. It is up to you to ensure that you do not use the
+// same number for multiple options.
+// * For options which will be published and used publicly by multiple
+// independent entities, e-mail protobuf-global-extension-registry@google.com
+// to reserve extension numbers. Simply provide your project name (e.g.
+// Objective-C plugin) and your project website (if available) -- there's no
+// need to explain how you intend to use them. Usually you only need one
+// extension number. You can declare multiple options with only one extension
+// number by putting them in a sub-message. See the Custom Options section of
+// the docs for examples:
+// https://developers.google.com/protocol-buffers/docs/proto#options
+// If this turns out to be popular, a web service will be set up
+// to automatically assign option numbers.
+
+message FileOptions {
+
+ // Sets the Java package where classes generated from this .proto will be
+ // placed. By default, the proto package is used, but this is often
+ // inappropriate because proto packages do not normally start with backwards
+ // domain names.
+ optional string java_package = 1;
+
+
+ // Controls the name of the wrapper Java class generated for the .proto file.
+ // That class will always contain the .proto file's getDescriptor() method as
+ // well as any top-level extensions defined in the .proto file.
+ // If java_multiple_files is disabled, then all the other classes from the
+ // .proto file will be nested inside the single wrapper outer class.
+ optional string java_outer_classname = 8;
+
+ // If enabled, then the Java code generator will generate a separate .java
+ // file for each top-level message, enum, and service defined in the .proto
+ // file. Thus, these types will *not* be nested inside the wrapper class
+ // named by java_outer_classname. However, the wrapper class will still be
+ // generated to contain the file's getDescriptor() method as well as any
+ // top-level extensions defined in the file.
+ optional bool java_multiple_files = 10 [default = false];
+
+ // This option does nothing.
+ optional bool java_generate_equals_and_hash = 20 [deprecated=true];
+
+ // If set true, then the Java2 code generator will generate code that
+ // throws an exception whenever an attempt is made to assign a non-UTF-8
+ // byte sequence to a string field.
+ // Message reflection will do the same.
+ // However, an extension field still accepts non-UTF-8 byte sequences.
+ // This option has no effect on when used with the lite runtime.
+ optional bool java_string_check_utf8 = 27 [default = false];
+
+
+ // Generated classes can be optimized for speed or code size.
+ enum OptimizeMode {
+ SPEED = 1; // Generate complete code for parsing, serialization,
+ // etc.
+ CODE_SIZE = 2; // Use ReflectionOps to implement these methods.
+ LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
+ }
+ optional OptimizeMode optimize_for = 9 [default = SPEED];
+
+ // Sets the Go package where structs generated from this .proto will be
+ // placed. If omitted, the Go package will be derived from the following:
+ // - The basename of the package import path, if provided.
+ // - Otherwise, the package statement in the .proto file, if present.
+ // - Otherwise, the basename of the .proto file, without extension.
+ optional string go_package = 11;
+
+
+
+
+ // Should generic services be generated in each language? "Generic" services
+ // are not specific to any particular RPC system. They are generated by the
+ // main code generators in each language (without additional plugins).
+ // Generic services were the only kind of service generation supported by
+ // early versions of google.protobuf.
+ //
+ // Generic services are now considered deprecated in favor of using plugins
+ // that generate code specific to your particular RPC system. Therefore,
+ // these default to false. Old code which depends on generic services should
+ // explicitly set them to true.
+ optional bool cc_generic_services = 16 [default = false];
+ optional bool java_generic_services = 17 [default = false];
+ optional bool py_generic_services = 18 [default = false];
+ optional bool php_generic_services = 42 [default = false];
+
+ // Is this file deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for everything in the file, or it will be completely ignored; in the very
+ // least, this is a formalization for deprecating files.
+ optional bool deprecated = 23 [default = false];
+
+ // Enables the use of arenas for the proto messages in this file. This applies
+ // only to generated classes for C++.
+ optional bool cc_enable_arenas = 31 [default = true];
+
+
+ // Sets the objective c class prefix which is prepended to all objective c
+ // generated classes from this .proto. There is no default.
+ optional string objc_class_prefix = 36;
+
+ // Namespace for generated classes; defaults to the package.
+ optional string csharp_namespace = 37;
+
+ // By default Swift generators will take the proto package and CamelCase it
+ // replacing '.' with underscore and use that to prefix the types/symbols
+ // defined. When this options is provided, they will use this value instead
+ // to prefix the types/symbols defined.
+ optional string swift_prefix = 39;
+
+ // Sets the php class prefix which is prepended to all php generated classes
+ // from this .proto. Default is empty.
+ optional string php_class_prefix = 40;
+
+ // Use this option to change the namespace of php generated classes. Default
+ // is empty. When this option is empty, the package name will be used for
+ // determining the namespace.
+ optional string php_namespace = 41;
+
+ // Use this option to change the namespace of php generated metadata classes.
+ // Default is empty. When this option is empty, the proto file name will be
+ // used for determining the namespace.
+ optional string php_metadata_namespace = 44;
+
+ // Use this option to change the package of ruby generated classes. Default
+ // is empty. When this option is not set, the package name will be used for
+ // determining the ruby package.
+ optional string ruby_package = 45;
+
+
+ // The parser stores options it doesn't recognize here.
+ // See the documentation for the "Options" section above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message.
+ // See the documentation for the "Options" section above.
+ extensions 1000 to max;
+
+ reserved 38;
+}
+
+message MessageOptions {
+ // Set true to use the old proto1 MessageSet wire format for extensions.
+ // This is provided for backwards-compatibility with the MessageSet wire
+ // format. You should not use this for any other reason: It's less
+ // efficient, has fewer features, and is more complicated.
+ //
+ // The message must be defined exactly as follows:
+ // message Foo {
+ // option message_set_wire_format = true;
+ // extensions 4 to max;
+ // }
+ // Note that the message cannot have any defined fields; MessageSets only
+ // have extensions.
+ //
+ // All extensions of your type must be singular messages; e.g. they cannot
+ // be int32s, enums, or repeated messages.
+ //
+ // Because this is an option, the above two restrictions are not enforced by
+ // the protocol compiler.
+ optional bool message_set_wire_format = 1 [default = false];
+
+ // Disables the generation of the standard "descriptor()" accessor, which can
+ // conflict with a field of the same name. This is meant to make migration
+ // from proto1 easier; new code should avoid fields named "descriptor".
+ optional bool no_standard_descriptor_accessor = 2 [default = false];
+
+ // Is this message deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for the message, or it will be completely ignored; in the very least,
+ // this is a formalization for deprecating messages.
+ optional bool deprecated = 3 [default = false];
+
+ reserved 4, 5, 6;
+
+ // Whether the message is an automatically generated map entry type for the
+ // maps field.
+ //
+ // For maps fields:
+ // map map_field = 1;
+ // The parsed descriptor looks like:
+ // message MapFieldEntry {
+ // option map_entry = true;
+ // optional KeyType key = 1;
+ // optional ValueType value = 2;
+ // }
+ // repeated MapFieldEntry map_field = 1;
+ //
+ // Implementations may choose not to generate the map_entry=true message, but
+ // use a native map in the target language to hold the keys and values.
+ // The reflection APIs in such implementations still need to work as
+ // if the field is a repeated message field.
+ //
+ // NOTE: Do not set the option in .proto files. Always use the maps syntax
+ // instead. The option should only be implicitly set by the proto compiler
+ // parser.
+ optional bool map_entry = 7;
+
+ reserved 8; // javalite_serializable
+ reserved 9; // javanano_as_lite
+
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+message FieldOptions {
+ // The ctype option instructs the C++ code generator to use a different
+ // representation of the field than it normally would. See the specific
+ // options below. This option is not yet implemented in the open source
+ // release -- sorry, we'll try to include it in a future version!
+ optional CType ctype = 1 [default = STRING];
+ enum CType {
+ // Default mode.
+ STRING = 0;
+
+ CORD = 1;
+
+ STRING_PIECE = 2;
+ }
+ // The packed option can be enabled for repeated primitive fields to enable
+ // a more efficient representation on the wire. Rather than repeatedly
+ // writing the tag and type for each element, the entire array is encoded as
+ // a single length-delimited blob. In proto3, only explicit setting it to
+ // false will avoid using packed encoding.
+ optional bool packed = 2;
+
+ // The jstype option determines the JavaScript type used for values of the
+ // field. The option is permitted only for 64 bit integral and fixed types
+ // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING
+ // is represented as JavaScript string, which avoids loss of precision that
+ // can happen when a large value is converted to a floating point JavaScript.
+ // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to
+ // use the JavaScript "number" type. The behavior of the default option
+ // JS_NORMAL is implementation dependent.
+ //
+ // This option is an enum to permit additional types to be added, e.g.
+ // goog.math.Integer.
+ optional JSType jstype = 6 [default = JS_NORMAL];
+ enum JSType {
+ // Use the default type.
+ JS_NORMAL = 0;
+
+ // Use JavaScript strings.
+ JS_STRING = 1;
+
+ // Use JavaScript numbers.
+ JS_NUMBER = 2;
+ }
+
+ // Should this field be parsed lazily? Lazy applies only to message-type
+ // fields. It means that when the outer message is initially parsed, the
+ // inner message's contents will not be parsed but instead stored in encoded
+ // form. The inner message will actually be parsed when it is first accessed.
+ //
+ // This is only a hint. Implementations are free to choose whether to use
+ // eager or lazy parsing regardless of the value of this option. However,
+ // setting this option true suggests that the protocol author believes that
+ // using lazy parsing on this field is worth the additional bookkeeping
+ // overhead typically needed to implement it.
+ //
+ // This option does not affect the public interface of any generated code;
+ // all method signatures remain the same. Furthermore, thread-safety of the
+ // interface is not affected by this option; const methods remain safe to
+ // call from multiple threads concurrently, while non-const methods continue
+ // to require exclusive access.
+ //
+ //
+ // Note that implementations may choose not to check required fields within
+ // a lazy sub-message. That is, calling IsInitialized() on the outer message
+ // may return true even if the inner message has missing required fields.
+ // This is necessary because otherwise the inner message would have to be
+ // parsed in order to perform the check, defeating the purpose of lazy
+ // parsing. An implementation which chooses not to check required fields
+ // must be consistent about it. That is, for any particular sub-message, the
+ // implementation must either *always* check its required fields, or *never*
+ // check its required fields, regardless of whether or not the message has
+ // been parsed.
+ //
+ // As of 2021, lazy does no correctness checks on the byte stream during
+ // parsing. This may lead to crashes if and when an invalid byte stream is
+ // finally parsed upon access.
+ //
+ // TODO(b/211906113): Enable validation on lazy fields.
+ optional bool lazy = 5 [default = false];
+
+ // unverified_lazy does no correctness checks on the byte stream. This should
+ // only be used where lazy with verification is prohibitive for performance
+ // reasons.
+ optional bool unverified_lazy = 15 [default = false];
+
+ // Is this field deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for accessors, or it will be completely ignored; in the very least, this
+ // is a formalization for deprecating fields.
+ optional bool deprecated = 3 [default = false];
+
+ // For Google-internal migration only. Do not use.
+ optional bool weak = 10 [default = false];
+
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+
+ reserved 4; // removed jtype
+}
+
+message OneofOptions {
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+message EnumOptions {
+
+ // Set this option to true to allow mapping different tag names to the same
+ // value.
+ optional bool allow_alias = 2;
+
+ // Is this enum deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for the enum, or it will be completely ignored; in the very least, this
+ // is a formalization for deprecating enums.
+ optional bool deprecated = 3 [default = false];
+
+ reserved 5; // javanano_as_lite
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+message EnumValueOptions {
+ // Is this enum value deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for the enum value, or it will be completely ignored; in the very least,
+ // this is a formalization for deprecating enum values.
+ optional bool deprecated = 1 [default = false];
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+message ServiceOptions {
+
+ // Note: Field numbers 1 through 32 are reserved for Google's internal RPC
+ // framework. We apologize for hoarding these numbers to ourselves, but
+ // we were already using them long before we decided to release Protocol
+ // Buffers.
+
+ // Is this service deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for the service, or it will be completely ignored; in the very least,
+ // this is a formalization for deprecating services.
+ optional bool deprecated = 33 [default = false];
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+message MethodOptions {
+
+ // Note: Field numbers 1 through 32 are reserved for Google's internal RPC
+ // framework. We apologize for hoarding these numbers to ourselves, but
+ // we were already using them long before we decided to release Protocol
+ // Buffers.
+
+ // Is this method deprecated?
+ // Depending on the target platform, this can emit Deprecated annotations
+ // for the method, or it will be completely ignored; in the very least,
+ // this is a formalization for deprecating methods.
+ optional bool deprecated = 33 [default = false];
+
+ // Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
+ // or neither? HTTP based RPC implementation may choose GET verb for safe
+ // methods, and PUT verb for idempotent methods instead of the default POST.
+ enum IdempotencyLevel {
+ IDEMPOTENCY_UNKNOWN = 0;
+ NO_SIDE_EFFECTS = 1; // implies idempotent
+ IDEMPOTENT = 2; // idempotent, but may have side effects
+ }
+ optional IdempotencyLevel idempotency_level = 34
+ [default = IDEMPOTENCY_UNKNOWN];
+
+ // The parser stores options it doesn't recognize here. See above.
+ repeated UninterpretedOption uninterpreted_option = 999;
+
+ // Clients can define custom options in extensions of this message. See above.
+ extensions 1000 to max;
+}
+
+
+// A message representing a option the parser does not recognize. This only
+// appears in options protos created by the compiler::Parser class.
+// DescriptorPool resolves these when building Descriptor objects. Therefore,
+// options protos in descriptor objects (e.g. returned by Descriptor::options(),
+// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
+// in them.
+message UninterpretedOption {
+ // The name of the uninterpreted option. Each string represents a segment in
+ // a dot-separated name. is_extension is true iff a segment represents an
+ // extension (denoted with parentheses in options specs in .proto files).
+ // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents
+ // "foo.(bar.baz).qux".
+ message NamePart {
+ required string name_part = 1;
+ required bool is_extension = 2;
+ }
+ repeated NamePart name = 2;
+
+ // The value of the uninterpreted option, in whatever type the tokenizer
+ // identified it as during parsing. Exactly one of these should be set.
+ optional string identifier_value = 3;
+ optional uint64 positive_int_value = 4;
+ optional int64 negative_int_value = 5;
+ optional double double_value = 6;
+ optional bytes string_value = 7;
+ optional string aggregate_value = 8;
+}
+
+// ===================================================================
+// Optional source code info
+
+// Encapsulates information about the original source file from which a
+// FileDescriptorProto was generated.
+message SourceCodeInfo {
+ // A Location identifies a piece of source code in a .proto file which
+ // corresponds to a particular definition. This information is intended
+ // to be useful to IDEs, code indexers, documentation generators, and similar
+ // tools.
+ //
+ // For example, say we have a file like:
+ // message Foo {
+ // optional string foo = 1;
+ // }
+ // Let's look at just the field definition:
+ // optional string foo = 1;
+ // ^ ^^ ^^ ^ ^^^
+ // a bc de f ghi
+ // We have the following locations:
+ // span path represents
+ // [a,i) [ 4, 0, 2, 0 ] The whole field definition.
+ // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional).
+ // [c,d) [ 4, 0, 2, 0, 5 ] The type (string).
+ // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo).
+ // [g,h) [ 4, 0, 2, 0, 3 ] The number (1).
+ //
+ // Notes:
+ // - A location may refer to a repeated field itself (i.e. not to any
+ // particular index within it). This is used whenever a set of elements are
+ // logically enclosed in a single code segment. For example, an entire
+ // extend block (possibly containing multiple extension definitions) will
+ // have an outer location whose path refers to the "extensions" repeated
+ // field without an index.
+ // - Multiple locations may have the same path. This happens when a single
+ // logical declaration is spread out across multiple places. The most
+ // obvious example is the "extend" block again -- there may be multiple
+ // extend blocks in the same scope, each of which will have the same path.
+ // - A location's span is not always a subset of its parent's span. For
+ // example, the "extendee" of an extension declaration appears at the
+ // beginning of the "extend" block and is shared by all extensions within
+ // the block.
+ // - Just because a location's span is a subset of some other location's span
+ // does not mean that it is a descendant. For example, a "group" defines
+ // both a type and a field in a single declaration. Thus, the locations
+ // corresponding to the type and field and their components will overlap.
+ // - Code which tries to interpret locations should probably be designed to
+ // ignore those that it doesn't understand, as more types of locations could
+ // be recorded in the future.
+ repeated Location location = 1;
+ message Location {
+ // Identifies which part of the FileDescriptorProto was defined at this
+ // location.
+ //
+ // Each element is a field number or an index. They form a path from
+ // the root FileDescriptorProto to the place where the definition occurs.
+ // For example, this path:
+ // [ 4, 3, 2, 7, 1 ]
+ // refers to:
+ // file.message_type(3) // 4, 3
+ // .field(7) // 2, 7
+ // .name() // 1
+ // This is because FileDescriptorProto.message_type has field number 4:
+ // repeated DescriptorProto message_type = 4;
+ // and DescriptorProto.field has field number 2:
+ // repeated FieldDescriptorProto field = 2;
+ // and FieldDescriptorProto.name has field number 1:
+ // optional string name = 1;
+ //
+ // Thus, the above path gives the location of a field name. If we removed
+ // the last element:
+ // [ 4, 3, 2, 7 ]
+ // this path refers to the whole field declaration (from the beginning
+ // of the label to the terminating semicolon).
+ repeated int32 path = 1 [packed = true];
+
+ // Always has exactly three or four elements: start line, start column,
+ // end line (optional, otherwise assumed same as start line), end column.
+ // These are packed into a single field for efficiency. Note that line
+ // and column numbers are zero-based -- typically you will want to add
+ // 1 to each before displaying to a user.
+ repeated int32 span = 2 [packed = true];
+
+ // If this SourceCodeInfo represents a complete declaration, these are any
+ // comments appearing before and after the declaration which appear to be
+ // attached to the declaration.
+ //
+ // A series of line comments appearing on consecutive lines, with no other
+ // tokens appearing on those lines, will be treated as a single comment.
+ //
+ // leading_detached_comments will keep paragraphs of comments that appear
+ // before (but not connected to) the current element. Each paragraph,
+ // separated by empty lines, will be one comment element in the repeated
+ // field.
+ //
+ // Only the comment content is provided; comment markers (e.g. //) are
+ // stripped out. For block comments, leading whitespace and an asterisk
+ // will be stripped from the beginning of each line other than the first.
+ // Newlines are included in the output.
+ //
+ // Examples:
+ //
+ // optional int32 foo = 1; // Comment attached to foo.
+ // // Comment attached to bar.
+ // optional int32 bar = 2;
+ //
+ // optional string baz = 3;
+ // // Comment attached to baz.
+ // // Another line attached to baz.
+ //
+ // // Comment attached to qux.
+ // //
+ // // Another line attached to qux.
+ // optional double qux = 4;
+ //
+ // // Detached comment for corge. This is not leading or trailing comments
+ // // to qux or corge because there are blank lines separating it from
+ // // both.
+ //
+ // // Detached comment for corge paragraph 2.
+ //
+ // optional string corge = 5;
+ // /* Block comment attached
+ // * to corge. Leading asterisks
+ // * will be removed. */
+ // /* Block comment attached to
+ // * grault. */
+ // optional int32 grault = 6;
+ //
+ // // ignored detached comments.
+ optional string leading_comments = 3;
+ optional string trailing_comments = 4;
+ repeated string leading_detached_comments = 6;
+ }
+}
+
+// Describes the relationship between generated code and its original source
+// file. A GeneratedCodeInfo message is associated with only one generated
+// source file, but may contain references to different source .proto files.
+message GeneratedCodeInfo {
+ // An Annotation connects some span of text in generated code to an element
+ // of its generating .proto file.
+ repeated Annotation annotation = 1;
+ message Annotation {
+ // Identifies the element in the original source .proto file. This field
+ // is formatted the same as SourceCodeInfo.Location.path.
+ repeated int32 path = 1 [packed = true];
+
+ // Identifies the filesystem path to the original source .proto.
+ optional string source_file = 2;
+
+ // Identifies the starting offset in bytes in the generated code
+ // that relates to the identified object.
+ optional int32 begin = 3;
+
+ // Identifies the ending offset in bytes in the generated code that
+ // relates to the identified offset. The end offset should be one past
+ // the last relevant byte (so the length of the text = end - begin).
+ optional int32 end = 4;
+ }
+}
diff --git a/third_party/google/protobuf/duration.proto b/third_party/google/protobuf/duration.proto
new file mode 100644
index 0000000..81c3e36
--- /dev/null
+++ b/third_party/google/protobuf/duration.proto
@@ -0,0 +1,116 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/durationpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DurationProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Duration represents a signed, fixed-length span of time represented
+// as a count of seconds and fractions of seconds at nanosecond
+// resolution. It is independent of any calendar and concepts like "day"
+// or "month". It is related to Timestamp in that the difference between
+// two Timestamp values is a Duration and it can be added or subtracted
+// from a Timestamp. Range is approximately +-10,000 years.
+//
+// # Examples
+//
+// Example 1: Compute Duration from two Timestamps in pseudo code.
+//
+// Timestamp start = ...;
+// Timestamp end = ...;
+// Duration duration = ...;
+//
+// duration.seconds = end.seconds - start.seconds;
+// duration.nanos = end.nanos - start.nanos;
+//
+// if (duration.seconds < 0 && duration.nanos > 0) {
+// duration.seconds += 1;
+// duration.nanos -= 1000000000;
+// } else if (duration.seconds > 0 && duration.nanos < 0) {
+// duration.seconds -= 1;
+// duration.nanos += 1000000000;
+// }
+//
+// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
+//
+// Timestamp start = ...;
+// Duration duration = ...;
+// Timestamp end = ...;
+//
+// end.seconds = start.seconds + duration.seconds;
+// end.nanos = start.nanos + duration.nanos;
+//
+// if (end.nanos < 0) {
+// end.seconds -= 1;
+// end.nanos += 1000000000;
+// } else if (end.nanos >= 1000000000) {
+// end.seconds += 1;
+// end.nanos -= 1000000000;
+// }
+//
+// Example 3: Compute Duration from datetime.timedelta in Python.
+//
+// td = datetime.timedelta(days=3, minutes=10)
+// duration = Duration()
+// duration.FromTimedelta(td)
+//
+// # JSON Mapping
+//
+// In JSON format, the Duration type is encoded as a string rather than an
+// object, where the string ends in the suffix "s" (indicating seconds) and
+// is preceded by the number of seconds, with nanoseconds expressed as
+// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+// microsecond should be expressed in JSON format as "3.000001s".
+//
+//
+message Duration {
+ // Signed seconds of the span of time. Must be from -315,576,000,000
+ // to +315,576,000,000 inclusive. Note: these bounds are computed from:
+ // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
+ int64 seconds = 1;
+
+ // Signed fractions of a second at nanosecond resolution of the span
+ // of time. Durations less than one second are represented with a 0
+ // `seconds` field and a positive or negative `nanos` field. For durations
+ // of one second or more, a non-zero value for the `nanos` field must be
+ // of the same sign as the `seconds` field. Must be from -999,999,999
+ // to +999,999,999 inclusive.
+ int32 nanos = 2;
+}
diff --git a/third_party/google/protobuf/empty.proto b/third_party/google/protobuf/empty.proto
new file mode 100644
index 0000000..5f992de
--- /dev/null
+++ b/third_party/google/protobuf/empty.proto
@@ -0,0 +1,52 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "google.golang.org/protobuf/types/known/emptypb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "EmptyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// A generic empty message that you can re-use to avoid defining duplicated
+// empty messages in your APIs. A typical example is to use it as the request
+// or the response type of an API method. For instance:
+//
+// service Foo {
+// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
+// }
+//
+// The JSON representation for `Empty` is empty JSON object `{}`.
+message Empty {}
diff --git a/third_party/google/protobuf/field_mask.proto b/third_party/google/protobuf/field_mask.proto
new file mode 100644
index 0000000..6b5104f
--- /dev/null
+++ b/third_party/google/protobuf/field_mask.proto
@@ -0,0 +1,245 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "FieldMaskProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb";
+option cc_enable_arenas = true;
+
+// `FieldMask` represents a set of symbolic field paths, for example:
+//
+// paths: "f.a"
+// paths: "f.b.d"
+//
+// Here `f` represents a field in some root message, `a` and `b`
+// fields in the message found in `f`, and `d` a field found in the
+// message in `f.b`.
+//
+// Field masks are used to specify a subset of fields that should be
+// returned by a get operation or modified by an update operation.
+// Field masks also have a custom JSON encoding (see below).
+//
+// # Field Masks in Projections
+//
+// When used in the context of a projection, a response message or
+// sub-message is filtered by the API to only contain those fields as
+// specified in the mask. For example, if the mask in the previous
+// example is applied to a response message as follows:
+//
+// f {
+// a : 22
+// b {
+// d : 1
+// x : 2
+// }
+// y : 13
+// }
+// z: 8
+//
+// The result will not contain specific values for fields x,y and z
+// (their value will be set to the default, and omitted in proto text
+// output):
+//
+//
+// f {
+// a : 22
+// b {
+// d : 1
+// }
+// }
+//
+// A repeated field is not allowed except at the last position of a
+// paths string.
+//
+// If a FieldMask object is not present in a get operation, the
+// operation applies to all fields (as if a FieldMask of all fields
+// had been specified).
+//
+// Note that a field mask does not necessarily apply to the
+// top-level response message. In case of a REST get operation, the
+// field mask applies directly to the response, but in case of a REST
+// list operation, the mask instead applies to each individual message
+// in the returned resource list. In case of a REST custom method,
+// other definitions may be used. Where the mask applies will be
+// clearly documented together with its declaration in the API. In
+// any case, the effect on the returned resource/resources is required
+// behavior for APIs.
+//
+// # Field Masks in Update Operations
+//
+// A field mask in update operations specifies which fields of the
+// targeted resource are going to be updated. The API is required
+// to only change the values of the fields as specified in the mask
+// and leave the others untouched. If a resource is passed in to
+// describe the updated values, the API ignores the values of all
+// fields not covered by the mask.
+//
+// If a repeated field is specified for an update operation, new values will
+// be appended to the existing repeated field in the target resource. Note that
+// a repeated field is only allowed in the last position of a `paths` string.
+//
+// If a sub-message is specified in the last position of the field mask for an
+// update operation, then new value will be merged into the existing sub-message
+// in the target resource.
+//
+// For example, given the target message:
+//
+// f {
+// b {
+// d: 1
+// x: 2
+// }
+// c: [1]
+// }
+//
+// And an update message:
+//
+// f {
+// b {
+// d: 10
+// }
+// c: [2]
+// }
+//
+// then if the field mask is:
+//
+// paths: ["f.b", "f.c"]
+//
+// then the result will be:
+//
+// f {
+// b {
+// d: 10
+// x: 2
+// }
+// c: [1, 2]
+// }
+//
+// An implementation may provide options to override this default behavior for
+// repeated and message fields.
+//
+// In order to reset a field's value to the default, the field must
+// be in the mask and set to the default value in the provided resource.
+// Hence, in order to reset all fields of a resource, provide a default
+// instance of the resource and set all fields in the mask, or do
+// not provide a mask as described below.
+//
+// If a field mask is not present on update, the operation applies to
+// all fields (as if a field mask of all fields has been specified).
+// Note that in the presence of schema evolution, this may mean that
+// fields the client does not know and has therefore not filled into
+// the request will be reset to their default. If this is unwanted
+// behavior, a specific service may require a client to always specify
+// a field mask, producing an error if not.
+//
+// As with get operations, the location of the resource which
+// describes the updated values in the request message depends on the
+// operation kind. In any case, the effect of the field mask is
+// required to be honored by the API.
+//
+// ## Considerations for HTTP REST
+//
+// The HTTP kind of an update operation which uses a field mask must
+// be set to PATCH instead of PUT in order to satisfy HTTP semantics
+// (PUT must only be used for full updates).
+//
+// # JSON Encoding of Field Masks
+//
+// In JSON, a field mask is encoded as a single string where paths are
+// separated by a comma. Fields name in each path are converted
+// to/from lower-camel naming conventions.
+//
+// As an example, consider the following message declarations:
+//
+// message Profile {
+// User user = 1;
+// Photo photo = 2;
+// }
+// message User {
+// string display_name = 1;
+// string address = 2;
+// }
+//
+// In proto a field mask for `Profile` may look as such:
+//
+// mask {
+// paths: "user.display_name"
+// paths: "photo"
+// }
+//
+// In JSON, the same mask is represented as below:
+//
+// {
+// mask: "user.displayName,photo"
+// }
+//
+// # Field Masks and Oneof Fields
+//
+// Field masks treat fields in oneofs just as regular fields. Consider the
+// following message:
+//
+// message SampleMessage {
+// oneof test_oneof {
+// string name = 4;
+// SubMessage sub_message = 9;
+// }
+// }
+//
+// The field mask can be:
+//
+// mask {
+// paths: "name"
+// }
+//
+// Or:
+//
+// mask {
+// paths: "sub_message"
+// }
+//
+// Note that oneof type names ("test_oneof" in this case) cannot be used in
+// paths.
+//
+// ## Field Mask Verification
+//
+// The implementation of any API method which has a FieldMask type field in the
+// request should verify the included field paths, and return an
+// `INVALID_ARGUMENT` error if any path is unmappable.
+message FieldMask {
+ // The set of field mask paths.
+ repeated string paths = 1;
+}
diff --git a/third_party/google/protobuf/source_context.proto b/third_party/google/protobuf/source_context.proto
new file mode 100644
index 0000000..06bfc43
--- /dev/null
+++ b/third_party/google/protobuf/source_context.proto
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "SourceContextProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb";
+
+// `SourceContext` represents information about the source of a
+// protobuf element, like the file in which it is defined.
+message SourceContext {
+ // The path-qualified name of the .proto file that contained the associated
+ // protobuf element. For example: `"google/protobuf/source_context.proto"`.
+ string file_name = 1;
+}
diff --git a/third_party/google/protobuf/struct.proto b/third_party/google/protobuf/struct.proto
new file mode 100644
index 0000000..0ac843c
--- /dev/null
+++ b/third_party/google/protobuf/struct.proto
@@ -0,0 +1,95 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/structpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "StructProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// `Struct` represents a structured data value, consisting of fields
+// which map to dynamically typed values. In some languages, `Struct`
+// might be supported by a native representation. For example, in
+// scripting languages like JS a struct is represented as an
+// object. The details of that representation are described together
+// with the proto support for the language.
+//
+// The JSON representation for `Struct` is JSON object.
+message Struct {
+ // Unordered map of dynamically typed values.
+ map fields = 1;
+}
+
+// `Value` represents a dynamically typed value which can be either
+// null, a number, a string, a boolean, a recursive struct value, or a
+// list of values. A producer of value is expected to set one of these
+// variants. Absence of any variant indicates an error.
+//
+// The JSON representation for `Value` is JSON value.
+message Value {
+ // The kind of value.
+ oneof kind {
+ // Represents a null value.
+ NullValue null_value = 1;
+ // Represents a double value.
+ double number_value = 2;
+ // Represents a string value.
+ string string_value = 3;
+ // Represents a boolean value.
+ bool bool_value = 4;
+ // Represents a structured value.
+ Struct struct_value = 5;
+ // Represents a repeated `Value`.
+ ListValue list_value = 6;
+ }
+}
+
+// `NullValue` is a singleton enumeration to represent the null value for the
+// `Value` type union.
+//
+// The JSON representation for `NullValue` is JSON `null`.
+enum NullValue {
+ // Null value.
+ NULL_VALUE = 0;
+}
+
+// `ListValue` is a wrapper around a repeated field of values.
+//
+// The JSON representation for `ListValue` is JSON array.
+message ListValue {
+ // Repeated field of dynamically typed values.
+ repeated Value values = 1;
+}
diff --git a/third_party/google/protobuf/timestamp.proto b/third_party/google/protobuf/timestamp.proto
new file mode 100644
index 0000000..3b2df6d
--- /dev/null
+++ b/third_party/google/protobuf/timestamp.proto
@@ -0,0 +1,147 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/timestamppb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TimestampProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Timestamp represents a point in time independent of any time zone or local
+// calendar, encoded as a count of seconds and fractions of seconds at
+// nanosecond resolution. The count is relative to an epoch at UTC midnight on
+// January 1, 1970, in the proleptic Gregorian calendar which extends the
+// Gregorian calendar backwards to year one.
+//
+// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+// second table is needed for interpretation, using a [24-hour linear
+// smear](https://developers.google.com/time/smear).
+//
+// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+// restricting to that range, we ensure that we can convert to and from [RFC
+// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+//
+// # Examples
+//
+// Example 1: Compute Timestamp from POSIX `time()`.
+//
+// Timestamp timestamp;
+// timestamp.set_seconds(time(NULL));
+// timestamp.set_nanos(0);
+//
+// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+//
+// struct timeval tv;
+// gettimeofday(&tv, NULL);
+//
+// Timestamp timestamp;
+// timestamp.set_seconds(tv.tv_sec);
+// timestamp.set_nanos(tv.tv_usec * 1000);
+//
+// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+//
+// FILETIME ft;
+// GetSystemTimeAsFileTime(&ft);
+// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+//
+// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+// Timestamp timestamp;
+// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+//
+// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+//
+// long millis = System.currentTimeMillis();
+//
+// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+// .setNanos((int) ((millis % 1000) * 1000000)).build();
+//
+//
+// Example 5: Compute Timestamp from Java `Instant.now()`.
+//
+// Instant now = Instant.now();
+//
+// Timestamp timestamp =
+// Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+// .setNanos(now.getNano()).build();
+//
+//
+// Example 6: Compute Timestamp from current time in Python.
+//
+// timestamp = Timestamp()
+// timestamp.GetCurrentTime()
+//
+// # JSON Mapping
+//
+// In JSON format, the Timestamp type is encoded as a string in the
+// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+// where {year} is always expressed using four digits while {month}, {day},
+// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+// is required. A proto3 JSON serializer should always use UTC (as indicated by
+// "Z") when printing the Timestamp type and a proto3 JSON parser should be
+// able to accept both UTC and other timezones (as indicated by an offset).
+//
+// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+// 01:30 UTC on January 15, 2017.
+//
+// In JavaScript, one can convert a Date object to this format using the
+// standard
+// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+// method. In Python, a standard `datetime.datetime` object can be converted
+// to this format using
+// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
+// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
+// the Joda Time's [`ISODateTimeFormat.dateTime()`](
+// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
+// ) to obtain a formatter capable of generating timestamps in this format.
+//
+//
+message Timestamp {
+ // Represents seconds of UTC time since Unix epoch
+ // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+ // 9999-12-31T23:59:59Z inclusive.
+ int64 seconds = 1;
+
+ // Non-negative fractions of a second at nanosecond resolution. Negative
+ // second values with fractions must still have non-negative nanos values
+ // that count forward in time. Must be from 0 to 999,999,999
+ // inclusive.
+ int32 nanos = 2;
+}
diff --git a/third_party/google/protobuf/type.proto b/third_party/google/protobuf/type.proto
new file mode 100644
index 0000000..d3f6a68
--- /dev/null
+++ b/third_party/google/protobuf/type.proto
@@ -0,0 +1,187 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/source_context.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TypeProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/protobuf/types/known/typepb";
+
+// A protocol buffer message type.
+message Type {
+ // The fully qualified message name.
+ string name = 1;
+ // The list of fields.
+ repeated Field fields = 2;
+ // The list of types appearing in `oneof` definitions in this type.
+ repeated string oneofs = 3;
+ // The protocol buffer options.
+ repeated Option options = 4;
+ // The source context.
+ SourceContext source_context = 5;
+ // The source syntax.
+ Syntax syntax = 6;
+}
+
+// A single field of a message type.
+message Field {
+ // Basic field types.
+ enum Kind {
+ // Field type unknown.
+ TYPE_UNKNOWN = 0;
+ // Field type double.
+ TYPE_DOUBLE = 1;
+ // Field type float.
+ TYPE_FLOAT = 2;
+ // Field type int64.
+ TYPE_INT64 = 3;
+ // Field type uint64.
+ TYPE_UINT64 = 4;
+ // Field type int32.
+ TYPE_INT32 = 5;
+ // Field type fixed64.
+ TYPE_FIXED64 = 6;
+ // Field type fixed32.
+ TYPE_FIXED32 = 7;
+ // Field type bool.
+ TYPE_BOOL = 8;
+ // Field type string.
+ TYPE_STRING = 9;
+ // Field type group. Proto2 syntax only, and deprecated.
+ TYPE_GROUP = 10;
+ // Field type message.
+ TYPE_MESSAGE = 11;
+ // Field type bytes.
+ TYPE_BYTES = 12;
+ // Field type uint32.
+ TYPE_UINT32 = 13;
+ // Field type enum.
+ TYPE_ENUM = 14;
+ // Field type sfixed32.
+ TYPE_SFIXED32 = 15;
+ // Field type sfixed64.
+ TYPE_SFIXED64 = 16;
+ // Field type sint32.
+ TYPE_SINT32 = 17;
+ // Field type sint64.
+ TYPE_SINT64 = 18;
+ }
+
+ // Whether a field is optional, required, or repeated.
+ enum Cardinality {
+ // For fields with unknown cardinality.
+ CARDINALITY_UNKNOWN = 0;
+ // For optional fields.
+ CARDINALITY_OPTIONAL = 1;
+ // For required fields. Proto2 syntax only.
+ CARDINALITY_REQUIRED = 2;
+ // For repeated fields.
+ CARDINALITY_REPEATED = 3;
+ }
+
+ // The field type.
+ Kind kind = 1;
+ // The field cardinality.
+ Cardinality cardinality = 2;
+ // The field number.
+ int32 number = 3;
+ // The field name.
+ string name = 4;
+ // The field type URL, without the scheme, for message or enumeration
+ // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`.
+ string type_url = 6;
+ // The index of the field type in `Type.oneofs`, for message or enumeration
+ // types. The first type has index 1; zero means the type is not in the list.
+ int32 oneof_index = 7;
+ // Whether to use alternative packed wire representation.
+ bool packed = 8;
+ // The protocol buffer options.
+ repeated Option options = 9;
+ // The field JSON name.
+ string json_name = 10;
+ // The string value of the default value of this field. Proto2 syntax only.
+ string default_value = 11;
+}
+
+// Enum type definition.
+message Enum {
+ // Enum type name.
+ string name = 1;
+ // Enum value definitions.
+ repeated EnumValue enumvalue = 2;
+ // Protocol buffer options.
+ repeated Option options = 3;
+ // The source context.
+ SourceContext source_context = 4;
+ // The source syntax.
+ Syntax syntax = 5;
+}
+
+// Enum value definition.
+message EnumValue {
+ // Enum value name.
+ string name = 1;
+ // Enum value number.
+ int32 number = 2;
+ // Protocol buffer options.
+ repeated Option options = 3;
+}
+
+// A protocol buffer option, which can be attached to a message, field,
+// enumeration, etc.
+message Option {
+ // The option's name. For protobuf built-in options (options defined in
+ // descriptor.proto), this is the short name. For example, `"map_entry"`.
+ // For custom options, it should be the fully-qualified name. For example,
+ // `"google.api.http"`.
+ string name = 1;
+ // The option's value packed in an Any message. If the value is a primitive,
+ // the corresponding wrapper type defined in google/protobuf/wrappers.proto
+ // should be used. If the value is an enum, it should be stored as an int32
+ // value using the google.protobuf.Int32Value type.
+ Any value = 2;
+}
+
+// The syntax in which a protocol buffer element is defined.
+enum Syntax {
+ // Syntax `proto2`.
+ SYNTAX_PROTO2 = 0;
+ // Syntax `proto3`.
+ SYNTAX_PROTO3 = 1;
+}
diff --git a/third_party/google/protobuf/wrappers.proto b/third_party/google/protobuf/wrappers.proto
new file mode 100644
index 0000000..d49dd53
--- /dev/null
+++ b/third_party/google/protobuf/wrappers.proto
@@ -0,0 +1,123 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+//
+// These wrappers have no meaningful use within repeated fields as they lack
+// the ability to detect presence on individual elements.
+// These wrappers have no meaningful use within a map or a oneof since
+// individual entries of a map or fields of a oneof can already detect presence.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "google.golang.org/protobuf/types/known/wrapperspb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "WrappersProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+ // The double value.
+ double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+ // The float value.
+ float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+ // The int64 value.
+ int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+ // The uint64 value.
+ uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+ // The int32 value.
+ int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+ // The uint32 value.
+ uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+ // The bool value.
+ bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+ // The string value.
+ string value = 1;
+}
+
+// Wrapper message for `bytes`.
+//
+// The JSON representation for `BytesValue` is JSON string.
+message BytesValue {
+ // The bytes value.
+ bytes value = 1;
+}
diff --git a/ueditor-config.json b/ueditor-config.json
new file mode 100644
index 0000000..a1484ce
--- /dev/null
+++ b/ueditor-config.json
@@ -0,0 +1,234 @@
+/* 前后端通信相关的配置,注释只允许使用多行方式 */
+{
+ /* 上传图片配置项 */
+ "imageActionName": "uploadimage",
+ /* 执行上传图片的action名称 */
+ "imageFieldName": "upfile",
+ /* 提交的图片表单名称 */
+ "imageMaxSize": 2048000,
+ /* 上传大小限制,单位B */
+ "imageAllowFiles": [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".bmp"
+ ],
+ /* 上传图片格式显示 */
+ "imageCompressEnable": true,
+ /* 是否压缩图片,默认是true */
+ "imageCompressBorder": 1600,
+ /* 图片压缩最长边限制 */
+ "imageInsertAlign": "none",
+ /* 插入的图片浮动方式 */
+ "imageUrlPrefix": "",
+ /* 图片访问路径前缀 */
+ "imagePathFormat": "/ue-img/{yy}{mm}{dd}/{filename}",
+ /* 涂鸦图片上传配置项 */
+ "scrawlActionName": "uploadscrawl",
+ /* 执行上传涂鸦的action名称 */
+ "scrawlFieldName": "upfile",
+ /* 提交的图片表单名称 */
+ /* "scrawlPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}",*/
+ /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ "scrawlPathFormat": "/ue-img/{yy}{mm}{dd}/{filename}",
+ /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ "scrawlMaxSize": 2048000,
+ /* 上传大小限制,单位B */
+ "scrawlUrlPrefix": "",
+ /* 图片访问路径前缀 */
+ "scrawlInsertAlign": "none",
+ /* 截图工具上传 */
+ "snapscreenActionName": "uploadimage",
+ /* 执行上传截图的action名称 */
+ "snapscreenPathFormat": "/ue-img/{yy}{mm}{dd}/{filename}",
+ /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ "snapscreenUrlPrefix": "",
+ /* 图片访问路径前缀 */
+ "snapscreenInsertAlign": "none",
+ /* 插入的图片浮动方式 */
+
+ /* 抓取远程图片配置 */
+ "catcherLocalDomain": [
+ "127.0.0.1",
+ "localhost",
+ "img.baidu.com"
+ ],
+ "catcherActionName": "catchimage",
+ /* 执行抓取远程图片的action名称 */
+ "catcherFieldName": "source",
+ /* 提交的图片列表表单名称 */
+ "catcherPathFormat": "/ue-img/{yy}{mm}{dd}/{filename}",
+ /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ "catcherUrlPrefix": "",
+ /* 图片访问路径前缀 */
+ "catcherMaxSize": 2048000,
+ /* 上传大小限制,单位B */
+ "catcherAllowFiles": [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".bmp"
+ ],
+ /* 抓取图片格式显示 */
+
+ /* 上传视频配置 */
+ "videoActionName": "uploadvideo",
+ /* 执行上传视频的action名称 */
+ "videoFieldName": "upfile",
+ /* 提交的视频表单名称 */
+ "videoPathFormat": "/ue-video/{yy}{mm}{dd}/{filename}",
+ /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ "videoUrlPrefix": "",
+ /* 视频访问路径前缀 */
+ "videoMaxSize": 102400000,
+ /* 上传大小限制,单位B,默认100MB */
+ "videoAllowFiles": [
+ ".flv",
+ ".swf",
+ ".mkv",
+ ".avi",
+ ".rm",
+ ".rmvb",
+ ".mpeg",
+ ".mpg",
+ ".ogg",
+ ".ogv",
+ ".mov",
+ ".wmv",
+ ".mp4",
+ ".webm",
+ ".mp3",
+ ".wav",
+ ".mid"
+ ],
+ /* 上传视频格式显示 */
+
+ /* 上传文件配置 */
+ "fileActionName": "uploadfile",
+ /* controller里,执行上传视频的action名称 */
+ "fileFieldName": "upfile",
+ /* 提交的文件表单名称 */
+ "filePathFormat": "/ue-file/{yy}{mm}{dd}/{filename}",
+ /* 上传保存路径,可以自定义保存路径和文件名格式 */
+ "fileUrlPrefix": "",
+ /* 文件访问路径前缀 */
+ "fileMaxSize": 51200000,
+ /* 上传大小限制,单位B,默认50MB */
+ "fileAllowFiles": [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".bmp",
+ ".flv",
+ ".swf",
+ ".mkv",
+ ".avi",
+ ".rm",
+ ".rmvb",
+ ".mpeg",
+ ".mpg",
+ ".ogg",
+ ".ogv",
+ ".mov",
+ ".wmv",
+ ".mp4",
+ ".webm",
+ ".mp3",
+ ".wav",
+ ".mid",
+ ".rar",
+ ".zip",
+ ".tar",
+ ".gz",
+ ".7z",
+ ".bz2",
+ ".cab",
+ ".iso",
+ ".doc",
+ ".docx",
+ ".xls",
+ ".xlsx",
+ ".ppt",
+ ".pptx",
+ ".pdf",
+ ".txt",
+ ".md",
+ ".xml"
+ ],
+ /* 上传文件格式显示 */
+
+ /* 列出指定目录下的图片 */
+ "imageManagerActionName": "listimage",
+ /* 执行图片管理的action名称 */
+ "imageManagerListPath": "/static/upload/images/",
+ /* 指定要列出图片的目录 */
+ "imageManagerListSize": 20,
+ /* 每次列出文件数量 */
+ "imageManagerUrlPrefix": "",
+ /* 图片访问路径前缀 */
+ "imageManagerInsertAlign": "none",
+ /* 插入的图片浮动方式 */
+ "imageManagerAllowFiles": [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".bmp"
+ ],
+ /* 列出的文件类型 */
+ /* 列出指定目录下的文件 */
+ "fileManagerActionName": "listfile",
+ /* 执行文件管理的action名称 */
+ "fileManagerListPath": "/static/upload/files/",
+ /* 指定要列出文件的目录 */
+ "fileManagerUrlPrefix": "",
+ /* 文件访问路径前缀 */
+ "fileManagerListSize": 20,
+ /* 每次列出文件数量 */
+ "fileManagerAllowFiles": [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".bmp",
+ ".flv",
+ ".swf",
+ ".mkv",
+ ".avi",
+ ".rm",
+ ".rmvb",
+ ".mpeg",
+ ".mpg",
+ ".ogg",
+ ".ogv",
+ ".mov",
+ ".wmv",
+ ".mp4",
+ ".webm",
+ ".mp3",
+ ".wav",
+ ".mid",
+ ".rar",
+ ".zip",
+ ".tar",
+ ".gz",
+ ".7z",
+ ".bz2",
+ ".cab",
+ ".iso",
+ ".doc",
+ ".docx",
+ ".xls",
+ ".xlsx",
+ ".ppt",
+ ".pptx",
+ ".pdf",
+ ".txt",
+ ".md",
+ ".xml"
+ ]
+ /* 列出的文件类型 */
+}
diff --git a/utils/array.go b/utils/array.go
new file mode 100644
index 0000000..d57e797
--- /dev/null
+++ b/utils/array.go
@@ -0,0 +1,19 @@
+package utils
+
+func ArrayStrRemove(old, loser []string) []string {
+ for j := 0; j < len(loser); j++ {
+ for i := 0; i < len(old); i++ {
+ if old[i] == loser[j] { //将元素剔除
+ if old[i] == loser[j] {
+ if i == len(old)-1 {
+ old = old[:i]
+ } else {
+ old = append(old[:i], old[i+1:]...)
+ i--
+ }
+ }
+ }
+ }
+ }
+ return old
+}
diff --git a/utils/breakpoint_continue.go b/utils/breakpoint_continue.go
new file mode 100644
index 0000000..2dd003d
--- /dev/null
+++ b/utils/breakpoint_continue.go
@@ -0,0 +1,102 @@
+package utils
+
+import (
+ "io/ioutil"
+ "os"
+ "strconv"
+)
+
+// 前端传来文件片与当前片为什么文件的第几片
+// 后端拿到以后比较次分片是否上传 或者是否为不完全片
+// 前端发送每片多大
+// 前端告知是否为最后一片且是否完成
+
+const breakpointDir = "./breakpointDir/"
+const finishDir = "./fileDir/"
+
+//@function: BreakPointContinue
+//@description: 断点续传
+//@param: content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string
+//@return: error, string
+
+func BreakPointContinue(content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string) (error, string) {
+ path := breakpointDir + fileMd5 + "/"
+ err := os.MkdirAll(path, os.ModePerm)
+ if err != nil {
+ return err, path
+ }
+ err, pathc := makeFileContent(content, fileName, path, contentNumber)
+ return err, pathc
+
+}
+
+//@function: CheckMd5
+//@description: 检查Md5
+//@param: content []byte, chunkMd5 string
+//@return: CanUpload bool
+
+func CheckMd5(content []byte, chunkMd5 string) (CanUpload bool) {
+ fileMd5 := MD5V(content)
+ if fileMd5 == chunkMd5 {
+ return true // 可以继续上传
+ } else {
+ return false // 切片不完整,废弃
+ }
+}
+
+//@function: makeFileContent
+//@description: 创建切片内容
+//@param: content []byte, fileName string, FileDir string, contentNumber int
+//@return: error, string
+
+func makeFileContent(content []byte, fileName string, FileDir string, contentNumber int) (error, string) {
+ path := FileDir + fileName + "_" + strconv.Itoa(contentNumber)
+ f, err := os.Create(path)
+ if err != nil {
+ return err, path
+ } else {
+ _, err = f.Write(content)
+ if err != nil {
+ return err, path
+ }
+ }
+ defer f.Close()
+ return nil, path
+}
+
+//@function: makeFileContent
+//@description: 创建切片文件
+//@param: fileName string, FileMd5 string
+//@return: error, string
+
+func MakeFile(fileName string, FileMd5 string) (error, string) {
+ rd, err := ioutil.ReadDir(breakpointDir + FileMd5)
+ if err != nil {
+ return err, finishDir + fileName
+ }
+ _ = os.MkdirAll(finishDir, os.ModePerm)
+ fd, err := os.OpenFile(finishDir+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
+ if err != nil {
+ return err, finishDir + fileName
+ }
+ defer fd.Close()
+ for k := range rd {
+ content, _ := ioutil.ReadFile(breakpointDir + FileMd5 + "/" + fileName + "_" + strconv.Itoa(k))
+ _, err = fd.Write(content)
+ if err != nil {
+ _ = os.Remove(finishDir + fileName)
+ return err, finishDir + fileName
+ }
+ }
+ return nil, finishDir + fileName
+}
+
+//@function: RemoveChunk
+//@description: 移除切片
+//@param: FileMd5 string
+//@return: error
+
+func RemoveChunk(FileMd5 string) error {
+ err := os.RemoveAll(breakpointDir + FileMd5)
+ return err
+}
diff --git a/utils/constant.go b/utils/constant.go
new file mode 100644
index 0000000..a8534e2
--- /dev/null
+++ b/utils/constant.go
@@ -0,0 +1,6 @@
+package utils
+
+const (
+ ConfigEnv = "MG_CONFIG"
+ ConfigFile = "config.yaml"
+)
diff --git a/utils/cors.go b/utils/cors.go
new file mode 100644
index 0000000..1fcc834
--- /dev/null
+++ b/utils/cors.go
@@ -0,0 +1,17 @@
+package utils
+
+import (
+ uuid "github.com/satori/go.uuid"
+ "github.com/taskcluster/slugid-go/slugid"
+)
+
+// Get22UUID ...
+func Get22UUID() string {
+ return slugid.Nice()
+}
+
+// GetUUIDv4 ...
+func GetUUIDv4() string {
+ uid := uuid.NewV4()
+ return uid.String()
+}
diff --git a/utils/db_automation.go b/utils/db_automation.go
new file mode 100644
index 0000000..60c082a
--- /dev/null
+++ b/utils/db_automation.go
@@ -0,0 +1,28 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "gorm.io/gorm"
+)
+
+//@function: ClearTable
+//@description: 清理数据库表数据
+//@param: db(数据库对象) *gorm.DB, tableName(表名) string, compareField(比较字段) string, interval(间隔) string
+//@return: error
+
+func ClearTable(db *gorm.DB, tableName string, compareField string, interval string) error {
+ if db == nil {
+ return errors.New("db Cannot be empty")
+ }
+ duration, err := time.ParseDuration(interval)
+ if err != nil {
+ return err
+ }
+ if duration < 0 {
+ return errors.New("parse duration < 0")
+ }
+ return db.Debug().Exec(fmt.Sprintf("DELETE FROM %s WHERE %s < ?", tableName, compareField), time.Now().Add(-duration)).Error
+}
diff --git a/utils/directory.go b/utils/directory.go
new file mode 100644
index 0000000..6a1d59d
--- /dev/null
+++ b/utils/directory.go
@@ -0,0 +1,46 @@
+package utils
+
+import (
+ "os"
+ "pure-admin/global"
+
+ "go.uber.org/zap"
+)
+
+//@function: PathExists
+//@description: 文件目录是否存在
+//@param: path string
+//@return: bool, error
+
+func PathExists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+//@function: CreateDir
+//@description: 批量创建文件夹
+//@param: dirs ...string
+//@return: err error
+
+func CreateDir(dirs ...string) (err error) {
+ for _, v := range dirs {
+ exist, err := PathExists(v)
+ if err != nil {
+ return err
+ }
+ if !exist {
+ global.MG_LOG.Debug("create directory" + v)
+ err = os.MkdirAll(v, os.ModePerm)
+ if err != nil {
+ global.MG_LOG.Error("create directory"+v, zap.Any(" error:", err))
+ }
+ }
+ }
+ return err
+}
diff --git a/utils/email.go b/utils/email.go
new file mode 100644
index 0000000..9b2f212
--- /dev/null
+++ b/utils/email.go
@@ -0,0 +1,78 @@
+package utils
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/smtp"
+ "strings"
+
+ "pure-admin/global"
+
+ "github.com/jordan-wright/email"
+)
+
+//@function: Email
+//@description: Email发送方法
+//@param: subject string, body string
+//@return: error
+
+func Email(subject string, body string) error {
+ to := strings.Split(global.MG_CONFIG.Email.To, ",")
+ return send(to, subject, body)
+}
+
+//@function: ErrorToEmail
+//@description: 给email中间件错误发送邮件到指定邮箱
+//@param: subject string, body string
+//@return: error
+
+func ErrorToEmail(subject string, body string) error {
+ to := strings.Split(global.MG_CONFIG.Email.To, ",")
+ if to[len(to)-1] == "" { // 判断切片的最后一个元素是否为空,为空则移除
+ to = to[:len(to)-1]
+ }
+ return send(to, subject, body)
+}
+
+//@function: EmailTest
+//@description: Email测试方法
+//@param: subject string, body string
+//@return: error
+
+func EmailTest(subject string, body string) error {
+ to := []string{global.MG_CONFIG.Email.From}
+ return send(to, subject, body)
+}
+
+//@function: send
+//@description: Email发送方法
+//@param: subject string, body string
+//@return: error
+
+func send(to []string, subject string, body string) error {
+ from := global.MG_CONFIG.Email.From
+ nickname := global.MG_CONFIG.Email.Nickname
+ secret := global.MG_CONFIG.Email.Secret
+ host := global.MG_CONFIG.Email.Host
+ port := global.MG_CONFIG.Email.Port
+ isSSL := global.MG_CONFIG.Email.IsSSL
+
+ auth := smtp.PlainAuth("", from, secret, host)
+ e := email.NewEmail()
+ if nickname != "" {
+ e.From = fmt.Sprintf("%s <%s>", nickname, from)
+ } else {
+ e.From = from
+ }
+ e.To = to
+ e.Subject = subject
+ e.HTML = []byte(body)
+ var err error
+ hostAddr := fmt.Sprintf("%s:%d", host, port)
+ if isSSL {
+ err = e.SendWithTLS(hostAddr, auth, &tls.Config{ServerName: host})
+ } else {
+ err = e.Send(hostAddr, auth)
+ }
+ return err
+}
diff --git a/utils/file_operations.go b/utils/file_operations.go
new file mode 100644
index 0000000..9b217f0
--- /dev/null
+++ b/utils/file_operations.go
@@ -0,0 +1,63 @@
+package utils
+
+import (
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+)
+
+//@function: FileMove
+//@description: 文件移动供外部调用
+//@param: src string, dst string(src: 源位置,绝对路径or相对路径, dst: 目标位置,绝对路径or相对路径,必须为文件夹)
+//@return: err error
+
+func FileMove(src string, dst string) (err error) {
+ if dst == "" {
+ return nil
+ }
+ src, err = filepath.Abs(src)
+ if err != nil {
+ return err
+ }
+ dst, err = filepath.Abs(dst)
+ if err != nil {
+ return err
+ }
+ var revoke = false
+ dir := filepath.Dir(dst)
+Redirect:
+ _, err = os.Stat(dir)
+ if err != nil {
+ err = os.MkdirAll(dir, 0755)
+ if err != nil {
+ return err
+ }
+ if !revoke {
+ revoke = true
+ goto Redirect
+ }
+ }
+ return os.Rename(src, dst)
+}
+
+//@function: TrimSpace
+//@description: 去除结构体空格
+//@param: target interface (target: 目标结构体,传入必须是指针类型)
+//@return: null
+
+func TrimSpace(target interface{}) {
+ t := reflect.TypeOf(target)
+ if t.Kind() != reflect.Ptr {
+ return
+ }
+ t = t.Elem()
+ v := reflect.ValueOf(target).Elem()
+ for i := 0; i < t.NumField(); i++ {
+ switch v.Field(i).Kind() {
+ case reflect.String:
+ v.Field(i).SetString(strings.TrimSpace(v.Field(i).String()))
+ }
+ }
+ return
+}
diff --git a/utils/float.go b/utils/float.go
new file mode 100644
index 0000000..57e0939
--- /dev/null
+++ b/utils/float.go
@@ -0,0 +1,28 @@
+package utils
+
+import (
+ "fmt"
+ "math"
+ "strconv"
+)
+
+func FormatFloat(num float64, decimal int) (float64, error) {
+ // 默认乘1
+ d := float64(1)
+ if decimal > 0 {
+ // 10的N次方
+ d = math.Pow10(decimal)
+ }
+ // math.trunc作用就是返回浮点数的整数部分
+ // 再除回去,小数点后无效的0也就不存在了
+ res := strconv.FormatFloat(math.Trunc(num*d)/d, 'f', -1, 64)
+ return strconv.ParseFloat(res, 64)
+}
+
+func FormatFloatToString(num float64) string {
+ float, err := FormatFloat(num, 2)
+ if err != nil {
+ return "-"
+ }
+ return fmt.Sprintf("%v", float)
+}
diff --git a/utils/fmt_plus.go b/utils/fmt_plus.go
new file mode 100644
index 0000000..e9258cc
--- /dev/null
+++ b/utils/fmt_plus.go
@@ -0,0 +1,36 @@
+package utils
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+//@function: StructToMap
+//@description: 利用反射将结构体转化为map
+//@param: obj interface{}
+//@return: map[string]interface{}
+
+func StructToMap(obj interface{}) map[string]interface{} {
+ obj1 := reflect.TypeOf(obj)
+ obj2 := reflect.ValueOf(obj)
+
+ var data = make(map[string]interface{})
+ for i := 0; i < obj1.NumField(); i++ {
+ if obj1.Field(i).Tag.Get("mapstructure") != "" {
+ data[obj1.Field(i).Tag.Get("mapstructure")] = obj2.Field(i).Interface()
+ } else {
+ data[obj1.Field(i).Name] = obj2.Field(i).Interface()
+ }
+ }
+ return data
+}
+
+//@function: ArrayToString
+//@description: 将数组格式化为字符串
+//@param: array []interface{}
+//@return: string
+
+func ArrayToString(array []interface{}) string {
+ return strings.Replace(strings.Trim(fmt.Sprint(array), "[]"), " ", ",", -1)
+}
diff --git a/utils/injectionCode.go b/utils/injectionCode.go
new file mode 100644
index 0000000..13ea4cc
--- /dev/null
+++ b/utils/injectionCode.go
@@ -0,0 +1,142 @@
+package utils
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "strings"
+)
+
+//@function: AutoInjectionCode
+//@description: 向文件中固定注释位置写入代码
+//@param: filepath string, funcName string, codeData string
+//@return: error
+
+func AutoInjectionCode(filepath string, funcName string, codeData string) error {
+ startComment := "Code generated by pure-admin Begin; DO NOT EDIT."
+ endComment := "Code generated by pure-admin End; DO NOT EDIT."
+ srcData, err := ioutil.ReadFile(filepath)
+ if err != nil {
+ return err
+ }
+ srcDataLen := len(srcData)
+ fset := token.NewFileSet()
+ fparser, err := parser.ParseFile(fset, filepath, srcData, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+ codeData = strings.TrimSpace(codeData)
+ var codeStartPos = -1
+ var codeEndPos = srcDataLen
+ var expectedFunction *ast.FuncDecl
+
+ var startCommentPos = -1
+ var endCommentPos = srcDataLen
+
+ // 如果指定了函数名,先寻找对应函数
+ if funcName != "" {
+ for _, decl := range fparser.Decls {
+ if funDecl, ok := decl.(*ast.FuncDecl); ok && funDecl.Name.Name == funcName {
+ expectedFunction = funDecl
+ codeStartPos = int(funDecl.Body.Lbrace)
+ codeEndPos = int(funDecl.Body.Rbrace)
+ break
+ }
+ }
+ }
+
+ // 遍历所有注释
+ for _, comment := range fparser.Comments {
+ if int(comment.Pos()) > codeStartPos && int(comment.End()) <= codeEndPos {
+ if startComment != "" && strings.Contains(comment.Text(), startComment) {
+ startCommentPos = int(comment.Pos()) // Note: Pos is the second '/'
+ }
+ if endComment != "" && strings.Contains(comment.Text(), endComment) {
+ endCommentPos = int(comment.Pos()) // Note: Pos is the second '/'
+ }
+ }
+ }
+
+ if endCommentPos == srcDataLen {
+ return fmt.Errorf("comment:%s not found", endComment)
+ }
+
+ // 在指定函数名,且函数中startComment和endComment都存在时,进行区间查重
+ if (codeStartPos != -1 && codeEndPos <= srcDataLen) && (startCommentPos != -1 && endCommentPos != srcDataLen) && expectedFunction != nil {
+ if exist := checkExist(&srcData, startCommentPos, endCommentPos, expectedFunction.Body, codeData); exist {
+ fmt.Printf("文件 %s 待插入数据 %s 已存在\n", filepath, codeData)
+ return nil // 这里不需要返回错误?
+ }
+ }
+
+ // 两行注释中间没有换行时,会被认为是一条Comment
+ if startCommentPos == endCommentPos {
+ endCommentPos = startCommentPos + strings.Index(string(srcData[startCommentPos:]), endComment)
+ for srcData[endCommentPos] != '/' {
+ endCommentPos--
+ }
+ }
+
+ // 记录"//"之前的空字符,保持写入后的格式一致
+ tmpSpace := make([]byte, 0, 10)
+ for tmp := endCommentPos - 2; tmp >= 0; tmp-- {
+ if srcData[tmp] != '\n' {
+ tmpSpace = append(tmpSpace, srcData[tmp])
+ } else {
+ break
+ }
+ }
+
+ reverseSpace := make([]byte, 0, len(tmpSpace))
+ for index := len(tmpSpace) - 1; index >= 0; index-- {
+ reverseSpace = append(reverseSpace, tmpSpace[index])
+ }
+
+ // 插入数据
+ indexPos := endCommentPos - 1
+ insertData := []byte(append([]byte(codeData+"\n"), reverseSpace...))
+
+ remainData := append([]byte{}, srcData[indexPos:]...)
+ srcData = append(append(srcData[:indexPos], insertData...), remainData...)
+
+ // 写回数据
+ return ioutil.WriteFile(filepath, srcData, 0600)
+}
+
+func checkExist(srcData *[]byte, startPos int, endPos int, blockStmt *ast.BlockStmt, target string) bool {
+ for _, list := range blockStmt.List {
+ switch stmt := list.(type) {
+ case *ast.ExprStmt:
+ if callExpr, ok := stmt.X.(*ast.CallExpr); ok &&
+ int(callExpr.Pos()) > startPos && int(callExpr.End()) < endPos {
+ text := string((*srcData)[int(callExpr.Pos()-1):int(callExpr.End())])
+ key := strings.TrimSpace(text)
+ if key == target {
+ return true
+ }
+ }
+ case *ast.BlockStmt:
+ if checkExist(srcData, startPos, endPos, stmt, target) {
+ return true
+ }
+ case *ast.AssignStmt:
+ // 为 model 中的代码进行检查
+ if len(stmt.Rhs) > 0 {
+ if callExpr, ok := stmt.Rhs[0].(*ast.CallExpr); ok {
+ for _, arg := range callExpr.Args {
+ if int(arg.Pos()) > startPos && int(arg.End()) < endPos {
+ text := string((*srcData)[int(arg.Pos()-1):int(arg.End())])
+ key := strings.TrimSpace(text)
+ if key == target {
+ return true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false
+}
diff --git a/utils/jpush/model.go b/utils/jpush/model.go
new file mode 100644
index 0000000..21b0b6f
--- /dev/null
+++ b/utils/jpush/model.go
@@ -0,0 +1,23 @@
+package jpush
+
+type notificationBody struct {
+}
+
+type cidList struct {
+ CidList []string `json:"cidlist"`
+}
+
+type tags struct {
+ Tags []string `json:"tags"`
+}
+
+type Error struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+type PushResult struct {
+ Sendno string `json:"sendno"`
+ MsgId string `json:"msg_id"`
+ Error Error `json:"error"`
+}
diff --git a/utils/jpush/push.go b/utils/jpush/push.go
new file mode 100644
index 0000000..c3071cb
--- /dev/null
+++ b/utils/jpush/push.go
@@ -0,0 +1,202 @@
+package jpush
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "pure-admin/global"
+ "strings"
+)
+
+func createAuthorization(req *http.Request) *http.Request {
+ var (
+ str string
+ )
+ str = global.MG_CONFIG.JPush.Appkey + ":" + global.MG_CONFIG.JPush.Secret
+ req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(str)))
+ return req
+}
+
+//JiGuangPush ...
+func JiGuangPush(reqParams map[string]interface{}) (result PushResult, err error) {
+ var (
+ client *http.Client
+ req *http.Request
+ params []byte
+ cid string
+ resBody []byte
+ options map[string]interface{}
+ )
+ options = make(map[string]interface{})
+ options["time_to_live"] = 0
+ if global.MG_CONFIG.System.Env == "mater" {
+ options["apns_production"] = true
+ } else {
+ options["apns_production"] = false
+ }
+ reqParams["options"] = options
+ cid, err = jiGuangCid()
+ if err != nil {
+ return
+ }
+ reqParams["cid"] = cid
+ params, err = json.Marshal(reqParams)
+ if err != nil {
+ return
+ }
+ client = &http.Client{}
+ req, err = http.NewRequest(http.MethodPost, "https://api.jpush.cn/v3/push", strings.NewReader(string(params)))
+ if err != nil {
+ return
+ }
+ req.Header.Add("Content-Type", "application/json")
+ createAuthorization(req)
+ resp, err := client.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ resBody, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ //fmt.Println(string(resBody))
+ global.MG_LOG.Info(string(resBody))
+ err = json.Unmarshal(resBody, &result)
+ if err != nil {
+ return
+ }
+ return
+}
+
+func jiGuangCid() (string, error) {
+ var (
+ err error
+ res string
+ client *http.Client
+ req *http.Request
+ resp *http.Response
+ resBody []byte
+ list cidList
+ )
+ client = &http.Client{}
+ req, err = http.NewRequest(http.MethodGet, "https://api.jpush.cn/v3/push/cid?count=10&type=push", nil)
+ if err != nil {
+ return res, err
+ }
+ req.Header.Add("Content-Type", "application/json")
+ createAuthorization(req)
+ resp, err = client.Do(req)
+ if err != nil {
+ return res, err
+ }
+ defer resp.Body.Close()
+ resBody, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return res, err
+ }
+ err = json.Unmarshal(resBody, &list)
+ if err != nil || len(list.CidList) == 0 {
+ return "", err
+ }
+ return list.CidList[0], err
+}
+
+func GetTagsList() (string, error) {
+ var (
+ err error
+ res string
+ client *http.Client
+ req *http.Request
+ resp *http.Response
+ resBody []byte
+ list tags
+ )
+ client = &http.Client{}
+ req, err = http.NewRequest(http.MethodGet, "https://device.jpush.cn/v3/tags/", nil)
+ if err != nil {
+ return res, err
+ }
+ req.Header.Add("Content-Type", "application/json")
+ createAuthorization(req)
+ resp, err = client.Do(req)
+ if err != nil {
+ return res, err
+ }
+ defer resp.Body.Close()
+ resBody, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return res, err
+ }
+ fmt.Println(string(resBody))
+ err = json.Unmarshal(resBody, &list)
+ if err != nil || len(list.Tags) == 0 {
+ return "", err
+ }
+ return list.Tags[0], err
+}
+
+func UpdateTags(tagValue string, registration map[string][]string) {
+ var (
+ err error
+ client *http.Client
+ req *http.Request
+ params []byte
+ reqParams map[string]interface{}
+ resBody []byte
+ )
+ reqParams = make(map[string]interface{})
+ reqParams["registration_ids"] = registration
+ params, err = json.Marshal(reqParams)
+ if err != nil {
+ return
+ }
+ client = &http.Client{}
+ req, err = http.NewRequest(http.MethodPost, "https://device.jpush.cn/v3/tags/"+tagValue, strings.NewReader(string(params)))
+ if err != nil {
+ return
+ }
+ req.Header.Add("Content-Type", "application/json")
+ createAuthorization(req)
+ resp, err := client.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ resBody, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ fmt.Println(string(resBody))
+ return
+}
+
+func GetRegistrationInfo(registration string) {
+ var (
+ err error
+ client *http.Client
+ req *http.Request
+ resp *http.Response
+ resBody []byte
+ )
+ client = &http.Client{}
+ req, err = http.NewRequest(http.MethodGet, "https://device.jpush.cn/v3/tags/", nil)
+ if err != nil {
+ return
+ }
+ req.Header.Add("Content-Type", "application/json")
+ createAuthorization(req)
+ resp, err = client.Do(req)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ resBody, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ fmt.Println(string(resBody))
+ return
+}
diff --git a/utils/md5.go b/utils/md5.go
new file mode 100644
index 0000000..e359dc9
--- /dev/null
+++ b/utils/md5.go
@@ -0,0 +1,17 @@
+package utils
+
+import (
+ "crypto/md5"
+ "encoding/hex"
+)
+
+//@function: MD5V
+//@description: md5加密
+//@param: str []byte
+//@return: string
+
+func MD5V(str []byte) string {
+ h := md5.New()
+ h.Write(str)
+ return hex.EncodeToString(h.Sum(nil))
+}
diff --git a/utils/qiniu/model.go b/utils/qiniu/model.go
new file mode 100644
index 0000000..d5fe9f2
--- /dev/null
+++ b/utils/qiniu/model.go
@@ -0,0 +1,83 @@
+package qiniu
+
+type VideoStreams struct {
+ Index int `json:"index"`
+ CodecName string `json:"codec_name"`
+ CodecLongName string `json:"codec_long_name"`
+ Profile string `json:"profile"`
+ CodecType string `json:"codec_type"`
+ CodecTimeBase string `json:"codec_time_base"`
+ CodecTagString string `json:"codec_tag_string"`
+ CodecTag string `json:"codec_tag"`
+ Width int `json:"width,omitempty"`
+ Height int `json:"height,omitempty"`
+ CodedWidth int `json:"coded_width,omitempty"`
+ CodedHeight int `json:"coded_height,omitempty"`
+ HasBFrames int `json:"has_b_frames,omitempty"`
+ SampleAspectRatio string `json:"sample_aspect_ratio,omitempty"`
+ DisplayAspectRatio string `json:"display_aspect_ratio,omitempty"`
+ PixFmt string `json:"pix_fmt,omitempty"`
+ Level int `json:"level,omitempty"`
+ ChromaLocation string `json:"chroma_location,omitempty"`
+ Refs int `json:"refs,omitempty"`
+ IsAvc string `json:"is_avc,omitempty"`
+ NalLengthSize string `json:"nal_length_size,omitempty"`
+ RFrameRate string `json:"r_frame_rate"`
+ AvgFrameRate string `json:"avg_frame_rate"`
+ TimeBase string `json:"time_base"`
+ StartPts int `json:"start_pts"`
+ StartTime string `json:"start_time"`
+ DurationTs int `json:"duration_ts"`
+ Duration string `json:"duration"`
+ BitRate string `json:"bit_rate"`
+ BitsPerRawSample string `json:"bits_per_raw_sample,omitempty"`
+ NbFrames string `json:"nb_frames"`
+ Disposition struct {
+ Default int `json:"default"`
+ Dub int `json:"dub"`
+ Original int `json:"original"`
+ Comment int `json:"comment"`
+ Lyrics int `json:"lyrics"`
+ Karaoke int `json:"karaoke"`
+ Forced int `json:"forced"`
+ HearingImpaired int `json:"hearing_impaired"`
+ VisualImpaired int `json:"visual_impaired"`
+ CleanEffects int `json:"clean_effects"`
+ AttachedPic int `json:"attached_pic"`
+ TimedThumbnails int `json:"timed_thumbnails"`
+ } `json:"disposition"`
+ Tags struct {
+ Language string `json:"language"`
+ HandlerName string `json:"handler_name"`
+ } `json:"tags"`
+ SampleFmt string `json:"sample_fmt,omitempty"`
+ SampleRate string `json:"sample_rate,omitempty"`
+ Channels int `json:"channels,omitempty"`
+ ChannelLayout string `json:"channel_layout,omitempty"`
+ BitsPerSample int `json:"bits_per_sample,omitempty"`
+ MaxBitRate string `json:"max_bit_rate,omitempty"`
+}
+
+type VideoFormat struct {
+ NbStreams int `json:"nb_streams"`
+ NbPrograms int `json:"nb_programs"`
+ FormatName string `json:"format_name"`
+ FormatLongName string `json:"format_long_name"`
+ StartTime string `json:"start_time"`
+ Duration string `json:"duration"`
+ Size string `json:"size"`
+ BitRate string `json:"bit_rate"`
+ ProbeScore int `json:"probe_score"`
+ Tags struct {
+ MajorBrand string `json:"major_brand"`
+ MinorVersion string `json:"minor_version"`
+ CompatibleBrands string `json:"compatible_brands"`
+ Encoder string `json:"encoder"`
+ Description string `json:"description"`
+ } `json:"tags"`
+}
+
+type VideoInfo struct {
+ Streams []VideoStreams `json:"streams"`
+ Format VideoFormat `json:"format"`
+}
diff --git a/utils/qiniu/video.go b/utils/qiniu/video.go
new file mode 100644
index 0000000..a9eb8cb
--- /dev/null
+++ b/utils/qiniu/video.go
@@ -0,0 +1,84 @@
+package qiniu
+
+import (
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/base64"
+ "encoding/json"
+ "io/ioutil"
+ "math"
+ "net/http"
+ "pure-admin/global"
+ "strconv"
+
+ "github.com/pili-engineering/pili-sdk-go.v2/pili"
+)
+
+// 计算token并加入Header
+func createAuthorization(req *http.Request, reqBody []byte) *http.Request {
+ var (
+ str string
+ hashed []byte
+ )
+
+ str = req.Method + " " + req.URL.Path
+ if req.URL.RawQuery != "" {
+ str += "?" + req.URL.RawQuery
+ }
+ str += "\nHost: " + req.Host
+ if contentType := req.Header.Get("Content-Type"); contentType != "" {
+ str += "\nContent-Type: " + contentType
+ }
+ str += "\n\n"
+ if len(reqBody) != 0 {
+ str += string(reqBody)
+ }
+ key := []byte(global.MG_CONFIG.Qiniu.SecretKey)
+ mac := hmac.New(sha1.New, key)
+ mac.Write([]byte(str))
+ hashed = mac.Sum(nil)
+ req.Header.Add("Authorization", "Qiniu "+global.MG_CONFIG.Qiniu.AccessKey+":"+base64.URLEncoding.EncodeToString(hashed))
+ return req
+}
+
+func getVideoInfo(url string) (result VideoInfo, err error) {
+ resp, err := http.Get(url + "?avinfo")
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ resBody, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ //fmt.Println(string(resBody))
+ global.MG_LOG.Info(string(resBody))
+ err = json.Unmarshal(resBody, &result)
+ return
+}
+
+func GetVideoLen(url string) int {
+ info, err := getVideoInfo(url)
+ if err != nil {
+ return 0
+ }
+ f1, err := strconv.ParseFloat(info.Format.Duration, 64)
+ if err != nil {
+ return 0
+ }
+ s1 := strconv.FormatFloat(math.Floor(f1), 'f', -1, 64)
+ i, _ := strconv.Atoi(s1)
+ return i
+}
+
+func ReturnUrl2(key, zone, domain string, start, end int64) (string, error) {
+ mac := &pili.MAC{AccessKey: global.MG_CONFIG.Qiniu.AccessKey, SecretKey: []byte(global.MG_CONFIG.Qiniu.SecretKey)}
+ client := pili.New(mac, nil)
+ hub := client.Hub(zone)
+ stream := hub.Stream(key)
+ fName, err := stream.Save(start, end)
+ if err != nil {
+ return "", err
+ }
+ return "http://" + domain + "/" + fName, nil
+}
diff --git a/utils/redis.go b/utils/redis.go
new file mode 100644
index 0000000..74cd062
--- /dev/null
+++ b/utils/redis.go
@@ -0,0 +1,319 @@
+package utils
+
+import (
+ "fmt"
+ "github.com/go-redis/redis"
+ "pure-admin/global"
+ "time"
+)
+
+// RedisGet ...
+func RedisGet(key string) string {
+ result, err := global.MG_REDIS.Get(key).Result()
+ if err != nil {
+ return ""
+ }
+ return result
+}
+
+func RedisSet(key string, value interface{}, expiration time.Duration) string {
+ result, err := global.MG_REDIS.Set(key, value, expiration).Result()
+ if err != nil {
+ return ""
+ }
+ return result
+}
+
+func RedisDel(key ...string) (int64, error) {
+ result, err := global.MG_REDIS.Del(key...).Result()
+ if err != nil {
+ return -1, err
+ }
+ return result, err
+}
+
+func RedisLLen(key string) (int64, error) {
+ result, err := global.MG_REDIS.LLen(key).Result()
+ if err != nil {
+ return -1, err
+ }
+ return result, err
+}
+
+// RedisHashSet 向key的hash中添加元素field的值
+func RedisHashSet(key, field string, data interface{}) error {
+ err := global.MG_REDIS.HSet(key, field, data).Err()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RedisBatchHashSet 批量向key的hash添加对应元素field的值
+func RedisBatchHashSet(key string, fields map[string]interface{}) (error, string) {
+ val, err := global.MG_REDIS.HMSet(key, fields).Result()
+ if err != nil {
+ return err, ""
+ }
+ return nil, val
+}
+
+func RedisHDel(client *redis.Client, key string, fields ...string) (int64, string) {
+ result, err := client.HDel(key, fields...).Result()
+ if err != nil {
+ return 0, ""
+ }
+ return result, err.Error()
+}
+
+func RedisHSet(key, field string, value interface{}) (error, bool) {
+ val, err := global.MG_REDIS.HSet(key, field, value).Result()
+ if err != nil {
+ return err, false
+ }
+ return nil, val
+}
+
+func RedisSetNX(key string, value interface{}, expiration time.Duration) (error, bool) {
+ val, err := global.MG_REDIS.SetNX(key, value, expiration).Result()
+ if err != nil {
+ return err, false
+ }
+ return nil, val
+}
+
+// RedisHashGet 通过key获取hash的元素值
+func RedisHashGet(key, field string) string {
+ val, err := global.MG_REDIS.HGet(key, field).Result()
+ if err != nil {
+ return ""
+ }
+ return val
+}
+
+func RedisHashMGet(key string, fields ...string) []interface{} {
+ val, err := global.MG_REDIS.HMGet(key, fields...).Result()
+ if err != nil {
+ return nil
+ }
+ return val
+}
+
+// HGetAll
+func RedisHashGetAll(key string) map[string]string {
+ val, err := global.MG_REDIS.HGetAll(key).Result()
+ if err != nil {
+ return nil
+ }
+ return val
+}
+
+// RedisBatchHashGet 批量获取key的hash中对应多元素值
+func RedisBatchHashGet(key string, fields ...string) map[string]interface{} {
+ resMap := make(map[string]interface{})
+ for _, field := range fields {
+ val, err := global.MG_REDIS.HGet(key, fmt.Sprintf("%s", field)).Result()
+ if err == nil && val != "" {
+ resMap[field] = val
+ }
+ }
+ return resMap
+}
+
+func RedisLPush(key string, values ...interface{}) (int64, error) {
+ result, err := global.MG_REDIS.LPush(key, values...).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisLPop(key string) (error, string) {
+ result, err := global.MG_REDIS.LPop(key).Result()
+ if err != nil {
+ return err, ""
+ }
+ return err, result
+}
+
+// RedisSAdd 无序集合添加
+func RedisSAdd(key string, values ...interface{}) (int64, error) {
+ result, err := global.MG_REDIS.SAdd(key, values...).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+// RedisSMembers 从无序集合中获取数据
+func RedisSMembers(key string) ([]string, error) {
+ val, err := global.MG_REDIS.SMembers(key).Result()
+ if err != nil {
+ return nil, err
+ }
+ return val, nil
+}
+
+// RedisZAdd 有序集合添加
+func RedisZAdd(key string, values map[float64]interface{}) (int64, error) {
+ var members []redis.Z
+ for scale, value := range values {
+ members = append(members, redis.Z{
+ Score: scale,
+ Member: value,
+ })
+ }
+ result, err := global.MG_REDIS.ZAdd(key, members...).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisZCard(key string) (int64, error) {
+ result, err := global.MG_REDIS.ZCard(key).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisZCount(key string, min, max string) (int64, error) {
+ result, err := global.MG_REDIS.ZCount(key, min, max).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisZRange(key string, start, stop int64, sort string) ([]interface{}, error) {
+ var val = make([]interface{}, 0)
+ if sort == "asc" { //scale 正序
+ result, err := global.MG_REDIS.ZRangeWithScores(key, start, stop).Result()
+ if err != nil {
+ return val, err
+ }
+ for _, z := range result {
+ val = append(val, z.Member)
+ }
+ } else { //scale 倒序
+ result, err := global.MG_REDIS.ZRevRange(key, start, stop).Result()
+ if err != nil {
+ return val, err
+ }
+ for _, z := range result {
+ val = append(val, z)
+ }
+ return val, nil
+ }
+
+ return val, nil
+}
+
+// RedisIncr 获取自增唯一ID
+func RedisIncr(key string) int {
+ val, err := global.MG_REDIS.Incr(key).Result()
+ if err != nil {
+ }
+ return int(val)
+}
+
+// RedisSetAdd 添加集合数据
+func RedisSetAdd(key, val string) {
+ global.MG_REDIS.SAdd(key, val)
+}
+
+func RedisRename(key, newKey string) (string, error) {
+ result, err := global.MG_REDIS.Rename(key, newKey).Result()
+ if err != nil {
+ return "", err
+ }
+ return result, nil
+}
+
+func RedisHashGet1(client *redis.Client, key, field string) string {
+ val, err := client.HGet(key, field).Result()
+ if err != nil {
+ return ""
+ }
+ return val
+}
+
+func RedisDel1(client *redis.Client, key ...string) (int64, error) {
+ result, err := client.Del(key...).Result()
+ if err != nil {
+ return -1, err
+ }
+ return result, err
+}
+
+func RedisBatchHashSet1(client *redis.Client, key string, fields map[string]interface{}) (error, string) {
+ val, err := client.HMSet(key, fields).Result()
+ if err != nil {
+ return err, ""
+ }
+ return nil, val
+}
+
+func RedisZAdd1(client *redis.Client, key string, values map[float64]interface{}) (int64, error) {
+ var members []redis.Z
+ for scale, value := range values {
+ members = append(members, redis.Z{
+ Score: scale,
+ Member: value,
+ })
+ }
+ result, err := client.ZAdd(key, members...).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisZRange1(client *redis.Client, key string, start, stop int64, sort string) ([]interface{}, error) {
+ var val = make([]interface{}, 0)
+ if sort == "asc" { //scale 正序
+ result, err := client.ZRangeWithScores(key, start, stop).Result()
+ if err != nil {
+ return val, err
+ }
+ for _, z := range result {
+ val = append(val, z.Member)
+ }
+ } else { //scale 倒序
+ result, err := client.ZRevRange(key, start, stop).Result()
+ if err != nil {
+ return val, err
+ }
+ for _, z := range result {
+ val = append(val, z)
+ }
+ return val, nil
+ }
+
+ return val, nil
+}
+
+func RedisZRemRangeByRank(client *redis.Client, key string, start, stop int64) error {
+ _, err := client.ZRemRangeByRank(key, start, stop).Result()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func RedisZCard1(client *redis.Client, key string) (int64, error) {
+ result, err := client.ZCard(key).Result()
+ if err != nil {
+ return 0, err
+ }
+ return result, nil
+}
+
+func RedisScan(client *redis.Client) ([]string, error) {
+ result, _, err := client.Scan(0, "", 0).Result()
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+}
diff --git a/utils/redis_store.go b/utils/redis_store.go
new file mode 100644
index 0000000..9c2a9e8
--- /dev/null
+++ b/utils/redis_store.go
@@ -0,0 +1,45 @@
+package utils
+
+import (
+ "fmt"
+ "pure-admin/global"
+ "strings"
+ "time"
+)
+
+const CAPTCHA = "captcha:"
+
+type RedisStore struct{}
+
+func (e RedisStore) Set(id, value string) error {
+ key := CAPTCHA + id
+ value = strings.ToLower(value)
+ err := global.MG_REDIS.Set(key, value, 2*time.Minute).Err()
+ if err != nil {
+ global.MG_LOG.Info(err.Error())
+ }
+ return nil
+}
+
+func (e RedisStore) Get(id string, clear bool) string {
+ key := CAPTCHA + id
+ val, err := global.MG_REDIS.Get(key).Result()
+ if err != nil {
+ fmt.Println(err)
+ return ""
+ }
+ if clear {
+ err := global.MG_REDIS.Del(key).Err()
+ if err != nil {
+ fmt.Println(err)
+ return ""
+ }
+ }
+ return val
+}
+
+func (e RedisStore) Verify(id, answer string, clear bool) bool {
+ v := RedisStore{}.Get(id, clear)
+ //fmt.Println("key:"+id+";value:"+v+";answer:"+answer)
+ return v == answer
+}
diff --git a/utils/reload.go b/utils/reload.go
new file mode 100644
index 0000000..de5499b
--- /dev/null
+++ b/utils/reload.go
@@ -0,0 +1,18 @@
+package utils
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "runtime"
+ "strconv"
+)
+
+func Reload() error {
+ if runtime.GOOS == "windows" {
+ return errors.New("系统不支持")
+ }
+ pid := os.Getpid()
+ cmd := exec.Command("kill", "-1", strconv.Itoa(pid))
+ return cmd.Run()
+}
diff --git a/utils/rotatelogs_unix.go b/utils/rotatelogs_unix.go
new file mode 100644
index 0000000..4ca851d
--- /dev/null
+++ b/utils/rotatelogs_unix.go
@@ -0,0 +1,30 @@
+// +build !windows
+
+package utils
+
+import (
+ "os"
+ "path"
+ "pure-admin/global"
+ "time"
+
+ zaprotatelogs "github.com/lestrrat-go/file-rotatelogs"
+ "go.uber.org/zap/zapcore"
+)
+
+//@function: GetWriteSyncer
+//@description: zap logger中加入file-rotatelogs
+//@return: zapcore.WriteSyncer, error
+
+func GetWriteSyncer() (zapcore.WriteSyncer, error) {
+ fileWriter, err := zaprotatelogs.New(
+ path.Join(global.MG_CONFIG.Zap.Director, "%Y-%m-%d.log"),
+ zaprotatelogs.WithLinkName(global.MG_CONFIG.Zap.LinkName),
+ zaprotatelogs.WithMaxAge(7*24*time.Hour),
+ zaprotatelogs.WithRotationTime(24*time.Hour),
+ )
+ if global.MG_CONFIG.Zap.LogInConsole {
+ return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err
+ }
+ return zapcore.AddSync(fileWriter), err
+}
diff --git a/utils/rotatelogs_windows.go b/utils/rotatelogs_windows.go
new file mode 100644
index 0000000..18d6e7e
--- /dev/null
+++ b/utils/rotatelogs_windows.go
@@ -0,0 +1,26 @@
+package utils
+
+import (
+ zaprotatelogs "github.com/lestrrat-go/file-rotatelogs"
+ "go.uber.org/zap/zapcore"
+ "os"
+ "path"
+ "pure-admin/global"
+ "time"
+)
+
+//@function: GetWriteSyncer
+//@description: zap logger中加入file-rotatelogs
+//@return: zapcore.WriteSyncer, error
+
+func GetWriteSyncer() (zapcore.WriteSyncer, error) {
+ fileWriter, err := zaprotatelogs.New(
+ path.Join(global.MG_CONFIG.Zap.Director, "%Y-%m-%d.log"),
+ zaprotatelogs.WithMaxAge(7*24*time.Hour),
+ zaprotatelogs.WithRotationTime(24*time.Hour),
+ )
+ if global.MG_CONFIG.Zap.LogInConsole {
+ return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), err
+ }
+ return zapcore.AddSync(fileWriter), err
+}
diff --git a/utils/sendSms.go b/utils/sendSms.go
new file mode 100644
index 0000000..9032ade
--- /dev/null
+++ b/utils/sendSms.go
@@ -0,0 +1,172 @@
+package utils
+
+import (
+ "crypto/hmac"
+ "crypto/sha1"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const sortQueryStringFmt string = "AccessKeyId=%s" +
+ "&Action=SendSms" +
+ "&Format=JSON" +
+ "&OutId=123" +
+ "&PhoneNumbers=%s" +
+ "&RegionId=cn-hangzhou" +
+ "&SignName=%s" +
+ "&SignatureMethod=HMAC-SHA1" +
+ "&SignatureNonce=%s" +
+ "&SignatureVersion=1.0" +
+ "&TemplateCode=%s" +
+ "&TemplateParam=%s" +
+ "&Timestamp=%s" +
+ "&Version=2017-05-25"
+
+func encodeLocal(encodeStr string) string {
+ urlEncode := url.QueryEscape(encodeStr)
+ urlEncode = strings.Replace(urlEncode, "+", "%%20", -1)
+ urlEncode = strings.Replace(urlEncode, "*", "%2A", -1)
+ urlEncode = strings.Replace(urlEncode, "%%7E", "~", -1)
+ urlEncode = strings.Replace(urlEncode, "/", "%%2F", -1)
+ return urlEncode
+}
+
+// SendSmsReply 发送短信返回
+type SendSmsReply struct {
+ Code string `json:"Code,omitempty"`
+ Message string `json:"Message,omitempty"`
+}
+
+func SendSms(phone string, paramStr string, signName string, templateCode string) error {
+ const token string = "IP2dC1XDM15A6mCt3yTRDk3ZWHri19&" // 阿里云 accessSecret 注意这个地方要添加一个 &
+
+ AccessKeyId := "LTAI5t6oHZUFW5bqNTbDwd5n" // 自己的阿里云 accessKeyID
+
+ SignatureNonce := strconv.Itoa(GenerateRandNumByCount(9))
+ TemplateParam := url.QueryEscape(paramStr)
+ Timestamp := url.QueryEscape(time.Now().UTC().Format("2006-01-02T15:04:05Z"))
+
+ sortQueryString := fmt.Sprintf(sortQueryStringFmt,
+ AccessKeyId,
+ phone,
+ url.QueryEscape(signName),
+ SignatureNonce,
+ templateCode,
+ TemplateParam,
+ Timestamp,
+ )
+
+ urlencode := encodeLocal(sortQueryString)
+ sign_str := fmt.Sprintf("GET&%%2F&%s", urlencode)
+
+ key := []byte(token)
+ mac := hmac.New(sha1.New, key)
+ mac.Write([]byte(sign_str))
+ signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
+ signature = encodeLocal(signature)
+ resp, err := http.Get("http://dysmsapi.aliyuncs.com/?Signature=" + signature + "&" + sortQueryString)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+
+ ssr := &SendSmsReply{}
+
+ if err := json.Unmarshal(body, ssr); err != nil {
+ return err
+ }
+
+ if ssr.Code == "SignatureNonceUsed" {
+ return SendSms(phone, paramStr, signName, templateCode)
+ } else if ssr.Code != "OK" {
+ return errors.New(ssr.Code)
+ }
+ return nil
+}
+
+func GenerateRandNumByCount(count int) int {
+ rand.Seed(time.Now().Unix())
+ rnd := rand.Intn(9*int(exponent(10, uint64(count-1)))-1) + int(exponent(10, uint64(count-1)))
+ return rnd
+}
+
+func exponent(a, n uint64) uint64 {
+ result := uint64(1)
+ for i := n; i > 0; i >>= 1 {
+ if i&1 != 0 {
+ result *= a
+ }
+ a *= a
+ }
+ return result
+}
+
+func SendMessageNotice(phone string, content string, signName string, templateCode string) {
+ const token string = "IXvbHeyNLAWJHC08mg4quGYQtmf84h&" // 阿里云 accessSecret 注意这个地方要添加一个 &
+
+ AccessKeyId := "LTAIZSh7LB8zkmtO" // 自己的阿里云 accessKeyID
+
+ SignatureNonce := strconv.Itoa(GenerateRandNumByCount(9))
+ //TemplateCode := "SMS_140625198"
+ TemplateParam := url.QueryEscape("{\"content\":\"" + content + "\"}")
+ Timestamp := url.QueryEscape(time.Now().UTC().Format("2006-01-02T15:04:05Z"))
+
+ sortQueryString := fmt.Sprintf(sortQueryStringFmt,
+ AccessKeyId,
+ phone,
+ url.QueryEscape(signName),
+ SignatureNonce,
+ templateCode,
+ TemplateParam,
+ Timestamp,
+ )
+
+ urlEncode := encodeLocal(sortQueryString)
+ signStr := fmt.Sprintf("GET&%%2F&%s", urlEncode)
+
+ key := []byte(token)
+ mac := hmac.New(sha1.New, key)
+ mac.Write([]byte(signStr))
+ signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
+ signature = encodeLocal(signature)
+ resp, err := http.Get("http://dysmsapi.aliyuncs.com/?Signature=" + signature + "&" + sortQueryString)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ ssr := &SendSmsReply{}
+
+ if err := json.Unmarshal(body, ssr); err != nil {
+ return
+ }
+
+ if ssr.Code == "SignatureNonceUsed" {
+ SendMessageNotice(phone, content, signName, templateCode)
+ return
+ } else if ssr.Code != "OK" {
+ return
+ }
+ return
+}
diff --git a/utils/string.go b/utils/string.go
new file mode 100644
index 0000000..4fec986
--- /dev/null
+++ b/utils/string.go
@@ -0,0 +1,110 @@
+package utils
+
+import (
+ "math"
+ "math/rand"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+)
+
+func GetInvitation() string {
+ var bs = make([]byte, 6)
+ for i := 0; i < 6; i++ {
+ rand.Seed(time.Now().UnixNano())
+ flag := rand.Intn(2)
+ if flag == 1 {
+ bs[i] = byte(rand.Float64()*10 + 48)
+ } else {
+ bs[i] = byte(rand.Float64()*26 + 65)
+ }
+ }
+ return string(bs)
+}
+
+func GetInvitationLen(length int) string {
+ var bs = make([]byte, length)
+ for i := 0; i < length; i++ {
+ rand.Seed(time.Now().UnixNano())
+ flag := rand.Intn(3)
+ if flag == 1 {
+ bs[i] = byte(rand.Float64()*10 + 48)
+ } else if flag == 0 {
+ bs[i] = byte(rand.Float64()*26 + 65)
+ } else {
+ bs[i] = byte(rand.Float64()*26 + 97)
+ }
+ }
+ return string(bs)
+}
+
+func GetInvitationLowerLen(length int) string {
+ var bs = make([]byte, length)
+ for i := 0; i < length; i++ {
+ rand.Seed(time.Now().UnixNano())
+ flag := rand.Intn(2)
+ if flag == 1 {
+ bs[i] = byte(rand.Float64()*10 + 48)
+ } else {
+ bs[i] = byte(rand.Float64()*26 + 97)
+ }
+ }
+ return string(bs)
+}
+
+func StringFloat64ToInt(s string) int {
+ f1, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return 0
+ }
+ s1 := strconv.FormatFloat(math.Ceil(f1), 'f', -1, 64)
+ i, _ := strconv.Atoi(s1)
+ return i
+}
+
+func HideStar(str string) (result string) {
+ if str == "" {
+ return "***"
+ }
+ if strings.Contains(str, "@") {
+ // 邮箱
+ res := strings.Split(str, "@")
+ if len(res[0]) < 3 {
+ resString := "***"
+ result = resString + "@" + res[1]
+ } else {
+ res2 := Substr2(str, 0, 3)
+ resString := res2 + "***"
+ result = resString + "@" + res[1]
+ }
+ return result
+ } else {
+ reg := `^1[0-9]\d{9}$`
+ rgx := regexp.MustCompile(reg)
+ mobileMatch := rgx.MatchString(str)
+ if mobileMatch {
+ // 手机号
+ result = Substr2(str, 0, 3) + "****" + Substr2(str, 7, 11)
+ } else {
+ nameRune := []rune(str)
+ lens := len(nameRune)
+ if lens <= 1 {
+ result = "***"
+ } else if lens == 2 {
+ result = string(nameRune[:1]) + "*"
+ } else if lens == 3 {
+ result = string(nameRune[:1]) + "*" + string(nameRune[2:3])
+ } else if lens == 4 {
+ result = string(nameRune[:1]) + "**" + string(nameRune[lens-1:lens])
+ } else if lens > 4 {
+ result = string(nameRune[:2]) + "***" + string(nameRune[lens-2:lens])
+ }
+ }
+ return
+ }
+}
+func Substr2(str string, start int, end int) string {
+ rs := []rune(str)
+ return string(rs[start:end])
+}
diff --git a/utils/time.go b/utils/time.go
new file mode 100644
index 0000000..ed95fd7
--- /dev/null
+++ b/utils/time.go
@@ -0,0 +1,14 @@
+package utils
+
+import "time"
+
+const (
+ DateTimeFormat string = "2006-01-02 15:04:05"
+ DateTimeFormat2 string = "2006-01-02 15:04"
+ DateFormat string = "2006-01-02"
+ Continuity = "20060102150405"
+)
+
+func GetCurrentTime() time.Time {
+ return time.Now()
+}
diff --git a/utils/timer/timed_task.go b/utils/timer/timed_task.go
new file mode 100644
index 0000000..881fdde
--- /dev/null
+++ b/utils/timer/timed_task.go
@@ -0,0 +1,109 @@
+package timer
+
+import (
+ "sync"
+
+ "github.com/robfig/cron/v3"
+)
+
+type Timer interface {
+ AddTaskByFunc(taskName string, spec string, task func()) (cron.EntryID, error)
+ AddTaskByJob(taskName string, spec string, job interface{ Run() }) (cron.EntryID, error)
+ FindCron(taskName string) (*cron.Cron, bool)
+ StartTask(taskName string)
+ StopTask(taskName string)
+ Remove(taskName string, id int)
+ Clear(taskName string)
+ Close()
+}
+
+// timer 定时任务管理
+type timer struct {
+ taskList map[string]*cron.Cron
+ sync.Mutex
+}
+
+// AddTaskByFunc 通过函数的方法添加任务
+func (t *timer) AddTaskByFunc(taskName string, spec string, task func()) (cron.EntryID, error) {
+ t.Lock()
+ defer t.Unlock()
+ if _, ok := t.taskList[taskName]; !ok {
+ t.taskList[taskName] = cron.New()
+ }
+ id, err := t.taskList[taskName].AddFunc(spec, task)
+ t.taskList[taskName].Start()
+ return id, err
+}
+
+// AddTaskByJob 通过接口的方法添加任务
+func (t *timer) AddTaskByJob(taskName string, spec string, job interface{ Run() }) (cron.EntryID, error) {
+ t.Lock()
+ defer t.Unlock()
+ if _, ok := t.taskList[taskName]; !ok {
+ t.taskList[taskName] = cron.New()
+ }
+ id, err := t.taskList[taskName].AddJob(spec, job)
+ t.taskList[taskName].Start()
+ return id, err
+}
+
+// FindCron 获取对应taskName的cron 可能会为空
+func (t *timer) FindCron(taskName string) (*cron.Cron, bool) {
+ t.Lock()
+ defer t.Unlock()
+ v, ok := t.taskList[taskName]
+ return v, ok
+}
+
+// StartTask 开始任务
+func (t *timer) StartTask(taskName string) {
+ t.Lock()
+ defer t.Unlock()
+ if v, ok := t.taskList[taskName]; ok {
+ v.Start()
+ }
+ return
+}
+
+// StopTask 停止任务
+func (t *timer) StopTask(taskName string) {
+ t.Lock()
+ defer t.Unlock()
+ if v, ok := t.taskList[taskName]; ok {
+ v.Stop()
+ }
+ return
+}
+
+// Remove 从taskName 删除指定任务
+func (t *timer) Remove(taskName string, id int) {
+ t.Lock()
+ defer t.Unlock()
+ if v, ok := t.taskList[taskName]; ok {
+ v.Remove(cron.EntryID(id))
+ }
+ return
+}
+
+// Clear 清除任务
+func (t *timer) Clear(taskName string) {
+ t.Lock()
+ defer t.Unlock()
+ if v, ok := t.taskList[taskName]; ok {
+ v.Stop()
+ delete(t.taskList, taskName)
+ }
+}
+
+// Close 释放资源
+func (t *timer) Close() {
+ t.Lock()
+ defer t.Unlock()
+ for _, v := range t.taskList {
+ v.Stop()
+ }
+}
+
+func NewTimerTask() Timer {
+ return &timer{taskList: make(map[string]*cron.Cron)}
+}
diff --git a/utils/upload/aliyun_oss.go b/utils/upload/aliyun_oss.go
new file mode 100644
index 0000000..011de90
--- /dev/null
+++ b/utils/upload/aliyun_oss.go
@@ -0,0 +1,75 @@
+package upload
+
+import (
+ "errors"
+ "mime/multipart"
+ "path/filepath"
+ "pure-admin/global"
+ "time"
+
+ "github.com/aliyun/aliyun-oss-go-sdk/oss"
+ "go.uber.org/zap"
+)
+
+type AliyunOSS struct{}
+
+func (*AliyunOSS) UploadFile(file *multipart.FileHeader) (string, string, error) {
+ bucket, err := NewBucket()
+ if err != nil {
+ global.MG_LOG.Error("function AliyunOSS.NewBucket() Failed", zap.Any("err", err.Error()))
+ return "", "", errors.New("function AliyunOSS.NewBucket() Failed, err:" + err.Error())
+ }
+
+ // 读取本地文件。
+ f, openError := file.Open()
+ if openError != nil {
+ global.MG_LOG.Error("function file.Open() Failed", zap.Any("err", openError.Error()))
+ return "", "", errors.New("function file.Open() Failed, err:" + openError.Error())
+ }
+ defer f.Close() // 创建文件 defer 关闭
+ // 上传阿里云路径 文件名格式 自己可以改 建议保证唯一性
+ yunFileTmpPath := filepath.Join("uploads", time.Now().Format("2006-01-02")) + "/" + file.Filename
+
+ // 上传文件流。
+ err = bucket.PutObject(yunFileTmpPath, f)
+ if err != nil {
+ global.MG_LOG.Error("function formUploader.Put() Failed", zap.Any("err", err.Error()))
+ return "", "", errors.New("function formUploader.Put() Failed, err:" + err.Error())
+ }
+
+ return global.MG_CONFIG.AliyunOSS.BucketUrl + "/" + yunFileTmpPath, yunFileTmpPath, nil
+}
+
+func (*AliyunOSS) DeleteFile(key string) error {
+ bucket, err := NewBucket()
+ if err != nil {
+ global.MG_LOG.Error("function AliyunOSS.NewBucket() Failed", zap.Any("err", err.Error()))
+ return errors.New("function AliyunOSS.NewBucket() Failed, err:" + err.Error())
+ }
+
+ // 删除单个文件。objectName表示删除OSS文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
+ // 如需删除文件夹,请将objectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。
+ err = bucket.DeleteObject(key)
+ if err != nil {
+ global.MG_LOG.Error("function bucketManager.Delete() Filed", zap.Any("err", err.Error()))
+ return errors.New("function bucketManager.Delete() Filed, err:" + err.Error())
+ }
+
+ return nil
+}
+
+func NewBucket() (*oss.Bucket, error) {
+ // 创建OSSClient实例。
+ client, err := oss.New(global.MG_CONFIG.AliyunOSS.Endpoint, global.MG_CONFIG.AliyunOSS.AccessKeyId, global.MG_CONFIG.AliyunOSS.AccessKeySecret)
+ if err != nil {
+ return nil, err
+ }
+
+ // 获取存储空间。
+ bucket, err := client.Bucket(global.MG_CONFIG.AliyunOSS.BucketName)
+ if err != nil {
+ return nil, err
+ }
+
+ return bucket, nil
+}
diff --git a/utils/upload/local.go b/utils/upload/local.go
new file mode 100644
index 0000000..6e7a6b6
--- /dev/null
+++ b/utils/upload/local.go
@@ -0,0 +1,79 @@
+package upload
+
+import (
+ "errors"
+ "io"
+ "mime/multipart"
+ "os"
+ "path"
+ "pure-admin/global"
+ "pure-admin/utils"
+ "strings"
+ "time"
+
+ "go.uber.org/zap"
+)
+
+type Local struct{}
+
+//@object: *Local
+//@function: UploadFile
+//@description: 上传文件
+//@param: file *multipart.FileHeader
+//@return: string, string, error
+
+func (*Local) UploadFile(file *multipart.FileHeader) (string, string, error) {
+ // 读取文件后缀
+ ext := path.Ext(file.Filename)
+ // 读取文件名并加密
+ name := strings.TrimSuffix(file.Filename, ext)
+ name = utils.MD5V([]byte(name))
+ // 拼接新文件名
+ filename := name + "_" + time.Now().Format("20060102150405") + ext
+ // 尝试创建此路径
+ mkdirErr := os.MkdirAll(global.MG_CONFIG.Local.Path, os.ModePerm)
+ if mkdirErr != nil {
+ global.MG_LOG.Error("function os.MkdirAll() Filed", zap.Any("err", mkdirErr.Error()))
+ return "", "", errors.New("function os.MkdirAll() Filed, err:" + mkdirErr.Error())
+ }
+ // 拼接路径和文件名
+ p := global.MG_CONFIG.Local.Path + "/" + filename
+
+ f, openError := file.Open() // 读取文件
+ if openError != nil {
+ global.MG_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))
+ return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())
+ }
+ defer f.Close() // 创建文件 defer 关闭
+
+ out, createErr := os.Create(p)
+ if createErr != nil {
+ global.MG_LOG.Error("function os.Create() Filed", zap.Any("err", createErr.Error()))
+
+ return "", "", errors.New("function os.Create() Filed, err:" + createErr.Error())
+ }
+ defer out.Close() // 创建文件 defer 关闭
+
+ _, copyErr := io.Copy(out, f) // 传输(拷贝)文件
+ if copyErr != nil {
+ global.MG_LOG.Error("function io.Copy() Filed", zap.Any("err", copyErr.Error()))
+ return "", "", errors.New("function io.Copy() Filed, err:" + copyErr.Error())
+ }
+ return p, filename, nil
+}
+
+//@object: *Local
+//@function: DeleteFile
+//@description: 删除文件
+//@param: key string
+//@return: error
+
+func (*Local) DeleteFile(key string) error {
+ p := global.MG_CONFIG.Local.Path + "/" + key
+ if strings.Contains(p, global.MG_CONFIG.Local.Path) {
+ if err := os.Remove(p); err != nil {
+ return errors.New("本地文件删除失败, err:" + err.Error())
+ }
+ }
+ return nil
+}
diff --git a/utils/upload/minio.go b/utils/upload/minio.go
new file mode 100644
index 0000000..830bfee
--- /dev/null
+++ b/utils/upload/minio.go
@@ -0,0 +1,123 @@
+package upload
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "strings"
+)
+
+const (
+ BaseUrl = "http://file-upload-test.mangguonews.com/upload"
+ ImgSuffix = "jpg,jpeg,png,gif,tif,tiff,bmp,"
+ Type = "graph,video,doc,"
+)
+
+type Response struct {
+ Data struct {
+ File string `json:"file"`
+ Hash string `json:"hash"`
+ ScaleImage string `json:"scaleImage"`
+ UploadFileName string `json:"uploadFileName"`
+ BaseUrl string `json:"baseUrl"` //给前端返回另加的参数
+ } `json:"data"`
+ Message struct {
+ Desc string `json:"desc"`
+ Error interface{} `json:"error"`
+ } `json:"message"`
+ Status bool `json:"status"`
+}
+
+type ParamsUpload struct {
+ File *multipart.FileHeader `form:"file"` //文件
+ Type string `form:"type"` //类型 img:图片
+ ParamsGet
+}
+
+type ParamsGet struct {
+ Width string `form:"width"`
+ Height string `form:"height"`
+ Scale string `form:"scale"`
+}
+
+func createMgNewsUploadRequest(file multipart.File, params map[string]string, fileName string) (*http.Request, error) {
+ var (
+ err error
+ body *bytes.Buffer
+ w *multipart.Writer
+ writer io.Writer
+ req *http.Request
+ contentType string
+ )
+ body = &bytes.Buffer{}
+ w = multipart.NewWriter(body)
+ contentType = w.FormDataContentType()
+ for key, val := range params {
+ _ = w.WriteField(key, val)
+ }
+ if file != nil {
+ writer, err = w.CreateFormFile("file", fileName)
+ _, err = io.Copy(writer, file)
+ if err != nil {
+ return nil, err
+ }
+ }
+ w.Close()
+ req, err = http.NewRequest(http.MethodPost, BaseUrl, body)
+ req.Header.Set("Content-Type", contentType)
+ return req, err
+}
+
+func MgNewsUpload(file multipart.File, pm map[string]string, fileName string) (respData Response, err error) {
+ var (
+ client *http.Client
+ req *http.Request
+ resp *http.Response
+ resBody []byte
+ )
+ client = &http.Client{}
+ req, err = createMgNewsUploadRequest(file, pm, fileName)
+ if err != nil {
+ return
+ }
+ resp, _ = client.Do(req)
+ defer resp.Body.Close()
+ resBody, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ if resp.StatusCode != 200 {
+ err = errors.New("上传失败")
+ return
+ }
+ //fmt.Println(string(resBody))
+ err = json.Unmarshal(resBody, &respData)
+ if err != nil {
+ return
+ }
+ return
+}
+
+func CheckType(t string) bool {
+ if strings.Index(Type, strings.ToLower(t)+",") >= 0 {
+ return true
+ }
+ return false
+}
+
+func CheckFileSuffix(fileName string, t string) bool {
+ var suffix string
+ suffix = fileName[(strings.LastIndex(fileName, "."))+1:]
+ if t == "graph" {
+ if strings.Index(ImgSuffix, strings.ToLower(suffix)+",") >= 0 {
+ return true
+ }
+ } else {
+ return false
+ }
+ return false
+}
diff --git a/utils/upload/qiniu.go b/utils/upload/qiniu.go
new file mode 100644
index 0000000..7eaa5c5
--- /dev/null
+++ b/utils/upload/qiniu.go
@@ -0,0 +1,114 @@
+package upload
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "mime/multipart"
+ "pure-admin/global"
+ "time"
+
+ "github.com/qiniu/api.v7/v7/auth/qbox"
+ "github.com/qiniu/api.v7/v7/storage"
+ "go.uber.org/zap"
+)
+
+type Qiniu struct{}
+
+//@object: *Qiniu
+//@function: UploadFile
+//@description: 上传文件
+//@param: file *multipart.FileHeader
+//@return: string, string, error
+
+func (*Qiniu) UploadFile(file *multipart.FileHeader) (string, string, error) {
+ putPolicy := storage.PutPolicy{Scope: global.MG_CONFIG.Qiniu.Bucket}
+ mac := qbox.NewMac(global.MG_CONFIG.Qiniu.AccessKey, global.MG_CONFIG.Qiniu.SecretKey)
+ upToken := putPolicy.UploadToken(mac)
+ cfg := qiniuConfig()
+ formUploader := storage.NewFormUploader(cfg)
+ ret := storage.PutRet{}
+ putExtra := storage.PutExtra{Params: map[string]string{"x:name": "github logo"}}
+
+ f, openError := file.Open()
+ if openError != nil {
+ global.MG_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))
+
+ return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())
+ }
+ defer f.Close() // 创建文件 defer 关闭
+ fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename) // 文件名格式 自己可以改 建议保证唯一性
+ putErr := formUploader.Put(context.Background(), &ret, upToken, fileKey, f, file.Size, &putExtra)
+ if putErr != nil {
+ global.MG_LOG.Error("function formUploader.Put() Filed", zap.Any("err", putErr.Error()))
+ return "", "", errors.New("function formUploader.Put() Filed, err:" + putErr.Error())
+ }
+ return global.MG_CONFIG.Qiniu.ImgPath + "/" + ret.Key, ret.Key, nil
+}
+
+func (*Qiniu) UploadFile2(fileKey string, file *multipart.FileHeader) (string, string, error) {
+ putPolicy := storage.PutPolicy{Scope: global.MG_CONFIG.Qiniu.Bucket}
+ mac := qbox.NewMac(global.MG_CONFIG.Qiniu.AccessKey, global.MG_CONFIG.Qiniu.SecretKey)
+ upToken := putPolicy.UploadToken(mac)
+ cfg := qiniuConfig()
+ formUploader := storage.NewFormUploader(cfg)
+ ret := storage.PutRet{}
+ putExtra := storage.PutExtra{Params: map[string]string{"x:name": "github logo"}}
+
+ f, openError := file.Open()
+ if openError != nil {
+ global.MG_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))
+
+ return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())
+ }
+ defer f.Close() // 创建文件 defer 关闭
+ //fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename) // 文件名格式 自己可以改 建议保证唯一性
+ putErr := formUploader.Put(context.Background(), &ret, upToken, fileKey, f, file.Size, &putExtra)
+ if putErr != nil {
+ global.MG_LOG.Error("function formUploader.Put() Filed", zap.Any("err", putErr.Error()))
+ return "", "", errors.New("function formUploader.Put() Filed, err:" + putErr.Error())
+ }
+ return global.MG_CONFIG.Qiniu.ImgPath + "/" + ret.Key, ret.Key, nil
+}
+
+//@object: *Qiniu
+//@function: DeleteFile
+//@description: 删除文件
+//@param: key string
+//@return: error
+
+func (*Qiniu) DeleteFile(key string) error {
+ mac := qbox.NewMac(global.MG_CONFIG.Qiniu.AccessKey, global.MG_CONFIG.Qiniu.SecretKey)
+ cfg := qiniuConfig()
+ bucketManager := storage.NewBucketManager(mac, cfg)
+ if err := bucketManager.Delete(global.MG_CONFIG.Qiniu.Bucket, key); err != nil {
+ global.MG_LOG.Error("function bucketManager.Delete() Filed", zap.Any("err", err.Error()))
+ return errors.New("function bucketManager.Delete() Filed, err:" + err.Error())
+ }
+ return nil
+}
+
+//@object: *Qiniu
+//@function: qiniuConfig
+//@description: 根据配置文件进行返回七牛云的配置
+//@return: *storage.Config
+
+func qiniuConfig() *storage.Config {
+ cfg := storage.Config{
+ UseHTTPS: global.MG_CONFIG.Qiniu.UseHTTPS,
+ UseCdnDomains: global.MG_CONFIG.Qiniu.UseCdnDomains,
+ }
+ switch global.MG_CONFIG.Qiniu.Zone { // 根据配置文件进行初始化空间对应的机房
+ case "ZoneHuadong":
+ cfg.Zone = &storage.ZoneHuadong
+ case "ZoneHuabei":
+ cfg.Zone = &storage.ZoneHuabei
+ case "ZoneHuanan":
+ cfg.Zone = &storage.ZoneHuanan
+ case "ZoneBeimei":
+ cfg.Zone = &storage.ZoneBeimei
+ case "ZoneXinjiapo":
+ cfg.Zone = &storage.ZoneXinjiapo
+ }
+ return &cfg
+}
diff --git a/utils/upload/tencent_cos.go b/utils/upload/tencent_cos.go
new file mode 100644
index 0000000..dd4a3fb
--- /dev/null
+++ b/utils/upload/tencent_cos.go
@@ -0,0 +1,60 @@
+package upload
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "pure-admin/global"
+ "time"
+
+ "github.com/tencentyun/cos-go-sdk-v5"
+ "go.uber.org/zap"
+)
+
+type TencentCOS struct{}
+
+// UploadFile upload file to COS
+func (*TencentCOS) UploadFile(file *multipart.FileHeader) (string, string, error) {
+ client := NewClient()
+ f, openError := file.Open()
+ if openError != nil {
+ global.MG_LOG.Error("function file.Open() Filed", zap.Any("err", openError.Error()))
+ return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())
+ }
+ defer f.Close() // 创建文件 defer 关闭
+ fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename)
+
+ _, err := client.Object.Put(context.Background(), global.MG_CONFIG.TencentCOS.PathPrefix+"/"+fileKey, f, nil)
+ if err != nil {
+ panic(err)
+ }
+ return global.MG_CONFIG.TencentCOS.BaseURL + "/" + global.MG_CONFIG.TencentCOS.PathPrefix + "/" + fileKey, fileKey, nil
+}
+
+// DeleteFile delete file form COS
+func (*TencentCOS) DeleteFile(key string) error {
+ client := NewClient()
+ name := global.MG_CONFIG.TencentCOS.PathPrefix + "/" + key
+ _, err := client.Object.Delete(context.Background(), name)
+ if err != nil {
+ global.MG_LOG.Error("function bucketManager.Delete() Filed", zap.Any("err", err.Error()))
+ return errors.New("function bucketManager.Delete() Filed, err:" + err.Error())
+ }
+ return nil
+}
+
+// NewClient init COS client
+func NewClient() *cos.Client {
+ urlStr, _ := url.Parse("https://" + global.MG_CONFIG.TencentCOS.Bucket + ".cos." + global.MG_CONFIG.TencentCOS.Region + ".myqcloud.com")
+ baseURL := &cos.BaseURL{BucketURL: urlStr}
+ client := cos.NewClient(baseURL, &http.Client{
+ Transport: &cos.AuthorizationTransport{
+ SecretID: global.MG_CONFIG.TencentCOS.SecretID,
+ SecretKey: global.MG_CONFIG.TencentCOS.SecretKey,
+ },
+ })
+ return client
+}
diff --git a/utils/upload/upload.go b/utils/upload/upload.go
new file mode 100644
index 0000000..19ae55c
--- /dev/null
+++ b/utils/upload/upload.go
@@ -0,0 +1,34 @@
+package upload
+
+import (
+ "mime/multipart"
+ "pure-admin/global"
+)
+
+//@interface_name: OSS
+//@description: OSS接口
+
+type OSS interface {
+ UploadFile(file *multipart.FileHeader) (string, string, error)
+ DeleteFile(key string) error
+}
+
+//@function: NewOss
+//@description: OSS接口
+//@description: OSS的实例化方法
+//@return: OSS
+
+func NewOss() OSS {
+ switch global.MG_CONFIG.System.OssType {
+ case "local":
+ return &Local{}
+ case "qiniu":
+ return &Qiniu{}
+ case "tencent-cos":
+ return &TencentCOS{}
+ case "aliyun-oss":
+ return &AliyunOSS{}
+ default:
+ return &Local{}
+ }
+}
diff --git a/utils/validator.go b/utils/validator.go
new file mode 100644
index 0000000..26eabd9
--- /dev/null
+++ b/utils/validator.go
@@ -0,0 +1,368 @@
+package utils
+
+import (
+ "bytes"
+ "errors"
+ "pure-admin/model/request"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+type Rules map[string][]string
+
+type RulesMap map[string]Rules
+
+var CustomizeMap = make(map[string]Rules)
+
+//@function: RegisterRule
+//@description: 注册自定义规则方案建议在路由初始化层即注册
+//@param: key string, rule Rules
+//@return: err error
+
+func RegisterRule(key string, rule Rules) (err error) {
+ if CustomizeMap[key] != nil {
+ return errors.New(key + "已注册,无法重复注册")
+ } else {
+ CustomizeMap[key] = rule
+ return nil
+ }
+}
+
+//@function: NotEmpty
+//@description: 非空 不能为其对应类型的0值
+//@return: string
+
+func NotEmpty() string {
+ return "notEmpty"
+}
+
+//@function: Lt
+//@description: 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+//@param: mark string
+//@return: string
+
+func Lt(mark string) string {
+ return "lt=" + mark
+}
+
+//@function: Le
+//@description: 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+//@param: mark string
+//@return: string
+
+func Le(mark string) string {
+ return "le=" + mark
+}
+
+//@function: Eq
+//@description: 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+//@param: mark string
+//@return: string
+
+func Eq(mark string) string {
+ return "eq=" + mark
+}
+
+//@function: Ne
+//@description: 不等于入参(!=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+//@param: mark string
+//@return: string
+
+func Ne(mark string) string {
+ return "ne=" + mark
+}
+
+//@function: Ge
+//@description: 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+//@param: mark string
+//@return: string
+
+func Ge(mark string) string {
+ return "ge=" + mark
+}
+
+//@function: Gt
+//@description: 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+//@param: mark string
+//@return: string
+
+func Gt(mark string) string {
+ return "gt=" + mark
+}
+
+func Ct(marks ...string) string {
+ var result bytes.Buffer
+ for _, value := range marks {
+ result.WriteString(value + ",")
+ }
+ return "ct=" + result.String()
+}
+
+//@function: Verify
+//@description: 校验方法
+//@param: st interface{}, roleMap Rules(入参实例,规则map)
+//@return: err error
+
+func Verify(st interface{}, roleMap Rules) (err error) {
+ compareMap := map[string]bool{
+ "lt": true,
+ "le": true,
+ "eq": true,
+ "ne": true,
+ "ge": true,
+ "gt": true,
+ }
+
+ compareMap2 := map[string]bool{
+ "ct": true,
+ }
+
+ typ := reflect.TypeOf(st)
+ val := reflect.ValueOf(st) // 获取reflect.Type类型
+
+ kd := val.Kind() // 获取到st对应的类别
+ if kd != reflect.Struct {
+ return errors.New("expect struct")
+ }
+ num := val.NumField()
+ // 遍历结构体的所有字段
+ for i := 0; i < num; i++ {
+ tagVal := typ.Field(i)
+ val := val.Field(i)
+ if len(roleMap[tagVal.Name]) > 0 {
+ for _, v := range roleMap[tagVal.Name] {
+ switch {
+ case v == "notEmpty":
+ if isBlank(val) {
+ return errors.New(tagVal.Name + "值不能为空")
+ }
+ case compareMap[strings.Split(v, "=")[0]]:
+ if !compareVerify(val, v) {
+ return errors.New(tagVal.Name + "长度或值不在合法范围," + v)
+ }
+ case compareMap2[strings.Split(v, "=")[0]]:
+ if !compareVerify2(val, v) {
+ return errors.New(tagVal.Name + "取值不可用," + v)
+ }
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func Verify2(st request.StructParams, roleMap Rules) (err error) {
+ compareMap := map[string]bool{
+ "lt": true,
+ "le": true,
+ "eq": true,
+ "ne": true,
+ "ge": true,
+ "gt": true,
+ }
+ compareMap2 := map[string]bool{
+ "ct": true,
+ }
+ for stk, stv := range st {
+ val := reflect.ValueOf(stv)
+ if len(roleMap[stk]) > 0 {
+ for _, v := range roleMap[stk] {
+ switch {
+ case v == "notEmpty":
+ if isBlank(val) {
+ return errors.New(stk + "值不能为空")
+ }
+ case compareMap[strings.Split(v, "=")[0]]:
+ if !compareVerify(val, v) {
+ return errors.New(stk + "长度或值不在合法范围," + v)
+ }
+ case compareMap2[strings.Split(v, "=")[0]]:
+ if !compareVerify2(val, v) {
+ return errors.New(stk + "取值不可用," + v)
+ }
+ }
+ }
+ }
+ }
+ return nil
+}
+
+//@function: compareVerify
+//@description: 长度和数字的校验方法 根据类型自动校验
+//@param: value reflect.Value, VerifyStr string
+//@return: bool
+
+func compareVerify(value reflect.Value, VerifyStr string) bool {
+ switch value.Kind() {
+ case reflect.String, reflect.Slice, reflect.Array:
+ return compare(value.Len(), VerifyStr)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return compare(value.Uint(), VerifyStr)
+ case reflect.Float32, reflect.Float64:
+ return compare(value.Float(), VerifyStr)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return compare(value.Int(), VerifyStr)
+ default:
+ return false
+ }
+}
+
+func compareVerify2(value reflect.Value, VerifyStr string) bool {
+ if value.String() != "" {
+ v := value.String() + ","
+ if strings.Index(VerifyStr, v) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+//@function: isBlank
+//@description: 非空校验
+//@param: value reflect.Value
+//@return: bool
+
+func isBlank(value reflect.Value) bool {
+ switch value.Kind() {
+ case reflect.String:
+ return value.Len() == 0
+ case reflect.Bool:
+ return !value.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return value.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return value.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return value.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return value.IsNil()
+ }
+ return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
+}
+
+//@function: compare
+//@description: 比较函数
+//@param: value interface{}, VerifyStr string
+//@return: bool
+
+func compare(value interface{}, VerifyStr string) bool {
+ VerifyStrArr := strings.Split(VerifyStr, "=")
+ val := reflect.ValueOf(value)
+ switch val.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64)
+ if VErr != nil {
+ return false
+ }
+ switch {
+ case VerifyStrArr[0] == "lt":
+ return val.Int() < VInt
+ case VerifyStrArr[0] == "le":
+ return val.Int() <= VInt
+ case VerifyStrArr[0] == "eq":
+ return val.Int() == VInt
+ case VerifyStrArr[0] == "ne":
+ return val.Int() != VInt
+ case VerifyStrArr[0] == "ge":
+ return val.Int() >= VInt
+ case VerifyStrArr[0] == "gt":
+ return val.Int() > VInt
+ default:
+ return false
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ VInt, VErr := strconv.Atoi(VerifyStrArr[1])
+ if VErr != nil {
+ return false
+ }
+ switch {
+ case VerifyStrArr[0] == "lt":
+ return val.Uint() < uint64(VInt)
+ case VerifyStrArr[0] == "le":
+ return val.Uint() <= uint64(VInt)
+ case VerifyStrArr[0] == "eq":
+ return val.Uint() == uint64(VInt)
+ case VerifyStrArr[0] == "ne":
+ return val.Uint() != uint64(VInt)
+ case VerifyStrArr[0] == "ge":
+ return val.Uint() >= uint64(VInt)
+ case VerifyStrArr[0] == "gt":
+ return val.Uint() > uint64(VInt)
+ default:
+ return false
+ }
+ case reflect.Float32, reflect.Float64:
+ VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64)
+ if VErr != nil {
+ return false
+ }
+ switch {
+ case VerifyStrArr[0] == "lt":
+ return val.Float() < VFloat
+ case VerifyStrArr[0] == "le":
+ return val.Float() <= VFloat
+ case VerifyStrArr[0] == "eq":
+ return val.Float() == VFloat
+ case VerifyStrArr[0] == "ne":
+ return val.Float() != VFloat
+ case VerifyStrArr[0] == "ge":
+ return val.Float() >= VFloat
+ case VerifyStrArr[0] == "gt":
+ return val.Float() > VFloat
+ default:
+ return false
+ }
+ default:
+ return false
+ }
+}
+
+func Transform(st request.StructParams, fields ...string) map[string]interface{} {
+ var (
+ fm = make(map[string]string)
+ res = make(map[string]interface{})
+ )
+ for _, v := range fields {
+ fm[v] = v
+ }
+ for k, v := range st {
+ if field, ok := fm[snakeString(k)]; ok {
+ res[field] = v
+ }
+ }
+ return res
+}
+
+func TransformRuin(st request.StructParams, fields ...string) map[string]interface{} {
+ var (
+ fm = make(map[string]string)
+ res = make(map[string]interface{})
+ )
+ for _, v := range fields {
+ fm[v] = v
+ }
+ for k, v := range st {
+ if _, ok := fm[snakeString(k)]; !ok {
+ res[snakeString(k)] = v
+ }
+ }
+ return res
+}
+
+func snakeString(s string) string {
+ data := make([]byte, 0, len(s)*2)
+ j := false
+ num := len(s)
+ for i := 0; i < num; i++ {
+ d := s[i]
+ if i > 0 && d >= 'A' && d <= 'Z' && j {
+ data = append(data, '_')
+ }
+ if d != '_' {
+ j = true
+ }
+ data = append(data, d)
+ }
+ return strings.ToLower(string(data[:]))
+}
diff --git a/utils/verify.go b/utils/verify.go
new file mode 100644
index 0000000..6b9abe7
--- /dev/null
+++ b/utils/verify.go
@@ -0,0 +1,79 @@
+package utils
+
+var (
+ IdVerify = Rules{"ID": {NotEmpty()}}
+ IdVerify2 = Rules{"id": {NotEmpty()}}
+ ApiVerify = Rules{"Path": {NotEmpty()}, "Description": {NotEmpty()}, "ApiGroup": {NotEmpty()}, "Method": {NotEmpty()}}
+ MenuVerify = Rules{"Path": {NotEmpty()}, "ParentId": {NotEmpty()}, "Name": {NotEmpty()}, "Component": {NotEmpty()}, "Sort": {Ge("0")}}
+ MenuMetaVerify = Rules{"Title": {NotEmpty()}}
+ LoginVerify = Rules{"CaptchaId": {NotEmpty()}, "Captcha": {NotEmpty()}, "Username": {NotEmpty()}, "Password": {NotEmpty()}}
+ RegisterVerify = Rules{"Username": {NotEmpty()}, "NickName": {NotEmpty()}, "Password": {NotEmpty()}, "AuthorityId": {NotEmpty()}}
+ PageInfoVerify = Rules{"Page": {NotEmpty()}, "PageSize": {NotEmpty()}}
+ CustomerVerify = Rules{"CustomerName": {NotEmpty()}, "CustomerPhoneData": {NotEmpty()}}
+ AutoCodeVerify = Rules{"Abbreviation": {NotEmpty()}, "StructName": {NotEmpty()}, "PackageName": {NotEmpty()}, "Fields": {NotEmpty()}}
+ AuthorityVerify = Rules{"AuthorityName": {NotEmpty()}}
+ AuthorityIdVerify = Rules{"AuthorityId": {NotEmpty()}}
+ OldAuthorityVerify = Rules{"OldAuthorityId": {NotEmpty()}}
+ ChangePasswordVerify = Rules{"Username": {NotEmpty()}, "Password": {NotEmpty()}, "NewPassword": {NotEmpty()}}
+ SetUserAuthorityVerify = Rules{"UUID": {NotEmpty()}, "AuthorityId": {NotEmpty()}}
+ BsChannelVerify = Rules{"Name": {NotEmpty()}, "JumpType": {NotEmpty()}, "Status": {NotEmpty()}}
+ BsBannerVerify = Rules{"ChannelId": {NotEmpty()}, "Title": {NotEmpty()}, "CoverUrl": {NotEmpty()}, "RelationType": {NotEmpty()}, "SourceType": {Ct("1", "2", "3")}}
+ BsNewsVerity = Rules{"Title": {NotEmpty()}, "TitleDesc": {NotEmpty()}, "DisplayMode": {NotEmpty()}, "LinkType": {NotEmpty()}, "AuthorId": {NotEmpty()}, "ShareTitle": {NotEmpty()}}
+ BsNews03Verity = Rules{"Title": {NotEmpty()}, "TitleDesc": {NotEmpty()}, "AuthorId": {NotEmpty()}, "ShareTitle": {NotEmpty()}, "Type": {NotEmpty()}}
+ AuthorVerity = Rules{"Name": {NotEmpty()}, "Avatar": {NotEmpty()}}
+ SearchNewsRecommendVerity = Rules{"NewsId": {NotEmpty()}}
+ UUIDVerity = Rules{"UUID": {NotEmpty()}}
+ NewsRecommendVerity = Rules{"NewsId": {NotEmpty()}, "RelationId": {NotEmpty()}}
+ BsTogetherVerity = Rules{"Type": {NotEmpty()}, "Title": {NotEmpty()}}
+ DisplayModeVerity = Rules{"DisplayMode": {NotEmpty()}}
+ SearchTogetherRelationVerity = Rules{"TogetherId": {NotEmpty()}}
+ ChannelIDVerity = Rules{"ChannelId": {NotEmpty()}}
+ SubjectIDVerity = Rules{"SubjectId": {NotEmpty()}}
+ GatherIDVerity = Rules{"GatherId": {NotEmpty()}}
+ LiveIDVerity = Rules{"LiveId": {NotEmpty()}}
+ TogetherRelationVerity = Rules{"TogetherId": {NotEmpty()}, "RelationId": {NotEmpty()}, "RelationType": {Ct("2", "3", "6")}}
+ BsSubjectVerity = Rules{"Type": {NotEmpty()}, "Title": {NotEmpty()}, "ListTitle": {NotEmpty()}, "ShareTitle": {NotEmpty()}, "Info": {NotEmpty()}, "CoverUrl": {NotEmpty()}, "ShareImage": {NotEmpty()}}
+ BsLeaderVerity = Rules{"Name": {NotEmpty()}, "Avatar": {NotEmpty()}, "Position": {NotEmpty()}, "Info": {NotEmpty()}, "ShareTitle": {NotEmpty()}}
+ BsSubjectGatherVerity = Rules{"SubjectId": {NotEmpty()}, "GatherTitle": {NotEmpty()}}
+ BsNewsGatherVerity = Rules{"GatherId": {NotEmpty()}, "RelationId": {NotEmpty()}}
+ LeaderIDVerity = Rules{"LeaderId": {NotEmpty()}}
+ BsLeaderRelationVerity = Rules{"LeaderId": {NotEmpty()}, "RelationId": {NotEmpty()}, "RelationType": {NotEmpty()}}
+ BsChannelPageVerity = Rules{"ChannelId": {NotEmpty()}, "RelationId": {NotEmpty()}, "RelationType": {NotEmpty()}}
+ UploadFileVerity = Rules{"File": {NotEmpty()}, "Type": {NotEmpty()}}
+ UploadFileVerity2 = Rules{"File": {NotEmpty()}}
+ SendPushVerity = Rules{"relationId": {NotEmpty()}, "relationType": {Ct("1", "3", "4", "6")}, "Body": {NotEmpty()}, "PushType": {NotEmpty()}}
+ VersionControlVerity = Rules{"Version": {NotEmpty()}, "Type": {NotEmpty()}, "Info": {NotEmpty()}, "Status": {NotEmpty()}}
+ BsLiveBannerVerity = Rules{"CoverUrl": {NotEmpty()}, "Status": {NotEmpty()}}
+ BsLiveNoticeVerity = Rules{"CoverUrl": {NotEmpty()}, "Title": {NotEmpty()}}
+ BsLiveBaseVerity = Rules{"Title": {NotEmpty()}, "CoverUrl": {NotEmpty()}, "VideoUrl": {NotEmpty()}, "Status": {NotEmpty()}, "Remark": {NotEmpty()}}
+ BsLiveVerity = Rules{"HlsUrl": {NotEmpty()}}
+ BsLivePlaybackVerity = Rules{"Title": {NotEmpty()}, "CoverUrl": {NotEmpty()}, "VideoUrl": {NotEmpty()}}
+ BsLiveImageTextVerity = Rules{"LiveId": {NotEmpty()}, "Content": {NotEmpty()}, "AuthorId": {NotEmpty()}}
+ SensitiveWordVerity = Rules{"Content": {NotEmpty()}}
+ HotWordVerity = Rules{"Title": {NotEmpty()}}
+ DictDataVerity = Rules{"TypeCode": {NotEmpty()}}
+ AppUserStatusVerity = Rules{"UUID": {NotEmpty()}, "Status": {Ct("1", "2")}}
+ AppUserVerity = Rules{"Nickname": {NotEmpty()}}
+ BsRedBoxVerity = Rules{"RelationId": {NotEmpty()}, "RelationType": {NotEmpty()}}
+ CategoryIDVerity = Rules{"CategoryID": {NotEmpty()}}
+ BsRankVerity = Rules{"CategoryID": {NotEmpty()}, "Ranks": {NotEmpty()}}
+ IdStatusVerity = Rules{"ID": {NotEmpty()}, "Status": {NotEmpty()}}
+ LevelVerity = Rules{"Level": {NotEmpty()}}
+ BsGovRelationVerity = Rules{"RelationId": {NotEmpty()}, "RelationType": {NotEmpty()}, "Level": {Ct("1", "2")}}
+ BsAdvertisementPoolVerity = Rules{"Cover": {NotEmpty()}, "Url": {NotEmpty()}, "Type": {NotEmpty()}, "Title": {NotEmpty()}, "LinkType": {NotEmpty()}}
+ GeneratePlaybackVerity = Rules{"ID": {NotEmpty()}, "Zone": {NotEmpty()}, "StreamName": {NotEmpty()}, "Start": {NotEmpty()}, "End": {NotEmpty()}}
+ TempVerity = Rules{"": {NotEmpty()}}
+ BsNewsDraftCheckVerity = Rules{"ID": {NotEmpty()}, "CheckStatus": {NotEmpty()}}
+ NaireVerity = Rules{"Data": {NotEmpty()}}
+ MBoardVerity = Rules{"Content": {NotEmpty()}}
+ VigourProgramVerity = Rules{"Name": {NotEmpty()}, "Mechanism": {NotEmpty()}, "Phone": {NotEmpty()}, "ProgramNum": {NotEmpty()}, "ProgramName": {NotEmpty()}, "ProgramType": {NotEmpty()}, "Belong": {NotEmpty()}}
+ VigourHostVerity = Rules{"Set": {NotEmpty()}, "Name": {NotEmpty()}, "Phone": {NotEmpty()}, "Email": {NotEmpty()}, "Belong": {NotEmpty()}, "Mechanism": {NotEmpty()}}
+ H5StructVerity = Rules{"Table": {NotEmpty()}}
+ BsCommentVerity = Rules{"RelationId": {NotEmpty()}, "RelationType": {Ct("1")}, "Content": {NotEmpty()}, "CreateBy": {NotEmpty()}}
+
+ MissionIdVerity = Rules{"MissionId": {NotEmpty()}}
+ ValueVerity = Rules{"Value": {NotEmpty()}}
+ OrderIDVerify = Rules{"OrderID": {NotEmpty()}}
+ ChainVerify = Rules{"Hash": {NotEmpty()}}
+ BannerVerity = Rules{"Title": {NotEmpty()}, "CoverUrl": {NotEmpty()}, "LinkType": {NotEmpty()}}
+)
diff --git a/utils/zipfiles.go b/utils/zipfiles.go
new file mode 100644
index 0000000..921c9b4
--- /dev/null
+++ b/utils/zipfiles.go
@@ -0,0 +1,71 @@
+package utils
+
+import (
+ "archive/zip"
+ "io"
+ "os"
+ "strings"
+)
+
+//@function: ZipFiles
+//@description: 压缩文件
+//@param: filename string, files []string, oldForm, newForm string
+//@return: error
+
+func ZipFiles(filename string, files []string, oldForm, newForm string) error {
+
+ newZipFile, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _ = newZipFile.Close()
+ }()
+
+ zipWriter := zip.NewWriter(newZipFile)
+ defer func() {
+ _ = zipWriter.Close()
+ }()
+
+ // 把files添加到zip中
+ for _, file := range files {
+
+ err = func(file string) error {
+ zipFile, err := os.Open(file)
+ if err != nil {
+ return err
+ }
+ defer zipFile.Close()
+ // 获取file的基础信息
+ info, err := zipFile.Stat()
+ if err != nil {
+ return err
+ }
+
+ header, err := zip.FileInfoHeader(info)
+ if err != nil {
+ return err
+ }
+
+ // 使用上面的FileInforHeader() 就可以把文件保存的路径替换成我们自己想要的了,如下面
+ header.Name = strings.Replace(file, oldForm, newForm, -1)
+
+ // 优化压缩
+ // 更多参考see http://golang.org/pkg/archive/zip/#pkg-constants
+ header.Method = zip.Deflate
+
+ writer, err := zipWriter.CreateHeader(header)
+ if err != nil {
+ return err
+ }
+ if _, err = io.Copy(writer, zipFile); err != nil {
+ return err
+ }
+ return nil
+ }(file)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
|