Ix#XWt;VYw$J3Q^cokKpWE64T2o+XMBTBYA3PY%HOR8$?Q@
z_4pJKH{#$p_f~hRwU_Vy0^s=wZ*E?A6fUe~$@=z;PyN;YC=;k0AC}MXi-JQD
zboxwb=2{vrTi(G~u-uQL#uEL&wm2_mAGpenwOW{^!E~+n_D@A}oqna~zY9*($Qnep
zy)c4A`Hg%Wtwm%57@u}1EHqS8UrQnlwP(^^h$!t$i*D~?ox*FWt1rXOGDGf9y&F2K
zb2{H?B=L@&E@v;v%VXg^+(hS!(nR6!lehoA=MSs&M1vF4NOVwJDWV=j65_`>**NG_
zgU9)q$7|SR$?3U9l=yz>pyl#EaO|B=!|m0QU9+cJhGek-R=gIRwynIt-s@4K3CH=l
z;7N5wtXi_3x2^?@%$Tc{%s=U2H6PqkP2L&B>oTn~ygUpoPwx;`$ss>v-(;#f>N--c
zC?FxFPo#HVz-mT2I%?Je~;RX%y``MJx&rd`C
zg1C=g(sCZo-mz42U(Fvb4Vv43VjH6bZiUsSK4->mRwadU1?FlgbXgQWMKOCr3Y@qjjB+fQpGaOb$K>;a
zpC{MIVqIZ=bucggnNWlqiT!6uqndd+fy}B17)m)2yGTKSL|nEvGc$|m-|$}ROSR(l
zZp>6uS09;}-kR|u@{XM=buc+qetlM`tfOO%uZ+fK&%GbZ@-xM%M{xZae`w>b9DIno
zSZmD$EHLZ7l&?{6fp}nYy;|-UIJt%wVe>Lz*WP-Qyg#}=`*UzBRHU1?i!JEzV5!YU
zyED7GnuD*3!HGuP`?p)C+Cwb-L#Q?rvh~&P68w^&!IE%gOMCGQ%uM@sUp+OmsJf0PFCU+oJ>N#Sm6}CwvXH|scXXXt
zI=pQb$UH7sGi%}L#veSA6Dhq|$2&t8Kc_I^U;2DJht1)NhYu0_lhKJ6NC^L#q5f?r
zjVon*T%qCwr7}0L{9v92jhocQRQ|Ge?o)4Hq(jJR=+TbEJj^us)Yr+W95!>(^ufpQ
znh`wsMZ2}!P76T!)csiJDYEZ&MHf`9(^6<`%oxMM!UZHsN=pVrtbhCq4EKTO61Rv*AQdT8cNB|_1o>$B`C&mSug9d
zy3B&*-22W=!Nf9hca>VrHXxQ|%-0DLVrD|!Y-Nx9r*i?|qe8}l%(*68C20zZI_;gS
z>z3h?iyi_T-j`sJEYv|4Yw=rnUO~YV5N3e9py}2s(r%)5d#m$87_`CTVj=@S7gWGnM8xo!$`hq;=8N}ey;U6fMbH1V=0-~b*!W@=cd)?(wyR@`PJwVOCr
z>^Twu$;e1KRJme>TBp#IAM1j`$T_J$-<>kAm~>445r6AnV&)7Rkx{C8hYJ73nnmON79A*wGSBK@8WA=LBQEV1eym@N=
z;N_&*+}e|J9m-cuwi(l$Bb0M!_f*K8G;10dw^&nM7GBbsXqDTjzXmg!$P-e;w_{`cgq+%L
z&56u4)_iYjGO!dFs83q^YC!wwQRk>Org!8TWvkcJ<9AkTBj*R1x$&P5bGA
zTVW1d4ddG{bG2ZjZ$b)0bJEMKo%I)gG3EX?6XMZjWNlA2;!Fp@-)k(HQCq-=nm-?8P7+f!V7Q5KVjCB0;
z^fWLv-{}bm#_xOSZfu@f!5LBWWQI(YkMoDy{HI%KB=Y$ImEZvYUJ_~}*!
zg78%hnxJ5A!nQgIQg4OIIvCvvBiD4Tew`-=sh{vfDKA3y_G&}FQ#q2wdvqz&JfYU7
zyu0Ti`*TwSku$fn#dma~?Ihuj!*z4Ow&HXlArTSOU$|ctncAusDvkK;c+Gh&hA>HM
zM17i|d8h7aoM)=NoBF`jy7002_r5B8*bVAu&w#Qf08uuDRiz9>!l{IYEP0P-OYOEG
zavLgs&v@3y1~&jxdVp|SzUC(;aq1m{)m!ww^rx2kDpUMjs+lHu6GQmj5zJX6wwtR1uI0jEBQ$k+xp;oAZbiC4OOCT-z1#b-?r*gbvI?XPX$
zY*U$U5L9b9B*)do+VLZ*w>`vfzM+!fXJgx1o}-Lt^KddhnLVjTQ>G|cRLr2174O}@
zU#BE;a>sbB_@zpJ;?&f~Yb1^UDU`|NqAY7$3^kxI1*?kp|Odd8iDB$B~Ie%+j*r6g>n8Gh$iPl=|AX*YZ-i?c|5Oj*wMV
zugs&wv(>Mdd3Q{l{HP$@m2}hMZLJv^8lo0)J7s=uf;}ug=iQD~)ATNzYOlU}?PgeL
zD6V(gq451kNJJ7paa=wGkqXp+dszrB7=>2?t$xIi2lQzVR_ZXVd)BhLknp8W30R-+
zSq?Q4mc=-fY8JTovi>kr(Zk#YTQ&J8r_>myIl*Q2Bsb`}6(1Sbm)feC=Raoo9W@z!
zQ6m&NN|53W`!M1A(!}PQ2aXe*w^8F01@R*MxDd|MXE}#MCZ+vob$efC;siS%PB7?!IygA!WisiOJD(WkA?sZ>
zOX~M;Dm}VjqS7(v%Axl=0@Ehq0u(f*);H9ZjaAnxG&s}j^zG|i3~Hi8x?+pZ@9UiU
zj>aNQeq<&A*ayD3vM({z}TK@UeV_fx?;@vRuDNgT-efu0pBYLc-r|0NPtx0{{
z3P5&*pRI7J#4I)3l~<#7<3*tl8Q87rWm>SD8!KYD5B89O^WeM1r~JN`uM
zqr)3#Ua;jg5jPZ-WR+btg6L>xt?(aH4V1DtR~bS}V5>6WS4$?()Up2RRv=%KbbkZhXbU(s;p~
zOGjT*?k>ma>}fX*tK-*oVXr-B`M%$UPk+orE7&;;>$|%j_X^CN9WO;OF*3SN?G;#C
zT56TOYvu7fA7)}m+awDVDi@Xi9tNs$#&;b7Wb&1O@Y_N6Q({L%c%JnqbM?Bt?~GwW
zcM+JGMXL5wfY)z~$EV*)r}Re@YFEVFzXn?O_X48$Oi7bP<#)cj?ybXjRo(k{u4rQB
zWDZ2pwK<{@74r}GM9?2~z
zu18u(oPMMD7-bPsEfO$V0;v(<2Hsu
zM!?*5u;|Tr4t*rr^9enDG5j10cV0`)^TMK`s1*mFJ+)ST_y
zxKtY-N7{w*BrJq}urV(YeXFW^Z`}Oa$;rtjTRsykZ&>RP>jS~lLY_)Oqp0=@i~iX>
zwH*7u^Py*Yx|!XvgU`bLpKw?$Wr-5aqpVCz}y
z=%n!UpKb!^FYDzFU}f&Fo1E~&eE^Ac>Jwt(;wl?H3D`s;Q~l0QTsFr#%SGK#dVK72
z_ASKGPxAuXeYuaa1Psb)tsGOWOQRgGy8P>nwnY4sJIdh=XK!xumoF%o|J177
zjXkrK2V%FIL-Z1;3-xQn{fpWTRnr;<>ia6_&NMbPb*mu^z@e@SCentP?8pr10S-ec
zUVNk(Ccks2mCvQY`3Y}#>}_%o2U|kb@-F_m@^RHne{}t{&aPF$TH-9-NYJKE+8OsfOBmXtXvv#33*cBJz`zgWxegMHlq&_@YH%L@
zL@PBnv2KTIeGDuAt|jRdk%O8o4=xco%dlWI5L#z*WSw!#^(0lUgYAC{C5Nz{ob)UB
zRsHa=$(+M5wFmqr_0=og{SZj_@drDOCT&08ZrKSjPHy170F+;?Q5x_mTmFhkZ&BRM
zBDFhdZnGXW2KW}1U&jMc&e(f{_`viLLSFecuSH*~XMu!+7F3>%iGiNAF+vp@QzBmIUY)8l!wVHNu-c!(L^#Lqb9xZ9J)hR4s;$A{p8qUHP+##?$`R6B*_n
zWp(%3%N~vMHBzy-%bt{UK*NHcVUQ9q0DGTqYT)DN8gj$d%!%pEZUMn_CZ?RVs)&$~g=kw&PN)n>SMi%@9r@;W1NpUN7ikbBYN|2mkG38U
zfIfVXvhbig@7tkms&Z*48G?xjd}1$gSwWd(p@yO;$-J((;Cs{j|UboFY@PirLsvke9
ztQ<*|UPqZXV;QY5zq*tMxdw5Qf|zN{cWg;oe+O{zqZwcJ6Cl_V%|E_OS-I76Xfs@Su6#*8YwnYk`U!V6ljaRHlIxItNtK3Ott0<%`fKC+N
z*(sI+7IKO;RJboCC1r-Gtr2?09j&l07!RQC-&P;^538!FH|oYT-$RFfEf5U(Cu1_X
zgBED@_RBWQ*L6V8WH7UDqN|JJo+1^~2iX$eq<7L$Jbh`6Q=3oemxle!CqWp#-9;0y
zP`ix|4D9xa?3kLF>HDBZ?L4${hV^|)-Ep{jav~xW^+&Kq&e<+MaeHBsD)&B?GYwyZ4d#W9eg((I$HrDR
zYD>%#tlA5T;|_;`^ixMls{5d+DK`V+f<5T>z(S8RK2|%tTk@pIA*bev(gYF`+%Gr1LW1CR
zC3%SxZ?WCzD=Wvn!%%k5PN|eGO%L}GD_)1qS01E!FLr)KG+8FDq{?&y&x#w%X}z2zwLTZ%M-V=vkI&M#rV7b`98DJZ8yBWB0
zWex)Z1Er*-N5)34RoA8Q7**XMCQk9~idEFDzIvn;0TeIDwr>Q3(~bA!dn(%vQ+`x%COL`V$`<9Q>8_Jl`z$s&Z#kJMtajAoDtZvG%LU
zjVhNl5eoAuw=^~9_2lyOx|PU@xV)l9;W|h;?#1Fl)H!Bww$UqUj%1o&KaM$6uI)*D8jsv*vUO2ybLn>D9!(+%>UIl4SPb12{6dv82VJolwL!+xx38@id
zH19P1su=x?cxNh#M#DFT0TUa}?oC$wWAMB8uI^2L;_-Xr=N8NHs<-*0r4pNzYNBFf
zxx=V@ai1V-rFd~?(J6m>{S(o?ia<^(8;P^smc_-z5|V0{`ofm1;4p=-3>TG|>hMJ0
z;ai>1Y_T+R3SJ)mtjwp^suwat5-nW%`nqn{rgCksuG6gA@*}!53aTJxTuEN;l-1Dp
ziY4fi#Vn?hH-r}~uorI|UTdEqyJvqSBqe82l8eT@BQfl;UUppd?r~CLc87op;uU
z!pwS~LV?H=sEHsslai9$R=3Z*x9=_83fno{R88&-`c1BumT37%;1k8u`Od7i$|R*8
z<&BLe1P0v#iJ-HH8Ly*cQ7ey{K2e|dgO*z`9W&F+_BKm#nZV#sd4(uPrAweUAFk5n
zZb;53a*exR*$Eqks@D}Ts8HRMVaU6yXn{{Xji{NMGOU+iKGIZ}NI%`_X@m~_
zkTir2&xDG!e#Yu`-FNzxO;Y?ZQu(_5&Gc}U#I^hU-`*UU_n*D1T3vtv@e45_!MJ`>
zZxTV>C3m1Y;Nyjs9jaSP#`Vq34c8mbMp73#Tm@YaGE0fRys2q3zylPC`{54dbYZWT
za-Ba2j?vB8K13M#PtJlO?M;nyS|os@2QGf#adx9SQNOsTAQG
z;OovT^V&Swlmy6VY5~_bAl)oN&y4*6HCm!_Q#Wy`c31-bMrx
z9zVAryNK$aTk73$I0|F&OSVfL%;dfCnt|Xn`}(U!TNuZFzJQEq+5>LuU7Ak@CH;dm
z@&pN&3KVs85=SYrT?&AcsKw>{?~5pTbE2Ri@pCrzn
z7Br=xjVN7XS-nBhHs|y5_hhmIaEuqga2FKr#-byVXp4@Q^4s_pqFtD&b!<|>#gVm#
z?*pVdrBU+0AKC!M=TFD$k(65Ext)a+KsKVK2L)>lzvyO^0Mt+lHB^*t<@5c9x1GOO
zfd9n+o^BpDeK|YZa8R?-?+wuf4n;5yX5Q+%JJ=g4rbq7Tar_IQ&oeyw7IEXB|IkQr
z=;$Na6!)#e_7+7_lMSZd&-{7sqtr`hmSX9;lf_-9oiO~QWVhfj-A>m(FL;s0>PoFW
zwcC0yT9P;SQ0nzASnKXGJj7bL+36Z
z&9+%@;H$lKxZsmCTYVwfvb4%&ODSFRpTXfckUbVQm-Om_oT!bW_wJ9<(3^R`pNR=h
z*F3C%_=7ro{x+1q9HI1b>n^XXA3UWI9<}dZ9&i^xn<(F-nLK74=>Z9Ewe&cB*ZWou
zL-|Op9`yB!-;p){{HZQU1&yxoC3T8W72l5|fRlbN!ueKqCEP;I?l<_WiI!0Cb|`uU
zjm{P1(>opHO{`Y@`TVk6C0%ma!r^m!oL#adDz@Qj&xb_UJMVA5{cm1;ojL2N;kAiA
zuP!w;-)Z7lX})v8N{w7kcz>4QriQr%#gB1k1(Q>E%TSdZ&JWlbfZna}v65lFQvQle
z@)5Tey-4TJLPb2Fw-`e9%%r~d=IIL=S%b$k
zwnPn?vV}#q@olrU?`>%p*J?J&HRqkSM7QQuhTriOAPkjryDRU;_IJeb{1WpOcX{8f
zqWZ6EKYwoXez`HoV7kMj5uGi$>d*I~0U=JYD%2f`@HR0@f{?Aki%YbJJx(MHceE0n
zE_o^!`adIiG@A3v6+}4YQcl
zF8i{6KG`=evEoLJ;qyYltwa6yPsi)`7xD(Fi2Um|fS*sFb*SV5$Gu(g&jAQGdmkVY
z?0taC?j3+h+B*PZ$-Tp#k7VxySapHVzkBlk;A;OHf8u|7wg2NA{x?_q*HiqPlmCw&
ze;2;JrfuK3(JZ*LW=ANyovrQo*1npY8v3~LzRH=yvL%nNw1a(b^O(23CV0WF
z(02dLKE>P_qqtIsAs3^@z#@YKm3KXF*z=a%Ydrt=%&psY@v3Lp3%hfw2n1>6GqZPP
zyvnVGP-Tl6pZq+o+!qniObWR5&jkN7BL58QKXdc13I6BgfBc*Oi(d~`fYf-|)q4!b
zE|18>pwRyK_A&j~l*`l2Q49r4iYZbl;L=(i`IkiK`j?%#HmqatF^fL=+L@X@7*`v%
z-vtpX<@oHn8>-m9mH)NVndqZ06gIiAjA_z`{f?pAQ-DC7&ek!%CLY%MmkBTN?PK;9
znj?jk!#7{+Y<`jLgL|w_6d+l|POvVF?$+0iNa{^jhtC9Ih6Lg+SyX&IWPUrJqS&~D
zhI-4w8kc)t0;m%9n7Ge-c3)cf1FcVfKX#I^eio1upA!!Ld2fJF5GP^!*1-riat*h<
z_>kV@!-{Wf_9qSK-Fj5^DkDb#L`;`yn-5ypbcZ(0IX}8-)4%^N$l9YLL9?0c*aAFH
znvTl?;Lgukn>icJ9L`JWfMkTzTSIFPx5&eDC$Vt1^DoFqMkHB>d_+_iK3U^!PL>_;
zt)sr~qDLA|>iy+g6(?`SidHaXp5B58cw&?nhA4_(N8ML}@rkhtoBukFWo<2Q%@72!yN}3cpFeJ#1Pqt<
zrh!C6JEI%JYDcnX1>>PtT{jmCgXXdIq}S}mZHU`!hPe$ax+b~oeKoK-=
z%u^0kz!%1$sYBaX@&4suBs;R@Rf>fhX)4T8u`%pBcR@zD5gDJvuG9b;S>%#XRW=Xa3#Cx)rbb?npis3OI&A>%|?a5-Yt!U!w2T%xw|re#opJW-~m$5$4T
zD-pSVgGjFP?*NBUc1!i+>2a3JC>O@|DgnAPqoWnlyW$w&z!?~=PM=M+*lep?*$~z*
zI?Hj3#Gm5Z+n~Wz|NDXrf|K$k`ANX4QGN5>JP3yG0T~O+@SxnxG)m^p(BWIDn>&S4
zh(g$rqPw|j5AuCF!hBPB*`jl_=B}7zsk6-VSfL6E<=Cc`aHm*t6VT3@Dn(>-$
z*qZu9*rbr!N6I#;0U6LLKy%YdL{^lGY@O5cH(A|PeX0tD&SrArzB7&W-+S+ol?6|X
z!P&0R#5iH)2Hi)P$K=FFDyuW4uLQ<$nbaiZW{))lL8_u(8^2^vQTlAI)!ARli!2km
z&7-w*@5~9C-qZ~CRipcz^-1^}4dC0l%xN4B9H$jmnGxDfWQLX0ZMFvGj%tRG7kFl{
zcin9SrTe7WkG25Tz}V6BV#YNkQ|Aa$lMON?naebl>x+0A=8?aNjL-WHbK#Es@81r8
zyH~SfKrNW?bMTd93VcygjU84qTiyQ4(*f7r{mWB8$SsP?M$uTq%Y|mOXJ{L_)L~-<
zS%o>WpeWZw3yM^(${m$=S^|94`x;1i$W%n&e5R=
zVPW{Y{Ae5eVCOJ=>x`B1R+(D!L|XL~R&~yjhEKdC^x;BQh=H=lKx_d!B
zPx*ruWAJ`If^iYxq_MN_Yzq4@Y)A!kT?ZQ{wI#j?$v`IB^KT^lV
z71pYkSns+Gj*d7T`!m(_-e_-q{yYz_8>_?(UV9u)=oG;Y;kOKm=p8JpPI4W+*U
z(vQzieQq5|r;QFuX9flLC4BM5w~K+UGwZK&xZa6r2rZUc0|MkhkP|e5|7%Z$UaTdn
z`)7JvGytC~F)QaGU47W~o8f1W9^H!}kV&JaFg(>|x%!9RdC&EQvPQaxxb7iTj<2ZR
z!6v?^IqkdcX^;Vj&b+0F;x`88QHi3xLpvitgIQAMC@}dWsGco?L(2#<@>0^2LJ7S57qTRCvALc@KJE{PRl0X$V48u*viH1k=(
zb#6kh#;V_2Rs2lUP=P@_{ceBj^i5l_!&3Tji@i5vFHL$mHqwnn2y^=W%Vd83XglQW
zNzYaB!3VF!QzZ<{N*y)dOrPbNtxgu$Pb`-_u4vylH*szBc&j&hViR)QM%Y0S1xe8v
z2ITjQr14x_%^f-Opn7}j726jc~D
zo(J_@b>R#7AgL3tH4B(|UQ2|2+o##{;ItdUxWLie2&>lubE3)Vf7Z9~9_@K!aa@Od
zte`sT+43D#G_h8+p;U+5Y8J!A^kQX%Fd3iagGs_Rca?8q4g$qN>8gUt{M4^J1^Rns
z#u>Yodx9M<9tRTeRvh{qt~S)633Q61jue>J&D7Tnj0d8umbPg|He~{?1UIwN7Tv8M
z_8iIO5g@ENy&hIwWKR`X2tJKV
zH{mBxyESd;o~WycKu??PfjTN+yQFHIbN0_>EnN@<3CZ@Eo=xbK<|?@@9jf*>-%O$MsVqGq`=T3l~Pn
z*i17d+^r8GW;?aGJ&Xe?1w|hBy_`pH+RYycE3R!E+!R)hh+<{88E+h!`!LwhFr;>g?m7hvzt|
zmSc{NM^hE$))=Ph4|@-2Ix^{+h-T(_K`40owi+%`?v(s7GJTG^`(FcE&1b
zVI4IgO1OO{zOkumJp8`Gq}u5HQ+65J#ILhUyapv4aU9*KH6Y3Dk|jvmK)I9sg9#Uh
zz!*O?2g*yTS3Jhtup5!OXlzkwRLw%O1nU+^Z~`cS=b3jH!doSy&apFi@X}(Na>?cP
zNrjeIt0P%!nIIlDbnx0pDTE^2hDVF}uo0z=Ksa)W#=YQ8ExGj}8Pm<{6{odO9tx+&9pvz;5CZb6;j}z^bqxjxf@bq{>d2Y6Li2BCnKv
zvL=dJFYg{xOt6T-iiWKKY=MXT-bAhHUP|=-Zf=1@eA5Wq?oG0UfC1sO4HKuC`
zT@)NGmD{d`<>NTFaai?D5faLOvx&Tr)^-~AvH~e)*;rmQ(mxv&ZohP4#w<;(32H5c
zP>;N4T5Mjw3+%Ufs-`$>u770BZYUoYo-xN|-0b8iXnKBTS<5Vd&@B#>uMuX5wseoY
zmfbnU^r2sELB^mgyn~V-$VkJwoLLPtHR@=e=?1jQ&-zc!r3GTj)?T>gC>maFLxv*;
zgrJxmd%Xm}#A;rlGKynk38$cpFu!?4giEknDn(+S5dDHFMn<@qypVjj@D#MfOojj4
zIb_2%6tgMw0pqJaoSh({vQ?ouWv9{XV&3uw3)v;gMjtoc5KqLV2L*YjjBa<5REI>{
zKSx~kERQE?=KAte;f5c_;s+Ottm;1p^ni!uPMXWbH<+_FmKU5Lvco&%1=92f`n3+E
zsOH*EA@##jiac)U4Ye%seyqZHJls;xvuxsXoUpbA&f6a1ey#VHVZ$}RIxjHhG9SBk+`WN9y({6;l`KzLz}<_uAS;lvR;2Ykf;|t)
zim24b))?Sw1XnobXAXJF@KforW)R=Ra#;v=RQp;}n5EkaFp;tJ>#Kf=v$7{UZ2F%>
zED?DJ15#xPDuSEumPeyGj&ln0SB4e%G(2!J$3iJ{X0pwZtj6Wpjd!w<((u2z6NK9b
zQGJE?q7+7Mya$C;C&MCi#tMaOD(4YpLcbvkv|-dZ;~?$u*vN6w8i={FPcgHmM1UZq
z>AIOzGTI8)Ws!BS&Al_rKgT&N6}W5sG41D-rdKVJoKS5B#9eg$!pRO(4OvYo^sIpW
z827*jl6DkYi{~96?2Rbj{ydf{jSO_wNNanmWRz31Vh^ck2GWV6x{)%spZex+v!GuF
z6U|W(ursPBFHB8b)585?IQVO@I+fWq`J4>6`~HN;Qqnn_a;COBLbI8kBvs3vL$YWM
z^`mN-czU`v=FN97isb+7v6k5bKE_`D(v5|6oU-@AsJoh6jNv#=%7`2U%cCIjLsj^J
zPY9jV^?KfL?$hzOzm;8XDj$o&Q%M48isvWq&v|GoVt0*LE}l|fvqN*RSg;h}$L6a+M6k%)c{E2ROFscFD-EmFZpJ|V&QAzX$dfdGs7w}*=
zZC@c(6`Mx{6tL${O5ii|Vv+E_FAw1)Ee7L=DF$PxQZt^wLFlUA<{hlbM+>BAw(`Cm
zDGA!0@ZI$s1h+GPWb2z#G(?{k!(mHa==F6OrnQ7z|AW1Hko*V14kanj?(pBTKiAuW
zAvH&MiVa0L`iGx%fwK%RreA4Z%?JT@VTMihFbIvZU43G?b=40!N{triB+BDs49U}KQ<)a$3?w@1`|gIXi>HI;O4)xU%CNVTzk8s^t*OwXnnPpYq3O%Y-bzuIUpWx
zvXb(>&!lg$X-HB{=6uVINr+8s1E97{GL}$<0dHA?+steTzdZA3_$AXlHUnF9ApoEC
z*w0f&axqGG_jUN{j7x`@geZAB?W{?S?kMXc0T2a^Vqr}~^S(g3&uP~Jy1>^w5>E5M
zcSk!34`Oog&$uK+zSbl(QCZ)c9%h`t?n_;^j2hxTTj*8!FyNM%7^skC@;C?;#
z&Z&Fe`chHhc^<-i^L=IrGoC0SLcXf@xUH47SDZ8HKR$3
zs$7Bo3T-0y{tiW{+kGr7^=aw&kGE3VVnYVv*RD2c88s8Aw*>Hr6T}(+*J9z1VT`Zg
zf~_@^o_*RB2@0)Sv;a3CJ~s5cPfQ@fH+*WSr&3+24V$s+`3wC~FGELi1|A6dO!^ok?iS1llTe4a
z7tM*a-FE*$F#T!sDVCf5Q4wh~f5OH86*0&bw%ZS+GYX-=pDy$GvBWA4n{{;
zN4zFVUAcM+e^&SAYx;^f(3n60-0hd9c)VHHx~6qpDBA2F$INt@gN35Q
z){keGk-(hd)t7?oKYVx*8=d#KM*hT$_G5wbF8e7({R4NV{iZ59_;|K=$2xqR{BFT+
z{#?d{B|8PqN{Z}=gZD3Vo?zTg`H&PDCwG+31(1(kp^c0>k*wS#M4E#9ne%NR=j0Q9q
z0+w%Ek!f4;IEq^ZvpdSI!-V>=1eSi)iQpEogor+&RbE68Y?8DobF%d1
zGVKR}!^Ionc0*@LOq}jYkp8mDpcnCzcEo@kdU$Otp$L2pDI)Kysj>0%ebI^0`@Y@K
znPmsfuty~=s--()w8Zn;Rjjm%v%=ZCEoep
zf4Z$x56uG&TjMCNX%S?+8=L9WbpHm&D}m`J%ii20Z^Ocv6mX*1%rPIJc#4T}&6FR^
zwd8I^pL6bOt?JG6Z>v^FCsw~vpuH@Rm%DrWl%B&~UriESv_w(lGfu4+vP>vlyLt?N#sIVkGy
zOF_{ow2Y+ZZCvE+e5CGMcm>y9j~ec+r0`by%#)we5lkr83VVjj2@8vg0f$YRw|Qr*
zd9+>x2TiHyB99(ucLE`?Ms45>3LVC
z2q0O*5OiwBt0v{_D}+9bg2z%AmTup69#wHf0ka9`4or0Sm~)yr(CPs-bwE>QeD>
z^IvW;9uJC&WzGG{RStjSR$!tz?{Veb*St(hOGXNbYOX)!6w&?rC;Eg@3Ndi_k|ANn
zZ^KY}n`S^UWL@WYwE&da%>0A6lFC`1{ujf`R<(M9;})e#%ul1+1FBhl{!hUIIXo#2
zyD66WcNN4zlQ(L2^hw}TvV9Y$p=xDHdjSzsqgQmQt~2onE%H3gGij&LKx~V6gQRyx
zMq3*NveJ~e#IO0VwJJQsP|bJ7Vd^#xP}&XI9HQ$R&h=$8uaK4@cc?3(53vL1-I`p7
zfa%!+u-t6`8)zxmS$5Y|TvaA3Ci*Sia}{l
z(w`W(oK2}cW*aJ+z82#%2eciiP?kF_*{ID;K)HoEn<8pim6q@uJonn;3>1ty=D#`5
zt2DG+VBh!mwb%$c&pd+QbC`eSy2IOvnXo$fjH|3wA~L@Nob3LXG0_W@J;&Z28w02j
zL)9OcH3wV!fU9s*|z$ee*zu_F+WE5K{AapBy;TBClYc
z7;FCtl{0IcJRK5(7d^nlU3x7}FOtL$Cww)o$o1b|4w9A`nJZN1P^~e>N2!`9Pgc2H
zwi~)y>oYA7rXG(k{Yc_;m;4>>zCAQ3oqu9gg1v*>)F@l&gKgh+&!~zlTE#VPS@Vtl
zLCspW{_%C7Z5525;+q;~I9BA`dRVKza7{l)>Fr(Hyeep_AQPyeQEVzVEpWtf>=gYe
zlWKQZmwLUGwS_GV2PYT&sbeU&0s1gKcvF@
zG6St6xU}_Y@cSBzG|9YD@n>kz?#xAK~xuCSK10I`u4KL%fnXJOr
zlt9#p51nrL-eHFWm-uY#n=WF%4A%X24&Y-%o)Uw63L@^cp+k;eW|<|myaK$>+SjK4
zh;Ch*bofYrloFS8IXLYSQe7bieBBu2FfkbT2Eqz^TtE=`q^wpky#60?%WZSw;pK7b@X~T>FPRTp(p0^d0*D5yZcAH&PR`i
zEsa@O4xDO;`F)z5arMy?LRH6go}*2RKPZ~(@&iIOktz{=35WWq
zI_(E78Ief>?cNKq^KQ17>JeoMClRf556zvUB-g10#&zj{K)RMbydlG)%9L3
z&Id>nH~-ry3G|)Nu!t>y{Yp)})U0d~)Hs&$YSYQwH*4YEP*%Tca8;yPm)q9lwWGmZ
z$xauoq^ApfY$&RSza3va?qEJMd9CL?M2;U-h;7ix-NeB|*^`hWj8~KXpAt8$Zcd+#VtwIgZOm^-cXnXMkI6%|I`(|NWt=|)
z+@VWBtdBp63e4^rt^gq-_~Kdqo{c3_?
z{mOza-<~-HdH991mZJfm`Ew+&Tf^z((=R=eHT*44V_$j?1{HP=y&rBdgv=Be={NvQ
z6bpY{g}uG9h~VqjFq=Tfe*>Uf6Ym?``LuXNRe*td>z1C0+BE;7NY?>(x437P>r{DG
z(xZEFhhuAFfZb|B1T}{uC1jw(HKY9n{8i(>q%&+c)ewC4yZx7b@!f+=BGc@#4dEGI
z(biPp!DTx!E4$hLegG~$jHUq|}wr+tcfr!I5(j3f;_?b|E7%oeHvATqN2QPTqMM}1P?
zFJs2YBuj0>X?M~M8@&M!lkI}D-=2ES--i&i-dzvtn65E9mL5X!xq|f{_)%QbWaQl#
zD}W8*o%?GbE-&r?#8lJ-vZOmRaRuN`4BbrA0T@%s{v7y2$T&^U#WJDCNP$b-e5vlKJ|bxs~-Lc5D!W=FN4?8@Cg
z82trsTIy#QR|Q&&6z}k;xSVyD{3+qLh1*14rs*;Jw~J0Ui(|B3kDT$Nzz-Zr?H6>1+rkYC2e
z=uJGAht$mRFUvBKli)Ofh7asNMkgY*F~E53kzlI*$S0l15HQUqT|2LQ;TLkq2NP40
zw(`|}89BAH@?Ia#=Z0Wa-*EI}L0d6-YD+*UN7X*HCd^Y!0MsG)qz$yo?U_+*A2kXo
zc`U|c`p(kLqCq2|={ysbbwZy#YivdCY
zUfbc_&pD1a-NAD%_bdvfjPc1>a%I-debCq((Vq==4?cunvb59}e^YpJ-^wqzT@ye=
zeuisDW%1rTPus)JBEBam3JGplRk_Qqhg>g8yB@(Mjs>trKv*DzMipMLBC^v*T>;Lh
zsFE`sfQy|$%|HW~lW(!BqhscYBLhgN5$J8WBUVFVX}23&>h2KKg`BEXDcU0A+1*4`
zymcAvTW5EL)D~gd*{qKEDvY;JLzB4xv~Sg(l_;33tES?bg*J23j7y1wuxjuBI2YV3
zg2Zg}1pa=;b%NOBv%90)HHv}FzC~8gdui1jWVEOC^yFt6Y|6?lZx3|`ju|r6Fuwml
zFHr!E0fMoB`g~wQ!bmYLXPEEzaRtXL;r(>4yP5!DlRJV{Ib!5+ma^{-r;5LoTdTD0
z>X2nNYW!J@qm}g%|InipSn1-78nD}4msR!(t@oI{CVl|B`JzrTnKjzAj6|fg9xG{t
zHoSc1x&W^q)J>$OLnbIDj^x((#u+smmuL2HzE
z?p?dx#1l6CV&MR!2r#|B0lp#x&Fk3AlQLrIeIuR2&5>EU3u2P?^AeDaQh{E~HR56I
z2R|YLcBZ39QPDd}MyvKEqhH-UFw|<_;I}s}KWh{W^h&6?95X|E-UEq~x>Gw38KMA;
zQnWfWaHc>;r@a##wVBtze#Ql1+^SV<%n7Up)=N;{Q4<}{o6US4#btZwcYrj~(u&1e
z@=TeJat2)e-WSHr+C;Ftrb1A@Czjb8{a&sKx`K*Dww}K_H1b^so?Ut-i=NBuzEusHYF{YJS^@GIC%FR8VQ66TB7LCJ7VJ@Iq7BEl$NL8w
z1j)u*?&)YqW`5cq2#4eE3cVb+O6sSQc5bj-;O(BRP-HmkC9QC4ELAzbmXLYVYd+2A
zQwn<_tqLXnyS-vb!V7>RhX?z={en!m$j#x!8F9Swm%T<_&JMlDU3iOckgq+j;ZWWt
z8yI%^D{BHE2^6*0s+v@EmcE(XH#di2lsi|K#&N|VKJK)&hZ?eY^epBQH}>0NU`_CiHP^
zx#gRAc)mmL4a*ljXeLtwjxB|KZ5OtI43@DKUsiyM%FCO>pNRsLP-HmBd&0_UEyqcS
z90~v%=-SLvZreAYFc+KTkf1a`QguUOCBuhC4Vam>`rJjp!4xTFw*2fp;K%AHBg|Ox
z`DP;xG^gqJ;Q^BO*(>1vkMrPeS-9MZv375<_ssS<;i&cRT-^60NWC3@b_8Eqs+4~)
zQk7g@F7ri|vaE%vPR#Ux(Tro0Iw9X|nj>cb8Ih?pAq0yzM~b%=6&$VT%PlGwkhi}I
z2*IL-*c&|}73WY;bCOv^kQW^cBzWA`BGL0f{g(^DTR*F5f^qxb@&=tb?4qlAF(Vr&
zW1Dx${A7k~lZPg>+%1FFh9Na6PJfZ>Z9toqWLz%)xkP;F|5S^z5|G3qd=6{FPxmU0
z)2^&GL_hi-KZ+0wiOihDL(BeZ)!g)|%s(Vwpb)_cf^h+x?Q~vRox5z-?&&lSHtqKB
zqSMGqf2+~i4a;4ex~D;Uq2q#^+i7$R&`5PSy6OIsGvlls1M2}Yjk`!UFD!$+Km>VM
z0qQ1?kzo>IHIDNxzTw{?ZV8FL
z+iH|U9{F}XaHTXPsqtBw;M8xF`#Z@{C;*~9Q}1eXjR{dmjh-o)g@>>J
z(v>Hi`cAz>P)3p!(JoxTZG92pIk+_JxuIn_b<}-5F;?MILx2kVBCc*@89>B+2`}FP
zpr6Qb_@du;{jc$sKC8?Qfy(fvb%#%yZrgx_b#iS^33j!&t92ap-W=W{yPs0gJMzBBXKuwhZ*!l^U0_T3LGfoYZrN
zWBuB}h{mnT&J~f0$*J&3T>iHjz|ov67_=A!>h;tDU}!CEr^biwrv{26dwSA9DL%`&
z;eTO_Rl$fSkz|X=D1486hq!XhXqg0ZDITy7&wsSZxtUCLD1GvWucP-C57Pyo{A@Q(
z%?}6w{F-yQ?68GTfDGc8wE2G8e*2oTJ&`ImpWDQf_vO!me1
zzFCSH0Y2V4cfI+@K?5hhj$K2-=mo@E`tvz8;(Jv<-b|N!T|^ZQ6t!%u!0I|gl+)SW
z>5BpuMA;JHU?S&>>>K!Q5e4c+lm)ul0!-|o`EG$Uv@N1~Xjn@uO@QZcTvy7I1RiEaD8%?Rs#B&r7MdezPt1*NIna&v1XTp~YYFPRvD(
z)MX`Ab0}ByYu}X4P`==itVc+1Pf5xa3mr$S0o0-(Lrahlu)7%!_Uy8`6r|v77m2>~
z%%bEzSNKge0g1EmCzv}O?1ww+3;79C0A9B=K~Jllk!9izd%eG6DY0|sEx%}UuLdP{
zYyShdHSVpWGj$pV&05?XG?n+x8WYIlI5QuxopQ0M7TZKIr85D6p7r^eo%F5e(A_71
zjE;|D(^KbcAH5^uyR|*(I~p9Su=D_YsAcqoyYU_+x%k1h%^>7)u@!}Nkbh6{Q2Ux?v67n2>Il*Ik^d3=&v
zWbZbNd;^>Eo^9)Ua3|DWBugcv*NZ5)Pbi!WxIk+n?J@vzKVlVa(xS5>b~)a{VhqcIY<9l#}qHeq8>7NEAt-eUWFX?bEfWR8$Bi
zHS)khZa}`uqQpLxyv2b;g$qrYY$>LIJg+uZV(#nIqHM$UL3t;KT7^e{65=0}u-?Do
zo-F-vD@HhQnTc~WP`YJ7Cj%g!#ObJp7+T)!tafnsBEY{!0X(wz)bi{EoT^RT)}R@!
zSZ&1=3Z*+}`AnUw*M-sm>DIPz&D|SZ;$uB;XD0)&RX|@P0pR=)
zarbW1vq3ChEq#tcSX*Dm#->KVZ5ig;Y?K5bsu${xG44xn_0ZdQ9c63m{Nn|$2(Cx5
zr~F=2yTG32{;AKAZ&C=D@A@63_qIBXd5as?_TQY{9Gj2rDe%VtnI^x1SXisrF@W^z
z_xSysoqZUvWyBCefa<^)yJueb_M6u|cswe^3{cY``v5dx0!X%L776D}1Rry7{Fx@6
z2BMAg@<{1FvR4-HPzgdx&RL^Pod0P2C-nEew&x=HuO5*9&IIu9p8S8}4ET@k@IP*wD+`5*u0KfnI}#X$bACh*@((|>+_&$agNt?K`+
gk@=m%R{X{x4lz1e)yIwouEb%eXL=iT^ZxVy0?RSU-T(jq
literal 0
HcmV?d00001
diff --git a/app/lib/weapp/uniform_message.go b/app/lib/weapp/uniform_message.go
new file mode 100644
index 0000000..31cdb42
--- /dev/null
+++ b/app/lib/weapp/uniform_message.go
@@ -0,0 +1,73 @@
+package weapp
+
+const (
+ apiSendUniformMessage = "/cgi-bin/message/wxopen/template/uniform_send"
+)
+
+// UniformMsgData 模板消息内容
+type UniformMsgData map[string]UniformMsgKeyword
+
+// UniformMsgKeyword 关键字
+type UniformMsgKeyword struct {
+ Value string `json:"value"`
+ Color string `json:"color,omitempty"`
+}
+
+// UniformWeappTmpMsg 小程序模板消息
+type UniformWeappTmpMsg struct {
+ TemplateID string `json:"template_id"`
+ Page string `json:"page"`
+ FormID string `json:"form_id"`
+ Data UniformMsgData `json:"data"`
+ EmphasisKeyword string `json:"emphasis_keyword,omitempty"`
+}
+
+// UniformMsgMiniprogram 小程序
+type UniformMsgMiniprogram struct {
+ AppID string `json:"appid"`
+ PagePath string `json:"pagepath"`
+}
+
+// UniformMpTmpMsg 公众号模板消息
+type UniformMpTmpMsg struct {
+ AppID string `json:"appid"`
+ TemplateID string `json:"template_id"`
+ URL string `json:"url"`
+ Miniprogram UniformMsgMiniprogram `json:"miniprogram"`
+ Data UniformMsgData `json:"data"`
+}
+
+// Miniprogram 小程序
+type Miniprogram struct {
+ AppID string `json:"appid"`
+ PagePath string `json:"pagepath"`
+}
+
+// UniformMsgSender 统一服务消息
+type UniformMsgSender struct {
+ ToUser string `json:"touser"` // 用户 openid
+ UniformWeappTmpMsg UniformWeappTmpMsg `json:"weapp_template_msg,omitempty"`
+ UniformMpTmpMsg UniformMpTmpMsg `json:"mp_template_msg,omitempty"`
+}
+
+// Send 统一服务消息
+//
+// token access_token
+func (msg *UniformMsgSender) Send(token string) (*CommonError, error) {
+ api := baseURL + apiSendUniformMessage
+ return msg.send(api, token)
+}
+
+func (msg *UniformMsgSender) send(api, token string) (*CommonError, error) {
+ api, err := tokenAPI(api, token)
+ if err != nil {
+ return nil, err
+ }
+
+ res := new(CommonError)
+ if err := postJSON(api, msg, res); err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
diff --git a/app/lib/weapp/uniform_message_test.go b/app/lib/weapp/uniform_message_test.go
new file mode 100644
index 0000000..2ad80f1
--- /dev/null
+++ b/app/lib/weapp/uniform_message_test.go
@@ -0,0 +1,102 @@
+package weapp
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestSendUniformMessage(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ if r.Method != "POST" {
+ t.Fatalf("Expect 'POST' get '%s'", r.Method)
+ }
+
+ path := r.URL.EscapedPath()
+ if path != apiSendUniformMessage {
+ t.Fatalf("Except to path '%s',get '%s'", apiSendUniformMessage, path)
+ }
+
+ if err := r.ParseForm(); err != nil {
+ t.Fatal(err)
+ }
+
+ if r.Form.Get("access_token") == "" {
+ t.Fatalf("access_token can not be empty")
+ }
+
+ params := struct {
+ ToUser string `json:"touser"` // 用户 openid
+
+ UniformWeappTmpMsg struct {
+ TemplateID string `json:"template_id"`
+ Page string `json:"page"`
+ FormID string `json:"form_id"`
+ Data map[string]struct {
+ Value string `json:"value"`
+ } `json:"data"`
+ EmphasisKeyword string `json:"emphasis_keyword"`
+ } `json:"weapp_template_msg"`
+ UniformMpTmpMsg struct {
+ AppID string `json:"appid"`
+ TemplateID string `json:"template_id"`
+ URL string `json:"url"`
+ Miniprogram struct {
+ AppID string `json:"appid"`
+ PagePath string `json:"pagepath"`
+ } `json:"miniprogram"`
+ Data map[string]struct {
+ Value string `json:"value"`
+ Color string `json:"color,omitempty"`
+ } `json:"data"`
+ } `json:"mp_template_msg"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+ t.Fatal(err)
+ }
+
+ if params.ToUser == "" {
+ t.Fatal("param touser can not be empty")
+ }
+
+ w.WriteHeader(http.StatusOK)
+
+ raw := `{
+ "errcode": 0,
+ "errmsg": "ok"
+ }`
+ if _, err := w.Write([]byte(raw)); err != nil {
+ t.Fatal(err)
+ }
+ }))
+ defer ts.Close()
+
+ sender := UniformMsgSender{
+ ToUser: "mock-open-id",
+ UniformWeappTmpMsg: UniformWeappTmpMsg{
+ TemplateID: "mock-template-id",
+ Page: "mock-page",
+ FormID: "mock-form-id",
+ Data: UniformMsgData{
+ "mock-keyword": UniformMsgKeyword{Value: "mock-value"},
+ },
+ EmphasisKeyword: "mock-keyword.DATA",
+ },
+ UniformMpTmpMsg: UniformMpTmpMsg{
+ AppID: "mock-app-id",
+ TemplateID: "mock-template-id",
+ URL: "mock-url",
+ Miniprogram: UniformMsgMiniprogram{"mock-miniprogram-app-id", "mock-page-path"},
+ Data: UniformMsgData{
+ "mock-keyword": UniformMsgKeyword{"mock-value", "mock-color"},
+ },
+ },
+ }
+
+ _, err := sender.send(ts.URL+apiSendUniformMessage, "mock-access-token")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/app/lib/weapp/updatable_message.go b/app/lib/weapp/updatable_message.go
new file mode 100644
index 0000000..d6016fa
--- /dev/null
+++ b/app/lib/weapp/updatable_message.go
@@ -0,0 +1,99 @@
+package weapp
+
+const (
+ apiCreateActivityID = "/cgi-bin/message/wxopen/activityid/create"
+ apiSetUpdatableMsg = "/cgi-bin/message/wxopen/updatablemsg/send"
+)
+
+// CreateActivityIDResponse 动态消息
+type CreateActivityIDResponse struct {
+ CommonError
+ ActivityID string `json:"activity_id"` // 动态消息的 ID
+ ExpirationTime uint `json:"expiration_time"` // activity_id 的过期时间戳。默认24小时后过期。
+}
+
+// CreateActivityID 创建被分享动态消息的 activity_id。
+// token 接口调用凭证
+func CreateActivityID(token string) (*CreateActivityIDResponse, error) {
+ api := baseURL + apiCreateActivityID
+ return createActivityID(api, token)
+}
+
+func createActivityID(api, token string) (*CreateActivityIDResponse, error) {
+ api, err := tokenAPI(api, token)
+ if err != nil {
+ return nil, err
+ }
+
+ res := new(CreateActivityIDResponse)
+ if err := getJSON(api, res); err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
+
+// UpdatableMsgTempInfo 动态消息对应的模板信息
+type UpdatableMsgTempInfo struct {
+ ParameterList []UpdatableMsgParameter `json:"parameter_list"` // 模板中需要修改的参数列表
+}
+
+// UpdatableMsgParameter 参数
+// member_count target_state = 0 时必填,文字内容模板中 member_count 的值
+// room_limit target_state = 0 时必填,文字内容模板中 room_limit 的值
+// path target_state = 1 时必填,点击「进入」启动小程序时使用的路径。
+// 对于小游戏,没有页面的概念,可以用于传递查询字符串(query),如 "?foo=bar"
+// version_type target_state = 1 时必填,点击「进入」启动小程序时使用的版本。
+// 有效参数值为:develop(开发版),trial(体验版),release(正式版)
+type UpdatableMsgParameter struct {
+ Name UpdatableMsgParamName `json:"name"` // 要修改的参数名
+ Value string `json:"value"` // 修改后的参数值
+}
+
+// UpdatableMsgTargetState 动态消息修改后的状态
+type UpdatableMsgTargetState = uint8
+
+// 动态消息状态
+const (
+ UpdatableMsgJoining UpdatableMsgTargetState = iota // 未开始
+ UpdatableMsgStarted // 已开始
+)
+
+// UpdatableMsgParamName 参数 name 的合法值
+type UpdatableMsgParamName = string
+
+// 动态消息状态
+const (
+ UpdatableMsgParamMemberCount UpdatableMsgParamName = "member_count" // target_state = 0 时必填,文字内容模板中 member_count 的值
+ UpdatableMsgParamRoomLimit = "room_limit" // target_state = 0 时必填,文字内容模板中 room_limit 的值
+ UpdatableMsgParamPath = "path" // target_state = 1 时必填,点击「进入」启动小程序时使用的路径。 对于小游戏,没有页面的概念,可以用于传递查询字符串(query),如 "?foo=bar"
+ UpdatableMsgParamVersionType = "version_type" // target_state = 1 时必填,点击「进入」启动小程序时使用的版本。有效参数值为:develop(开发版),trial(体验版),release(正式版)
+)
+
+// UpdatableMsgSetter 动态消息
+type UpdatableMsgSetter struct {
+ ActivityID string `json:"activity_id"` // 动态消息的 ID,通过 updatableMessage.createActivityId 接口获取
+ TargetState UpdatableMsgTargetState `json:"target_state"` // 动态消息修改后的状态(具体含义见后文)
+ TemplateInfo UpdatableMsgTempInfo `json:"template_info"` // 动态消息对应的模板信息
+}
+
+// Set 修改被分享的动态消息。
+// accessToken 接口调用凭证
+func (setter *UpdatableMsgSetter) Set(token string) (*CommonError, error) {
+ api := baseURL + apiSetUpdatableMsg
+ return setter.set(api, token)
+}
+
+func (setter *UpdatableMsgSetter) set(api, token string) (*CommonError, error) {
+ api, err := tokenAPI(api, token)
+ if err != nil {
+ return nil, err
+ }
+
+ res := new(CommonError)
+ if err := postJSON(api, setter, res); err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
diff --git a/app/lib/weapp/updatable_message_test.go b/app/lib/weapp/updatable_message_test.go
new file mode 100644
index 0000000..3e200d3
--- /dev/null
+++ b/app/lib/weapp/updatable_message_test.go
@@ -0,0 +1,129 @@
+package weapp
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestCreateActivityID(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ if r.Method != "GET" {
+ t.Fatalf("Expect 'GET' get '%s'", r.Method)
+ }
+
+ realPath := r.URL.EscapedPath()
+ expectPath := "/cgi-bin/message/wxopen/activityid/create"
+ if realPath != expectPath {
+ t.Fatalf("Expect to path '%s',get '%s'", expectPath, realPath)
+ }
+
+ if err := r.ParseForm(); err != nil {
+ t.Fatal(err)
+ }
+
+ if r.Form.Get("access_token") == "" {
+ t.Fatalf("access_token can not be empty")
+ }
+
+ w.WriteHeader(http.StatusOK)
+
+ raw := `{
+ "expiration_time": 1000,
+ "activity_id": "ok",
+ "errcode": 0,
+ "errmsg": "ok"
+ }`
+ if _, err := w.Write([]byte(raw)); err != nil {
+ t.Fatal(err)
+ }
+ }))
+ defer ts.Close()
+
+ res, err := createActivityID(ts.URL+apiCreateActivityID, "mock-access-token")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if res.ActivityID == "" {
+ t.Error("Response column activity_id can not be empty")
+ }
+
+ if res.ExpirationTime == 0 {
+ t.Error("Response column expiration_time can not be zero")
+ }
+}
+
+func TestSetUpdatableMsg(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ if r.Method != "POST" {
+ t.Fatalf("Expect 'POST' get '%s'", r.Method)
+ }
+
+ realPath := r.URL.EscapedPath()
+ expectPath := "/cgi-bin/message/wxopen/updatablemsg/send"
+ if realPath != expectPath {
+ t.Fatalf("Expect to path '%s',get '%s'", expectPath, realPath)
+ }
+
+ if err := r.ParseForm(); err != nil {
+ t.Fatal(err)
+ }
+
+ if r.Form.Get("access_token") == "" {
+ t.Fatalf("access_token can not be empty")
+ }
+
+ params := struct {
+ ActivityID string `json:"activity_id"`
+ TargetState uint8 `json:"target_state"`
+ TemplateInfo struct {
+ ParameterList []struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+ } `json:"parameter_list"`
+ } `json:"template_info"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+ t.Fatal(err)
+ }
+
+ if params.ActivityID == "" {
+ t.Fatal("param activity_id can not be empty")
+ }
+
+ if len(params.TemplateInfo.ParameterList) == 0 {
+ t.Fatal("param template_info.parameter_list can not be empty")
+ }
+
+ w.WriteHeader(http.StatusOK)
+
+ raw := `{
+ "errcode": 0,
+ "errmsg": "ok"
+ }`
+ if _, err := w.Write([]byte(raw)); err != nil {
+ t.Fatal(err)
+ }
+ }))
+ defer ts.Close()
+
+ setter := UpdatableMsgSetter{
+ "mock-activity-id",
+ UpdatableMsgJoining,
+ UpdatableMsgTempInfo{
+ []UpdatableMsgParameter{
+ {UpdatableMsgParamMemberCount, "mock-parameter-value-number"},
+ {UpdatableMsgParamRoomLimit, "mock-parameter-value-number"},
+ },
+ },
+ }
+
+ _, err := setter.set(ts.URL+apiSetUpdatableMsg, "mock-access-token")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/app/lib/weapp/util.go b/app/lib/weapp/util.go
new file mode 100644
index 0000000..edf9b46
--- /dev/null
+++ b/app/lib/weapp/util.go
@@ -0,0 +1,149 @@
+package weapp
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/json"
+ "io"
+ "log"
+ "math/rand"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "os"
+ "time"
+)
+
+// tokenAPI 获取带 token 的 API 地址
+func tokenAPI(api, token string) (string, error) {
+ queries := requestQueries{
+ "access_token": token,
+ }
+
+ return encodeURL(api, queries)
+}
+
+// encodeURL add and encode parameters.
+func encodeURL(api string, params requestQueries) (string, error) {
+ url, err := url.Parse(api)
+ if err != nil {
+ return "", err
+ }
+
+ query := url.Query()
+
+ for k, v := range params {
+ query.Set(k, v)
+ }
+
+ url.RawQuery = query.Encode()
+
+ return url.String(), nil
+}
+
+// randomString random string generator
+//
+// ln length of return string
+func randomString(ln int) string {
+ letters := []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ b := make([]rune, ln)
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ for i := range b {
+ b[i] = letters[r.Intn(len(letters))]
+ }
+
+ return string(b)
+}
+
+// postJSON perform a HTTP/POST request with json body
+func postJSON(url string, params interface{}, response interface{}) error {
+ resp, err := postJSONWithBody(url, params)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return json.NewDecoder(resp.Body).Decode(response)
+}
+
+func getJSON(url string, response interface{}) error {
+ resp, err := httpClient().Get(url)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ return json.NewDecoder(resp.Body).Decode(response)
+}
+
+// postJSONWithBody return with http body.
+func postJSONWithBody(url string, params interface{}) (*http.Response, error) {
+ b := &bytes.Buffer{}
+ if params != nil {
+ enc := json.NewEncoder(b)
+ enc.SetEscapeHTML(false)
+ err := enc.Encode(params)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return httpClient().Post(url, "application/json; charset=utf-8", b)
+}
+
+func postFormByFile(url, field, filename string, response interface{}) error {
+ // Add your media file
+ file, err := os.Open(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ return postForm(url, field, filename, file, response)
+}
+
+func postForm(url, field, filename string, reader io.Reader, response interface{}) error {
+ // Prepare a form that you will submit to that URL.
+ buf := new(bytes.Buffer)
+ w := multipart.NewWriter(buf)
+ fw, err := w.CreateFormFile(field, filename)
+ if err != nil {
+ return err
+ }
+
+ if _, err = io.Copy(fw, reader); err != nil {
+ return err
+ }
+
+ // Don't forget to close the multipart writer.
+ // If you don't close it, your request will be missing the terminating boundary.
+ w.Close()
+
+ // Now that you have a form, you can submit it to your handler.
+ req, err := http.NewRequest("POST", url, buf)
+ if err != nil {
+ return err
+ }
+ // Don't forget to set the content type, this will contain the boundary.
+ req.Header.Set("Content-Type", w.FormDataContentType())
+
+ // Submit the request
+ client := httpClient()
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ return json.NewDecoder(resp.Body).Decode(response)
+}
+
+func httpClient() *http.Client {
+ log.Print("myweapp use http")
+ return &http.Client{
+ Timeout: 10 * time.Second,
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ },
+ }
+}
diff --git a/app/lib/weapp/weapp.go b/app/lib/weapp/weapp.go
new file mode 100644
index 0000000..5577192
--- /dev/null
+++ b/app/lib/weapp/weapp.go
@@ -0,0 +1,12 @@
+package weapp
+
+const (
+ // baseURL 微信请求基础URL
+ baseURL = "https://api.weixin.qq.com"
+)
+
+// POST 参数
+type requestParams map[string]interface{}
+
+// URL 参数
+type requestQueries map[string]string
diff --git a/app/lib/wechat/.gitignore b/app/lib/wechat/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/app/lib/wechat/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/app/lib/wxpay/.gitignore b/app/lib/wxpay/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/app/lib/wxpay/api.go b/app/lib/wxpay/api.go
new file mode 100644
index 0000000..275623d
--- /dev/null
+++ b/app/lib/wxpay/api.go
@@ -0,0 +1,303 @@
+package wxpay
+
+import (
+ "applet/app/utils"
+ "applet/app/utils/logx"
+ "fmt"
+ "github.com/iGoogle-ink/gopay"
+ "github.com/iGoogle-ink/gopay/pkg/util"
+ "github.com/iGoogle-ink/gopay/wechat"
+ v3 "github.com/iGoogle-ink/gopay/wechat/v3"
+ "strconv"
+ "time"
+)
+
+func NewClient(appId, mchId, apiKey string, isProd bool) *wechat.Client {
+ // 初始化微信客户端
+ // appId:应用ID
+ // mchId:商户ID
+ // apiKey:API秘钥值
+ // isProd:是否是正式环境
+ client := wechat.NewClient(appId, mchId, apiKey, isProd)
+ // 打开Debug开关,输出请求日志,默认关闭
+ client.DebugSwitch = gopay.DebugOn
+ // 设置国家:不设置默认 中国国内
+ // wechat.China:中国国内
+ // wechat.China2:中国国内备用
+ // wechat.SoutheastAsia:东南亚
+ // wechat.Other:其他国家
+ client.SetCountry(wechat.China)
+ // 添加微信证书 Path 路径
+ // certFilePath:apiclient_cert.pem 路径
+ // keyFilePath:apiclient_key.pem 路径
+ // pkcs12FilePath:apiclient_cert.p12 路径
+ // 返回err
+ //client.AddCertFilePath()
+
+ // 添加微信证书内容 Content
+ // certFileContent:apiclient_cert.pem 内容
+ // keyFileContent:apiclient_key.pem 内容
+ // pkcs12FileContent:apiclient_cert.p12 内容
+ // 返回err
+ //client.AddCertFileContent()
+ return client
+}
+
+// TradeAppPay is 微信APP支付
+func TradeAppPay(client *wechat.Client, subject, orderID, amount, notifyUrl string) (map[string]string, error) {
+ // 初始化 BodyMap
+ bm := make(gopay.BodyMap)
+ bm.Set("nonce_str", util.GetRandomString(32)).
+ Set("body", subject).
+ Set("out_trade_no", orderID).
+ Set("total_fee", amount).
+ Set("spbill_create_ip", "127.0.0.1").
+ Set("notify_url", notifyUrl).
+ Set("trade_type", wechat.TradeType_App).
+ Set("sign_type", wechat.SignType_MD5)
+ /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
+ // 预下单
+ wxRsp, err := client.UnifiedOrder(bm)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ //if !ok {
+ // return nil, errors.New("验签失败")
+ //}
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
+ paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
+ res := map[string]string{
+ "appid": client.AppId,
+ "partnerid": client.MchId,
+ "prepayid": wxRsp.PrepayId,
+ "sign": paySign,
+ "package": "Sign=WXPay",
+ "noncestr": wxRsp.NonceStr,
+ "timestamp": timeStamp,
+ }
+ return res, nil
+}
+
+// TradeAppPay is 微信H5支付
+func TradeH5Pay(client *wechat.Client, subject, orderID, amount, notifyUrl string) (map[string]string, error) {
+ // 初始化 BodyMap
+ bm := make(gopay.BodyMap)
+ bm.Set("nonce_str", util.GetRandomString(32)).
+ Set("body", subject).
+ Set("out_trade_no", orderID).
+ Set("total_fee", amount).
+ Set("spbill_create_ip", "121.196.29.49").
+ Set("notify_url", notifyUrl).
+ Set("trade_type", wechat.TradeType_H5).
+ Set("sign_type", wechat.SignType_MD5).
+ SetBodyMap("scene_info", func(bm gopay.BodyMap) {
+ bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) {
+ bm.Set("type", "Wap")
+ bm.Set("wap_url", "https://www.fumm.cc")
+ bm.Set("wap_name", "zyos")
+ })
+ })
+ /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
+ // 预下单
+ wxRsp, err := client.UnifiedOrder(bm)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
+ packages := "prepay_id=" + wxRsp.PrepayId
+ paySign := wechat.GetH5PaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey)
+ fmt.Println("paySign===", paySign)
+ r := map[string]string{
+ "redirect_url": wxRsp.MwebUrl,
+ }
+ return r, nil
+}
+
+// TradeMiniProgPay is 微信小程序支付 ☑️
+func TradeMiniProgPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) {
+ // 初始化 BodyMap
+ bm := make(gopay.BodyMap)
+ bm.Set("nonce_str", util.GetRandomString(32)).
+ Set("body", subject).
+ Set("openid", openid).
+ Set("out_trade_no", orderID).
+ Set("total_fee", amount).
+ Set("spbill_create_ip", "127.0.0.1").
+ Set("notify_url", notifyUrl).
+ Set("trade_type", wechat.TradeType_Mini).
+ Set("sign_type", wechat.SignType_MD5)
+ // 预下单
+ wxRsp, err := client.UnifiedOrder(bm)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ fmt.Println(wxRsp)
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
+ packages := "prepay_id=" + wxRsp.PrepayId
+ paySign := wechat.GetMiniPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey)
+ res := map[string]string{
+ "appId": client.AppId,
+ "paySign": paySign,
+ "signType": wechat.SignType_MD5,
+ "package": packages,
+ "nonceStr": wxRsp.NonceStr,
+ "timeStamp": timeStamp,
+ }
+ return res, nil
+}
+
+// TradeAppPayV3 is 微信APP支付v3
+func TradeAppPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (map[string]string, error) {
+ // 初始化 BodyMap
+ amountNew := utils.AnyToFloat64(amount) * 100
+ bm := make(gopay.BodyMap)
+ bm.Set("nonce_str", util.GetRandomString(32)).
+ Set("body", subject).
+ Set("out_trade_no", orderID).
+ Set("total_fee", amountNew).
+ Set("spbill_create_ip", "127.0.0.1").
+ Set("notify_url", notifyUrl).
+ Set("trade_type", wechat.TradeType_App).
+ Set("sign_type", wechat.SignType_MD5)
+ /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
+ //// 预下单
+ //wxRsp, err := v3.UnifiedOrder(bm)
+ //if err != nil {
+ // _ = logx.Warn(err)
+ // return nil, err
+ //}
+ //_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
+ //if err != nil {
+ // _ = logx.Warn(err)
+ // return nil, err
+ //}
+ ////if !ok {
+ //// return nil, errors.New("验签失败")
+ ////}
+ //timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
+ //paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
+ //res := map[string]string{
+ // "appid": client.AppId,
+ // "partnerid": client.MchId,
+ // "prepayid": wxRsp.PrepayId,
+ // "sign": paySign,
+ // "package": "Sign=WXPay",
+ // "noncestr": wxRsp.NonceStr,
+ // "timestamp": timeStamp,
+ //}
+ //return res, nil
+ return nil, nil
+}
+
+//// TradeJSAPIPay is 微信JSAPI支付
+func TradeJSAPIPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) {
+ // 初始化 BodyMap
+ bm := make(gopay.BodyMap)
+ bm.Set("nonce_str", util.GetRandomString(32)).
+ Set("body", subject).
+ Set("out_trade_no", orderID).
+ Set("total_fee", amount).
+ Set("spbill_create_ip", "121.196.29.49").
+ Set("notify_url", notifyUrl).
+ Set("trade_type", wechat.TradeType_JsApi).
+ Set("sign_type", wechat.SignType_MD5).
+ Set("openid", openid).
+ SetBodyMap("scene_info", func(bm gopay.BodyMap) {
+ bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) {
+ bm.Set("type", "Wap")
+ bm.Set("wap_url", "https://www.fumm.cc")
+ bm.Set("wap_name", "zyos")
+ })
+ })
+ // 预下单
+ wxRsp, err := client.UnifiedOrder(bm)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
+ if err != nil {
+ _ = logx.Warn(err)
+ return nil, err
+ }
+ //if !ok {
+ // return nil, errors.New("验签失败")
+ //}
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
+ //paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
+ packages := "prepay_id=" + wxRsp.PrepayId
+ paySign := wechat.GetJsapiPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey)
+
+ logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId)
+ logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId)
+ logx.Info("wxRsp.PrepayId:" + openid)
+ res := map[string]string{
+ "appid": client.AppId,
+ "partnerid": client.MchId,
+ "prepayid": wxRsp.PrepayId,
+ "sign": paySign,
+ "package": "prepay_id=" + wxRsp.PrepayId,
+ "noncestr": wxRsp.NonceStr,
+ "timestamp": timeStamp,
+ }
+ return res, nil
+}
+
+// TradeH5PayV3 is 微信H5支付v3
+func TradeH5PayV3(client *wechat.Client, subject, orderID, amount, notifyUrl string) (string, error) {
+ // 初始化 BodyMap
+ bm := make(gopay.BodyMap)
+ bm.Set("nonce_str", util.GetRandomString(32)).
+ Set("body", subject).
+ Set("out_trade_no", orderID).
+ Set("total_fee", amount).
+ Set("spbill_create_ip", "127.0.0.1").
+ Set("notify_url", notifyUrl).
+ Set("trade_type", wechat.TradeType_App).
+ Set("device_info", "WEB").
+ Set("sign_type", wechat.SignType_MD5).
+ SetBodyMap("scene_info", func(bm gopay.BodyMap) {
+ bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) {
+ bm.Set("type", "Wap")
+ bm.Set("wap_url", "https://www.fumm.cc")
+ bm.Set("wap_name", "H5测试支付")
+ })
+ }) /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
+ // 预下单
+ wxRsp, err := client.UnifiedOrder(bm)
+ if err != nil {
+ _ = logx.Warn(err)
+ return "", err
+ }
+ // ====APP支付 paySign====
+ timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
+ // 获取APP支付的 paySign
+ // 注意:package 参数因为是固定值,无需开发者再传入
+ // appId:AppID
+ // partnerid:partnerid
+ // nonceStr:随机字符串
+ // prepayId:统一下单成功后得到的值
+ // signType:签名方式,务必与统一下单时用的签名方式一致
+ // timeStamp:时间
+ // apiKey:API秘钥值
+ paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
+ return paySign, nil
+}
+
+// TradeMiniProgPayV3 is 微信小程序支付v3
+func TradeMiniProgPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (string, error) {
+ return "", nil
+}
diff --git a/app/md/alipay.go b/app/md/alipay.go
new file mode 100644
index 0000000..0eafa67
--- /dev/null
+++ b/app/md/alipay.go
@@ -0,0 +1,69 @@
+package md
+
+// AliPayCallback 支付宝的回调结构体
+type AliPayCallback struct {
+ AppID string `json:"app_id"`
+ AuthAppID string `json:"auth_app_id"`
+ BuyerID string `json:"buyer_id"`
+ BuyerLogonID string `json:"buyer_logon_id"`
+ BuyerPayAmount string `json:"buyer_pay_amount"`
+ Charset string `json:"charset"`
+ FundBillList string `json:"fund_bill_list"`
+ GmtCreate string `json:"gmt_create"`
+ GmtPayment string `json:"gmt_payment"`
+ InvoiceAmount string `json:"invoice_amount"`
+ OrderType string `json:"order_type"`
+ MasterID string `json:"master_id"`
+ NotifyID string `json:"notify_id"`
+ NotifyTime string `json:"notify_time"`
+ NotifyType string `json:"notify_type"`
+ OutTradeNo string `json:"out_trade_no"`
+ PassbackParams string `json:"passback_params"`
+ PointAmount string `json:"point_amount"`
+ ReceiptAmount string `json:"receipt_amount"`
+ SellerEmail string `json:"seller_email"`
+ SellerID string `json:"seller_id"`
+ Sign string `json:"sign"`
+ SignType string `json:"sign_type"`
+ Subject string `json:"subject"`
+ TotalAmount string `json:"total_amount"`
+ TradeNo string `json:"trade_no"`
+ TradeStatus string `json:"trade_status"`
+ Version string `json:"version"`
+ PayMethod string `json:"pay_method"`
+}
+
+type AliPayPayParams struct {
+ Subject string `json:"subject" binding:"required"`
+ Amount string `json:"amount" binding:"required"`
+ OrderType string `json:"order_type" binding:"required"`
+ OrdId string `json:"ord_id"`
+ Uid string `json:"uid"`
+ Phone string `json:"phone"`
+}
+type PayData struct {
+ PayAppCertSn string `json:"pay_app_cert_sn"`
+ PayAlipayRootCertSn string `json:"pay_alipay_root_cert_sn"`
+ PayAlipayrsaPublicKey string `json:"pay_alipayrsa_public_key"`
+ PayAliUseType string `json:"pay_ali_use_type"`
+ PriKey string `json:"pay_ali_new_private_key"`
+}
+
+type AlipayUserCertdocCertverifyPreconsult struct {
+ AlipayUserCertdocCertverifyPreconsultResponse struct {
+ Code string `json:"code"`
+ Msg string `json:"msg"`
+ VerifyID string `json:"verify_id"`
+ } `json:"alipay_user_certdoc_certverify_preconsult_response"`
+ Sign string `json:"sign"`
+}
+type AlipayUserCertdocCertverifyConsult struct {
+ AlipayUserCertdocCertverifyConsultResponse struct {
+ Code string `json:"code"`
+ FailParams string `json:"fail_params"`
+ FailReason string `json:"fail_reason"`
+ Msg string `json:"msg"`
+ Passed string `json:"passed"`
+ } `json:"alipay_user_certdoc_certverify_consult_response"`
+ Sign string `json:"sign"`
+}
diff --git a/app/md/app_redis_key.go b/app/md/app_redis_key.go
new file mode 100644
index 0000000..3ffa25f
--- /dev/null
+++ b/app/md/app_redis_key.go
@@ -0,0 +1,23 @@
+package md
+
+// 缓存key统一管理, %s格式化为masterId
+const (
+ AppCfgCacheKey = "%s:app_cfg_cache:" // 占位符: masterId, key的第一个字母
+ VirtualCoinCfgCacheKey = "%s:virtual_coin_cfg"
+ PlanRewardCfgCacheKey = "%s:plan_reward_cfg"
+ UnionSetCacheCfg = "%s:union_set_cfg:%s" // 联盟设置缓存key
+
+ UserFinValidUpdateLock = "%s:user_fin_valid_update_lock:%s" // 用户余额更新锁(能拿到锁才能更新余额)
+
+ WithdrawApplyQueueListKey = "withdraw_apply_queue" // 提现队列
+
+ TplBottomNavRedisKey = "%s:tpl_nav_bottom_key:%s" // master_id platform
+
+ SysModByIdRedisKey = "%s:sys_mod_tpl_by_id:%s"
+
+ AppLimiterLock = "%s:ZhiOs_app_limiter_lock:%s" // 限流器锁
+ DealAppLimiterRequestIdPrefix = "%s:ZhiOs_app_comm_limiter_request_id:%s"
+ DealAppNewcomersLimiterRequestIdPrefix = "%s:ZhiOs_app_newcomers_limiter_request_id:%s"
+
+ CfgCacheTime = 60 * 60 * 4
+)
diff --git a/app/md/cfg_key.go b/app/md/cfg_key.go
new file mode 100644
index 0000000..f93828d
--- /dev/null
+++ b/app/md/cfg_key.go
@@ -0,0 +1,282 @@
+package md
+
+// 获取用户的缓存key
+const (
+ KEY_SYS_CFG_CACHE = "sys_cfg_cache"
+ FunctionPermissionCfgCacheKey = "%s:function_permission_cfg"
+ // 文件缓存的key
+ KEY_CFG_FILE_PVD = "file_provider" // 文件供应商
+ KEY_CFG_FILE_BUCKET = "file_bucket"
+ KEY_CFG_FILE_REGION = "file_bucket_region"
+ KEY_CFG_FILE_HOST = "file_bucket_host"
+ KEY_CFG_FILE_SCHEME = "file_bucket_scheme"
+ KEY_CFG_FILE_AK = "file_access_key"
+ KEY_CFG_FILE_SK = "file_secret_key"
+ KEY_CFG_FILE_MAX_SIZE = "file_user_upload_max_size"
+ KEY_CFG_FILE_EXT = "file_ext"
+ KEY_CFG_FILE_AVATAR_THUMBNAIL = "file_avatar_thumbnail" // 默认头像缩略图参数,宽高120px,格式webp.
+ // 智盟
+ KEY_CFG_ZM_JD_SITE_ID = "third_zm_jd_site_id" // 智盟京东联盟id
+ KEY_CFG_ZM_WEB_ID = "third_zm_web_id" // 智盟网站ID
+ KEY_CFG_ZM_AK = "third_zm_app_key"
+ KEY_CFG_ZM_SK = "third_zm_app_secret"
+ KEY_CFG_ZM_SMS_AK = "third_zm_sms_ak"
+ KEY_CFG_ZM_SMS_SK = "third_zm_sms_sk"
+ KEY_CFG_APP_NAME = "app_name"
+
+ KEY_CFG_WHITELIST = "api_cfg_whitelist" // API允许的访问的设置白名单
+
+ // 淘宝
+ KEY_CFG_TB_AUTH_AK = "third_taobao_auth_ak"
+ KEY_CFG_TB_AUTH_SK = "third_taobao_auth_sk"
+ KEY_CFG_TB_INVITER_CODE = "third_taobao_auth_inviter_code"
+ KEY_CFG_TB_AK = "third_taobao_ak"
+ KEY_CFG_TB_SK = "third_taobao_sk"
+ KEY_CFG_TB_PID = "third_taobao_pid" // 淘宝推广ID,如:mm_123_456_789,123是联盟ID,456是site_id,789是adzone_id
+ KEY_CFG_TB_SID = "third_taobao_sid" // 淘宝session id ,又称access_token
+
+ // 苏宁
+ KEY_CFG_SN_AK = "third_suning_ak"
+ KEY_CFG_SN_SK = "third_suning_sk"
+
+ KEY_CFG_JD_AK = ""
+ KEY_CFG_JD_SK = ""
+
+ KEY_CFG_KL_AK = "third_kaola_ak"
+ KEY_CFG_KL_SK = "third_kaola_sk"
+
+ KEY_CFG_VIP_AK = ""
+ KEY_CFG_VIP_SK = ""
+
+ // 自动任务配置
+ KEY_CFG_CRON_TB = "cron_order_taobao"
+ KEY_CFG_CRON_TBSETTLEORDER = "cron_order_taobao_settle_order"
+ KEY_CFG_CRON_JD = "cron_order_jd"
+ KEY_CFG_CRON_PDD = "cron_order_pdd"
+ KEY_CFG_CRON_PDD_SUCC = "cron_order_pdd_succ"
+ KEY_CFG_CRON_PDDBYCREATETIME = "cron_order_pdd_by_create_time"
+ KEY_CFG_CRON_PDDBYLOOPTIME = "cron_order_pdd_by_loop_time"
+ KEY_CFG_CRON_PDDBYLOOPMONTHTIME = "cron_order_pdd_by_loop_month_ago_time"
+ KEY_CFG_CRON_JDBYCREATETIME = "cron_order_jd_by_create_time"
+ KEY_CFG_CRON_JDBYSUCCESS = "cron_order_jd_by_success"
+ KEY_CFG_CRON_JDFAILBYCREATETIME = "cron_order_jd_fail_by_create_time"
+ KEY_CFG_CRON_PDDBYAGOTIME = "cron_order_pdd_by_ago_time"
+ KEY_CFG_CRON_PDDBYSTATUS = "cron_order_pdd_by_status"
+ KEY_CFG_CRON_PDDBYSTATUSSUCCESS = "cron_order_pdd_by_status_success"
+ KEY_CFG_CRON_PDDBYSTATUSFAIL = "cron_order_pdd_by_status_fail"
+ KEY_CFG_CRON_JDBYSTATUS = "cron_order_jd_by_status"
+ KEY_CFG_CRON_TBBYAGOTIME = "cron_order_tb_by_ago_time"
+ KEY_CFG_CRON_TBBYPAY = "cron_order_tb_by_pay"
+ KEY_CFG_CRON_TB12 = "cron_order_tb12"
+ KEY_CFG_CRON_TB13 = "cron_order_tb13"
+ KEY_CFG_CRON_TB3 = "cron_order_tb3"
+ KEY_CFG_CRON_TB14 = "cron_order_tb14"
+
+ KEY_CFG_CRON_PDDREFUND = "cron_order_pdd_refund"
+ KEY_CFG_CRON_TBREFUND = "cron_order_tb_refund"
+ KEY_CFG_CRON_WPHREFUND = "cron_order_wph_refund"
+ KEY_CFG_CRON_JDREFUND = "cron_order_jd_refund"
+ KEY_CFG_CRON_SN = "cron_order_suning"
+ KEY_CFG_CRON_VIP = "cron_order_vip"
+ KEY_CFG_CRON_KL = "cron_order_kaola"
+ KEY_CFG_CRON_DUOMAI = "cron_order_duomai"
+ KEY_CFG_CRON_HIS = "cron_order_his" // 迁移到历史订单
+ KEY_CFG_CRON_SETTLE = "cron_order_settle" //结算
+ KEY_CFG_CRON_FREE_SETTLE = "cron_order_free_settle" //结算
+ KEY_CFG_CRON_SECOND_FREE_SETTLE = "cron_order_second_free_settle"
+ KEY_CFG_CRON_THIRD_FREE_SETTLE = "cron_order_third_free_settle"
+ KEY_CFG_CRON_ACQUISTION_SETTLE = "cron_acquistion_settle" // 拉新结算
+ KEY_CFG_CRON_NEW_ACQUISTION_SETTLE = "cron_new_acquistion_settle" // 拉新结算
+ KEY_CFG_CRON_PUBLISHER = "cron_taobao_publisher" // 跟踪淘宝备案信息绑定会员运营id 针对小程序
+ KEY_CFG_CRON_AUTO_UN_FREEZE = "cron_auto_un_freeze"
+ KEY_CFG_CRON_MEITUAN = "cron_order_meituan_fxlm" //美团
+ KEY_CFG_CRON_MEITUANLM = "cron_order_meituan_lm" //美团联盟
+ KEY_CFG_CRON_MEITUANLM_START = "cron_order_meituan_lm_start" //美团联盟
+ KEY_CFG_CRON_ORDER_SUCCESS_CHECK = "cron_order_success_check"
+ KEY_CFG_CRON_MEITUAN_START = "cron_order_meituan_start" //美团联盟
+ KEY_CFG_CRON_STARBUCKS = "cron_order_starbucks" //海威星巴克
+ KEY_CFG_CRON_HWMOVIE = "cron_order_hw_movie" //海威电影票
+ KEY_CFG_CRON_MCDONALD = "cron_order_mcdonald" //海威麦当劳
+ KEY_CFG_CRON_NAYUKI = "cron_order_nayuki" //海威奈雪
+ KEY_CFG_CRON_BURGERKING = "cron_order_burger_king" //海威汉堡王
+ KEY_CFG_CRON_HEYTEA = "cron_order_heytea" //海威喜茶
+ KEY_CFG_CRON_TIKTOKLIFE = "cron_order_tik_tok_life" //
+ KEY_CFG_CRON_FAST_REFUND = "cron_order_fast_refund"
+ KEY_CFG_CRON_CHECK_GUIDE_STORE_ORDER = "cron_check_guide_store_order"
+ KEY_CFG_CRON_CHECK_BUCKLE_ORDER = "cron_check_buckle_order"
+ KEY_CFG_CRON_BUCKLE = "cron_order_buckle"
+ KEY_CFG_CRON_FAST_SUCCESS = "cron_order_fast_success"
+ KEY_CFG_CRON_PIZZA = "cron_order_pizza" //海威
+ KEY_CFG_CRON_WALLACE = "cron_order_wallace" //海威
+ KEY_CFG_CRON_TOURISM = "cron_order_tourism" //海威
+ KEY_CFG_CRON_NEAR = "cron_order_near" //海威
+ KEY_CFG_CRON_FLOWERCAKE = "cron_order_flowerCake" //海威
+ KEY_CFG_CRON_DELIVERY = "cron_order_delivery" //海威
+ KEY_CFG_CRON_TO_KFC = "cron_order_to_kfc" //
+ KEY_CFG_CRON_PAGODA = "cron_order_pagoda" //
+ KEY_CFG_CRON_LUCKIN = "cron_order_luckin" //
+ KEY_CFG_CRON_STATIONMEITUANLM = "cron_order_station_meituan_lm" //站长美团联盟
+ KEY_CFG_CRON_MEITUANOFFICIAL = "cron_order_meituan_official" //美团联盟智莺
+ KEY_CFG_CRON_OILSTATION = "cron_order_oilstation" //加油
+ KEY_CFG_CRON_BRIGHTOILSTATION = "cron_order_bright_oilstation" //加油
+ KEY_CFG_CRON_KFC = "cron_order_kfc" //肯德基
+ KEY_CFG_CRON_CINEMA = "cron_order_cinema" //电影票
+ KEY_CFG_CRON_OilRequest = "cron_order_oilrequest" //加入主动请求抓单
+ KEY_CFG_CRON_AGOTB = "cron_order_agotaobao" //n天前的淘宝订单
+ KEY_CFG_CRON_CREDIT_CARD = "cron_order_credit_card"
+ KEY_CFG_CRON_ORDER_STAT = "cron_order_stat" // 订单统计任务
+ KEY_CFG_CRON_CARD_UPDATE = "cron_card_update" // 权益卡更新
+ KEY_CFG_CRON_USER_LV_UP_SETTLE = "cron_user_lv_up_settle" //会员费订单结算
+ KEY_CFG_CRON_DUOYOUORD_SETTLE = "cron_duoyou_settle" //会员费订单结算
+ KEY_CFG_CRON_LIANLIAN_SETTLE = "cron_lianlian_settle" //会员费订单结算
+ KEY_CFG_CRON_SWIPE_SETTLE = "cron_swipe_settle"
+ KEY_CFG_CRON_AGGREGATION_RECHARGE_SETTLE = "cron_aggregation_recharge_settle"
+ KEY_CFG_CRON_ACQUISITION_CONDITION = "cron_acquisition_condition"
+ KEY_CFG_CRON_ACQUISITION_CONDITION_BY_LV = "cron_acquisition_condition_by_lv"
+ KEY_CFG_CRON_ACQUISITION_REWARD = "cron_acquisition_reward"
+ KEY_CFG_CRON_PLAYLET_SETTLE = "cron_playlet_settle"
+ KEY_CFG_CRON_TIKTOK_AUTH = "cron_tik_tok_auth"
+ KEY_CFG_CRON_TASKBOX_SETTLE = "cron_task_box_settle" //会员费订单结算
+ KEY_CFG_CRON_PRIVILEGE_CARD_SETTLE = "cron_privilege_card_settle" //权益卡订单结算
+ KEY_CFG_CRON_CARD_RETURN = "cron_card_return" //权益卡退款
+ KEY_CFG_CRON_PUBLISHER_RELATION = "cron_taobao_publisher_relation" //获取淘宝渠道
+ KEY_CFG_CRON_PUBLISHER_RELATION_NEW = "cron_taobao_publisher_relation_new" //获取淘宝渠道
+
+ KEY_CFG_CRON_DTKBRAND = "cron_dtk_brand" //大淘客品牌信息
+ KEY_CFG_CRON_PUBLISHER_RELATION_BIND = "cron_taobao_publisher_relation_bind" //获取淘宝渠道绑定
+ KEY_CFG_CRON_GOODS_SHELF = "cron_goods_shelf" //商品上下架定时任务
+ KEY_CFG_CRON_DIDI_ENERGY = "cron_order_didi_energy" //
+ KEY_CFG_CRON_T3_CAR = "cron_order_t3_car" //
+ KEY_CFG_CRON_DIDI_ONLINE_CAR = "cron_order_didi_online_car" //
+ KEY_CFG_CRON_KING_FLOWER = "cron_order_king_flower" //
+ KEY_CFG_CRON_DIDI_CHAUFFEUR = "cron_order_didi_chauffeur" //
+ KEY_CFG_CRON_PLAYLET_ORDER = "cron_order_playlet_order" //
+ KEY_CFG_CRON_PLAYLET_GOODS = "cron_order_playlet_goods" //
+ KEY_CFG_CRON_CARD_CHECK_RETURN = "cron_card_check_return" //
+ KEY_CFG_CRON_CARD_CHECK_UPDATE = "cron_card_check_update" //
+ KEY_CFG_CRON_DIDI_FREIGHT = "cron_order_didi_freight" //
+ KEY_CFG_CRON_TB_PUNISH_REFUND = "cron_order_tb_punish_refund"
+ KEY_CFG_CRON_TIKTOK = "cron_order_tikTok"
+ KEY_CFG_CRON_ELM = "cron_order_elm"
+ KEY_CFG_CRON_AUTO_ADD_TIKTOK_GOODS = "cron_order_auto_add_tiktok_goods"
+ KEY_CFG_CRON_TIKTOKOwn = "cron_order_tikTokOwn"
+ KEY_CFG_CRON_TIKTOKCsjp = "cron_order_tikTokCsjp"
+ KEY_CFG_CRON_TIKTOKCsjpLive = "cron_order_tikTokCsjpLive"
+ KEY_CFG_CRON_TIKTOKOwnCsjp = "cron_order_tikTokOwnCsjp"
+ KEY_CFG_CRON_TIKTOKOwnCsjpLive = "cron_order_tikTokOwnCsjpLive"
+ KEY_CFG_CRON_TIKTOKOwnCsjpActivity = "cron_order_tikTokOwnCsjpActivity"
+ KEY_CFG_CRON_PlayLet_Total = "cron_playlet_total"
+ KEY_CFG_CRON_TIKTOKOwnCreate = "cron_order_tikTokOwnCreate"
+ KEY_CFG_CRON_KuaishouOwn = "cron_order_kuaishouOwn"
+ KEY_CFG_CRON_KuaishouOwnCreate = "cron_order_kuaishouOwnCreate"
+ KEY_CFG_CRON_TIKTOKOwnACtivity = "cron_order_tikTokOwnActivity"
+ KEY_CFG_CRON_DUOYOUORD = "cron_order_DouYouOrd"
+ KEY_CFG_CRON_TASKBOX = "cron_order_TaskBox"
+ KEY_CFG_CRON_TASKBOXSECOND = "cron_order_TaskBoxSecond"
+ KEY_CFG_CRON_TIKTOKOwnMixH5 = "cron_order_tikTokOwnMixH5"
+
+ KEY_CFG_CRON_TIKTOKLIVE_UPDATE = "cron_order_tikTokLive_update"
+ KEY_CFG_CRON_KUAISHOU = "cron_order_kuaishou"
+ KEY_CFG_CRON_KUAISHOUOFFICIAL = "cron_order_kuaishou_official"
+ KEY_CFG_CRON_KUAISHOUOFFICIALLive = "cron_order_kuaishou_official_live"
+ KEY_CFG_CRON_MEITUANFFICIAL = "cron_order_meituan_official"
+ KEY_CFG_CRON_TIKTOKLIVE = "cron_order_tikTok_live"
+ KEY_CFG_CRON_TIKTOKLIVEOWN = "cron_order_tikTok_live_own"
+ KEY_CFG_CRON_TIKTOKACTIVITY = "cron_order_tikTok_activity"
+ KEY_CFG_CRON_KUAISHOULIVE = "cron_order_kuaishou_live"
+
+ ZhimengCronPlayletVideoOrder = "cron_playlet_video_order" //短剧订单
+ ZhimengCronPlayletAdvOrder = "cron_playlet_adv_order" //短剧广告订单
+ ZhimengCronPlayletVideoOrderYesterDay = "cron_playlet_video_order_yesterday"
+ ZhimengCronPlayletVideoOrderMonth = "cron_playlet_video_order_month"
+ ZhimengCronPlayletAdvOrderYesterDay = "cron_playlet_adv_order_yesterday"
+ ZhimengCronPlayletAdvOrderMonth = "cron_playlet_adv_order_month"
+ ZhimengCronPlayletAdvOrderYesterDayToMoney = "cron_playlet_adv_order_yesterday_to_money"
+ KEY_CFG_TIK_TOK_TEAM_ORDER_PAY = "cron_tik_tok_team_order_pay"
+ KEY_CFG_KUAISHOU_TEAM_ORDER_PAY = "cron_kuaishou_team_order_pay"
+ KEY_CFG_KUAISHOU_TEAM_ORDER_UPDATE = "cron_kuaishou_team_order_update"
+ KEY_CFG_KUAISHOU_AUTH = "cron_kuaishou_auth"
+ KEY_CFG_VERIFY = "cron_verify"
+ KEY_CFG_TIK_TOK_TEAM_ORDER_UPDATE = "cron_tik_tok_team_order_update"
+ KEY_CFG_TIK_TOK_TEAM_USER_BIND_BUYINID = "cron_tik_tok_team_user_bind_buyinid"
+ // 自动任务运行时设置
+ KEY_CFG_CRON_TIME_PIZZA = "crontab_order_time_pizza"
+ KEY_CFG_CRON_TIME_WALLACE = "crontab_order_time_wallace"
+ KEY_CFG_CRON_TIME_TOURISM = "crontab_order_time_tourism"
+ KEY_CFG_CRON_TIME_NEAR = "crontab_order_time_pizza"
+ KEY_CFG_CRON_TIME_FLOWERCAKE = "crontab_order_time_flowerCake"
+ KEY_CFG_CRON_TIME_DELIVERY = "crontab_order_time_delivery"
+ KEY_CFG_CRON_TIME_TIKTOK = "crontab_order_time_tikTok"
+ KEY_CFG_CRON_TIME_ELM = "crontab_order_time_elm"
+ KEY_CFG_CRON_TIME_TIKTOKOwn = "crontab_order_time_tikTokOwn"
+ KEY_CFG_CRON_TIME_TIKTOKOwnCreate = "crontab_order_time_tikTokOwnCreate"
+ KEY_CFG_CRON_TIME_KuaishouOwn = "crontab_order_time_kuaishouOwn"
+ KEY_CFG_CRON_TIME_KuaishouOwnCreate = "crontab_order_time_kuaishouOwnCreate"
+ KEY_CFG_CRON_TIME_TIKTOKOwnActivity = "KEY_CFG_CRON_TIME_TIKTOKOwnActivity"
+ KEY_CFG_CRON_TIME_TIKTOKOwnMix = "KEY_CFG_CRON_TIME_TIKTOKOwnMix"
+ KEY_CFG_CRON_TIME_TIKTOKOwnLive = "crontab_order_time_tikTokOwnLive"
+ KEY_CFG_CRON_TIME_KUAISHOU = "crontab_order_time_kuaishou"
+ KEY_CFG_CRON_TIME_TIKTOKLIVE = "crontab_order_time_tikTok_live"
+ KEY_CFG_CRON_TIME_KUAISHOULIVE = "crontab_order_time_kuaishou_live"
+ KEY_CFG_CRON_TIME_TB = "crontab_order_time_taobao"
+ KEY_CFG_CRON_TIME_CSJP = "crontab_order_time_csjp"
+ KEY_CFG_CRON_TIME_KUAISHOU_OFFICIAL = "crontab_order_time_kuaishou_official"
+ KEY_CFG_CRON_TIME_KUAISHOU_OFFICIAL_LIVE = "crontab_order_time_kuaishou_official_live"
+ KEY_CFG_CRON_TIME_MEITUAN_OFFICIAL = "crontab_order_time_meituan_official"
+ KEY_CFG_CRON_TIME_CSJP_Live = "crontab_order_time_csjp_live"
+ KEY_CFG_CRON_TIME_OWN_CSJP = "crontab_order_time_own_csjp"
+ KEY_CFG_CRON_TIME_TIKTOK_TEAM_ORDER = "crontab_order_time_tiktok_team_order"
+ KEY_CFG_CRON_TIME_OWN_CSJP_Live = "crontab_order_time_own_csjp_live"
+ KEY_CFG_CRON_TIME_OWN_CSJP_ACTIVITY = "crontab_order_time_own_csjp_activity"
+ KEY_CFG_CRON_TIME_TBREFUND = "crontab_order_time_taobao_refund"
+ KEY_CFG_CRON_TIME_TBPUNISHREFUND = "crontab_order_time_taobao_punish_refund_new"
+ KEY_CFG_CRON_TIME_JD = "crontab_order_time_jd"
+ KEY_CFG_CRON_TIME_PDD = "crontab_order_time_pdd"
+ KEY_CFG_CRON_TIME_TBBYCREATETIME = "crontab_order_time_tb_by_create_time"
+ KEY_CFG_CRON_TIME_TBBYPAY = "crontab_order_time_tb_by_pay"
+ KEY_CFG_CRON_TIME_TB12 = "crontab_order_time_tb12"
+ KEY_CFG_CRON_TIME_TB13 = "crontab_order_time_tb13"
+ KEY_CFG_CRON_TIME_TB14 = "crontab_order_time_tb14"
+ KEY_CFG_CRON_TIME_TB3 = "crontab_order_time_tb3"
+ KEY_CFG_CRON_TIME_TBBYSETTLE = "crontab_order_time_tb_by_settle"
+ KEY_CFG_CRON_TIME_PDDBYCREATETIME = "crontab_order_time_pdd_by_create_time"
+ KEY_CFG_CRON_TIME_JDBYCREATETIME = "crontab_order_time_jd_by_create_time"
+ KEY_CFG_CRON_TIME_JDBYSUCCESS = "crontab_order_time_jd_by_success"
+ KEY_CFG_CRON_TIME_JDFAILBYCREATETIME = "crontab_order_time_jd_fail_by_create_time"
+ KEY_CFG_CRON_TIME_PDDBYAGOTIME = "crontab_order_time_pdd_by_ago_time"
+ KEY_CFG_CRON_TIME_PDDBYSTATUSSUCCESS = "crontab_order_time_pdd_by_status_success"
+ KEY_CFG_CRON_TIME_PDDBYSTATUSFAIL = "crontab_order_time_pdd_by_status_fail"
+ KEY_CFG_CRON_TIME_PDDBYSTATUS = "crontab_order_time_pdd_by_status"
+ KEY_CFG_CRON_TIME_JDBYSTATUS = "crontab_order_time_jd_by_status"
+ KEY_CFG_CRON_TIME_SN = "crontab_order_time_suning"
+ KEY_CFG_CRON_TIME_VIP = "crontab_order_time_vip"
+ KEY_CFG_CRON_TIME_KL = "crontab_order_time_kaola"
+ KEY_CFG_CRON_TIME_DUOMAI = "crontab_order_time_duomai"
+ KEY_CFG_CRON_TIME_PUBLISHER = "crontab_taobao_time_publisher" // 跟踪淘宝备案信息绑定会员运营id 针对小程序
+ KEY_CFG_CRON_TIME_MEITUAN = "crontab_order_time_meituan" //美团
+ KEY_CFG_CRON_TIME_MEITUANLM = "crontab_order_time_meituan_lm" //美团联盟
+ KEY_CFG_CRON_TIME_MEITUANLMSTART = "crontab_order_time_meituan_lm_start" //美团联盟
+ KEY_CFG_CRON_TIME_MEITUANSTART = "crontab_order_time_meituan_start" //美团联盟
+ KEY_CFG_CRON_TIME_STATIONMEITUANLM = "crontab_order_time_station_meituan_lm" //美团联盟
+ KEY_CFG_CRON_TIME_OILSTATION = "crontab_order_time_oilstation" //加油
+ KEY_CFG_CRON_TIME_BRIGHT_OILSTATION = "crontab_order_time_bright_oilstation" //加油
+ KEY_CFG_CRON_TIME_KFC = "crontab_order_time_kfc" //肯德基
+ KEY_CFG_CRON_TIME_CINEMA = "crontab_order_time_cinema" //电影票
+ KEY_CFG_CRON_TIME_STARBUCKS = "crontab_order_time_starbucks" //海威星巴克
+ KEY_CFG_CRON_TIME_MCDONALD = "crontab_order_time_mcdonald" //海威麦当劳
+ KEY_CFG_CRON_TIME_NAYUKI = "crontab_order_time_nayuki" //海威奈雪
+ KEY_CFG_CRON_TIME_BURGERKING = "crontab_order_time_burger_king" //海威汉堡王
+ KEY_CFG_CRON_TIME_HEYTEA = "crontab_order_time_heytea" //海威喜茶
+ KEY_CFG_CRON_TIME_HWMOVIE = "crontab_order_time_hw_movie" //海威电影票
+ KEY_CFG_CRON_TIME_TIKTOKLIFE = "crontab_order_time_tik_tok_life" //海威电影票
+ KEY_CFG_CRON_TIME_PAGODA = "crontab_order_time_pagoda" //
+ KEY_CFG_CRON_TIME_TO_KFC = "crontab_order_time_to_kfc" //
+ KEY_CFG_CRON_TIME_LUCKIN = "crontab_order_time_luckin" //
+ KEY_CFG_CRON_TIME_DIDI_ENERGY = "crontab_order_time_didi_energy" //
+ KEY_CFG_CRON_TIME_T3_CAR = "crontab_order_time_t3_car" //
+ KEY_CFG_CRON_TIME_DIDI_ONLINE_CAR = "crontab_order_time_didi_online_car" //
+ KEY_CFG_CRON_TIME_KING_FLOWER = "crontab_order_time_king_flower" //
+ KEY_CFG_CRON_TIME_DIDI_FREIGHT = "crontab_order_time_didi_freight" //
+ KEY_CFG_CRON_TIME_DIDI_CHAUFFEUR = "crontab_order_time_didi_chauffeur" //
+ KEY_CFG_CRON_USER_RELATE = "cron_user_relate"
+)
diff --git a/app/md/md_order.go b/app/md/md_order.go
new file mode 100644
index 0000000..5ee6618
--- /dev/null
+++ b/app/md/md_order.go
@@ -0,0 +1,47 @@
+package md
+
+type OrderTotal struct {
+ StoreId string `json:"store_id"`
+ BuyPhone string `json:"buy_phone"`
+ MealNum string `json:"meal_num"`
+ Memo string `json:"memo"`
+ CouponId string `json:"coupon_id"`
+ IsNow string `json:"is_now"`
+ Timer string `json:"timer"`
+ GoodsInfo []GoodsInfo `json:"goods_info"`
+}
+type GoodsInfo struct {
+ GoodsId string `json:"goods_id"`
+ SkuId string `json:"sku_id"`
+ Num string `json:"num"`
+}
+
+type OneSkuPriceInfo struct {
+ Num string `json:"num"`
+ Amount string `json:"amount"`
+ GoodsId int `json:"goods_id"`
+}
+type SkuPriceStruct struct {
+ SkuId int64 `json:"skuId"`
+ Ratio string `json:"ratio"`
+ OneSkuPriceInfo OneSkuPriceInfo `json:"oneSkuPriceInfo"`
+}
+
+// 每一种sku价格结构
+type SkuId2priceInfo map[int64]OneSkuPriceInfo
+
+type SkuId2originPrice map[int64]string
+type CouponList struct {
+ Id string `json:"id"`
+ Title string `json:"title"`
+ Timer string `json:"timer"`
+ Label string `json:"label"`
+ Img string `json:"img"`
+ Content string `json:"content"`
+ IsCanUse string `json:"is_can_use"`
+ NotUseStr string `json:"not_use_str"`
+}
+type Sku struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+}
diff --git a/app/md/pay.go b/app/md/pay.go
new file mode 100644
index 0000000..4994c8f
--- /dev/null
+++ b/app/md/pay.go
@@ -0,0 +1,60 @@
+package md
+
+const (
+ CALLBACK_URL = "%s/api/v1/communityTeam/pay/callback?master_id=%s&order_type=%s&pay_method=%s"
+ RECHARGGE_CALLBACK_URL = "http://%s/api/v1/new_recharge/callback/%s"
+
+ BALANCE_PAY = "balance_pay"
+ ALIPAY = "alipay"
+ WX_PAY = "wxpay"
+ FB_PAY_ALI = "fb_pay_ali"
+ FB_PAY_WX = "fb_pay_wx"
+ FB_PAY_WX_SUB = "fb_pay_wx_sub"
+ FBCALLBACK_URL = "%s/api/v1/pay/fb/callback"
+)
+
+var PayMethod = map[string]string{
+ BALANCE_PAY: "余额支付",
+ ALIPAY: "支付宝支付",
+ WX_PAY: "微信支付",
+ FB_PAY_ALI: "乐刷支付宝",
+ FB_PAY_WX: "乐刷微信",
+ FB_PAY_WX_SUB: "乐刷微信",
+}
+var PayMethodIdToName = map[int]string{
+ 1: "余额支付",
+ 2: "支付宝支付",
+ 3: "微信支付",
+ 18: "乐刷支付宝",
+ 19: "乐刷微信",
+}
+var PayMethodIDs = map[string]int{
+ BALANCE_PAY: 1,
+ ALIPAY: 2,
+ WX_PAY: 3,
+ FB_PAY_ALI: 18,
+ FB_PAY_WX: 19,
+ FB_PAY_WX_SUB: 19,
+}
+
+const (
+ CommunityTeam = "community_team"
+ PrivilegeCard = "privilege_card"
+ LianlianPay = "lianlian_pay"
+ BusinessCollege = "business_college"
+ UserLevel = "user_level"
+ PrivilegeOpenCard = "privilege_open_card"
+ BusinessCollegeSub = "business_college_sub"
+ UserLevelSub = "user_level_sub"
+ PrivilegeOpenCardSub = "privilege_open_card_sub"
+ AggregationRecharge = "aggregation_recharge"
+ Swipe = "swipe"
+)
+
+var NeedPayPart = map[string]string{
+ PrivilegeCard: "权益卡",
+ BusinessCollege: "商学院",
+ UserLevel: "会员VIP升级",
+ PrivilegeOpenCard: "权益卡开卡",
+ AggregationRecharge: "聚合充值",
+}
diff --git a/app/md/platform.go b/app/md/platform.go
new file mode 100644
index 0000000..b479ab5
--- /dev/null
+++ b/app/md/platform.go
@@ -0,0 +1,40 @@
+package md
+
+const (
+ /*********** DEVICE ***********/
+ PLATFORM_WX_APPLET = "wx_applet" // 小程序
+ PLATFORM_TOUTIAO_APPLET = "toutiao_applet"
+ PLATFORM_TIKTOK_APPLET = "tiktok_applet"
+ PLATFORM_BAIDU_APPLET = "baidu_applet"
+ PLATFORM_ALIPAY_APPLET = "alipay_applet"
+ PLATFORM_WAP = "wap" //h5
+ PLATFORM_ANDROID = "android"
+ PLATFORM_IOS = "ios"
+ PLATFORM_PC = "pc"
+ PLATFORM_JSAPI = "jsapi" // 公众号
+)
+
+const WX_PAY_BROWSER = "wx_pay_browser" // 用于判断显示支付方式
+
+var PlatformList = map[string]struct{}{
+ PLATFORM_WX_APPLET: {},
+ PLATFORM_TOUTIAO_APPLET: {},
+ PLATFORM_TIKTOK_APPLET: {},
+ PLATFORM_BAIDU_APPLET: {},
+ PLATFORM_ALIPAY_APPLET: {},
+ PLATFORM_WAP: {},
+ PLATFORM_ANDROID: {},
+ PLATFORM_IOS: {},
+ PLATFORM_PC: {},
+}
+
+var PlatformMap = map[string]string{
+ "android": "2",
+ "ios": "2",
+ "wap": "4", // 和小程序公用模板
+ "wx_applet": "4", //微信小程序
+ "tiktok_applet": "4",
+ "baidu_applet": "4",
+ "alipay_applet": "4",
+ "toutiao_applet": "4",
+}
diff --git a/app/md/user_info.go b/app/md/user_info.go
new file mode 100644
index 0000000..f6c5648
--- /dev/null
+++ b/app/md/user_info.go
@@ -0,0 +1,43 @@
+package md
+
+import (
+ "applet/app/db/model"
+ "applet/app/lib/arkid"
+)
+
+type UserInfoResponse struct {
+ Avatar string `json:"avatar"`
+ NickName string `json:"nickname"`
+ Gender string `json:"gender"`
+ Birthday string `json:"birthday"`
+ RegisterTime string `json:"register_time"`
+ FileBucketURL string `json:"file_bucket_url"`
+ FileFormat string `json:"file_format"`
+ IsNoChange string `json:"is_no_change"`
+ IsUpLoadWx string `json:"is_upload_wx"`
+ IsShowDelUserBtn string `json:"is_show_del_user_btn"`
+ IsShowQq string `json:"is_show_qq"`
+ IsShowSalePhone string `json:"is_show_sale_phone"`
+ IsShowWallet string `json:"is_show_wallet"`
+ IsShowBankCard string `json:"is_show_bank_card"`
+ Qq string `json:"qq"`
+ SalePhone string `json:"sale_phone"`
+ LevelName string `json:"level_name"`
+ Phone string `json:"phone"`
+}
+
+type User struct {
+ Ark *arkid.ArkIDUser
+ Info *model.User
+ Profile *model.UserProfile
+ Level *model.UserLevel
+ Tags []string
+}
+
+type UserRelation struct {
+ Uid int
+ CurUid int
+ Diff int // 与当前用户级别差
+ Level int // 用户当前等级
+ OldDiff int // 旧的级别
+}
diff --git a/app/md/wxpay.go b/app/md/wxpay.go
new file mode 100644
index 0000000..88a9f8d
--- /dev/null
+++ b/app/md/wxpay.go
@@ -0,0 +1,30 @@
+package md
+
+type WxPayParams struct {
+ Subject string `json:"subject" binding:"required"`
+ Amount string `json:"amount" binding:"required"`
+ OrderType string `json:"order_type" binding:"required"`
+ OrdId string `json:"ord_id"`
+}
+
+type WxPayCallback struct {
+ AppId string `json:"appid"`
+ BankType string `json:"bank_type"`
+ CashFee string `json:"cash_fee"`
+ FeeType string `json:"fee_type"`
+ IsSubscribe string `json:"is_subscribe"`
+ MasterID string `json:"master_id"`
+ MchID string `json:"mch_id"`
+ NonceStr string `json:"nonce_str"`
+ Openid string `json:"openid"`
+ OrderType string `json:"order_type"`
+ OutTradeNo string `json:"out_trade_no"`
+ PayMethod string `json:"pay_method"`
+ ResultCode string `json:"result_code"`
+ ReturnCode string `json:"return_code"`
+ Sign string `json:"sign"`
+ TimeEnd string `json:"time_end"`
+ TotalFee string `json:"total_fee"`
+ TradeType string `json:"trade_type"`
+ TransactionID string `json:"transaction_id"`
+}
diff --git a/app/mw/mw_access_log.go b/app/mw/mw_access_log.go
new file mode 100644
index 0000000..84f6b52
--- /dev/null
+++ b/app/mw/mw_access_log.go
@@ -0,0 +1,31 @@
+package mw
+
+import (
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+
+ "applet/app/utils/logx"
+)
+
+// access log
+func AccessLog(c *gin.Context) {
+ start := time.Now()
+ c.Next()
+ cost := time.Since(start)
+
+ logx.Info(c.Request.URL.Path)
+
+ logger := &zap.Logger{}
+ logger.Info(c.Request.URL.Path,
+ zap.Int("status", c.Writer.Status()),
+ zap.String("method", c.Request.Method),
+ zap.String("path", c.Request.URL.Path),
+ zap.String("query", c.Request.URL.RawQuery),
+ zap.String("ip", c.ClientIP()),
+ zap.String("user-agent", c.Request.UserAgent()),
+ zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
+ zap.Duration("cost", cost),
+ )
+}
diff --git a/app/mw/mw_auth.go b/app/mw/mw_auth.go
new file mode 100644
index 0000000..645dbe3
--- /dev/null
+++ b/app/mw/mw_auth.go
@@ -0,0 +1,72 @@
+package mw
+
+import (
+ "errors"
+
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/lib/arkid"
+ "applet/app/md"
+ "applet/app/utils"
+
+ "github.com/gin-gonic/gin"
+)
+
+// 检查权限, 签名等等
+func Auth(c *gin.Context) {
+
+ for k, v := range c.Request.Header {
+ c.Set(k, v[0])
+ }
+ token, ok := c.Get("Token")
+ if !ok {
+ e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("没有找到token"))
+ return
+ }
+ if token == "" {
+ e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 不能为空"))
+ return
+ }
+ tokenStr := utils.AnyToString(token)
+ arkIdSdk := arkid.NewArkID()
+ var err error
+ signUser := &md.User{}
+ arkIdUser := new(arkid.ArkIDUser)
+ if err = arkIdSdk.SelectFunction("arkid_user_info").
+ WithArgs(arkid.RequestBody{Token: tokenStr}).
+ Result(arkIdUser); err != nil {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, err) //token 不存在
+ return
+ }
+ if arkIdUser.Username == "" {
+ e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("Token error"))
+ return
+ }
+ if err = arkIdSdk.SelectFunction("arkid_login").
+ WithArgs(arkid.RequestBody{Username: arkIdUser.Username, Password: utils.Md5(arkIdUser.Username)}).
+ Result(arkIdUser); err != nil {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, err)
+ return
+ }
+ signUser.Ark = arkIdUser
+ if signUser.Ark == nil {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("无效token"))
+ return
+ }
+ signUser.Info, err = db.UserFindByArkidUserName(db.DBs[c.GetString("mid")], arkIdUser.Username)
+ if err != nil {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, err)
+ return
+ }
+ if signUser.Info == nil {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("无效token"))
+ return
+ }
+ signUser.Profile, err = db.UserProfileFindByArkID(db.DBs[c.GetString("mid")], utils.IntToStr(arkIdUser.UserID))
+ if err != nil {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, err)
+ return
+ }
+ c.Set("user", signUser)
+ c.Next()
+}
diff --git a/app/mw/mw_auth_jwt.go b/app/mw/mw_auth_jwt.go
new file mode 100644
index 0000000..cd4875b
--- /dev/null
+++ b/app/mw/mw_auth_jwt.go
@@ -0,0 +1,122 @@
+package mw
+
+import (
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/e"
+ "applet/app/lib/auth"
+ "applet/app/md"
+ "applet/app/svc"
+ "applet/app/utils"
+ "applet/app/utils/cache"
+ "applet/app/utils/logx"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+// AuthJWT is jwt middleware
+func AuthJWT(c *gin.Context) {
+ authHeader := c.Request.Header.Get("Authorization")
+ if authHeader == "" {
+ e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 不能为空"))
+ return
+ }
+
+ // 按空格分割
+ parts := strings.SplitN(authHeader, " ", 2)
+ if !(len(parts) == 2 && parts[0] == "Bearer") {
+ e.OutErr(c, e.ERR_TOKEN_FORMAT, errors.New("token 格式不对"))
+ return
+ }
+
+ // parts[1]是token
+ mc, err := utils.ParseToken(parts[1])
+ if err != nil {
+ e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 过期或无效"))
+ return
+ }
+
+ // 获取user
+ u, err := db.UserFindByID(db.DBs[c.GetString("mid")], mc.UID)
+ if err != nil {
+ e.OutErr(c, e.ERR_DB_ORM, err)
+ return
+ }
+ if u == nil {
+ e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 过期或无效"))
+ return
+ }
+
+ // 检验账号是否未激活或被冻结
+ switch u.State {
+ case 0:
+ e.OutErr(c, e.ERR_USER_NO_ACTIVE)
+ return
+ case 2:
+ if c.GetString("mid") == "31585332" {
+ utils.FilePutContents("ERR_USER_IS_BAN", utils.SerializeStr(map[string]interface{}{
+ "token": parts[1],
+ "mc": mc,
+ "user": u,
+ }))
+ }
+ e.OutErr(c, e.ERR_USER_IS_BAN)
+ return
+ }
+
+ // 校验是否和缓存的token一致,只能有一个token 是真实有效
+ key := fmt.Sprintf("%s:token:%s", c.GetString("mid"), u.Username)
+ cjwt, err := cache.GetString(key)
+ fmt.Println("====================token", u.Username, key, cjwt, parts[1])
+ if err != nil {
+ fmt.Println("====================token", err)
+ logx.Warn(err)
+ NOCACHE(c, parts, mc, u, false)
+ return
+ }
+ if parts[1] != cjwt {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("token expired"))
+ return
+ }
+ NOCACHE(c, parts, mc, u, true)
+}
+
+func NOCACHE(c *gin.Context, parts []string, mc *auth.JWTUser, u *model.User, isTrue bool) {
+ // 获取user profile
+ up, err := db.UserProfileFindByID(db.DBs[c.GetString("mid")], mc.UID)
+ if err != nil || up == nil {
+ e.OutErr(c, e.ERR_DB_ORM, err)
+ return
+ }
+ if parts[1] != up.ArkidToken && isTrue == false || up.ArkidToken == "" {
+ e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("token expired"))
+ return
+ }
+ if parts[1] != up.ArkidToken && isTrue {
+ up.ArkidToken = parts[1]
+ db.UserProfileUpdate(svc.MasterDb(c), up.Uid, up, "arkid_token")
+ }
+ if up.AvatarUrl == "" {
+ up.AvatarUrl = c.GetString("appUserDefaultAvatar")
+ }
+ // 获取user 等级
+ ul, err := db.UserLevelByID(db.DBs[c.GetString("mid")], u.Level)
+ if err != nil {
+ e.OutErr(c, e.ERR_DB_ORM, err)
+ return
+ }
+
+ user := &md.User{
+ Info: u,
+ Profile: up,
+ Level: ul,
+ }
+
+ // 将当前请求的username信息保存到请求的上下文c上
+ c.Set("user", user)
+ c.Next() // 后续的处理函数可以用过c.Get("user")来获取当前请求的用户信息
+
+}
diff --git a/app/mw/mw_breaker.go b/app/mw/mw_breaker.go
new file mode 100644
index 0000000..fefc078
--- /dev/null
+++ b/app/mw/mw_breaker.go
@@ -0,0 +1,30 @@
+package mw
+
+import (
+ "errors"
+ "net/http"
+ "strconv"
+
+ "github.com/afex/hystrix-go/hystrix"
+ "github.com/gin-gonic/gin"
+)
+
+// 熔断器, 此组件需要在gin.Recovery中间之前进行调用, 否则可能会导致panic时候, 无法recovery, 正确顺序如下
+//r.Use(BreakerWrapper)
+//r.Use(gin.Recovery())
+func Breaker(c *gin.Context) {
+ name := c.Request.Method + "-" + c.Request.RequestURI
+ hystrix.Do(name, func() error {
+ c.Next()
+ statusCode := c.Writer.Status()
+ if statusCode >= http.StatusInternalServerError {
+ return errors.New("status code " + strconv.Itoa(statusCode))
+ }
+ return nil
+ }, func(e error) error {
+ if e == hystrix.ErrCircuitOpen {
+ c.String(http.StatusAccepted, "请稍后重试") //todo 修改报错方法
+ }
+ return e
+ })
+}
diff --git a/app/mw/mw_change_header.go b/app/mw/mw_change_header.go
new file mode 100644
index 0000000..c10bdb9
--- /dev/null
+++ b/app/mw/mw_change_header.go
@@ -0,0 +1,18 @@
+package mw
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// 修改传过来的头部字段
+func ChangeHeader(c *gin.Context) {
+ appvserison := c.GetHeader("AppVersionName")
+ if appvserison == "" {
+ appvserison = c.GetHeader("app_version_name")
+ }
+ if appvserison != "" {
+ c.Request.Header.Add("app_version_name", appvserison)
+ }
+
+ c.Next()
+}
diff --git a/app/mw/mw_check_sign.go b/app/mw/mw_check_sign.go
new file mode 100644
index 0000000..599c512
--- /dev/null
+++ b/app/mw/mw_check_sign.go
@@ -0,0 +1,37 @@
+package mw
+
+import (
+ "applet/app/e"
+ "applet/app/utils"
+ "bytes"
+ "errors"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "io/ioutil"
+)
+
+// CheckSign is 中间件 用来检查签名
+func CheckSign(c *gin.Context) {
+ if utils.SignCheck(c) == false {
+ e.OutErr(c, 400, errors.New("请求失败~~"))
+ return
+ }
+ c.Next()
+}
+
+func CheckBody(c *gin.Context) {
+ c.Set("api_version", "1")
+ if utils.GetApiVersion(c) > 0 {
+ body, _ := ioutil.ReadAll(c.Request.Body)
+ fmt.Println("check_", c.GetString("mid"), string(body))
+ if string(body) != "" {
+ str := utils.ResultAesDecrypt(c, string(body))
+ fmt.Println("check_de", c.GetString("mid"), str)
+ if str != "" {
+ c.Set("body_str", str)
+ c.Request.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(str)))
+ }
+ }
+ }
+ c.Next()
+}
diff --git a/app/mw/mw_checker.go b/app/mw/mw_checker.go
new file mode 100644
index 0000000..44ee434
--- /dev/null
+++ b/app/mw/mw_checker.go
@@ -0,0 +1,22 @@
+package mw
+
+import (
+ "strings"
+
+ "github.com/gin-gonic/gin"
+
+ "applet/app/e"
+ "applet/app/md"
+)
+
+// 检查设备等, 把头部信息下放到hdl可以获取
+func Checker(c *gin.Context) {
+ // 校验平台支持
+ platform := strings.ToLower(c.GetHeader("Platform"))
+ //fmt.Println(platform)
+ if _, ok := md.PlatformList[platform]; !ok {
+ e.OutErr(c, e.ERR_PLATFORM)
+ return
+ }
+ c.Next()
+}
diff --git a/app/mw/mw_cors.go b/app/mw/mw_cors.go
new file mode 100644
index 0000000..3433553
--- /dev/null
+++ b/app/mw/mw_cors.go
@@ -0,0 +1,29 @@
+package mw
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// cors跨域
+func Cors(c *gin.Context) {
+ // 放行所有OPTIONS方法
+ if c.Request.Method == "OPTIONS" {
+ c.AbortWithStatus(204)
+ return
+ }
+
+ origin := c.Request.Header.Get("Origin") // 请求头部
+ if origin != "" {
+ c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问来源域
+ c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
+ // header的类型
+ c.Header("Access-Control-Allow-Headers", "Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma,X-Mx-ReqToken")
+ // 允许跨域设置,可以返回其他子段
+ // 跨域关键设置 让浏览器可以解析
+ c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
+ c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
+ c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true
+ c.Set("Content-Type", "Application/json") // 设置返回格式是json
+ }
+ c.Next()
+}
diff --git a/app/mw/mw_csrf.go b/app/mw/mw_csrf.go
new file mode 100644
index 0000000..b15619b
--- /dev/null
+++ b/app/mw/mw_csrf.go
@@ -0,0 +1,136 @@
+package mw
+
+import (
+ "crypto/sha1"
+ "encoding/base64"
+ "errors"
+ "io"
+
+ "github.com/dchest/uniuri"
+ "github.com/gin-contrib/sessions"
+ "github.com/gin-gonic/gin"
+)
+
+// csrf,xsrf检查
+const (
+ csrfSecret = "csrfSecret"
+ csrfSalt = "csrfSalt"
+ csrfToken = "csrfToken"
+)
+
+var defaultIgnoreMethods = []string{"GET", "HEAD", "OPTIONS"}
+
+var defaultErrorFunc = func(c *gin.Context) {
+ panic(errors.New("CSRF token mismatch"))
+}
+
+var defaultTokenGetter = func(c *gin.Context) string {
+ r := c.Request
+
+ if t := r.FormValue("_csrf"); len(t) > 0 {
+ return t
+ } else if t := r.URL.Query().Get("_csrf"); len(t) > 0 {
+ return t
+ } else if t := r.Header.Get("X-CSRF-TOKEN"); len(t) > 0 {
+ return t
+ } else if t := r.Header.Get("X-XSRF-TOKEN"); len(t) > 0 {
+ return t
+ }
+
+ return ""
+}
+
+// Options stores configurations for a CSRF middleware.
+type Options struct {
+ Secret string
+ IgnoreMethods []string
+ ErrorFunc gin.HandlerFunc
+ TokenGetter func(c *gin.Context) string
+}
+
+func tokenize(secret, salt string) string {
+ h := sha1.New()
+ io.WriteString(h, salt+"-"+secret)
+ hash := base64.URLEncoding.EncodeToString(h.Sum(nil))
+
+ return hash
+}
+
+func inArray(arr []string, value string) bool {
+ inarr := false
+
+ for _, v := range arr {
+ if v == value {
+ inarr = true
+ break
+ }
+ }
+
+ return inarr
+}
+
+// Middleware validates CSRF token.
+func Middleware(options Options) gin.HandlerFunc {
+ ignoreMethods := options.IgnoreMethods
+ errorFunc := options.ErrorFunc
+ tokenGetter := options.TokenGetter
+
+ if ignoreMethods == nil {
+ ignoreMethods = defaultIgnoreMethods
+ }
+
+ if errorFunc == nil {
+ errorFunc = defaultErrorFunc
+ }
+
+ if tokenGetter == nil {
+ tokenGetter = defaultTokenGetter
+ }
+
+ return func(c *gin.Context) {
+ session := sessions.Default(c)
+ c.Set(csrfSecret, options.Secret)
+
+ if inArray(ignoreMethods, c.Request.Method) {
+ c.Next()
+ return
+ }
+
+ salt, ok := session.Get(csrfSalt).(string)
+
+ if !ok || len(salt) == 0 {
+ errorFunc(c)
+ return
+ }
+
+ token := tokenGetter(c)
+
+ if tokenize(options.Secret, salt) != token {
+ errorFunc(c)
+ return
+ }
+
+ c.Next()
+ }
+}
+
+// GetToken returns a CSRF token.
+func GetToken(c *gin.Context) string {
+ session := sessions.Default(c)
+ secret := c.MustGet(csrfSecret).(string)
+
+ if t, ok := c.Get(csrfToken); ok {
+ return t.(string)
+ }
+
+ salt, ok := session.Get(csrfSalt).(string)
+ if !ok {
+ salt = uniuri.New()
+ session.Set(csrfSalt, salt)
+ session.Save()
+ }
+ token := tokenize(secret, salt)
+ c.Set(csrfToken, token)
+
+ return token
+}
diff --git a/app/mw/mw_db.go b/app/mw/mw_db.go
new file mode 100644
index 0000000..6e2d41f
--- /dev/null
+++ b/app/mw/mw_db.go
@@ -0,0 +1,149 @@
+package mw
+
+import (
+ "applet/app/svc"
+ "applet/app/utils"
+ "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/rule/mw"
+ "errors"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "strings"
+
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/md"
+)
+
+// DB is 中间件 用来检查master_id是否有对应的数据库engine
+func DB(c *gin.Context) {
+ fmt.Println(c.Request.Header)
+ masterID := mw.GetMasterId(db.Db, c)
+ _, ok := db.DBs[masterID]
+ if !ok {
+ e.OutErr(c, e.ERR_MASTER_ID, errors.New("not found master_id in DBs"))
+ return
+ }
+
+ fmt.Println("master_id", masterID)
+ c.Set("mid", masterID)
+ //判断应用是不是过期了
+ isOverTime := svc.SysCfgGet(c, "is_over_time")
+ if isOverTime == "1" {
+ str := "应用已过期"
+ overTimeStr := svc.SysCfgGet(c, "over_time_str")
+ if overTimeStr != "" {
+ str = overTimeStr
+ }
+ e.OutErr(c, 400, e.NewErr(400, str))
+ return
+ }
+ closeStation := svc.SysCfgGet(c, "close_station")
+ closeAppVersion := svc.SysCfgGet(c, "close_app_version")
+ platform := c.GetHeader("Platform")
+ if strings.Contains(closeStation, platform) && closeStation != "" && utils.StrToInt64(c.GetHeader("app_version")) <= utils.StrToInt64(closeAppVersion) {
+ str := "应用关闭"
+ overTimeStr := svc.SysCfgGet(c, "over_time_str")
+ if overTimeStr != "" {
+ str = overTimeStr
+ }
+ e.OutErr(c, 400, e.NewErr(400, str))
+ return
+ }
+ //判断是否有独立域名
+ domainWapBase := svc.GetWebSiteDomainInfo(c, "wap")
+
+ httpStr := "http://"
+ if c.GetHeader("Platform") == md.PLATFORM_WX_APPLET || c.GetHeader("Platform") == md.PLATFORM_ALIPAY_APPLET || c.GetHeader("Platform") == md.PLATFORM_BAIDU_APPLET || c.GetHeader("Platform") == md.PLATFORM_TOUTIAO_APPLET || c.GetHeader("Platform") == md.PLATFORM_TIKTOK_APPLET {
+ httpStr = "https://"
+ domainWapBase = strings.Replace(domainWapBase, "http://", httpStr, 1)
+ }
+ c.Set("domain_wap_base", domainWapBase)
+ c.Set("domain_wap_base_new", svc.GetWebSiteLiveBroadcastDomainInfo(c, "wap", masterID))
+ c.Set("domain_wap_base_second", svc.GetWebSiteDomainInfoSecond(c, "wap"))
+
+ c.Set("http_host", httpStr)
+
+ c.Set("h5_api_secret_key", svc.SysCfgGet(c, "h5_api_secret_key"))
+ c.Set("app_api_secret_key", svc.SysCfgGet(c, "app_api_secret_key"))
+ c.Set("applet_api_secret_key", svc.SysCfgGet(c, "applet_api_secret_key"))
+ c.Set("integral_prec", svc.SysCfgGet(c, "integral_prec"))
+ fanOrderCommissionPrec := svc.SysCfgGet(c, "fan_order_commission_prec")
+ if fanOrderCommissionPrec == "" {
+ fanOrderCommissionPrec = "2"
+ }
+ c.Set("fan_order_commission_prec", fanOrderCommissionPrec)
+ areaOrderCommissionPrec := svc.SysCfgGet(c, "area_order_commission_prec")
+ if areaOrderCommissionPrec == "" {
+ areaOrderCommissionPrec = "2"
+ }
+ c.Set("area_order_commission_prec", areaOrderCommissionPrec)
+
+ commissionPrec := svc.SysCfgGet(c, "commission_prec")
+ c.Set("commission_prec", commissionPrec)
+ pricePrec := svc.SysCfgGet(c, "price_prec")
+ if pricePrec == "" {
+ pricePrec = commissionPrec
+ }
+ dsChcek := svc.SysCfgGet(c, "ds_check")
+ if dsChcek == "1" {
+ pricePrec = commissionPrec
+ }
+ c.Set("price_prec", pricePrec)
+ c.Set("is_show_point", svc.SysCfgGet(c, "is_show_point"))
+ c.Set("appUserDefaultAvatar", svc.SysCfgGet(c, "app_user_default_avatar"))
+
+ translateOpen := ""
+ if strings.Contains(c.GetHeader("locale"), "zh_Hant_") {
+ translateOpen = "zh_Hant_"
+ }
+ if strings.Contains(c.GetHeader("locale"), "ug_CN") {
+ translateOpen = "ug_CN"
+ }
+ c.Set("translate_open", translateOpen)
+ orderVirtualCoinType := db.SysCfgGet(c, "order_virtual_coin_type")
+ c.Set("order_virtual_coin_type", orderVirtualCoinType)
+ orderVirtualCoinName := db.SysCfgGet(c, "order_virtual_coin_name")
+ if orderVirtualCoinName == "" {
+ orderVirtualCoinName = "收益:¥"
+ }
+ c.Set("orderVirtualCoinName", orderVirtualCoinName)
+ h5AppletMustSign := svc.SysCfgGet(c, "h5_applet_must_sign")
+ c.Set("h5_applet_must_sign", h5AppletMustSign)
+ androidMustSign := svc.SysCfgGet(c, "android_must_sign")
+ c.Set("android_must_sign", androidMustSign)
+ iosMustSign := svc.SysCfgGet(c, "ios_must_sign")
+ c.Set("ios_must_sign", iosMustSign)
+ c.Set("is_not_change_url", "0")
+ smsType := svc.SysCfgGet(c, "sms_type")
+ c.Set("sms_type", smsType)
+ if utils.StrToInt64(c.GetHeader("app_version")) > 1678445020 {
+ // || utils.StrToInt64(c.GetHeader("BuildVersion")) > 1678676004
+ c.Set("is_not_change_url", "1")
+ }
+ if utils.InArr(c.GetHeader("platform"), []string{md.PLATFORM_ANDROID, md.PLATFORM_IOS}) == false {
+ c.Set("is_not_change_url", "1")
+ }
+ GetHeaderParam(c)
+ c.Next()
+}
+
+func GetHeaderParam(c *gin.Context) {
+ var appTypeList = []string{"app_type", "App_type", "AppType"}
+ appType := ""
+ for _, v := range appTypeList {
+ val := c.GetHeader(v)
+ if val != "" {
+ appType = val
+ }
+ }
+ c.Set("app_type", appType)
+ var storeIdList = []string{"store_id", "Store_id", "StoreId"}
+ storeId := ""
+ for _, v := range storeIdList {
+ val := c.GetHeader(v)
+ if val != "" {
+ storeId = val
+ }
+ }
+ c.Set("store_id", storeId)
+}
diff --git a/app/mw/mw_limiter.go b/app/mw/mw_limiter.go
new file mode 100644
index 0000000..3c9fb79
--- /dev/null
+++ b/app/mw/mw_limiter.go
@@ -0,0 +1,77 @@
+package mw
+
+import (
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/svc"
+ "applet/app/utils"
+ "applet/app/utils/cache"
+ "bytes"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "io/ioutil"
+)
+
+func Limiter(c *gin.Context) {
+ limit := 200 // 限流次数
+ ttl := 2 // 限流过期时间
+ ip := c.ClientIP()
+ // 读取token或者ip
+ token := c.GetHeader("Authorization")
+ mid := c.GetString("mid")
+ // 判断是否已经超出限额次数
+ method := c.Request.Method
+ host := c.Request.Host
+ uri := c.Request.URL.String()
+
+ buf := make([]byte, 5120*10)
+ num, _ := c.Request.Body.Read(buf)
+ body := buf[:num]
+ // Write body back
+ c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
+ //queryValue := utils.SerializeStr(c.Request.URL.Query()) //不建议开启,失去限流的意义
+
+ //TODO::分布式锁阻拦(保证原子性)
+ requestIdPrefix := fmt.Sprintf(md.DealAppLimiterRequestIdPrefix, mid, ip)
+ cb, err := svc.HandleLimiterDistributedLock(mid, ip, requestIdPrefix)
+ if err != nil {
+ e.OutErr(c, e.ERR, err.Error())
+ return
+ }
+ if cb != nil {
+ defer cb() // 释放锁
+ }
+
+ Md5 := utils.Md5(ip + token + method + host + uri + string(body))
+ //Md5 := utils.Md5(ip + token + method + host + uri + string(body) + queryValue)
+ if cache.Exists(Md5) {
+ c.AbortWithStatusJSON(428, gin.H{
+ "code": 428,
+ "msg": "don't repeat the request",
+ "data": struct{}{},
+ })
+ return
+ }
+
+ // 2s后没返回自动释放
+ go cache.SetEx(Md5, "0", ttl)
+
+ key := "NEW_LIMITER_APP_COMM_" + ip
+ reqs, _ := cache.GetInt(key)
+ if reqs >= limit {
+ c.AbortWithStatusJSON(429, gin.H{
+ "code": 429,
+ "msg": "too many requests",
+ "data": struct{}{},
+ })
+ return
+ }
+ if reqs > 0 {
+ //go cache.Incr(key)
+ go cache.SetEx(key, reqs+1, ttl)
+ } else {
+ go cache.SetEx(key, 1, ttl)
+ }
+ c.Next()
+ go cache.Del(Md5)
+}
diff --git a/app/mw/mw_limiter_newcomers.go b/app/mw/mw_limiter_newcomers.go
new file mode 100644
index 0000000..7cf7d2b
--- /dev/null
+++ b/app/mw/mw_limiter_newcomers.go
@@ -0,0 +1,77 @@
+package mw
+
+import (
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/svc"
+ "applet/app/utils"
+ "applet/app/utils/cache"
+ "bytes"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "io/ioutil"
+)
+
+func LimiterNewComers(c *gin.Context) {
+ limit := 200 // 限流次数
+ ttl := 2 // 限流过期时间
+ ip := c.ClientIP()
+ // 读取token或者ip
+ token := c.GetHeader("Authorization")
+ mid := c.GetString("mid")
+ // 判断是否已经超出限额次数
+ method := c.Request.Method
+ host := c.Request.Host
+ uri := c.Request.URL.String()
+
+ buf := make([]byte, 5120*10)
+ num, _ := c.Request.Body.Read(buf)
+ body := buf[:num]
+ // Write body back
+ c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
+ //queryValue := utils.SerializeStr(c.Request.URL.Query()) //不建议开启,失去限流的意义
+
+ //TODO::分布式锁阻拦(保证原子性)
+ requestIdPrefix := fmt.Sprintf(md.DealAppNewcomersLimiterRequestIdPrefix, mid, ip)
+ cb, err := svc.HandleLimiterDistributedLock(mid, ip, requestIdPrefix)
+ if err != nil {
+ e.OutErr(c, e.ERR, err.Error())
+ return
+ }
+ if cb != nil {
+ defer cb() // 释放锁
+ }
+
+ Md5 := utils.Md5(ip + token + method + host + uri + string(body))
+ //Md5 := utils.Md5(ip + token + method + host + uri + string(body) + queryValue)
+ if cache.Exists(Md5) {
+ c.AbortWithStatusJSON(428, gin.H{
+ "code": 428,
+ "msg": "请求频繁",
+ "data": struct{}{},
+ })
+ return
+ }
+
+ // 2s后没返回自动释放
+ go cache.SetEx(Md5, "0", ttl)
+
+ key := "NEW_LIMITER_APP_NEWCOMERS_COMM_" + ip
+ reqs, _ := cache.GetInt(key)
+ if reqs >= limit {
+ c.AbortWithStatusJSON(429, gin.H{
+ "code": 429,
+ "msg": "请求频繁",
+ "data": struct{}{},
+ })
+ return
+ }
+ if reqs > 0 {
+ //go cache.Incr(key)
+ go cache.SetEx(key, reqs+1, ttl)
+ } else {
+ go cache.SetEx(key, 1, ttl)
+ }
+ c.Next()
+ go cache.Del(Md5)
+}
diff --git a/app/mw/mw_recovery.go b/app/mw/mw_recovery.go
new file mode 100644
index 0000000..b32cc82
--- /dev/null
+++ b/app/mw/mw_recovery.go
@@ -0,0 +1,57 @@
+package mw
+
+import (
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "runtime/debug"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+func Recovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ defer func() {
+ if err := recover(); err != nil {
+ var brokenPipe bool
+ if ne, ok := err.(*net.OpError); ok {
+ if se, ok := ne.Err.(*os.SyscallError); ok {
+ if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
+ brokenPipe = true
+ }
+ }
+ }
+
+ httpRequest, _ := httputil.DumpRequest(c.Request, false)
+ if brokenPipe {
+ logger.Error(c.Request.URL.Path,
+ zap.Any("error", err),
+ zap.String("request", string(httpRequest)),
+ )
+ // If the connection is dead, we can't write a status to it.
+ c.Error(err.(error))
+ c.Abort()
+ return
+ }
+
+ if stack {
+ logger.Error("[Recovery from panic]",
+ zap.Any("error", err),
+ zap.String("request", string(httpRequest)),
+ zap.String("stack", string(debug.Stack())),
+ )
+ } else {
+ logger.Error("[Recovery from panic]",
+ zap.Any("error", err),
+ zap.String("request", string(httpRequest)),
+ )
+ }
+ c.AbortWithStatus(http.StatusInternalServerError)
+ }
+ }()
+ c.Next()
+ }
+}
diff --git a/app/router/router.go b/app/router/router.go
new file mode 100644
index 0000000..94edf78
--- /dev/null
+++ b/app/router/router.go
@@ -0,0 +1,72 @@
+package router
+
+import (
+ "applet/app/cfg"
+ "applet/app/hdl"
+ "applet/app/mw"
+ _ "applet/docs"
+ "github.com/gin-gonic/gin"
+)
+
+// 初始化路由
+// 1
+func Init() *gin.Engine {
+ // debug, release, test 项目阶段
+ mode := "release"
+ if cfg.Debug {
+ mode = "debug"
+ }
+ gin.SetMode(mode)
+ //创建一个新的启动器
+ r := gin.New()
+ r.Use(mw.ChangeHeader)
+
+ // 是否打印访问日志, 在非正式环境都打印
+ if mode != "release" {
+ r.Use(gin.Logger())
+ }
+ r.Use(gin.Recovery())
+
+ r.GET("/favicon.ico", func(c *gin.Context) {
+ c.Status(204)
+ })
+ r.NoRoute(func(c *gin.Context) {
+ c.JSON(404, gin.H{"code": 404, "msg": "page not found", "data": []struct{}{}})
+ })
+ r.NoMethod(func(c *gin.Context) {
+ c.JSON(405, gin.H{"code": 405, "msg": "method not allowed", "data": []struct{}{}})
+ })
+ r.Use(mw.Cors)
+ routeCommunityTeam(r.Group("/api/v1/communityTeam"))
+ return r
+}
+func routeCommunityTeam(r *gin.RouterGroup) {
+ r.Use(mw.DB) // 下面接口再根据mid 获取数据库名
+ r.Use(mw.CheckBody) //body参数转换
+ r.Use(mw.CheckSign) //签名校验
+ r.Use(mw.Checker)
+ r.GET("/cate", hdl.Cate)
+ r.GET("/bank/store/cate", hdl.BankStoreCate)
+ r.POST("/bank/store/list", hdl.BankStore)
+ r.POST("/store", hdl.Store)
+ // 用户授权后调用的接口
+ r.Use(mw.AuthJWT)
+ r.POST("/store/addLike", hdl.StoreAddLike)
+ r.POST("/store/cancelLike", hdl.StoreCancelLike)
+ r.POST("/goods", hdl.Goods)
+ r.POST("/goods/sku", hdl.GoodsSku)
+ r.POST("/goods/coupon", hdl.GoodsCoupon)
+ r.POST("/order/total", hdl.OrderTotal)
+ r.POST("/order/create", hdl.OrderCreate)
+ r.POST("/order/cancel", hdl.OrderCancel)
+ r.POST("/order/coupon", hdl.OrderCoupon)
+ r.POST("/order/list", hdl.OrderList)
+ r.POST("/order/detail", hdl.OrderDetail)
+ r.GET("/order/cate", hdl.OrderCate)
+ r.POST("/pay/:payMethod/:orderType", hdl.Pay)
+
+ r.POST("/store/order/list", hdl.StoreOrderList)
+ r.POST("/store/order/detail", hdl.StoreOrderDetail)
+ r.POST("/store/order/confirm", hdl.StoreOrderConfirm)
+ r.GET("/store/order/cate", hdl.StoreOrderCate)
+}
diff --git a/app/svc/svc_alipay.go b/app/svc/svc_alipay.go
new file mode 100644
index 0000000..b2b384f
--- /dev/null
+++ b/app/svc/svc_alipay.go
@@ -0,0 +1,106 @@
+package svc
+
+import (
+ "applet/app/cfg"
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/logx"
+ "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/pay"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "github.com/iGoogle-ink/gopay/alipay"
+)
+
+// 支付宝回调处理
+func AlipayCallback(c *gin.Context) (string, error) {
+ data, ok := c.Get("callback")
+ if data == nil || !ok {
+ return "", e.NewErrCode(e.ERR_INVALID_ARGS)
+ }
+ args := data.(*md.AliPayCallback)
+ _, ok = db.DBs[args.MasterID]
+ if !ok {
+ return "", logx.Warn("Alipay Failed : master_id not found")
+ }
+ c.Set("mid", args.MasterID)
+ // 回调交易状态失败
+ if args.TradeStatus != "TRADE_SUCCESS" {
+ return "", logx.Warn("Alipay Failed : trade status failed")
+ }
+ return args.OutTradeNo, nil
+}
+
+func PrepareAlipayCode(c *gin.Context, p *md.AliPayPayParams) (interface{}, error) {
+ req, err := CommAlipayConfig(c, p)
+ if err != nil {
+ return "", err
+ }
+ var param interface{}
+ switch req["platform"] {
+ case md.PLATFORM_ALIPAY_APPLET:
+ param, err = pay.AlipayApplet(req)
+ case md.PLATFORM_WAP:
+ param, err = pay.AlipayWap(req)
+ case md.PLATFORM_ANDROID, md.PLATFORM_IOS:
+ param, err = pay.AlipayApp(req)
+ default:
+ return "", e.NewErrCode(e.ERR_PLATFORM)
+ }
+ if err != nil {
+ fmt.Println("支付宝错误日志")
+ fmt.Println(param)
+ fmt.Println(err)
+ return "", e.NewErrCode(e.ERR_ALIPAY_ORDER_ERR)
+ }
+ return utils.AnyToString(param), nil
+
+}
+
+func CommAlipayConfig(c *gin.Context, p *md.AliPayPayParams) (map[string]string, error) {
+ //获取支付配置
+ req := map[string]string{
+ "pay_ali_use_type": SysCfgGet(c, "pay_ali_use_type"),
+ "private_key": SysCfgGet(c, "pay_ali_private_key"),
+ "app_id": SysCfgGet(c, "pay_ali_app_id"),
+ "rsa": SysCfgGet(c, "pay_ali_key_len_type"),
+ "pkcs": SysCfgGet(c, "pay_ali_key_format_type"),
+ }
+ if req["pay_ali_use_type"] == "1" {
+ req["private_key"] = SysCfgGet(c, "pay_ali_new_private_key")
+ req["app_id"] = SysCfgGet(c, "pay_ali_new_app_id")
+ appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + SysCfgGet(c, "pay_app_cert_sn"))
+ if err != nil {
+ fmt.Println(err)
+ return nil, err
+ }
+ if appCertSN == "" {
+ fmt.Println(err)
+ return nil, err
+ }
+ req["pay_app_cert_sn"] = appCertSN
+ aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + SysCfgGet(c, "pay_alipayrsa_public_key"))
+ if err != nil {
+ fmt.Println(err)
+ return nil, err
+ }
+ if aliPayPublicCertSN == "" {
+ fmt.Println(err)
+ return nil, err
+ }
+ req["pay_alipayrsa_public_key"] = aliPayPublicCertSN
+ }
+ if req["private_key"] == "" || req["app_id"] == "" {
+ return req, e.NewErr(400, "请在后台正确配置支付宝")
+ }
+ req["ord_id"] = p.OrdId
+ req["amount"] = p.Amount
+ req["subject"] = p.Subject
+ req["order_type"] = p.OrderType
+ req["notify_url"] = fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), p.OrderType, md.ALIPAY)
+ req["platform"] = c.GetHeader("Platform")
+ req["page_url"] = c.Query("page_url")
+ utils.FilePutContents(c.GetString("mid")+"alipay", utils.SerializeStr(req))
+ return req, nil
+}
diff --git a/app/svc/svc_auth.go b/app/svc/svc_auth.go
new file mode 100644
index 0000000..988a3d7
--- /dev/null
+++ b/app/svc/svc_auth.go
@@ -0,0 +1,69 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/md"
+ "applet/app/utils"
+ "errors"
+ "github.com/gin-gonic/gin"
+ "strings"
+)
+
+// 因为在mw_auth已经做完所有校验, 因此在此不再做任何校验
+// GetUser is get user model
+func GetUser(c *gin.Context) *md.User {
+ user, _ := c.Get("user")
+ if user == nil {
+ return nil
+ }
+ return user.(*md.User)
+}
+
+func GetUid(c *gin.Context) string {
+ user, _ := c.Get("user")
+ u := user.(*md.User)
+ return utils.IntToStr(u.Info.Uid)
+}
+
+func CheckUser(c *gin.Context) (*md.User, error) {
+ token := c.GetHeader("Authorization")
+ if token == "" {
+ return nil, errors.New("token not exist")
+ }
+ // 按空格分割
+ parts := strings.SplitN(token, " ", 2)
+ if !(len(parts) == 2 && parts[0] == "Bearer") {
+ return nil, errors.New("token format error")
+ }
+ // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
+ mc, err := utils.ParseToken(parts[1])
+ if err != nil {
+ return nil, err
+ }
+
+ // 获取user
+ u, err := db.UserFindByID(db.DBs[c.GetString("mid")], mc.UID)
+ if err != nil {
+ return nil, err
+ }
+ if u == nil {
+ return nil, errors.New("token can not find user")
+ }
+ // 获取user profile
+ up, err := db.UserProfileFindByID(db.DBs[c.GetString("mid")], mc.UID)
+ if err != nil {
+ return nil, err
+ }
+ // 获取user 等级
+ ul, err := db.UserLevelByID(db.DBs[c.GetString("mid")], u.Level)
+ if err != nil {
+ return nil, err
+ }
+
+ user := &md.User{
+ Info: u,
+ Profile: up,
+ Level: ul,
+ }
+ return user, nil
+}
diff --git a/app/svc/svc_balance.go b/app/svc/svc_balance.go
new file mode 100644
index 0000000..6e30eec
--- /dev/null
+++ b/app/svc/svc_balance.go
@@ -0,0 +1,74 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/logx"
+ "github.com/gin-gonic/gin"
+ "time"
+ "xorm.io/xorm"
+)
+
+func BalancePay(c *gin.Context, money, oid string, types string) error {
+
+ user, err := CheckUser(c)
+ if user == nil || err != nil {
+ return err
+ }
+ // 获取余额更新锁
+ cb, err := HandleBalanceDistributedLock(c.GetString("mid"), utils.IntToStr(user.Info.Uid), "balance_pay")
+ if err != nil {
+ return err
+ }
+ // 释放锁
+ if cb != nil {
+ defer cb()
+ }
+ finValid := utils.AnyToFloat64(user.Profile.FinValid)
+ needMoney := utils.AnyToFloat64(money)
+ if finValid < needMoney {
+ return e.NewErrCode(e.ERR_BALANCE_NOT_ENOUGH)
+ }
+ user.Profile.FinValid = utils.AnyToString(finValid - needMoney)
+ affect, err := db.UserProfileUpdate(db.DBs[c.GetString("mid")], user.Profile.Uid, user.Profile, "fin_valid")
+ if err != nil || affect != 1 {
+ return err
+ }
+ var str = ""
+ if types == md.CommunityTeam {
+ str = "小店下单"
+ }
+ flowInsert(db.DBs[c.GetString("mid")], user.Profile.Uid, money, 21, utils.StrToInt64(oid), 0, 0, str+"余额支付", types, 1, utils.Float64ToStr(finValid), user.Profile.FinValid)
+ return nil
+}
+func flowInsert(eg *xorm.Engine, uid int, paidPrice string, orderAction int, ordId int64, id int64, goodsId int, ItemTitle string, ordType string, types int, beforeAmount string, afterAmount string) {
+ session := eg.NewSession()
+
+ now := time.Now()
+ if err := db.FinUserFlowInsertOneWithSession(
+ session,
+ &model.FinUserFlow{
+ Type: types,
+ Uid: uid,
+ Amount: paidPrice,
+ BeforeAmount: beforeAmount,
+ AfterAmount: afterAmount,
+ OrdType: ordType,
+ OrdId: utils.Int64ToStr(ordId),
+ OrdAction: orderAction,
+ OrdDetail: utils.IntToStr(goodsId),
+ State: 2,
+ OtherId: id,
+ OrdTitle: ItemTitle,
+ OrdTime: int(now.Unix()),
+ CreateAt: now,
+ UpdateAt: now,
+ }); err != nil {
+ _ = session.Rollback()
+ _ = logx.Warn(err)
+ return
+ }
+}
diff --git a/app/svc/svc_cate.go b/app/svc/svc_cate.go
new file mode 100644
index 0000000..1b02dbe
--- /dev/null
+++ b/app/svc/svc_cate.go
@@ -0,0 +1,24 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/utils"
+ "github.com/gin-gonic/gin"
+)
+
+func Cate(c *gin.Context) {
+ cate := db.GetCate(MasterDb(c), "0")
+ cateList := make([]map[string]string, 0)
+ if cate != nil {
+ for _, v := range *cate {
+ tmp := map[string]string{
+ "id": utils.IntToStr(v.Id),
+ "name": v.Title,
+ }
+ cateList = append(cateList, tmp)
+ }
+ }
+ e.OutSuc(c, cateList, nil)
+ return
+}
diff --git a/app/svc/svc_comm.go b/app/svc/svc_comm.go
new file mode 100644
index 0000000..f5b28e3
--- /dev/null
+++ b/app/svc/svc_comm.go
@@ -0,0 +1,25 @@
+package svc
+
+import (
+ "applet/app/utils"
+ "github.com/gin-gonic/gin"
+ "strings"
+)
+
+func GetCommissionPrec(c *gin.Context, sum, commPrec, isShowPoint string) string {
+ if sum == "" {
+ sum = "0"
+ }
+ sum = utils.StrToFormat(c, sum, utils.StrToInt(commPrec))
+ ex := strings.Split(sum, ".")
+ if len(ex) == 2 && isShowPoint != "1" {
+ if utils.StrToFloat64(ex[1]) == 0 {
+ sum = ex[0]
+ } else {
+ val := utils.Float64ToStrByPrec(utils.StrToFloat64(ex[1]), 0)
+ valNew := strings.ReplaceAll(val, "0", "")
+ sum = ex[0] + "." + strings.ReplaceAll(ex[1], val, valNew)
+ }
+ }
+ return sum
+}
diff --git a/app/svc/svc_db.go b/app/svc/svc_db.go
new file mode 100644
index 0000000..99b1e0d
--- /dev/null
+++ b/app/svc/svc_db.go
@@ -0,0 +1,11 @@
+package svc
+
+import (
+ "applet/app/db"
+ "github.com/gin-gonic/gin"
+ "xorm.io/xorm"
+)
+
+func MasterDb(c *gin.Context) *xorm.Engine {
+ return db.DBs[c.GetString("mid")]
+}
diff --git a/app/svc/svc_default_user.go b/app/svc/svc_default_user.go
new file mode 100644
index 0000000..63bdaaa
--- /dev/null
+++ b/app/svc/svc_default_user.go
@@ -0,0 +1,50 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/md"
+ "errors"
+ "github.com/gin-gonic/gin"
+ "strings"
+)
+
+// GetDefaultUser is 获取默认账号,uid =0 为系统默认账号,用于一些请求需要渠道id之类的东西
+func GetDefaultUser(c *gin.Context, token string) (*md.User, error) {
+ user := new(md.User)
+ if c.GetString("convert_url") == "1" { //转链接口
+ goto DEFALUT
+ } else {
+ // Token 不为空时拿对应的用户数据
+ if token != "" && strings.Contains(token, "Bearer") {
+
+ user, err := CheckUser(c)
+ if user == nil {
+ return nil, errors.New("token is expired")
+ }
+ if err != nil {
+ // 有报错自己拿默认用户
+ goto DEFALUT
+ }
+ return user, nil
+ }
+ }
+
+DEFALUT:
+ // 默认拿uid 等于0的用户数据
+ profile, err := db.UserProfileFindByID(db.DBs[c.GetString("mid")], 0)
+ if err != nil {
+ return nil, err
+ }
+ info, err := db.UserFindByID(db.DBs[c.GetString("mid")], 0)
+ if err != nil {
+ return nil, err
+ }
+ ul, err := db.UserLevelInIDescByWeightLowWithOne(db.DBs[c.GetString("mid")])
+ if err != nil {
+ return nil, err
+ }
+ user.Info = info
+ user.Profile = profile
+ user.Level = ul
+ return user, nil
+}
diff --git a/app/svc/svc_domain_info.go b/app/svc/svc_domain_info.go
new file mode 100644
index 0000000..9318ad9
--- /dev/null
+++ b/app/svc/svc_domain_info.go
@@ -0,0 +1,206 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/db/offical"
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/logx"
+ "github.com/gin-gonic/gin"
+ "github.com/tidwall/gjson"
+ "strings"
+)
+
+// 获取指定类型的域名:admin、wap、api
+func GetWebSiteDomainInfo(c *gin.Context, domainType string) string {
+ if domainType == "" {
+ domainType = "wap"
+ }
+
+ domainSetting := SysCfgGet(c, "domain_setting")
+
+ domainTypePath := domainType + ".type"
+ domainSslPath := domainType + ".isOpenHttps"
+ domainPath := domainType + ".domain"
+
+ domainTypeValue := gjson.Get(domainSetting, domainTypePath).String()
+ domainSslValue := gjson.Get(domainSetting, domainSslPath).String()
+ domain := gjson.Get(domainSetting, domainPath).String()
+
+ scheme := "http://"
+ if domainSslValue == "1" {
+ scheme = "https://"
+ }
+
+ // 有自定义域名 返回自定义的
+ if domainTypeValue == "own" && domain != "" {
+ return scheme + domain
+ }
+ // 否则返回官方的
+ official, err := db.GetOfficialDomainInfoByType(db.Db, c.GetString("mid"), domainType)
+ if err != nil {
+ _ = logx.Errorf("Get Official Domain Fail! %s", err)
+ return ""
+ }
+ if strings.Contains(official, "http") {
+ return official
+ }
+ return scheme + official
+}
+func GetWebSiteDomainInfoToAgent(c *gin.Context, domainType string) string {
+ if domainType == "" {
+ domainType = "wap"
+ }
+
+ domainSetting := SysCfgGet(c, "domain_setting")
+
+ domainTypePath := domainType + ".type"
+ domainSslPath := domainType + ".isOpenHttps"
+ domainPath := domainType + ".domain"
+
+ domainTypeValue := gjson.Get(domainSetting, domainTypePath).String()
+ domainSslValue := gjson.Get(domainSetting, domainSslPath).String()
+ domain := gjson.Get(domainSetting, domainPath).String()
+
+ scheme := "http://"
+ if domainSslValue == "1" {
+ scheme = "https://"
+ }
+
+ // 有自定义域名 返回自定义的
+ if domainTypeValue == "own" && domain != "" {
+ return scheme + domain
+ }
+ // 否则返回官方的
+ puid := AppUserListPuid(c)
+ var official = ""
+ var err error
+ if puid != "" && puid != "0" {
+ official, err = db.GetOfficialDomainInfoByTypeToAgent(db.Db, c.GetString("mid"), puid, domainType)
+ if err != nil {
+ _ = logx.Errorf("Get Official Domain Fail! %s", err)
+ return ""
+ }
+ } else {
+ official, err = db.GetOfficialDomainInfoByType(db.Db, c.GetString("mid"), domainType)
+ if err != nil {
+ _ = logx.Errorf("Get Official Domain Fail! %s", err)
+ return ""
+ }
+ }
+ if strings.Contains(official, "http") {
+ return official
+ }
+ return scheme + official
+}
+func GetWebSiteDomainInfoOfficial(c *gin.Context, domainType string) string {
+ if domainType == "" {
+ domainType = "wap"
+ }
+
+ domainSetting := SysCfgGet(c, "domain_setting")
+
+ domainSslPath := domainType + ".isOpenHttps"
+
+ domainSslValue := gjson.Get(domainSetting, domainSslPath).String()
+
+ scheme := "http://"
+ if domainSslValue == "1" {
+ scheme = "https://"
+ }
+
+ // 有自定义域名 返回自定义的
+ // 否则返回官方的
+ official, err := db.GetOfficialDomainInfoByType(db.Db, c.GetString("mid"), domainType)
+ if err != nil {
+ _ = logx.Errorf("Get Official Domain Fail! %s", err)
+ return ""
+ }
+ if strings.Contains(official, "http") {
+ return official
+ }
+ return scheme + official
+}
+
+// 获取指定类型的域名对应的masterId:admin、wap、api
+func GetWebSiteDomainMasterId(domainType string, host string) string {
+ obj := new(model.UserAppDomain)
+ has, err := db.Db.Where("domain=? and type=?", host, domainType).Get(obj)
+ if err != nil || !has {
+ return ""
+ }
+ return utils.AnyToString(obj.Uuid)
+}
+
+func GetWebSiteAppSmsPlatform(mid string) string {
+ obj := new(model.UserAppList)
+ has, err := db.Db.Where("uuid=? ", mid).Asc("id").Get(obj)
+ if err != nil || !has {
+ return ""
+ }
+ return utils.AnyToString(obj.SmsPlatform)
+}
+
+// 获取指定类型的域名:admin、wap、api
+func GetWebSiteLiveBroadcastDomainInfo(c *gin.Context, domainType, mid string) string {
+ if domainType == "" {
+ domainType = "wap"
+ }
+
+ domainSetting := SysCfgGet(c, "domain_setting")
+ domainSslPath := domainType + ".isOpenHttps"
+ domainSslValue := gjson.Get(domainSetting, domainSslPath).String()
+ //masterid.izhyin.cn
+ domain := mid + ".izhim.net"
+
+ scheme := "http://"
+ if domainSslValue == "1" {
+ scheme = "https://"
+ }
+ if c.GetHeader("platform") == md.PLATFORM_WX_APPLET { //小程序需要https
+ scheme = "https://"
+ }
+ return scheme + domain
+}
+func GetWebSiteDomainInfoSecond(c *gin.Context, domainType string) string {
+ if domainType == "" {
+ domainType = "wap"
+ }
+
+ domainSetting := SysCfgGet(c, "domain_setting")
+ domainSslPath := domainType + ".isOpenHttps"
+ domainSslValue := gjson.Get(domainSetting, domainSslPath).String()
+ domain := c.GetString("mid") + ".izhim.net"
+ domainTypePath := domainType + ".type"
+ domainTypeValue := gjson.Get(domainSetting, domainTypePath).String()
+ scheme := "http://"
+ if domainSslValue == "1" {
+ scheme = "https://"
+ }
+ if c.GetHeader("platform") == md.PLATFORM_WX_APPLET { //小程序需要https
+ scheme = "https://"
+ }
+ // 有自定义域名 返回自定义的
+ if domainTypeValue == "own" {
+ domainPath := domainType + ".domain"
+ domain = gjson.Get(domainSetting, domainPath).String()
+ }
+ return scheme + domain
+}
+func AppUserListPuid(c *gin.Context) string {
+ appList := offical.GetUserAppList(c.GetString("mid"))
+ uid := "0"
+ if appList != nil && appList.Puid > 0 {
+ uid = utils.IntToStr(appList.Puid)
+ }
+ return uid
+}
+func AppUserListPuidWithDb(dbName string) string {
+ appList := offical.GetUserAppList(dbName)
+ uid := "0"
+ if appList != nil && appList.Puid > 0 {
+ uid = utils.IntToStr(appList.Puid)
+ }
+ return uid
+}
diff --git a/app/svc/svc_file_img_format.go b/app/svc/svc_file_img_format.go
new file mode 100644
index 0000000..4131981
--- /dev/null
+++ b/app/svc/svc_file_img_format.go
@@ -0,0 +1,81 @@
+package svc
+
+import (
+ "applet/app/utils"
+ "fmt"
+ "github.com/syyongx/php2go"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+// ImageFormat is 格式化 图片
+func ImageFormat(c *gin.Context, name string) string {
+ if strings.Contains(name, "https:") || strings.Contains(name, "http:") {
+ return name
+ }
+ scheme := SysCfgGet(c, "file_bucket_scheme")
+ domain := SysCfgGet(c, "file_bucket_host")
+ name, _ = php2go.URLDecode(name)
+ name = php2go.URLEncode(name)
+ return fmt.Sprintf("%s://%s/%s", scheme, domain, name)
+}
+
+// OffImageFormat is 格式化官方 图片
+func OffImageFormat(c *gin.Context, name string) string {
+ if strings.Contains(name, "https:") || strings.Contains(name, "http:") {
+ return name
+ }
+ name, _ = php2go.URLDecode(name)
+ name = php2go.URLEncode(name)
+
+ return fmt.Sprintf("%s://%s/%s", "http", "ossn.izhim.net", name)
+}
+
+// ImageBucket is 获取域名
+func ImageBucket(c *gin.Context) (string, string) {
+ return SysCfgGet(c, "file_bucket_scheme"), SysCfgGet(c, "file_bucket_host")
+}
+
+// ImageFormatWithBucket is 格式化成oss 域名
+func ImageFormatWithBucket(scheme, domain, name string) string {
+ if strings.Contains(name, "http") || name == "" {
+ return name
+ }
+ name, _ = php2go.URLDecode(name)
+ name = php2go.URLEncode(name)
+ return fmt.Sprintf("%s://%s/%s", scheme, domain, name)
+}
+
+// ImageBucketNew is 获取域名
+func ImageBucketNew(c *gin.Context) (string, string, string, map[string]string) {
+ var list = make(map[string]string, 0)
+ for i := 1; i < 10; i++ {
+ keys := "file_bucket_sub_host" + utils.IntToStr(i)
+ list[keys] = SysCfgGet(c, keys)
+ }
+ return SysCfgGet(c, "file_bucket_scheme"), SysCfgGet(c, "file_bucket_host"), SysCfgGet(c, "file_bucket_sub_host"), list
+}
+
+// ImageFormatWithBucket is 格式化成oss 域名
+func ImageFormatWithBucketNew(scheme, domain, subDomain string, moreSubDomain map[string]string, name string) string {
+ if strings.Contains(name, "http") {
+ return name
+ }
+ if strings.Contains(name, "{{subhost}}") && subDomain != "" { //读副域名 有可能是其他平台的
+ domain = subDomain
+ }
+ //为了兼容一些客户自营商城导到不同系统 并且七牛云不一样
+ for i := 1; i < 10; i++ {
+ keys := "file_bucket_sub_host" + utils.IntToStr(i)
+ if strings.Contains(name, "{{subhost"+utils.IntToStr(i)+"}}") && moreSubDomain[keys] != "" {
+ domain = moreSubDomain[keys]
+ }
+ name = strings.ReplaceAll(name, "{{subhost"+utils.IntToStr(i)+"}}", "")
+ }
+ name = strings.ReplaceAll(name, "{{host}}", "")
+ name = strings.ReplaceAll(name, "{{subhost}}", "")
+ name, _ = php2go.URLDecode(name)
+ name = php2go.URLEncode(name)
+ return fmt.Sprintf("%s://%s/%s", scheme, domain, name)
+}
diff --git a/app/svc/svc_goods.go b/app/svc/svc_goods.go
new file mode 100644
index 0000000..7c6f19e
--- /dev/null
+++ b/app/svc/svc_goods.go
@@ -0,0 +1,75 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/utils"
+ "encoding/json"
+ "github.com/gin-gonic/gin"
+)
+
+func Goods(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ goods := db.GetGoods(MasterDb(c), arg)
+ goodsList := make([]map[string]interface{}, 0)
+ if goods != nil {
+ for _, v := range *goods {
+ speImageList := make([]string, 0)
+ if v.IsSpeImageOn == 1 {
+ json.Unmarshal([]byte(v.SpeImages), &speImageList)
+ }
+ tmp := map[string]interface{}{
+ "id": utils.IntToStr(v.Id),
+ "title": v.Title,
+ "img": v.Img,
+ "info": v.Info,
+ "price": v.Price,
+ "stock": utils.IntToStr(v.Stock),
+ "is_single_sku": utils.IntToStr(v.IsSingleSku),
+ "sale_count": utils.IntToStr(v.SaleCount),
+ "spe_image_list": speImageList,
+ }
+ goodsList = append(goodsList, tmp)
+ }
+ }
+ e.OutSuc(c, goodsList, nil)
+ return
+}
+func GoodsSku(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ sku := db.GetGoodsSku(MasterDb(c), arg["goods_id"])
+ skuList := make([]map[string]string, 0)
+ if sku != nil {
+ for _, v := range *sku {
+ tmp := map[string]string{
+ "sku_id": utils.Int64ToStr(v.SkuId),
+ "goods_id": utils.IntToStr(v.GoodsId),
+ "price": v.Price,
+ "stock": utils.IntToStr(v.Stock),
+ "indexes": v.Indexes,
+ "sku": v.Sku,
+ }
+ skuList = append(skuList, tmp)
+ }
+ }
+ e.OutSuc(c, skuList, nil)
+ return
+}
+func GoodsCoupon(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ returnData := CommCoupon(c, arg["amount"])
+ e.OutSuc(c, returnData, nil)
+ return
+}
diff --git a/app/svc/svc_order.go b/app/svc/svc_order.go
new file mode 100644
index 0000000..d0be942
--- /dev/null
+++ b/app/svc/svc_order.go
@@ -0,0 +1,656 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/e"
+ "applet/app/enum"
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/cache"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "github.com/shopspring/decimal"
+ "time"
+ "xorm.io/xorm"
+)
+
+func OrderCate(c *gin.Context) {
+ var cate = []map[string]string{
+ {"name": "全部", "value": ""},
+ {"name": "待付款", "value": "0"},
+ {"name": "待提货", "value": "1"},
+ {"name": "已完成", "value": "2"},
+ {"name": "已取消", "value": "3"},
+ }
+ e.OutSuc(c, cate, nil)
+ return
+}
+func OrderList(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ user := GetUser(c)
+ arg["uid"] = utils.IntToStr(user.Info.Uid)
+ data := db.GetOrderList(MasterDb(c), arg)
+ var state = []string{"待付款", "待提货", "已完成", "已取消"}
+ list := make([]map[string]string, 0)
+ if data != nil {
+ now := time.Now().Unix()
+ for _, v := range *data {
+ store := db.GetStoreIdEg(MasterDb(c), utils.IntToStr(v.StoreUid))
+ info := db.GetOrderInfoEg(MasterDb(c), utils.Int64ToStr(v.Oid))
+ downTime := "0"
+ if v.State == 0 {
+ downTime = utils.IntToStr(int(v.CreateAt.Unix() + 15*60 - now))
+ if now > v.CreateAt.Unix()+15*60 {
+ v.State = 3
+ }
+ if utils.StrToInt(downTime) < 0 {
+ downTime = "0"
+ }
+ }
+ img := ""
+ title := ""
+ storeName := ""
+ if store != nil {
+ storeName = store.Name
+ }
+ if info != nil {
+ img = info.Img
+ title = info.Title
+ }
+ tmp := map[string]string{
+ "oid": utils.Int64ToStr(v.Oid),
+ "label": "自提",
+ "state": utils.IntToStr(v.State),
+ "state_str": state[v.State],
+ "store_name": storeName,
+ "img": img,
+ "title": title,
+ "amount": v.Amount,
+ "num": utils.IntToStr(v.Num),
+ "timer": "",
+ "down_time": downTime,
+ }
+ if v.Type == 1 {
+ tmp["label"] = "外卖"
+ }
+ if v.IsNow == 1 {
+ tmp["timer"] = "立即提货"
+ } else if v.Timer != "" {
+ tmp["timer"] = "提货时间:" + v.Timer
+ }
+ list = append(list, tmp)
+ }
+ }
+ e.OutSuc(c, list, nil)
+ return
+}
+func OrderDetail(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ data := db.GetOrderEg(MasterDb(c), arg["oid"])
+ var state = []string{"待付款", "待提货", "已完成", "已取消"}
+ now := time.Now().Unix()
+ store := db.GetStoreIdEg(MasterDb(c), utils.IntToStr(data.StoreUid))
+ downTime := "0"
+ if data.State == 0 {
+ downTime = utils.IntToStr(int(data.CreateAt.Unix() + 15*60 - now))
+ if now > data.CreateAt.Unix()+15*60 {
+ data.State = 3
+ }
+ if utils.StrToInt(downTime) < 0 {
+ downTime = "0"
+ }
+ }
+ img := ""
+ title := ""
+ storeName := ""
+ storeAddress := ""
+ lat := ""
+ lng := ""
+ km := ""
+ if store != nil {
+ storeName = store.Name
+ storeAddress = store.Address
+ lat = store.Lat
+ lng = store.Lng
+ km = ""
+ if arg["lat"] != "" && arg["lng"] != "" {
+ km1 := utils.CalculateDistance(utils.StrToFloat64(lat), utils.StrToFloat64(lng), utils.StrToFloat64(arg["lat"]), utils.StrToFloat64(arg["lng"]))
+ if km1 < 1 {
+ km = utils.Float64ToStr(km1*1000) + "m"
+ } else {
+ km = utils.Float64ToStr(km1) + "km"
+ }
+ }
+ }
+ confirmAt := ""
+ if data.ConfirmAt.IsZero() == false {
+ confirmAt = data.ConfirmAt.Format("2006-01-02 15:04:05")
+ }
+ payMethod := "-"
+ if data.PayMethod > 0 {
+ payMethod = md.PayMethodIdToName[data.PayMethod]
+ }
+ orderInfo := []map[string]string{
+ {"title": "订单编号", "content": utils.Int64ToStr(data.Oid)},
+ {"title": "下单时间", "content": data.CreateAt.Format("2006-01-02 15:04:05")},
+ {"title": "提货时间", "content": confirmAt},
+ {"title": "预留电话", "content": data.Phone},
+ {"title": "支付方式", "content": payMethod},
+ {"title": "备注信息", "content": data.Memo},
+ }
+ goodsInfo := make([]map[string]string, 0)
+ info := db.GetOrderInfoAllEg(MasterDb(c), utils.Int64ToStr(data.Oid))
+ if info != nil {
+ for _, v := range *info {
+ tmp := map[string]string{
+ "img": v.Img,
+ "title": v.Title,
+ "price": v.Price,
+ "num": utils.IntToStr(v.Num),
+ "sku_str": "",
+ }
+ skuData := make([]md.Sku, 0)
+ json.Unmarshal([]byte(v.SkuInfo), &skuData)
+ skuStr := ""
+ for _, v1 := range skuData {
+ if skuStr != "" {
+ skuStr += ";"
+ }
+ skuStr += v1.Value
+ }
+ tmp["sku_str"] = skuStr
+ goodsInfo = append(goodsInfo, tmp)
+ }
+ }
+ tmp := map[string]interface{}{
+ "oid": utils.Int64ToStr(data.Oid),
+ "label": "自提",
+ "state": utils.IntToStr(data.State),
+ "state_str": state[data.State],
+ "store_name": storeName,
+ "store_address": storeAddress,
+ "lat": lat,
+ "lng": lng,
+ "km": km,
+ "img": img,
+ "title": title,
+ "amount": data.Amount,
+ "num": utils.IntToStr(data.Num),
+ "timer": "",
+ "code": data.Code,
+ "down_time": downTime,
+ "order_info": orderInfo,
+ "goods_info": goodsInfo,
+ "goods_count": utils.IntToStr(len(goodsInfo)),
+ }
+ if data.Type == 1 {
+ tmp["label"] = "外卖"
+ }
+ if data.IsNow == 1 {
+ tmp["timer"] = "立即提货"
+ } else if data.Timer != "" {
+ tmp["timer"] = data.Timer
+ }
+ e.OutSuc(c, tmp, nil)
+ return
+}
+func OrderCoupon(c *gin.Context) {
+ var arg md.OrderTotal
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ totalPrice := commGoods(c, arg)
+ returnData := CommCoupon(c, totalPrice)
+ e.OutSuc(c, returnData, nil)
+ return
+}
+func CommCoupon(c *gin.Context, totalPrice string) map[string]interface{} {
+ var err error
+ engine := MasterDb(c)
+ user := GetUser(c)
+ now := time.Now().Format("2006-01-02 15:04:05")
+ var ActCouponUserList []*model.CommunityTeamCouponUser
+ sess := engine.Table("act_coupon_user").
+ Where("store_type=? and uid = ? AND is_use = ? AND (valid_time_start < ? AND valid_time_end > ?)", 0,
+ user.Info.Uid, 0, now, now)
+ err = sess.Find(&ActCouponUserList)
+ if err != nil {
+ return map[string]interface{}{}
+ }
+ var ids = make([]int, 0)
+ for _, v := range ActCouponUserList {
+ ids = append(ids, v.MerchantSchemeId)
+ }
+ var merchantScheme []model.CommunityTeamCoupon
+ engine.In("id", ids).Find(&merchantScheme)
+ var merchantSchemeMap = make(map[int]model.CommunityTeamCoupon)
+ for _, v := range merchantScheme {
+ merchantSchemeMap[v.Id] = v
+ }
+
+ var couponList []md.CouponList // 可使用的
+ couponList = make([]md.CouponList, 0)
+ count := 0
+ for _, item := range ActCouponUserList {
+ var coupon = md.CouponList{
+ Id: utils.Int64ToStr(item.Id),
+ Title: item.Name,
+ Timer: item.ValidTimeStart.Format("2006.01.02") + "-" + item.ValidTimeEnd.Format("2006.01.02"),
+ Label: "全部商品可用",
+ Img: item.Img,
+ Content: item.ActivityStatement,
+ IsCanUse: "0",
+ NotUseStr: "",
+ }
+ var cal struct {
+ Reach string `json:"reach"`
+ Reduce string `json:"reduce"`
+ }
+ err = json.Unmarshal([]byte(item.Cal), &cal)
+ if err != nil {
+ return map[string]interface{}{}
+ }
+ switch item.Kind {
+ case int(enum.ActCouponTypeImmediate):
+ if utils.AnyToFloat64(totalPrice) >= utils.AnyToFloat64(cal.Reduce) {
+ coupon.IsCanUse = "1"
+ }
+ case int(enum.ActCouponTypeReachReduce):
+ if utils.AnyToFloat64(totalPrice) >= utils.AnyToFloat64(cal.Reduce) {
+ coupon.IsCanUse = "1"
+ }
+ case int(enum.ActCouponTypeReachDiscount):
+ if utils.AnyToFloat64(totalPrice) >= utils.AnyToFloat64(cal.Reduce) && utils.AnyToFloat64(cal.Reduce) > 0 {
+ coupon.IsCanUse = "1"
+ }
+ if utils.AnyToFloat64(cal.Reduce) == 0 {
+ coupon.IsCanUse = "1"
+ }
+ }
+ if coupon.IsCanUse != "1" {
+ coupon.NotUseStr = "订单金额未满" + cal.Reduce + "元"
+ }
+ if coupon.IsCanUse == "1" {
+ count++
+ }
+ couponList = append(couponList, coupon)
+ }
+
+ returnData := map[string]interface{}{
+ "total": utils.IntToStr(count),
+ "coupon_list": couponList,
+ }
+
+ return returnData
+}
+func OrderCancel(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ // 加锁 防止并发提取
+ mutexKey := fmt.Sprintf("%s:team.OrderCancel:%s", c.GetString("mid"), arg["oid"])
+ withdrawAvailable, err := cache.Do("SET", mutexKey, 1, "EX", 5, "NX")
+ if err != nil {
+ e.OutErr(c, e.ERR, err)
+ return
+ }
+ if withdrawAvailable != "OK" {
+ e.OutErr(c, e.ERR, e.NewErr(400000, "请求过于频繁,请稍后再试"))
+ return
+ }
+ sess := MasterDb(c).NewSession()
+ defer sess.Close()
+ sess.Begin()
+ order := db.GetOrder(sess, arg["oid"])
+ if order == nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单不存在"))
+ return
+ }
+ if order.State > 0 {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单不能取消"))
+ return
+ }
+ orderInfo := db.GetOrderInfo(sess, arg["oid"])
+ if orderInfo != nil {
+ goodsMap := make(map[int]int)
+ skuMap := make(map[int]int)
+ for _, v := range *orderInfo {
+ goodsMap[v.GoodsId] += v.Num
+ skuMap[v.SkuId] += v.Num
+ }
+ for k, v := range goodsMap {
+ sql := `update community_team_goods set stock=stock+%d where id=%d`
+ sql = fmt.Sprintf(sql, v, k)
+ _, err := db.QueryNativeStringWithSess(sess, sql)
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单取消失败"))
+ return
+ }
+ }
+ for k, v := range skuMap {
+ sql := `update community_team_sku set stock=stock+%d where sku_id=%d`
+ sql = fmt.Sprintf(sql, v, k)
+ _, err := db.QueryNativeStringWithSess(sess, sql)
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单取消失败"))
+ return
+ }
+ }
+ }
+ order.State = 3
+ order.UpdateAt = time.Now()
+ update, err := sess.Where("id=?", order.Id).Cols("state,update_at").Update(order)
+ if update == 0 || err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单取消失败"))
+ return
+ }
+ if order.CouponId > 0 {
+ update, err = sess.Where("id=?", order.CouponId).Cols("is_use").Update(&model.CommunityTeamCouponUser{IsUse: 0})
+ if update == 0 || err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单取消失败"))
+ return
+ }
+ }
+ sess.Commit()
+ e.OutSuc(c, "success", nil)
+ return
+}
+func OrderCreate(c *gin.Context) {
+ var arg md.OrderTotal
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ user := GetUser(c)
+ // 加锁 防止并发提取
+ mutexKey := fmt.Sprintf("%s:team.OrderCreate:%s", c.GetString("mid"), utils.IntToStr(user.Info.Uid))
+ withdrawAvailable, err := cache.Do("SET", mutexKey, 1, "EX", 5, "NX")
+ if err != nil {
+ e.OutErr(c, e.ERR, err)
+ return
+ }
+ if withdrawAvailable != "OK" {
+ e.OutErr(c, e.ERR, e.NewErr(400000, "请求过于频繁,请稍后再试"))
+ return
+ }
+ sess := MasterDb(c).NewSession()
+ defer sess.Close()
+ err = sess.Begin()
+ if err != nil {
+ e.OutErr(c, 400, err.Error())
+ return
+ }
+ totalPrice := commGoods(c, arg)
+ coupon := "0"
+ totalPrice, coupon, err = CouponProcess(c, sess, totalPrice, arg)
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, err.Error())
+ return
+ }
+ ordId := utils.OrderUUID(user.Info.Uid)
+ // 获取店铺信息
+ store := db.GetStoreId(sess, arg.StoreId)
+ num := 0
+ for _, item := range arg.GoodsInfo {
+ num += utils.StrToInt(item.Num)
+ }
+ var order = &model.CommunityTeamOrder{
+ Uid: user.Info.Uid,
+ StoreUid: utils.StrToInt(arg.StoreId),
+ Commission: utils.Float64ToStr(utils.FloatFormat(utils.AnyToFloat64(totalPrice)*(utils.AnyToFloat64(store.Commission)/100), 2)),
+ CreateAt: time.Now(),
+ UpdateAt: time.Now(),
+ BuyPhone: arg.BuyPhone,
+ Coupon: coupon,
+ Num: num,
+ IsNow: utils.StrToInt(arg.IsNow),
+ Timer: arg.Timer,
+ Memo: arg.Memo,
+ Oid: utils.StrToInt64(ordId),
+ Amount: totalPrice,
+ MealNum: utils.StrToInt(arg.MealNum),
+ }
+ if utils.StrToFloat64(coupon) > 0 {
+ order.CouponId = utils.StrToInt(arg.CouponId)
+ }
+ insert, err := sess.Insert(order)
+ if insert == 0 || err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "下单失败"))
+ return
+ }
+ for _, item := range arg.GoodsInfo {
+ // 获取详细信息
+ goodsInterface, has, err := db.GetComm(MasterDb(c), &model.CommunityTeamGoods{Id: utils.StrToInt(item.GoodsId)})
+ if err != nil || !has {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "商品不存在"))
+ return
+ }
+ goodsModel := goodsInterface.(*model.CommunityTeamGoods)
+ var skuInterface interface{}
+ if item.SkuId != "-1" {
+ skuInterface, _, _ = db.GetComm(MasterDb(c), &model.CommunityTeamSku{GoodsId: utils.StrToInt(item.GoodsId), SkuId: utils.StrToInt64(item.SkuId)})
+ } else {
+ skuInterface, _, _ = db.GetComm(MasterDb(c), &model.CommunityTeamSku{GoodsId: utils.StrToInt(item.GoodsId)})
+ }
+ if err != nil || !has {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "商品不存在"))
+ return
+ }
+ skuModel := skuInterface.(*model.CommunityTeamSku)
+ var goodsSaleCount int
+ // 走普通逻辑
+ stock := skuModel.Stock - utils.StrToInt(item.Num)
+ saleCount := skuModel.SaleCount + utils.StrToInt(item.Num)
+ goodsSaleCount = goodsModel.SaleCount + utils.StrToInt(item.Num)
+ if stock < 0 {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "库存不足"))
+ return
+ }
+ update, err := sess.Where("sku_id=?", skuModel.SkuId).Cols("stock", "sale_count").Update(&model.CommunityTeamSku{Stock: stock, SaleCount: saleCount})
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "商品不存在"))
+ return
+ }
+ if update != 1 {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "商品不存在"))
+ return
+ }
+ // 更新销量
+ goodsModel.SaleCount = goodsSaleCount
+ goodsModel.Stock = goodsModel.Stock - utils.StrToInt(item.Num)
+ _, err = sess.Where("id = ?", goodsModel.Id).Cols("sale_count,stock").Update(goodsModel)
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "商品不存在"))
+ return
+ }
+
+ // 插入订单
+ insert, err := sess.Insert(&model.CommunityTeamOrderInfo{
+ Oid: utils.StrToInt64(ordId),
+ Title: goodsModel.Title,
+ Img: goodsModel.Img,
+ Price: skuModel.Price,
+ Num: utils.StrToInt(item.Num),
+ SkuInfo: skuModel.Sku,
+ GoodsId: skuModel.GoodsId,
+ SkuId: int(skuModel.SkuId),
+ })
+
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "下单失败"))
+ return
+ }
+ if insert != 1 {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "下单失败"))
+ return
+ }
+ }
+ // 更新优惠券使用状态
+ if utils.StrToInt(arg.CouponId) > 0 {
+ affect, err := sess.Where("id = ?", arg.CouponId).
+ Update(&model.CommunityTeamCouponUser{IsUse: 1})
+ if err != nil {
+ e.OutErr(c, 400, e.NewErr(400, "下单失败"))
+ return
+ }
+ if affect != 1 {
+ e.OutErr(c, 400, e.NewErr(400, "下单失败"))
+ return
+ }
+ }
+
+ err = sess.Commit()
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, err.Error())
+ return
+ }
+ sess.Commit()
+ return
+}
+func OrderTotal(c *gin.Context) {
+ var arg md.OrderTotal
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ sess := MasterDb(c).NewSession()
+ defer sess.Close()
+ err := sess.Begin()
+ if err != nil {
+ e.OutErr(c, 400, err.Error())
+ return
+ }
+ totalPrice := commGoods(c, arg)
+ oldTotalPrice := totalPrice
+ coupon := "0"
+ totalPrice, coupon, err = CouponProcess(c, sess, totalPrice, arg)
+ if err != nil {
+ sess.Rollback()
+ e.OutErr(c, 400, err.Error())
+ return
+ }
+ user := GetUser(c)
+ result := map[string]interface{}{
+ "balance_money": GetCommissionPrec(c, user.Profile.FinValid, SysCfgGet(c, "commission_prec"), SysCfgGet(c, "is_show_point")),
+ "small_amount": GetCommissionPrec(c, oldTotalPrice, SysCfgGet(c, "commission_prec"), SysCfgGet(c, "is_show_point")),
+ "all_amount": GetCommissionPrec(c, totalPrice, SysCfgGet(c, "commission_prec"), SysCfgGet(c, "is_show_point")),
+ "coupon": GetCommissionPrec(c, coupon, SysCfgGet(c, "commission_prec"), SysCfgGet(c, "is_show_point")),
+ }
+ sess.Commit()
+ e.OutSuc(c, result, nil)
+ return
+}
+func CouponProcess(c *gin.Context, sess *xorm.Session, total string, args md.OrderTotal) (string, string, error) {
+ if utils.StrToInt(args.CouponId) == 0 {
+ return total, "0", nil
+ }
+ now := time.Now().Format("2006-01-02 15:04:05")
+ user := GetUser(c)
+ var goodsIds []int
+ var skuIds []string
+ for _, item := range args.GoodsInfo {
+ goodsIds = append(goodsIds, utils.StrToInt(item.GoodsId))
+ skuIds = append(skuIds, utils.AnyToString(item.SkuId))
+ }
+ // 获取优惠券信息
+ var mallUserCoupon model.CommunityTeamCouponUser
+ isExist, err := sess.
+ Where("id = ? AND uid = ? AND is_use = ? AND (valid_time_start < ? AND valid_time_end > ?)", args.CouponId, user.Info.Uid, 0, now, now).
+ Get(&mallUserCoupon)
+ if err != nil {
+ return "", "", err
+ }
+ if !isExist {
+ return "", "", errors.New("无相关优惠券信息")
+ }
+
+ var cal struct {
+ Reach string `json:"reach"`
+ Reduce string `json:"reduce"`
+ }
+ _ = json.Unmarshal([]byte(mallUserCoupon.Cal), &cal)
+ reach, err := decimal.NewFromString(cal.Reach)
+ reduce, err := decimal.NewFromString(cal.Reduce)
+ if err != nil {
+ return "", "", err
+ }
+
+ var specialTotal = total
+ // 是否满足优惠条件
+ if !reach.IsZero() { // 满减及有门槛折扣
+ if utils.StrToFloat64(specialTotal) < utils.StrToFloat64(reach.String()) {
+ return "", "", errors.New("不满足优惠条件")
+ }
+ } else {
+ if mallUserCoupon.Kind == 1 { //立减
+ if utils.StrToFloat64(specialTotal) < utils.StrToFloat64(reduce.String()) {
+ return "", "", errors.New("付款金额有误")
+ }
+ }
+ }
+ // 计算优惠后支付金额
+ couponTotal := "0"
+ if mallUserCoupon.Kind == int(enum.ActCouponTypeImmediate) ||
+ mallUserCoupon.Kind == int(enum.ActCouponTypeReachReduce) { // 立减 || 满减
+ couponTotal = reduce.String()
+ total = utils.Float64ToStr(utils.StrToFloat64(total) - utils.StrToFloat64(reduce.String()))
+ } else { // 折扣
+ couponTotal = utils.Float64ToStr(utils.StrToFloat64(total) - utils.StrToFloat64(total)*utils.StrToFloat64(reduce.String())/10)
+ total = utils.Float64ToStr(utils.StrToFloat64(total) * utils.StrToFloat64(reduce.String()) / 10)
+ }
+ return total, couponTotal, nil
+}
+
+func commGoods(c *gin.Context, arg md.OrderTotal) (totalPrice string) {
+ engine := MasterDb(c)
+ var totalPriceAmt float64 = 0
+ for _, item := range arg.GoodsInfo {
+ goodsInterface, _, _ := db.GetComm(engine, &model.CommunityTeamGoods{Id: utils.StrToInt(item.GoodsId)})
+ goodsModel := goodsInterface.(*model.CommunityTeamGoods)
+ var skuInterface interface{}
+ if item.SkuId != "-1" {
+ skuInterface, _, _ = db.GetComm(engine, &model.CommunityTeamSku{GoodsId: utils.StrToInt(item.GoodsId), SkuId: utils.StrToInt64(item.SkuId)})
+ } else {
+ skuInterface, _, _ = db.GetComm(engine, &model.CommunityTeamSku{GoodsId: utils.StrToInt(item.GoodsId)})
+ }
+ skuModel := skuInterface.(*model.CommunityTeamSku)
+ priceOne := goodsModel.Price
+ if item.SkuId != "-1" {
+ priceOne = skuModel.Price
+ }
+ totalPriceAmt += utils.StrToFloat64(priceOne) * utils.StrToFloat64(item.Num)
+ }
+ return utils.Float64ToStr(totalPriceAmt)
+
+}
diff --git a/app/svc/svc_pay.go b/app/svc/svc_pay.go
new file mode 100644
index 0000000..a888629
--- /dev/null
+++ b/app/svc/svc_pay.go
@@ -0,0 +1,21 @@
+package svc
+
+import (
+ "applet/app/md"
+ "github.com/gin-gonic/gin"
+)
+
+var PayFuncList = map[string]map[string]func(*gin.Context) (interface{}, error){
+ md.CommunityTeam: {
+ md.BALANCE_PAY: BalanceCommunityTeam,
+ md.ALIPAY: AlipayCommunityTeam,
+ md.WX_PAY: WxPayCommunityTeam,
+ },
+}
+var PayCallbackFuncList = map[string]map[string]func(*gin.Context){
+ md.CommunityTeam: {
+ md.BALANCE_PAY: nil,
+ md.ALIPAY: AlipayCallbackCommunityTeam,
+ md.WX_PAY: WxPayCallbackCommunityTeam,
+ },
+}
diff --git a/app/svc/svc_pay_community_team.go b/app/svc/svc_pay_community_team.go
new file mode 100644
index 0000000..e88b408
--- /dev/null
+++ b/app/svc/svc_pay_community_team.go
@@ -0,0 +1,142 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/utils"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "github.com/shopspring/decimal"
+ "math"
+ "math/rand"
+ "time"
+)
+
+func BalanceCommunityTeam(c *gin.Context) (interface{}, error) {
+
+ ord, err := CheckCommunityTeam(c)
+ if err != nil || ord == nil {
+ return nil, err
+ }
+ err = BalancePay(c, ord.Amount, utils.Int64ToStr(ord.Oid), md.CommunityTeam)
+ if err != nil {
+ return nil, err
+ }
+ // 回调
+ CommonCallbackCommunityTeam(c, utils.AnyToString(ord.Oid), md.BALANCE_PAY)
+ return nil, nil
+}
+func AlipayCommunityTeam(c *gin.Context) (interface{}, error) {
+ ord, err := CheckCommunityTeam(c)
+ if err != nil {
+ return nil, err
+ }
+ payParams := &md.AliPayPayParams{
+ Subject: "小店下单",
+ Amount: ord.Amount,
+ OrdId: utils.AnyToString(ord.Oid),
+ OrderType: md.CommunityTeam,
+ Uid: utils.IntToStr(ord.Uid),
+ }
+ r, err := PrepareAlipayCode(c, payParams)
+ if err != nil {
+ return nil, err
+ }
+ return r, nil
+}
+func WxPayCommunityTeam(c *gin.Context) (interface{}, error) {
+ var r interface{}
+ var err error
+ ord, err := CheckCommunityTeam(c)
+ if err != nil {
+ return nil, err
+ }
+ params := map[string]string{
+ "subject": md.NeedPayPart[md.AggregationRecharge],
+ "amount": wxMoneyMulHundred(ord.Amount),
+ "order_type": md.AggregationRecharge,
+ "ord_id": utils.AnyToString(ord.Oid),
+ "pay_wx_mch_id": SysCfgGet(c, "pay_wx_mch_id"),
+ "pay_wx_api_key": SysCfgGet(c, "pay_wx_api_key"),
+ "uid": utils.IntToStr(ord.Uid),
+ }
+ params["notify_url"] = fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params["order_type"], md.WX_PAY)
+ r, err = CommPayData(c, params)
+ if err != nil {
+ return nil, err
+ }
+
+ return r, nil
+}
+func AlipayCallbackCommunityTeam(c *gin.Context) {
+ orderId, err := AlipayCallback(c)
+ if err != nil || orderId == "" {
+ return
+ }
+ CommonCallbackCommunityTeam(c, orderId, md.ALIPAY)
+}
+func WxPayCallbackCommunityTeam(c *gin.Context) {
+ orderId, err := wxPayCallback(c)
+ if err != nil || orderId == "" {
+ return
+ }
+ CommonCallbackCommunityTeam(c, orderId, md.WX_PAY)
+}
+
+// 微信金额乘100
+func wxMoneyMulHundred(m string) string {
+ amount, _ := decimal.NewFromString(m)
+ newM := amount.Mul(decimal.NewFromInt(100))
+ return newM.String()
+}
+func CommonCallbackCommunityTeam(c *gin.Context, orderId string, payMethod string) {
+ ord := db.GetOrderEg(db.DBs[c.GetString("mid")], orderId)
+ if ord == nil {
+ return
+ }
+ // 判断是否失效
+ if ord.State != 0 {
+ return
+ }
+ ord.State = 1
+ ord.UpdateAt = time.Now()
+ ord.Code = Code()
+ ord.PayAt = time.Now()
+ ord.PayMethod = md.PayMethodIDs[payMethod]
+ MasterDb(c).Where("id=?", ord.Id).Cols("state,update_at,code,pay_at,pay_method").Update(ord)
+ return
+}
+func Code() string {
+ rand.Seed(time.Now().UnixNano())
+ var digits = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
+ b := make([]rune, 6)
+ for i := range b {
+ if fl := float64(rand.Intn(10)); fl > math.Log10(float64(i+1)) {
+ b[i] = digits[rand.Intn(len(digits))]
+ } else {
+ b[i] = digits[rand.Intn(10)]
+ }
+ }
+ fmt.Println(string(b))
+ return string(b)
+}
+func CheckCommunityTeam(c *gin.Context) (*model.CommunityTeamOrder, error) {
+ var args struct {
+ MainOrdId string `json:"main_ord_id"`
+ }
+ if err := c.ShouldBindJSON(&args); err != nil || args.MainOrdId == "" {
+ return nil, e.NewErrCode(e.ERR_INVALID_ARGS)
+ }
+ // 查询订单
+ ord := db.GetOrderEg(db.DBs[c.GetString("mid")], args.MainOrdId)
+ if ord == nil {
+ return nil, e.NewErr(403000, "不存在该订单")
+ }
+ // 判断是否失效
+ if ord.State != 0 {
+ return nil, e.NewErr(403000, "订单已处理")
+ }
+ return ord, nil
+}
diff --git a/app/svc/svc_redis_mutex_lock.go b/app/svc/svc_redis_mutex_lock.go
new file mode 100644
index 0000000..f35e0f9
--- /dev/null
+++ b/app/svc/svc_redis_mutex_lock.go
@@ -0,0 +1,100 @@
+package svc
+
+import (
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/cache"
+ "errors"
+ "fmt"
+ "math/rand"
+ "reflect"
+ "time"
+)
+
+const redisMutexLockExpTime = 15
+
+// TryGetDistributedLock 分布式锁获取
+// requestId 用于标识请求客户端,可以是随机字符串,需确保唯一
+func TryGetDistributedLock(lockKey, requestId string, isNegative bool) bool {
+ if isNegative { // 多次尝试获取
+ retry := 1
+ for {
+ ok, err := cache.Do("SET", lockKey, requestId, "EX", redisMutexLockExpTime, "NX")
+ // 获取锁成功
+ if err == nil && ok == "OK" {
+ return true
+ }
+ // 尝试多次没获取成功
+ if retry > 10 {
+ return false
+ }
+ time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
+ retry += 1
+ }
+ } else { // 只尝试一次
+ ok, err := cache.Do("SET", lockKey, requestId, "EX", redisMutexLockExpTime, "NX")
+ // 获取锁成功
+ if err == nil && ok == "OK" {
+ return true
+ }
+
+ return false
+ }
+}
+
+// ReleaseDistributedLock 释放锁,通过比较requestId,用于确保客户端只释放自己的锁,使用lua脚本保证操作的原子型
+func ReleaseDistributedLock(lockKey, requestId string) (bool, error) {
+ luaScript := `
+ if redis.call("get",KEYS[1]) == ARGV[1]
+ then
+ return redis.call("del",KEYS[1])
+ else
+ return 0
+ end`
+
+ do, err := cache.Do("eval", luaScript, 1, lockKey, requestId)
+ fmt.Println(reflect.TypeOf(do))
+ fmt.Println(do)
+
+ if utils.AnyToInt64(do) == 1 {
+ return true, err
+ } else {
+ return false, err
+ }
+}
+
+func GetDistributedLockRequestId(prefix string) string {
+ return prefix + utils.IntToStr(rand.Intn(100000000))
+}
+
+// HandleBalanceDistributedLock 处理余额更新时获取锁和释放锁 如果加锁成功,使用语句 ` defer cb() ` 释放锁
+func HandleBalanceDistributedLock(masterId, uid, requestIdPrefix string) (cb func(), err error) {
+ // 获取余额更新锁
+ balanceLockKey := fmt.Sprintf(md.UserFinValidUpdateLock, masterId, uid)
+ requestId := GetDistributedLockRequestId(requestIdPrefix)
+ balanceLockOk := TryGetDistributedLock(balanceLockKey, requestId, true)
+ if !balanceLockOk {
+ return nil, errors.New("系统繁忙,请稍后再试")
+ }
+
+ cb = func() {
+ _, _ = ReleaseDistributedLock(balanceLockKey, requestId)
+ }
+
+ return cb, nil
+}
+
+func HandleLimiterDistributedLock(masterId, ip, requestIdPrefix string) (cb func(), err error) {
+ balanceLockKey := fmt.Sprintf(md.AppLimiterLock, masterId, ip)
+ requestId := GetDistributedLockRequestId(requestIdPrefix)
+ balanceLockOk := TryGetDistributedLock(balanceLockKey, requestId, true)
+ if !balanceLockOk {
+ return nil, errors.New("系统繁忙,请稍后再试")
+ }
+
+ cb = func() {
+ _, _ = ReleaseDistributedLock(balanceLockKey, requestId)
+ }
+
+ return cb, nil
+}
diff --git a/app/svc/svc_store.go b/app/svc/svc_store.go
new file mode 100644
index 0000000..0ec26ab
--- /dev/null
+++ b/app/svc/svc_store.go
@@ -0,0 +1,135 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/e"
+ "applet/app/utils"
+ "github.com/gin-gonic/gin"
+ "time"
+)
+
+func BankStore(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ arg["store_type"] = "0"
+ user, _ := GetDefaultUser(c, c.GetHeader("Authorization"))
+ var store = make([]map[string]string, 0)
+ if arg["cid"] == "2" {
+ store = db.GetStoreLike(MasterDb(c), arg)
+ } else {
+ store = db.GetStore(MasterDb(c), arg)
+ }
+ storeList := make([]map[string]interface{}, 0)
+ for _, v := range store {
+ if utils.StrToFloat64(v["km"]) < 1 {
+ v["km"] = utils.IntToStr(int(utils.StrToFloat64(v["km"])*1000)) + "m"
+ } else {
+ v["km"] += "km"
+ }
+ if utils.StrToFloat64(v["km"]) <= 0 {
+ v["km"] = "-"
+ }
+ tmp := map[string]interface{}{
+ "lat": v["lat"],
+ "lng": v["lng"],
+ "address": v["address"],
+ "name": v["name"],
+ "id": v["id"],
+ "km": v["km"],
+ "time_str": v["timer"],
+ "phone": v["phone"],
+ "logo": v["logo"],
+ "is_like": "0",
+ "fan": "",
+ }
+ if user != nil {
+ count, _ := MasterDb(c).Where("uid=? and store_id=?", user.Info.Uid, v["id"]).Count(&model.CommunityTeamStoreLike{})
+ if count > 0 {
+ tmp["is_like"] = "1"
+ }
+ }
+ storeList = append(storeList, tmp)
+ }
+ e.OutSuc(c, storeList, nil)
+ return
+}
+func Store(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ arg["store_type"] = "0"
+
+ user, _ := GetDefaultUser(c, c.GetHeader("Authorization"))
+ store := db.GetStore(MasterDb(c), arg)
+ storeList := make([]map[string]interface{}, 0)
+ for _, v := range store {
+ if utils.StrToFloat64(v["km"]) < 1 {
+ v["km"] = utils.IntToStr(int(utils.StrToFloat64(v["km"])*1000)) + "m"
+ } else {
+ v["km"] += "km"
+ }
+ label := make([]string, 0)
+ tmp := map[string]interface{}{
+ "lat": v["lat"],
+ "lng": v["lng"],
+ "address": v["address"],
+ "work_state": v["work_state"],
+ "name": v["name"],
+ "id": v["id"],
+ "km": v["km"],
+ "time_str": v["timer"],
+ "phone": v["phone"],
+ "label": label,
+ "is_like": "0",
+ }
+ if user != nil {
+ count, _ := MasterDb(c).Where("uid=? and store_id=?", user.Info.Uid, v["id"]).Count(&model.CommunityTeamStoreLike{})
+ if count > 0 {
+ tmp["is_like"] = "1"
+ }
+ }
+
+ storeList = append(storeList, tmp)
+ }
+ e.OutSuc(c, storeList, nil)
+ return
+}
+func StoreAddLike(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ user := GetUser(c)
+ count, _ := MasterDb(c).Where("uid=? and store_id=?", user.Info.Uid, arg["id"]).Count(&model.CommunityTeamStoreLike{})
+ if count > 0 {
+ e.OutErr(c, 400, e.NewErr(400, "已收藏"))
+ return
+ }
+ var data = model.CommunityTeamStoreLike{
+ Uid: user.Info.Uid,
+ StoreId: utils.StrToInt(arg["id"]),
+ Time: time.Now(),
+ }
+ MasterDb(c).Insert(&data)
+ e.OutSuc(c, "success", nil)
+ return
+}
+
+func StoreCancelLike(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ user := GetUser(c)
+ MasterDb(c).Where("uid=? and store_id=?", user.Info.Uid, arg["id"]).Delete(&model.CommunityTeamStoreLike{})
+ e.OutSuc(c, "success", nil)
+ return
+}
diff --git a/app/svc/svc_store_order.go b/app/svc/svc_store_order.go
new file mode 100644
index 0000000..79cc221
--- /dev/null
+++ b/app/svc/svc_store_order.go
@@ -0,0 +1,277 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/cache"
+ "encoding/json"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "time"
+)
+
+func StoreOrderCate(c *gin.Context) {
+ var cate = []map[string]string{
+ {"name": "全部", "value": ""},
+ {"name": "待付款", "value": "0"},
+ {"name": "待提货", "value": "1"},
+ {"name": "已完成", "value": "2"},
+ {"name": "已取消", "value": "3"},
+ }
+ e.OutSuc(c, cate, nil)
+ return
+}
+func StoreOrderList(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ user := GetUser(c)
+ arg["store_uid"] = utils.IntToStr(user.Info.Uid)
+ data := db.GetOrderList(MasterDb(c), arg)
+ var state = []string{"待付款", "待提货", "已完成", "已取消"}
+ list := make([]map[string]interface{}, 0)
+ if data != nil {
+ now := time.Now().Unix()
+ for _, v := range *data {
+ store := db.GetStoreIdEg(MasterDb(c), utils.IntToStr(v.StoreUid))
+ info := db.GetOrderInfoAllEg(MasterDb(c), utils.Int64ToStr(v.Oid))
+ downTime := "0"
+ if v.State == 0 {
+ downTime = utils.IntToStr(int(v.CreateAt.Unix() + 15*60 - now))
+ if now > v.CreateAt.Unix()+15*60 {
+ v.State = 3
+ }
+ if utils.StrToInt(downTime) < 0 {
+ downTime = "0"
+ }
+ }
+ storeName := ""
+ if store != nil {
+ storeName = store.Name
+ }
+ goodsInfo := make([]map[string]string, 0)
+ if info != nil {
+ for _, v1 := range *info {
+ skuData := make([]md.Sku, 0)
+ json.Unmarshal([]byte(v1.SkuInfo), &skuData)
+ skuStr := ""
+ for _, v2 := range skuData {
+ if skuStr != "" {
+ skuStr += ";"
+ }
+ skuStr += v2.Value
+ }
+ if skuStr != "" {
+ skuStr = "(" + skuStr + ")"
+ }
+ tmp := map[string]string{
+ "title": v1.Title + skuStr,
+ "num": utils.IntToStr(v1.Num),
+ "img": v1.Img,
+ }
+ goodsInfo = append(goodsInfo, tmp)
+ }
+ }
+ user1, _ := db.UserFindByID(MasterDb(c), v.Uid)
+ userProfile, _ := db.UserProfileFindByID(MasterDb(c), v.Uid)
+ nickname := ""
+ headImg := ""
+ if userProfile != nil {
+ headImg = userProfile.AvatarUrl
+ }
+ if user1 != nil {
+ if user1.Nickname != user1.Phone {
+ user1.Nickname += " " + user1.Phone
+ }
+ nickname = user1.Nickname
+ }
+ tmp := map[string]interface{}{
+ "goods_info": goodsInfo,
+ "oid": utils.Int64ToStr(v.Oid),
+ "label": "自提",
+ "state": utils.IntToStr(v.State),
+ "state_str": state[v.State],
+ "store_name": storeName,
+ "coupon": v.Coupon,
+ "username": nickname,
+ "head_img": headImg,
+ "amount": v.Amount,
+ "num": utils.IntToStr(v.Num),
+ "timer": "",
+ "down_time": downTime,
+ "create_at": v.CreateAt.Format("2006-01-02 15:04:05"),
+ "pay_at": "",
+ "confirm_at": "",
+ }
+ if v.PayAt.IsZero() == false {
+ tmp["pay_at"] = v.PayAt.Format("2006-01-02 15:04:05")
+ }
+ if v.ConfirmAt.IsZero() == false {
+ tmp["confirm_at"] = v.ConfirmAt.Format("2006-01-02 15:04:05")
+ }
+ if v.Type == 1 {
+ tmp["label"] = "外卖"
+ }
+ if v.IsNow == 1 {
+ tmp["timer"] = "立即提货"
+ } else if v.Timer != "" {
+ tmp["timer"] = v.Timer
+ }
+ list = append(list, tmp)
+ }
+ }
+ e.OutSuc(c, list, nil)
+ return
+}
+func StoreOrderDetail(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ data := db.GetOrderEg(MasterDb(c), arg["oid"])
+ var state = []string{"待付款", "待提货", "已完成", "已取消"}
+ now := time.Now().Unix()
+ downTime := "0"
+ if data.State == 0 {
+ downTime = utils.IntToStr(int(data.CreateAt.Unix() + 15*60 - now))
+ if now > data.CreateAt.Unix()+15*60 {
+ data.State = 3
+ }
+ if utils.StrToInt(downTime) < 0 {
+ downTime = "0"
+ }
+ }
+ img := ""
+ title := ""
+ confirmAt := ""
+ if data.ConfirmAt.IsZero() == false {
+ confirmAt = data.ConfirmAt.Format("2006-01-02 15:04:05")
+ }
+ payAt := ""
+ if data.PayAt.IsZero() == false {
+ payAt = data.PayAt.Format("2006-01-02 15:04:05")
+ }
+ timer := ""
+ if data.IsNow == 1 {
+ timer = "立即提货"
+ } else if data.Timer != "" {
+ timer = data.Timer
+ }
+ orderInfo := []map[string]string{
+ {"title": "订单编号", "content": utils.Int64ToStr(data.Oid)},
+ {"title": "提货码", "content": data.Code},
+ {"title": "下单时间", "content": data.CreateAt.Format("2006-01-02 15:04:05")},
+ {"title": "付款时间", "content": payAt},
+ {"title": "预计提货时间", "content": timer},
+ {"title": "提货时间", "content": confirmAt},
+ {"title": "预留电话", "content": data.Phone},
+ {"title": "备注信息", "content": data.Memo},
+ }
+ goodsInfo := make([]map[string]string, 0)
+ info := db.GetOrderInfoAllEg(MasterDb(c), utils.Int64ToStr(data.Oid))
+ if info != nil {
+ for _, v := range *info {
+ tmp := map[string]string{
+ "img": v.Img,
+ "title": v.Title,
+ "price": v.Price,
+ "num": utils.IntToStr(v.Num),
+ "sku_str": "",
+ }
+ skuData := make([]md.Sku, 0)
+ json.Unmarshal([]byte(v.SkuInfo), &skuData)
+ skuStr := ""
+ for _, v1 := range skuData {
+ if skuStr != "" {
+ skuStr += ";"
+ }
+ skuStr += v1.Value
+ }
+ tmp["sku_str"] = skuStr
+ goodsInfo = append(goodsInfo, tmp)
+ }
+ }
+ user1, _ := db.UserFindByID(MasterDb(c), data.Uid)
+ userProfile, _ := db.UserProfileFindByID(MasterDb(c), data.Uid)
+ nickname := ""
+ headImg := ""
+ if userProfile != nil {
+ headImg = userProfile.AvatarUrl
+ }
+ if user1 != nil {
+ if user1.Nickname != user1.Phone {
+ user1.Nickname += " " + user1.Phone
+ }
+ nickname = user1.Nickname
+ }
+ tmp := map[string]interface{}{
+ "oid": utils.Int64ToStr(data.Oid),
+ "label": "自提",
+ "username": nickname,
+ "head_img": headImg,
+ "state": utils.IntToStr(data.State),
+ "state_str": state[data.State],
+ "img": img,
+ "title": title,
+ "amount": data.Amount,
+ "all_amount": utils.Float64ToStr(utils.StrToFloat64(data.Amount) + utils.StrToFloat64(data.Coupon)),
+ "coupon": data.Coupon,
+ "num": utils.IntToStr(data.Num),
+ "code": data.Code,
+ "down_time": downTime,
+ "order_info": orderInfo,
+ "goods_info": goodsInfo,
+ "goods_count": utils.IntToStr(len(goodsInfo)),
+ }
+ if data.Type == 1 {
+ tmp["label"] = "外卖"
+ }
+ e.OutSuc(c, tmp, nil)
+ return
+}
+func StoreOrderConfirm(c *gin.Context) {
+ var arg map[string]string
+ if err := c.ShouldBindJSON(&arg); err != nil {
+ e.OutErr(c, e.ERR_INVALID_ARGS, err)
+ return
+ }
+ // 加锁 防止并发提取
+ mutexKey := fmt.Sprintf("%s:team.StoreOrderConfirm:%s", c.GetString("mid"), arg["oid"])
+ withdrawAvailable, err := cache.Do("SET", mutexKey, 1, "EX", 5, "NX")
+ if err != nil {
+ e.OutErr(c, e.ERR, err)
+ return
+ }
+ if withdrawAvailable != "OK" {
+ e.OutErr(c, e.ERR, e.NewErr(400000, "请求过于频繁,请稍后再试"))
+ return
+ }
+ sess := MasterDb(c).NewSession()
+ defer sess.Close()
+ sess.Begin()
+ order := db.GetOrder(sess, arg["oid"])
+ if order == nil {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单不存在"))
+ return
+ }
+ if order.State != 1 {
+ sess.Rollback()
+ e.OutErr(c, 400, e.NewErr(400, "订单不能确认"))
+ return
+ }
+ order.State = 2
+ order.UpdateAt = time.Now()
+ update, err := sess.Where("id=?", order.Id).Cols("state,update_at").Update(order)
+ if update == 0 || err != nil {
+ e.OutErr(c, 400, e.NewErr(400, "订单确认失败"))
+ return
+ }
+ e.OutSuc(c, "success", nil)
+ return
+}
diff --git a/app/svc/svc_sys_cfg_get.go b/app/svc/svc_sys_cfg_get.go
new file mode 100644
index 0000000..976a7a3
--- /dev/null
+++ b/app/svc/svc_sys_cfg_get.go
@@ -0,0 +1,218 @@
+package svc
+
+import (
+ "applet/app/md"
+ "errors"
+ "github.com/gin-gonic/gin"
+ "strings"
+ "xorm.io/xorm"
+
+ "applet/app/cfg"
+ "applet/app/db"
+
+ "applet/app/utils"
+ "applet/app/utils/cache"
+)
+
+// 单挑记录获取
+func SysCfgGet(c *gin.Context, key string) string {
+ mid := c.GetString("mid")
+ eg := db.DBs[mid]
+ return db.SysCfgGetWithDb(eg, mid, key)
+}
+
+// 多条记录获取
+func SysCfgFind(c *gin.Context, keys ...string) map[string]string {
+ var masterId string
+ if c == nil {
+ masterId = ""
+ } else {
+ masterId = c.GetString("mid")
+ }
+ tmp := SysCfgFindComm(masterId, keys...)
+ return tmp
+}
+
+// SysCfgGetByMasterId get one config by master id
+func SysCfgGetByMasterId(masterId, key string) string {
+ res := SysCfgFindComm(masterId, key)
+ if _, ok := res[key]; !ok {
+ return ""
+ }
+ return res[key]
+}
+
+// SysCfgFindComm get cfg by master id
+func SysCfgFindComm(masterId string, keys ...string) map[string]string {
+ var eg *xorm.Engine
+ if masterId == "" {
+ eg = db.Db
+ } else {
+ eg = db.DBs[masterId]
+ }
+ res := map[string]string{}
+ //TODO::判断keys长度(大于10个直接查数据库)
+ if len(keys) > 10 {
+ cfgList, _ := db.SysCfgGetAll(eg)
+ if cfgList == nil {
+ return nil
+ }
+ for _, v := range *cfgList {
+ res[v.Key] = v.Val
+ }
+ } else {
+ for _, key := range keys {
+ res[key] = db.SysCfgGetWithDb(eg, masterId, key)
+ }
+ }
+ return res
+}
+
+// 多条记录获取
+func EgSysCfgFind(keys ...string) map[string]string {
+ var e *xorm.Engine
+ res := map[string]string{}
+ if len(res) == 0 {
+ cfgList, _ := db.SysCfgGetAll(e)
+ if cfgList == nil {
+ return nil
+ }
+ for _, v := range *cfgList {
+ res[v.Key] = v.Val
+ }
+ // 先不设置缓存
+ // cache.SetJson(md.KEY_SYS_CFG_CACHE, res, 60)
+ }
+ if len(keys) == 0 {
+ return res
+ }
+ tmp := map[string]string{}
+ for _, v := range keys {
+ if val, ok := res[v]; ok {
+ tmp[v] = val
+ } else {
+ tmp[v] = ""
+ }
+ }
+ return tmp
+}
+
+// 清理系统配置信息
+func SysCfgCleanCache() {
+ cache.Del(md.KEY_SYS_CFG_CACHE)
+}
+
+// 写入系统设置
+func SysCfgSet(c *gin.Context, key, val, memo string) bool {
+ cfg, err := db.SysCfgGetOne(db.DBs[c.GetString("mid")], key)
+ if err != nil || cfg == nil {
+ return db.SysCfgInsert(db.DBs[c.GetString("mid")], key, val, memo)
+ }
+ if memo != "" && cfg.Memo != memo {
+ cfg.Memo = memo
+ }
+ SysCfgCleanCache()
+ return db.SysCfgUpdate(db.DBs[c.GetString("mid")], key, val, cfg.Memo)
+}
+
+// 多条记录获取
+func SysCfgFindByIds(eg *xorm.Engine, keys ...string) map[string]string {
+ key := utils.Md5(eg.DataSourceName() + md.KEY_SYS_CFG_CACHE)
+ res := map[string]string{}
+ c, ok := cfg.MemCache.Get(key).(map[string]string)
+ if !ok || len(c) == 0 {
+ cfgList, _ := db.DbsSysCfgGetAll(eg)
+ if cfgList == nil {
+ return nil
+ }
+ for _, v := range *cfgList {
+ res[v.Key] = v.Val
+ }
+ cfg.MemCache.Put(key, res, 10)
+ } else {
+ res = c
+ }
+ if len(keys) == 0 {
+ return res
+ }
+ tmp := map[string]string{}
+ for _, v := range keys {
+ if val, ok := res[v]; ok {
+ tmp[v] = val
+ } else {
+ tmp[v] = ""
+ }
+ }
+ return tmp
+}
+
+// 多条记录获取
+func SysCfgFindByIdsToCache(eg *xorm.Engine, dbName string, keys ...string) map[string]string {
+ key := utils.Md5(dbName + md.KEY_SYS_CFG_CACHE)
+ res := map[string]string{}
+ c, ok := cfg.MemCache.Get(key).(map[string]string)
+ if !ok || len(c) == 0 {
+ cfgList, _ := db.DbsSysCfgGetAll(eg)
+ if cfgList == nil {
+ return nil
+ }
+ for _, v := range *cfgList {
+ res[v.Key] = v.Val
+ }
+ cfg.MemCache.Put(key, res, 1800)
+ } else {
+ res = c
+ }
+ if len(keys) == 0 {
+ return res
+ }
+ tmp := map[string]string{}
+ for _, v := range keys {
+ if val, ok := res[v]; ok {
+ tmp[v] = val
+ } else {
+ tmp[v] = ""
+ }
+ }
+ return tmp
+}
+
+// 支付配置
+func SysCfgFindPayment(c *gin.Context) ([]map[string]string, error) {
+ platform := c.GetHeader("platform")
+ payCfg := SysCfgFind(c, "pay_wx_pay_img", "pay_ali_pay_img", "pay_balance_img", "pay_type")
+ if payCfg["pay_wx_pay_img"] == "" || payCfg["pay_ali_pay_img"] == "" || payCfg["pay_balance_img"] == "" || payCfg["pay_type"] == "" {
+ return nil, errors.New("lack of payment config")
+ }
+ payCfg["pay_wx_pay_img"] = ImageFormat(c, payCfg["pay_wx_pay_img"])
+ payCfg["pay_ali_pay_img"] = ImageFormat(c, payCfg["pay_ali_pay_img"])
+ payCfg["pay_balance_img"] = ImageFormat(c, payCfg["pay_balance_img"])
+
+ var result []map[string]string
+
+ if strings.Contains(payCfg["pay_type"], "aliPay") && platform != md.PLATFORM_WX_APPLET {
+ item := make(map[string]string)
+ item["pay_channel"] = "alipay"
+ item["img"] = payCfg["pay_ali_pay_img"]
+ item["name"] = "支付宝支付"
+ result = append(result, item)
+ }
+
+ if strings.Contains(payCfg["pay_type"], "wxPay") {
+ item := make(map[string]string)
+ item["pay_channel"] = "wx"
+ item["img"] = payCfg["pay_wx_pay_img"]
+ item["name"] = "微信支付"
+ result = append(result, item)
+ }
+
+ if strings.Contains(payCfg["pay_type"], "walletPay") {
+ item := make(map[string]string)
+ item["pay_channel"] = "fin"
+ item["img"] = payCfg["pay_balance_img"]
+ item["name"] = "余额支付"
+ result = append(result, item)
+ }
+
+ return result, nil
+}
diff --git a/app/svc/svc_wx.go b/app/svc/svc_wx.go
new file mode 100644
index 0000000..c551fbf
--- /dev/null
+++ b/app/svc/svc_wx.go
@@ -0,0 +1,103 @@
+package svc
+
+import (
+ "applet/app/db"
+ "applet/app/e"
+ "applet/app/md"
+ "applet/app/utils/logx"
+ "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/pay"
+ "github.com/gin-gonic/gin"
+ "strings"
+)
+
+// 微信支付回调处理
+func wxPayCallback(c *gin.Context) (string, error) {
+ data, ok := c.Get("callback")
+ if data == nil || !ok {
+ return "", e.NewErrCode(e.ERR_INVALID_ARGS)
+ }
+ args := data.(*md.WxPayCallback)
+ _, ok = db.DBs[args.MasterID]
+ if !ok {
+ return "", logx.Warn("wxpay Failed : master_id not found")
+ }
+ c.Set("mid", args.MasterID)
+ //回调交易状态失败
+ if args.ResultCode != "SUCCESS" || args.ReturnCode != "SUCCESS" {
+ return "", logx.Warn("wxpay Failed : trade status failed")
+ }
+ return args.OutTradeNo, nil
+}
+func CommPayData(c *gin.Context, params map[string]string) (interface{}, error) {
+ platform := c.GetHeader("Platform")
+ browser := c.GetHeader("browser")
+ var r interface{}
+ var err error
+ switch platform {
+ case md.PLATFORM_WX_APPLET:
+ params = WxMiniProgPayConfig(c, params)
+ r, err = pay.WxMiniProgPay(params)
+ case md.PLATFORM_WAP:
+ if strings.Contains(browser, "wx_pay_browser") {
+ params = WxJsApiConfig(c, params)
+ r, err = pay.WxAppJSAPIPay(params)
+ } else {
+ params = WxH5PayConfig(c, params)
+ r, err = pay.WxH5Pay(params)
+ }
+ case md.PLATFORM_ANDROID, md.PLATFORM_IOS, md.PLATFORM_JSAPI:
+ params = WxAPPConfig(c, params)
+ r, err = pay.WxAppPay(params)
+ default:
+ return nil, e.NewErrCode(e.ERR_PLATFORM)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ return r, nil
+}
+func WxH5PayConfig(c *gin.Context, params map[string]string) map[string]string {
+ params["pay_wx_appid"] = SysCfgGet(c, "wx_official_account_app_id")
+ return params
+}
+func WxAPPConfig(c *gin.Context, params map[string]string) map[string]string {
+ params["pay_wx_appid"] = SysCfgGet(c, "pay_wx_appid")
+ return params
+}
+
+// 小程序v2
+func WxMiniProgPayConfig(c *gin.Context, params map[string]string) map[string]string {
+ //读取小程序设置的
+ wxAppletCfg := db.GetAppletKey(c, MasterDb(c))
+ params["pay_wx_appid"] = wxAppletCfg["app_id"]
+ // 兼容未登录支付 api/v1/unlogin/pay/:payMethod/:orderType(因为该路由未经过jwt-auth中间件)
+ user, err := CheckUser(c)
+ if user == nil || err != nil {
+ return params
+ }
+ if c.GetHeader("openid") != "" { //前端会传过来
+ user.Profile.ThirdPartyWechatMiniOpenid = c.GetHeader("openid")
+ }
+ if user.Profile.ThirdPartyWechatMiniOpenid == "" {
+ return params
+ }
+ params["third_party_wechat_openid"] = user.Profile.ThirdPartyWechatMiniOpenid
+ return params
+}
+func WxJsApiConfig(c *gin.Context, params map[string]string) map[string]string {
+ params["pay_wx_appid"] = SysCfgGet(c, "wx_official_account_app_id")
+ // 兼容未登录支付 api/v1/unlogin/pay/:payMethod/:orderType(因为该路由未经过jwt-auth中间件)
+ user, err := CheckUser(c)
+ if user == nil || err != nil {
+ return params
+ }
+ if c.GetHeader("openid") != "" { //前端会传过来
+ user.Profile.ThirdPartyWechatH5Openid = c.GetHeader("openid")
+ }
+ if user.Profile.ThirdPartyWechatH5Openid == "" {
+ return params
+ }
+ params["third_party_wechat_openid"] = user.Profile.ThirdPartyWechatH5Openid
+ return params
+}
diff --git a/app/task/init.go b/app/task/init.go
new file mode 100644
index 0000000..c35f775
--- /dev/null
+++ b/app/task/init.go
@@ -0,0 +1,300 @@
+package task
+
+import (
+ "github.com/robfig/cron/v3"
+ "time"
+
+ "applet/app/cfg"
+ "applet/app/db"
+ "applet/app/db/model"
+ "applet/app/md"
+ "applet/app/utils"
+ "applet/app/utils/logx"
+ "xorm.io/xorm"
+)
+
+var (
+ timer *cron.Cron
+ jobs = map[string]func(*xorm.Engine, string){}
+ baseEntryId cron.EntryID
+ entryIds []cron.EntryID
+ taskCfgList map[string]*[]model.SysCfg
+ ch = make(chan int, 50)
+ workerNum = 50 // 智盟跟单并发数量
+ orderStatWorkerNum = 50 // 智盟跟单并发数量
+ tbagoworkerNum = 50 // 智盟跟单并发数量
+ tbsettleworkerNum = 50 // 智盟跟单并发数量
+ pddOrderWorkerNum = 50 // 拼多多跟单并发数量
+ orderSuccessWorkerNum = 50 //
+ tbOrderWorkerNum = 50 // 淘宝跟单并发数量
+ jdOrderWorkerNum = 50 // 京东跟单并发数量
+ wphOrderWorkerNum = 50 // 唯品会跟单并发数量
+ cardWorkerNum = 20 // 权益卡并发数量
+ tbRelationWorkerNum = 50 // 淘宝并发数量
+ hw365WorkerNum = 50 // 海威并发数量
+ hw365TourismWorkerNum = 50 // 海威并发数量
+ tbpubWorkerNum = 50 // 海威并发数量
+ liveWorkerNum = 50 // 海威并发数量
+ tikTokOwnWorkerNum = 50 // 海威并发数量
+ cardUpdateWorkerNum = 50 // 海威并发数量
+ lifeWorkerNum = 50 //生活服务跟单
+ pddWorkerNum = 50 //
+ oilWorkerNum = 50 //
+ otherWorkerNum = 50 // 淘宝, 苏宁, 考拉并发量
+ jdWorkerNum = 50 //
+ tikTokWorkerNum = 50 //
+ teamGoodsWorkerNum = 50
+ jdCh = make(chan int, 50)
+ jdWorkerNum1 = 50 //
+ orderStatCh = make(chan int, 50)
+ jdCh1 = make(chan int, 50)
+ oilCh = make(chan int, 50)
+ otherCh = make(chan int, 50)
+ otherTourismCh = make(chan int, 50)
+ liveOtherCh = make(chan int, 50)
+ teamGoodsCh = make(chan int, 50)
+ tikTokOwnOtherCh = make(chan int, 50)
+ cardUpdateCh = make(chan int, 50)
+ tbpubCh = make(chan int, 50)
+ cardCh = make(chan int, 20)
+ pddCh = make(chan int, 50)
+ tikTokCh = make(chan int, 50)
+ tbRefundCh = make(chan int, 50)
+ tbagodCh = make(chan int, 50)
+ tbsettleCh = make(chan int, 50)
+ pddFailCh = make(chan int, 50)
+ orderSuccessCh = make(chan int, 50)
+ tbRelationCh = make(chan int, 50)
+)
+
+func Init() {
+ // 初始化任务列表
+ initTasks()
+ var err error
+ timer = cron.New()
+ if baseEntryId, err = timer.AddFunc("@every 20m", reload); err != nil {
+ _ = logx.Fatal(err)
+ }
+}
+
+func Run() {
+ reload()
+ timer.Start()
+ _ = logx.Info("auto tasks running...")
+}
+
+func reload() {
+ // 重新初始化数据库
+ db.InitMapDbs(cfg.DB, cfg.Prd)
+
+ if len(taskCfgList) == 0 {
+ taskCfgList = map[string]*[]model.SysCfg{}
+ }
+
+ // 获取所有站长的配置信息
+ for dbName, v := range db.DBs {
+ if conf := db.MapCrontabCfg(v); conf != nil {
+ if cfg.Debug {
+ dbInfo := md.SplitDbInfo(v)
+ // 去掉模版库
+ if dbName == "000000" {
+ continue
+ }
+ _ = logx.Debugf("【MasterId】%s, 【Host】%s, 【Name】%s, 【User】%s, 【prd】%v, 【Task】%v\n", dbName, dbInfo.Host, dbInfo.Name, dbInfo.User, cfg.Prd, utils.SerializeStr(*conf))
+ }
+ taskCfgList[dbName] = conf
+ }
+ }
+ if len(taskCfgList) > 0 {
+ // 删除原有所有任务
+ if len(entryIds) > 0 {
+ for _, v := range entryIds {
+ if v != baseEntryId {
+ timer.Remove(v)
+ }
+ }
+ entryIds = nil
+ }
+ var (
+ entryId cron.EntryID
+ err error
+ )
+ // 添加任务
+ for dbName, v := range taskCfgList {
+ for _, vv := range *v {
+ if _, ok := jobs[vv.Key]; ok && vv.Val != "" {
+ // fmt.Println(vv.Val)
+ if entryId, err = timer.AddFunc(vv.Val, doTask(dbName, vv.Key)); err == nil {
+ entryIds = append(entryIds, entryId)
+ }
+ }
+ }
+ }
+
+ }
+}
+
+func doTask(dbName, fnName string) func() {
+ return func() {
+ begin := time.Now().Local()
+ jobs[fnName](db.DBs[dbName], dbName)
+ end := time.Now().Local()
+ logx.Infof(
+ "[%s] AutoTask <%s> started at <%s>, ended at <%s> duration <%s>",
+ dbName,
+ fnName,
+ begin.Format("2006-01-02 15:04:05.000"),
+ end.Format("2006-01-02 15:04:05.000"),
+ time.Duration(end.UnixNano()-begin.UnixNano()).String(),
+ )
+ }
+}
+
+// 增加自动任务队列
+func initTasks() {
+ //v2
+ //jobs[md.KEY_CFG_CRON_BUCKLE] = taskOrderBuckle //
+ //jobs[md.KEY_CFG_CRON_CHECK_BUCKLE_ORDER] = taskCheckBuckleOrder //
+ //jobs[md.KEY_CFG_CRON_USER_RELATE] = taskUserRelate //
+
+ //v3
+ //jobs[md.KEY_CFG_CRON_TB12] = taskOrderTaobao12 //淘宝抓单
+ //jobs[md.KEY_CFG_CRON_TBBYAGOTIME] = taskAgoOrderTB //用于恢复个别时间丢单的
+ //jobs[md.KEY_CFG_CRON_TB_PUNISH_REFUND] = taskTbPunishOrderRefund //淘宝退款订单处理
+ //jobs[md.KEY_CFG_CRON_TBREFUND] = taskTbOrderRefund //淘宝退款订单处理
+ //jobs[md.KEY_CFG_CRON_PUBLISHER_RELATION] = taskTaobaoPublisherRelation //获取渠道信息
+ //jobs[md.KEY_CFG_CRON_PUBLISHER_RELATION_NEW] = taskTaobaoPublisherRelationNew //获取渠道信息
+ //jobs[md.KEY_CFG_CRON_TBSETTLEORDER] = taskOrderTaobaoSettleOrder //淘宝抓单结算订单
+
+ //v4
+ //jobs[md.KEY_CFG_CRON_PDD_SUCC] = taskOrderPddSucc
+ //jobs[md.KEY_CFG_CRON_PDDBYSTATUS] = taskOrderPddStatus
+ //jobs[md.KEY_CFG_CRON_PDDBYSTATUSSUCCESS] = taskOrderPddStatusSuccess
+ //jobs[md.KEY_CFG_CRON_PDDBYSTATUSFAIL] = taskOrderPddStatusFail
+ //jobs[md.KEY_CFG_CRON_PDDBYLOOPTIME] = taskLoopOrderPdd //拼多多创建时间循环当天
+ //jobs[md.KEY_CFG_CRON_PDDREFUND] = taskPddOrderRefund //拼多多退款订单处理
+ //jobs[md.KEY_CFG_CRON_PDDBYAGOTIME] = taskAgoOrderPdd //用于恢复个别时间丢单的
+ //jobs[md.KEY_CFG_CRON_PDDBYCREATETIME] = taskOrderPddByCreateTime //拼多多创建时间跟踪订单
+ //jobs[md.KEY_CFG_CRON_PDD] = taskOrderPdd
+
+ //v5-guide-settle
+ //jobs[md.KEY_CFG_CRON_SETTLE] = taskOrderSettle // 结算
+ //v5
+ //jobs[md.KEY_CFG_CRON_FREE_SETTLE] = taskOrderFreeSettle // 结算
+ //jobs[md.KEY_CFG_CRON_SECOND_FREE_SETTLE] = taskOrderSecondFreeSettle // 结算
+ //jobs[md.KEY_CFG_CRON_THIRD_FREE_SETTLE] = taskOrderMoreFreeSettle // 结算
+ //jobs[md.KEY_CFG_CRON_AGGREGATION_RECHARGE_SETTLE] = taskAggregationRechargeSettle //
+ //jobs[md.KEY_CFG_CRON_PLAYLET_SETTLE] = taskAggregationPlaylet //
+ //jobs[md.KEY_CFG_CRON_DUOYOUORD_SETTLE] = taskDuoYouSettle //
+ //jobs[md.KEY_CFG_CRON_LIANLIAN_SETTLE] = taskLianlianSettle //
+ //jobs[md.KEY_CFG_CRON_SWIPE_SETTLE] = taskSwipeSettle //
+ //jobs[md.KEY_CFG_CRON_USER_LV_UP_SETTLE] = taskUserLvUpOrderSettle // 会员费订单结算
+ //jobs[md.KEY_CFG_CRON_PRIVILEGE_CARD_SETTLE] = taskPrivilegeCardOrderSettle // 权益卡订单结算
+ //jobs[md.KEY_CFG_CRON_ACQUISITION_CONDITION] = taskAcquisitionCondition
+ //jobs[md.KEY_CFG_CRON_ACQUISITION_CONDITION_BY_LV] = taskAcquisitionConditionByLv
+ //jobs[md.KEY_CFG_CRON_ACQUISITION_REWARD] = taskAcquisitionReward
+ //jobs[md.KEY_CFG_CRON_NEW_ACQUISTION_SETTLE] = taskNewAcquisition // 拉新
+ //jobs[md.KEY_CFG_CRON_ACQUISTION_SETTLE] = taskAcquisition // 拉新
+ //jobs[md.KEY_CFG_VERIFY] = taskVerify //团长
+
+ //v7
+ //jobs[md.KEY_CFG_CRON_JD] = taskOrderJd
+ //jobs[md.KEY_CFG_CRON_JDFAILBYCREATETIME] = taskOrderJDFailByCreateTime //拼多多创建时间跟踪订单
+ //jobs[md.KEY_CFG_CRON_JDBYCREATETIME] = taskOrderJDByCreateTime //拼多多创建时间跟踪订单
+ //jobs[md.KEY_CFG_CRON_JDBYSUCCESS] = taskOrderJDBySuccess //拼多多创建时间跟踪订单
+ //jobs[md.KEY_CFG_CRON_ORDER_SUCCESS_CHECK] = taskOrderSuccessCheck //
+ //jobs[md.KEY_CFG_CRON_JDBYSTATUS] = taskOrderJdStatus
+ //jobs[md.KEY_CFG_CRON_JDREFUND] = taskJdOrderRefund //京东退款订单处理
+
+ //v6
+ jobs[md.KEY_CFG_CRON_TIKTOKLIFE] = taskOrderTikTokLife //抖音本地生活
+ jobs[md.KEY_CFG_KUAISHOU_AUTH] = taskKuaishouAuth //团长
+ jobs[md.KEY_CFG_TIK_TOK_TEAM_ORDER_PAY] = taskTikTokTeamOrder //团长
+ jobs[md.KEY_CFG_TIK_TOK_TEAM_ORDER_UPDATE] = taskTikTokTeamOrderUpdate //团长
+ jobs[md.KEY_CFG_TIK_TOK_TEAM_USER_BIND_BUYINID] = taskTikTokTeamUserBindBuyinid //达人buyin_id
+ jobs[md.KEY_CFG_KUAISHOU_TEAM_ORDER_PAY] = taskKuaishouTeamOrder //团长
+ jobs[md.KEY_CFG_KUAISHOU_TEAM_ORDER_UPDATE] = taskKuaishouTeamOrderUpdate //团长
+ jobs[md.KEY_CFG_CRON_AUTO_ADD_TIKTOK_GOODS] = taskAutoAddTikTokGoods //
+ jobs[md.KEY_CFG_CRON_KuaishouOwn] = taskOrderKuaishouOwn //
+ jobs[md.KEY_CFG_CRON_KuaishouOwnCreate] = taskOrderKuaishouOwnCreate //
+ jobs[md.KEY_CFG_CRON_KUAISHOU] = taskOrderKuaishou //
+ jobs[md.KEY_CFG_CRON_KUAISHOULIVE] = taskOrderKuaishouLive //
+ jobs[md.KEY_CFG_CRON_TIKTOKCsjp] = taskOrderTIKTokCsjp //
+ jobs[md.KEY_CFG_CRON_TIKTOKCsjpLive] = taskOrderTIKTokCsjpLive //
+ jobs[md.KEY_CFG_CRON_TIKTOKOwnCsjp] = taskOrderTIKTokOwnCsjp //
+ jobs[md.KEY_CFG_CRON_TIKTOKOwnCsjpLive] = taskOrderTIKTokOwnCsjpLive //
+ jobs[md.KEY_CFG_CRON_TIKTOKOwnCsjpActivity] = taskOrderTIKTokOwnCsjpActivity //
+ jobs[md.KEY_CFG_CRON_KUAISHOUOFFICIAL] = taskOrderKuaishouOfficial //
+ jobs[md.KEY_CFG_CRON_KUAISHOUOFFICIALLive] = taskOrderKuaishouOfficialLive //
+
+ //v8
+ //jobs[md.KEY_CFG_CRON_MEITUANLM_START] = taskOrderMeituanLmStart //智盟返回的美团联盟
+ //jobs[md.KEY_CFG_CRON_MEITUAN_START] = taskOrderMeituanStart //智盟返回的美团联盟
+ //jobs[md.KEY_CFG_CRON_MEITUAN] = taskOrderMeituan
+ //jobs[md.KEY_CFG_CRON_MEITUANLM] = taskOrderMeituanLm //智盟返回的美团联盟
+ //jobs[md.KEY_CFG_CRON_STATIONMEITUANLM] = taskOrderStationMeituanLm //站长自己美团联盟
+ //jobs[md.KEY_CFG_CRON_MEITUANOFFICIAL] = taskOrderMeituanOfficial //站长自己美团联盟
+
+ //v9
+ //jobs[md.KEY_CFG_CRON_ELM] = taskOrderElm //
+ //jobs[md.KEY_CFG_CRON_HEYTEA] = taskOrderHeytea //海威365喜茶
+ //jobs[md.KEY_CFG_CRON_PIZZA] = taskOrderPizza //海威365
+ //jobs[md.KEY_CFG_CRON_WALLACE] = taskOrderWallace //海威365
+ //jobs[md.KEY_CFG_CRON_TOURISM] = taskOrderTourism //海威365
+ //jobs[md.KEY_CFG_CRON_FLOWERCAKE] = taskOrderFlowerCake //海威365
+ //jobs[md.KEY_CFG_CRON_DELIVERY] = taskOrderDelivery //海威365
+ //jobs[md.KEY_CFG_CRON_BURGERKING] = taskOrderBurgerKing //海威365汉堡王
+ //jobs[md.KEY_CFG_CRON_STARBUCKS] = taskOrderStarbucks //海威365星巴克
+ //jobs[md.KEY_CFG_CRON_MCDONALD] = taskOrderMcdonald //海威365麦当劳
+ //jobs[md.KEY_CFG_CRON_HWMOVIE] = taskOrderHwMovie //海威365电影票
+ //jobs[md.KEY_CFG_CRON_NAYUKI] = taskOrderNayuki //海威365奈雪
+ //jobs[md.KEY_CFG_CRON_TO_KFC] = taskOrderToKfc //海威365
+ //jobs[md.KEY_CFG_CRON_PAGODA] = taskOrderPagoda //海威365
+ //jobs[md.KEY_CFG_CRON_LUCKIN] = taskOrderLuckin //海威365
+
+ //v10
+
+ ////jobs[md.KEY_CFG_CRON_WPHREFUND] = taskWphOrderRefund //唯品会退款订单处理
+ ////jobs[md.KEY_CFG_CRON_VIP] = taskOrderVip
+ //jobs[md.KEY_CFG_CRON_KFC] = taskOrderKfc
+ //jobs[md.KEY_CFG_CRON_CINEMA] = taskOrderCinema
+ //jobs[md.KEY_CFG_CRON_DUOMAI] = taskOrderDuomai //多麦跟单
+ //jobs[md.KEY_CFG_CRON_PLAYLET_ORDER] = taskPlayletOrder
+ //jobs[md.KEY_CFG_CRON_DIDI_ENERGY] = taskOrderDidiEnergy //滴滴加油
+ //jobs[md.KEY_CFG_CRON_T3_CAR] = taskOrderT3Car //T3打车
+ //jobs[md.KEY_CFG_CRON_DIDI_ONLINE_CAR] = taskOrderDidiOnlineCar //滴滴网约车
+ //jobs[md.KEY_CFG_CRON_KING_FLOWER] = taskOrderKingFlower //滴滴网约车
+ //jobs[md.KEY_CFG_CRON_DIDI_FREIGHT] = taskOrderDidiFreight //滴滴货运
+ //jobs[md.KEY_CFG_CRON_DIDI_CHAUFFEUR] = taskOrderDidiChauffeur //滴滴代驾
+ //jobs[md.KEY_CFG_CRON_OILSTATION] = taskOrderOilstation
+ //jobs[md.KEY_CFG_CRON_BRIGHTOILSTATION] = taskOrderBrightOilstation
+ //jobs[md.KEY_CFG_CRON_SN] = taskOrderSuning
+ //jobs[md.KEY_CFG_CRON_KL] = taskOrderKaola
+
+ ////原来的
+ //jobs[md.KEY_CFG_CRON_PlayLet_Total] = taskPlayletTotal //
+ //jobs[md.KEY_CFG_CRON_CHECK_GUIDE_STORE_ORDER] = taskCheckGuideStoreOrder //
+ //jobs[md.KEY_CFG_CRON_FAST_REFUND] = taskOrderFastRefund //
+ //jobs[md.KEY_CFG_CRON_FAST_SUCCESS] = taskOrderFastSuccess //
+ //jobs[md.KEY_CFG_CRON_DUOYOUORD] = taskOrderDuoYouOrd //
+ //jobs[md.KEY_CFG_CRON_TASKBOX] = taskOrderTaskBoxOrd //
+ //jobs[md.KEY_CFG_CRON_TASKBOXSECOND] = taskOrderTaskSecondOrd //
+ //jobs[md.KEY_CFG_CRON_CARD_CHECK_UPDATE] = taskCardCheckUpdate //权益卡退款
+ //jobs[md.KEY_CFG_CRON_CARD_UPDATE] = taskCardUpdate // 权益卡更新
+ //jobs[md.KEY_CFG_CRON_ORDER_STAT] = taskOrderStat // 订单统计
+ //jobs[md.KEY_CFG_CRON_GOODS_SHELF] = taskGoodsShelf //站内商品上下架
+ //jobs[md.KEY_CFG_CRON_CARD_RETURN] = taskCardReturn //权益卡退款
+ //jobs[md.KEY_CFG_CRON_DTKBRAND] = taskTaoKeBrandInfo // 大淘客品牌信息
+ //jobs[md.KEY_CFG_CRON_PUBLISHER] = taskTaobaoPublisherInfo // 淘宝备案信息绑定
+ //jobs[md.KEY_CFG_CRON_AUTO_UN_FREEZE] = taskAutoUnFreeze // 定时解冻
+
+ //先不用
+ //jobs[md.ZhimengCronPlayletVideoOrder] = taskPlayletVideoOrder //
+ //jobs[md.ZhimengCronPlayletVideoOrderYesterDay] = taskPlayletVideoOrderYesterday //
+ //jobs[md.ZhimengCronPlayletVideoOrderMonth] = taskPlayletVideoOrderMonth //
+ //jobs[md.ZhimengCronPlayletAdvOrderMonth] = taskPlayletAdvOrderMonth //
+ //jobs[md.ZhimengCronPlayletAdvOrder] = taskPlayletAdvOrder //
+ //jobs[md.ZhimengCronPlayletAdvOrderYesterDay] = taskPlayletAdvOrderYesterday //
+ //jobs[md.ZhimengCronPlayletAdvOrderYesterDayToMoney] = taskPlayletAdvOrderYesterdayToMoney //
+
+}
diff --git a/app/utils/aes.go b/app/utils/aes.go
new file mode 100644
index 0000000..5a39181
--- /dev/null
+++ b/app/utils/aes.go
@@ -0,0 +1,172 @@
+package utils
+
+import (
+ "applet/app/cfg"
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+)
+
+func AesEncrypt(rawData, key []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ blockSize := block.BlockSize()
+ rawData = PKCS5Padding(rawData, blockSize)
+ // rawData = ZeroPadding(rawData, block.BlockSize())
+ blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
+ encrypted := make([]byte, len(rawData))
+ // 根据CryptBlocks方法的说明,如下方式初始化encrypted也可以
+ // encrypted := rawData
+ blockMode.CryptBlocks(encrypted, rawData)
+ return encrypted, nil
+}
+
+func AesDecrypt(encrypted, key []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ blockSize := block.BlockSize()
+ blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
+ rawData := make([]byte, len(encrypted))
+ // rawData := encrypted
+ blockMode.CryptBlocks(rawData, encrypted)
+ rawData = PKCS5UnPadding(rawData)
+ // rawData = ZeroUnPadding(rawData)
+ return rawData, nil
+}
+
+func ZeroPadding(cipherText []byte, blockSize int) []byte {
+ padding := blockSize - len(cipherText)%blockSize
+ padText := bytes.Repeat([]byte{0}, padding)
+ return append(cipherText, padText...)
+}
+
+func ZeroUnPadding(rawData []byte) []byte {
+ length := len(rawData)
+ unPadding := int(rawData[length-1])
+ return rawData[:(length - unPadding)]
+}
+
+func PKCS5Padding(cipherText []byte, blockSize int) []byte {
+ padding := blockSize - len(cipherText)%blockSize
+ padText := bytes.Repeat([]byte{byte(padding)}, padding)
+ return append(cipherText, padText...)
+}
+
+func PKCS5UnPadding(rawData []byte) []byte {
+ length := len(rawData)
+ // 去掉最后一个字节 unPadding 次
+ unPadding := int(rawData[length-1])
+ return rawData[:(length - unPadding)]
+}
+
+// 填充0
+func zeroFill(key *string) {
+ l := len(*key)
+ if l != 16 && l != 24 && l != 32 {
+ if l < 16 {
+ *key = *key + fmt.Sprintf("%0*d", 16-l, 0)
+ } else if l < 24 {
+ *key = *key + fmt.Sprintf("%0*d", 24-l, 0)
+ } else if l < 32 {
+ *key = *key + fmt.Sprintf("%0*d", 32-l, 0)
+ } else {
+ *key = string([]byte(*key)[:32])
+ }
+ }
+}
+
+type AesCrypt struct {
+ Key []byte
+ Iv []byte
+}
+
+func (a *AesCrypt) Encrypt(data []byte) ([]byte, error) {
+ aesBlockEncrypt, err := aes.NewCipher(a.Key)
+ if err != nil {
+ println(err.Error())
+ return nil, err
+ }
+
+ content := pKCS5Padding(data, aesBlockEncrypt.BlockSize())
+ cipherBytes := make([]byte, len(content))
+ aesEncrypt := cipher.NewCBCEncrypter(aesBlockEncrypt, a.Iv)
+ aesEncrypt.CryptBlocks(cipherBytes, content)
+ return cipherBytes, nil
+}
+
+func (a *AesCrypt) Decrypt(src []byte) (data []byte, err error) {
+ decrypted := make([]byte, len(src))
+ var aesBlockDecrypt cipher.Block
+ aesBlockDecrypt, err = aes.NewCipher(a.Key)
+ if err != nil {
+ println(err.Error())
+ return nil, err
+ }
+ aesDecrypt := cipher.NewCBCDecrypter(aesBlockDecrypt, a.Iv)
+ aesDecrypt.CryptBlocks(decrypted, src)
+ return pKCS5Trimming(decrypted), nil
+}
+
+func pKCS5Padding(cipherText []byte, blockSize int) []byte {
+ padding := blockSize - len(cipherText)%blockSize
+ padText := bytes.Repeat([]byte{byte(padding)}, padding)
+ return append(cipherText, padText...)
+}
+
+func pKCS5Trimming(encrypt []byte) []byte {
+ padding := encrypt[len(encrypt)-1]
+ return encrypt[:len(encrypt)-int(padding)]
+}
+
+// AesAdminCurlPOST is 与后台接口加密交互
+func AesAdminCurlPOST(aesData string, url string, uid int) ([]byte, error) {
+ adminKey := cfg.Admin.AesKey
+ adminVI := cfg.Admin.AesIV
+ crypto := AesCrypt{
+ Key: []byte(adminKey),
+ Iv: []byte(adminVI),
+ }
+
+ encrypt, err := crypto.Encrypt([]byte(aesData))
+ if err != nil {
+ return nil, err
+ }
+
+ // 发送请求到后台
+ postData := map[string]string{
+ "postData": base64.StdEncoding.EncodeToString(encrypt),
+ }
+ fmt.Println(adminKey)
+ fmt.Println(adminVI)
+ fmt.Println("=======ADMIN请求=====")
+ fmt.Println(postData)
+ postDataByte, _ := json.Marshal(postData)
+ rdata, err := CurlPost(url, postDataByte, nil)
+ fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>>", err)
+
+ if err != nil {
+ return nil, err
+ }
+
+ FilePutContents("cash_out", fmt.Sprintf("curl Result返回:%s uid:%d, >>>>>>>>>>>>>>>>>>>>", string(rdata), uid))
+ pass, err := base64.StdEncoding.DecodeString(string(rdata))
+ if err != nil {
+ return nil, err
+ }
+ fmt.Println(pass)
+
+ decrypt, err := crypto.Decrypt(pass)
+ fmt.Println(err)
+
+ if err != nil {
+ return nil, err
+ }
+ return decrypt, nil
+}
diff --git a/app/utils/auth.go b/app/utils/auth.go
new file mode 100644
index 0000000..d7bd9ae
--- /dev/null
+++ b/app/utils/auth.go
@@ -0,0 +1,46 @@
+package utils
+
+import (
+ "errors"
+ "time"
+
+ "applet/app/lib/auth"
+
+ "github.com/dgrijalva/jwt-go"
+)
+
+// GenToken 生成JWT
+func GenToken(uid int, username, phone, appname, MiniOpenID, MiniSK string) (string, error) {
+ // 创建一个我们自己的声明
+ c := auth.JWTUser{
+ uid,
+ username,
+ phone,
+ appname,
+ MiniOpenID,
+ MiniSK,
+ jwt.StandardClaims{
+ ExpiresAt: time.Now().Add(auth.TokenExpireDuration).Unix(), // 过期时间
+ Issuer: "zyos", // 签发人
+ },
+ }
+ // 使用指定的签名方法创建签名对象
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
+ // 使用指定的secret签名并获得完整的编码后的字符串token
+ return token.SignedString(auth.Secret)
+}
+
+// ParseToken 解析JWT
+func ParseToken(tokenString string) (*auth.JWTUser, error) {
+ // 解析token
+ token, err := jwt.ParseWithClaims(tokenString, &auth.JWTUser{}, func(token *jwt.Token) (i interface{}, err error) {
+ return auth.Secret, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ if claims, ok := token.Claims.(*auth.JWTUser); ok && token.Valid { // 校验token
+ return claims, nil
+ }
+ return nil, errors.New("invalid token")
+}
diff --git a/app/utils/base64.go b/app/utils/base64.go
new file mode 100644
index 0000000..661b552
--- /dev/null
+++ b/app/utils/base64.go
@@ -0,0 +1,117 @@
+package utils
+
+import (
+ "encoding/base64"
+ "fmt"
+ "regexp"
+)
+
+const (
+ Base64Std = iota
+ Base64Url
+ Base64RawStd
+ Base64RawUrl
+)
+
+func Base64StdEncode(str interface{}) string {
+ return Base64Encode(str, Base64Std)
+}
+
+func Base64StdDecode(str interface{}) string {
+ return Base64Decode(str, Base64Std)
+}
+
+func Base64UrlEncode(str interface{}) string {
+ return Base64Encode(str, Base64Url)
+}
+
+func Base64UrlDecode(str interface{}) string {
+ return Base64Decode(str, Base64Url)
+}
+
+func Base64RawStdEncode(str interface{}) string {
+ return Base64Encode(str, Base64RawStd)
+}
+
+func Base64RawStdDecode(str interface{}) string {
+ return Base64Decode(str, Base64RawStd)
+}
+
+func Base64RawUrlEncode(str interface{}) string {
+ return Base64Encode(str, Base64RawUrl)
+}
+
+func Base64RawUrlDecode(str interface{}) string {
+ return Base64Decode(str, Base64RawUrl)
+}
+
+func Base64Encode(str interface{}, encode int) string {
+ newEncode := base64Encode(encode)
+ if newEncode == nil {
+ return ""
+ }
+ switch v := str.(type) {
+ case string:
+ return newEncode.EncodeToString([]byte(v))
+ case []byte:
+ return newEncode.EncodeToString(v)
+ }
+ return newEncode.EncodeToString([]byte(fmt.Sprint(str)))
+}
+
+func Base64Decode(str interface{}, encode int) string {
+ var err error
+ var b []byte
+ newEncode := base64Encode(encode)
+ if newEncode == nil {
+ return ""
+ }
+ switch v := str.(type) {
+ case string:
+ b, err = newEncode.DecodeString(v)
+ case []byte:
+ b, err = newEncode.DecodeString(string(v))
+ default:
+ return ""
+ }
+ if err != nil {
+ return ""
+ }
+ return string(b)
+}
+
+func base64Encode(encode int) *base64.Encoding {
+ switch encode {
+ case Base64Std:
+ return base64.StdEncoding
+ case Base64Url:
+ return base64.URLEncoding
+ case Base64RawStd:
+ return base64.RawStdEncoding
+ case Base64RawUrl:
+ return base64.RawURLEncoding
+ default:
+ return nil
+ }
+}
+
+func JudgeBase64(str string) bool {
+ pattern := "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$"
+ matched, err := regexp.MatchString(pattern, str)
+ if err != nil {
+ return false
+ }
+ if !(len(str)%4 == 0 && matched) {
+ return false
+ }
+ unCodeStr, err := base64.StdEncoding.DecodeString(str)
+ if err != nil {
+ return false
+ }
+ tranStr := base64.StdEncoding.EncodeToString(unCodeStr)
+ //return str==base64.StdEncoding.EncodeToString(unCodeStr)
+ if str == tranStr {
+ return true
+ }
+ return false
+}
diff --git a/app/utils/boolean.go b/app/utils/boolean.go
new file mode 100644
index 0000000..fdfd986
--- /dev/null
+++ b/app/utils/boolean.go
@@ -0,0 +1,26 @@
+package utils
+
+import "reflect"
+
+// 检验一个值是否为空
+func Empty(val interface{}) bool {
+ v := reflect.ValueOf(val)
+ switch v.Kind() {
+ case reflect.String, reflect.Array:
+ return v.Len() == 0
+ case reflect.Map, reflect.Slice:
+ return v.Len() == 0 || v.IsNil()
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ }
+
+ return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface())
+}
diff --git a/app/utils/cache/base.go b/app/utils/cache/base.go
new file mode 100644
index 0000000..64648dd
--- /dev/null
+++ b/app/utils/cache/base.go
@@ -0,0 +1,421 @@
+package cache
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "time"
+)
+
+const (
+ redisDialTTL = 10 * time.Second
+ redisReadTTL = 3 * time.Second
+ redisWriteTTL = 3 * time.Second
+ redisIdleTTL = 10 * time.Second
+ redisPoolTTL = 10 * time.Second
+ redisPoolSize int = 512
+ redisMaxIdleConn int = 64
+ redisMaxActive int = 512
+)
+
+var (
+ ErrNil = errors.New("nil return")
+ ErrWrongArgsNum = errors.New("args num error")
+ ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64")
+)
+
+// 以下为提供类型转换
+
+func Int(reply interface{}, err error) (int, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int:
+ return reply, nil
+ case int8:
+ return int(reply), nil
+ case int16:
+ return int(reply), nil
+ case int32:
+ return int(reply), nil
+ case int64:
+ x := int(reply)
+ if int64(x) != reply {
+ return 0, strconv.ErrRange
+ }
+ return x, nil
+ case uint:
+ n := int(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case uint8:
+ return int(reply), nil
+ case uint16:
+ return int(reply), nil
+ case uint32:
+ n := int(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case uint64:
+ n := int(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(data, 10, 0)
+ return int(n), err
+ case string:
+ if len(reply) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(reply, 10, 0)
+ return int(n), err
+ case nil:
+ return 0, ErrNil
+ case error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply)
+}
+
+func Int64(reply interface{}, err error) (int64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int:
+ return int64(reply), nil
+ case int8:
+ return int64(reply), nil
+ case int16:
+ return int64(reply), nil
+ case int32:
+ return int64(reply), nil
+ case int64:
+ return reply, nil
+ case uint:
+ n := int64(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case uint8:
+ return int64(reply), nil
+ case uint16:
+ return int64(reply), nil
+ case uint32:
+ return int64(reply), nil
+ case uint64:
+ n := int64(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(data, 10, 64)
+ return n, err
+ case string:
+ if len(reply) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(reply, 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply)
+}
+
+func Uint64(reply interface{}, err error) (uint64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case uint:
+ return uint64(reply), nil
+ case uint8:
+ return uint64(reply), nil
+ case uint16:
+ return uint64(reply), nil
+ case uint32:
+ return uint64(reply), nil
+ case uint64:
+ return reply, nil
+ case int:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int8:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int16:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int32:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int64:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseUint(data, 10, 64)
+ return n, err
+ case string:
+ if len(reply) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseUint(reply, 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply)
+}
+
+func Float64(reply interface{}, err error) (float64, error) {
+ if err != nil {
+ return 0, err
+ }
+
+ var value float64
+ err = nil
+ switch v := reply.(type) {
+ case float32:
+ value = float64(v)
+ case float64:
+ value = v
+ case int:
+ value = float64(v)
+ case int8:
+ value = float64(v)
+ case int16:
+ value = float64(v)
+ case int32:
+ value = float64(v)
+ case int64:
+ value = float64(v)
+ case uint:
+ value = float64(v)
+ case uint8:
+ value = float64(v)
+ case uint16:
+ value = float64(v)
+ case uint32:
+ value = float64(v)
+ case uint64:
+ value = float64(v)
+ case []byte:
+ data := string(v)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+ value, err = strconv.ParseFloat(string(v), 64)
+ case string:
+ if len(v) == 0 {
+ return 0, ErrNil
+ }
+ value, err = strconv.ParseFloat(v, 64)
+ case nil:
+ err = ErrNil
+ case error:
+ err = v
+ default:
+ err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v)
+ }
+
+ return value, err
+}
+
+func Bool(reply interface{}, err error) (bool, error) {
+ if err != nil {
+ return false, err
+ }
+ switch reply := reply.(type) {
+ case bool:
+ return reply, nil
+ case int64:
+ return reply != 0, nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return false, ErrNil
+ }
+
+ return strconv.ParseBool(data)
+ case string:
+ if len(reply) == 0 {
+ return false, ErrNil
+ }
+
+ return strconv.ParseBool(reply)
+ case nil:
+ return false, ErrNil
+ case error:
+ return false, reply
+ }
+ return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply)
+}
+
+func Bytes(reply interface{}, err error) ([]byte, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ if len(reply) == 0 {
+ return nil, ErrNil
+ }
+ return reply, nil
+ case string:
+ data := []byte(reply)
+ if len(data) == 0 {
+ return nil, ErrNil
+ }
+ return data, nil
+ case nil:
+ return nil, ErrNil
+ case error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply)
+}
+
+func String(reply interface{}, err error) (string, error) {
+ if err != nil {
+ return "", err
+ }
+
+ value := ""
+ err = nil
+ switch v := reply.(type) {
+ case string:
+ if len(v) == 0 {
+ return "", ErrNil
+ }
+
+ value = v
+ case []byte:
+ if len(v) == 0 {
+ return "", ErrNil
+ }
+
+ value = string(v)
+ case int:
+ value = strconv.FormatInt(int64(v), 10)
+ case int8:
+ value = strconv.FormatInt(int64(v), 10)
+ case int16:
+ value = strconv.FormatInt(int64(v), 10)
+ case int32:
+ value = strconv.FormatInt(int64(v), 10)
+ case int64:
+ value = strconv.FormatInt(v, 10)
+ case uint:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint8:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint16:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint32:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint64:
+ value = strconv.FormatUint(v, 10)
+ case float32:
+ value = strconv.FormatFloat(float64(v), 'f', -1, 32)
+ case float64:
+ value = strconv.FormatFloat(v, 'f', -1, 64)
+ case bool:
+ value = strconv.FormatBool(v)
+ case nil:
+ err = ErrNil
+ case error:
+ err = v
+ default:
+ err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v)
+ }
+
+ return value, err
+}
+
+func Strings(reply interface{}, err error) ([]string, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ result := make([]string, len(reply))
+ for i := range reply {
+ if reply[i] == nil {
+ continue
+ }
+ switch subReply := reply[i].(type) {
+ case string:
+ result[i] = subReply
+ case []byte:
+ result[i] = string(subReply)
+ default:
+ return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i])
+ }
+ }
+ return result, nil
+ case []string:
+ return reply, nil
+ case nil:
+ return nil, ErrNil
+ case error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply)
+}
+
+func Values(reply interface{}, err error) ([]interface{}, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ return reply, nil
+ case nil:
+ return nil, ErrNil
+ case error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply)
+}
diff --git a/app/utils/cache/cache/cache.go b/app/utils/cache/cache/cache.go
new file mode 100644
index 0000000..e43c5f0
--- /dev/null
+++ b/app/utils/cache/cache/cache.go
@@ -0,0 +1,107 @@
+package cache
+
+import (
+ "fmt"
+ "time"
+)
+
+var c Cache
+
+type Cache interface {
+ // get cached value by key.
+ Get(key string) interface{}
+ // GetMulti is a batch version of Get.
+ GetMulti(keys []string) []interface{}
+ // set cached value with key and expire time.
+ Put(key string, val interface{}, timeout time.Duration) error
+ // delete cached value by key.
+ Delete(key string) error
+ // increase cached int value by key, as a counter.
+ Incr(key string) error
+ // decrease cached int value by key, as a counter.
+ Decr(key string) error
+ // check if cached value exists or not.
+ IsExist(key string) bool
+ // clear all cache.
+ ClearAll() error
+ // start gc routine based on config string settings.
+ StartAndGC(config string) error
+}
+
+// Instance is a function create a new Cache Instance
+type Instance func() Cache
+
+var adapters = make(map[string]Instance)
+
+// Register makes a cache adapter available by the adapter name.
+// If Register is called twice with the same name or if driver is nil,
+// it panics.
+func Register(name string, adapter Instance) {
+ if adapter == nil {
+ panic("cache: Register adapter is nil")
+ }
+ if _, ok := adapters[name]; ok {
+ panic("cache: Register called twice for adapter " + name)
+ }
+ adapters[name] = adapter
+}
+
+// NewCache Create a new cache driver by adapter name and config string.
+// config need to be correct JSON as string: {"interval":360}.
+// it will start gc automatically.
+func NewCache(adapterName, config string) (adapter Cache, err error) {
+ instanceFunc, ok := adapters[adapterName]
+ if !ok {
+ err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
+ return
+ }
+ adapter = instanceFunc()
+ err = adapter.StartAndGC(config)
+ if err != nil {
+ adapter = nil
+ }
+ return
+}
+
+func InitCache(adapterName, config string) (err error) {
+ instanceFunc, ok := adapters[adapterName]
+ if !ok {
+ err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
+ return
+ }
+ c = instanceFunc()
+ err = c.StartAndGC(config)
+ if err != nil {
+ c = nil
+ }
+ return
+}
+
+func Get(key string) interface{} {
+ return c.Get(key)
+}
+
+func GetMulti(keys []string) []interface{} {
+ return c.GetMulti(keys)
+}
+func Put(key string, val interface{}, ttl time.Duration) error {
+ return c.Put(key, val, ttl)
+}
+func Delete(key string) error {
+ return c.Delete(key)
+}
+func Incr(key string) error {
+ return c.Incr(key)
+}
+func Decr(key string) error {
+ return c.Decr(key)
+}
+func IsExist(key string) bool {
+ return c.IsExist(key)
+}
+func ClearAll() error {
+ return c.ClearAll()
+}
+func StartAndGC(cfg string) error {
+ return c.StartAndGC(cfg)
+}
diff --git a/app/utils/cache/cache/conv.go b/app/utils/cache/cache/conv.go
new file mode 100644
index 0000000..6b700ae
--- /dev/null
+++ b/app/utils/cache/cache/conv.go
@@ -0,0 +1,86 @@
+package cache
+
+import (
+ "fmt"
+ "strconv"
+)
+
+// GetString convert interface to string.
+func GetString(v interface{}) string {
+ switch result := v.(type) {
+ case string:
+ return result
+ case []byte:
+ return string(result)
+ default:
+ if v != nil {
+ return fmt.Sprint(result)
+ }
+ }
+ return ""
+}
+
+// GetInt convert interface to int.
+func GetInt(v interface{}) int {
+ switch result := v.(type) {
+ case int:
+ return result
+ case int32:
+ return int(result)
+ case int64:
+ return int(result)
+ default:
+ if d := GetString(v); d != "" {
+ value, _ := strconv.Atoi(d)
+ return value
+ }
+ }
+ return 0
+}
+
+// GetInt64 convert interface to int64.
+func GetInt64(v interface{}) int64 {
+ switch result := v.(type) {
+ case int:
+ return int64(result)
+ case int32:
+ return int64(result)
+ case int64:
+ return result
+ default:
+
+ if d := GetString(v); d != "" {
+ value, _ := strconv.ParseInt(d, 10, 64)
+ return value
+ }
+ }
+ return 0
+}
+
+// GetFloat64 convert interface to float64.
+func GetFloat64(v interface{}) float64 {
+ switch result := v.(type) {
+ case float64:
+ return result
+ default:
+ if d := GetString(v); d != "" {
+ value, _ := strconv.ParseFloat(d, 64)
+ return value
+ }
+ }
+ return 0
+}
+
+// GetBool convert interface to bool.
+func GetBool(v interface{}) bool {
+ switch result := v.(type) {
+ case bool:
+ return result
+ default:
+ if d := GetString(v); d != "" {
+ value, _ := strconv.ParseBool(d)
+ return value
+ }
+ }
+ return false
+}
diff --git a/app/utils/cache/cache/file.go b/app/utils/cache/cache/file.go
new file mode 100644
index 0000000..5c4e366
--- /dev/null
+++ b/app/utils/cache/cache/file.go
@@ -0,0 +1,241 @@
+package cache
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/gob"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+ "time"
+)
+
+// FileCacheItem is basic unit of file cache adapter.
+// it contains data and expire time.
+type FileCacheItem struct {
+ Data interface{}
+ LastAccess time.Time
+ Expired time.Time
+}
+
+// FileCache Config
+var (
+ FileCachePath = "cache" // cache directory
+ FileCacheFileSuffix = ".bin" // cache file suffix
+ FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
+ FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
+)
+
+// FileCache is cache adapter for file storage.
+type FileCache struct {
+ CachePath string
+ FileSuffix string
+ DirectoryLevel int
+ EmbedExpiry int
+}
+
+// NewFileCache Create new file cache with no config.
+// the level and expiry need set in method StartAndGC as config string.
+func NewFileCache() Cache {
+ // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
+ return &FileCache{}
+}
+
+// StartAndGC will start and begin gc for file cache.
+// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
+func (fc *FileCache) StartAndGC(config string) error {
+
+ var cfg map[string]string
+ json.Unmarshal([]byte(config), &cfg)
+ if _, ok := cfg["CachePath"]; !ok {
+ cfg["CachePath"] = FileCachePath
+ }
+ if _, ok := cfg["FileSuffix"]; !ok {
+ cfg["FileSuffix"] = FileCacheFileSuffix
+ }
+ if _, ok := cfg["DirectoryLevel"]; !ok {
+ cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
+ }
+ if _, ok := cfg["EmbedExpiry"]; !ok {
+ cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
+ }
+ fc.CachePath = cfg["CachePath"]
+ fc.FileSuffix = cfg["FileSuffix"]
+ fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
+ fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
+
+ fc.Init()
+ return nil
+}
+
+// Init will make new dir for file cache if not exist.
+func (fc *FileCache) Init() {
+ if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
+ _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
+ }
+}
+
+// get cached file name. it's md5 encoded.
+func (fc *FileCache) getCacheFileName(key string) string {
+ m := md5.New()
+ io.WriteString(m, key)
+ keyMd5 := hex.EncodeToString(m.Sum(nil))
+ cachePath := fc.CachePath
+ switch fc.DirectoryLevel {
+ case 2:
+ cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
+ case 1:
+ cachePath = filepath.Join(cachePath, keyMd5[0:2])
+ }
+
+ if ok, _ := exists(cachePath); !ok { // todo : error handle
+ _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
+ }
+
+ return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
+}
+
+// Get value from file cache.
+// if non-exist or expired, return empty string.
+func (fc *FileCache) Get(key string) interface{} {
+ fileData, err := FileGetContents(fc.getCacheFileName(key))
+ if err != nil {
+ return ""
+ }
+ var to FileCacheItem
+ GobDecode(fileData, &to)
+ if to.Expired.Before(time.Now()) {
+ return ""
+ }
+ return to.Data
+}
+
+// GetMulti gets values from file cache.
+// if non-exist or expired, return empty string.
+func (fc *FileCache) GetMulti(keys []string) []interface{} {
+ var rc []interface{}
+ for _, key := range keys {
+ rc = append(rc, fc.Get(key))
+ }
+ return rc
+}
+
+// Put value into file cache.
+// timeout means how long to keep this file, unit of ms.
+// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
+func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
+ gob.Register(val)
+
+ item := FileCacheItem{Data: val}
+ if timeout == FileCacheEmbedExpiry {
+ item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
+ } else {
+ item.Expired = time.Now().Add(timeout)
+ }
+ item.LastAccess = time.Now()
+ data, err := GobEncode(item)
+ if err != nil {
+ return err
+ }
+ return FilePutContents(fc.getCacheFileName(key), data)
+}
+
+// Delete file cache value.
+func (fc *FileCache) Delete(key string) error {
+ filename := fc.getCacheFileName(key)
+ if ok, _ := exists(filename); ok {
+ return os.Remove(filename)
+ }
+ return nil
+}
+
+// Incr will increase cached int value.
+// fc value is saving forever unless Delete.
+func (fc *FileCache) Incr(key string) error {
+ data := fc.Get(key)
+ var incr int
+ if reflect.TypeOf(data).Name() != "int" {
+ incr = 0
+ } else {
+ incr = data.(int) + 1
+ }
+ fc.Put(key, incr, FileCacheEmbedExpiry)
+ return nil
+}
+
+// Decr will decrease cached int value.
+func (fc *FileCache) Decr(key string) error {
+ data := fc.Get(key)
+ var decr int
+ if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
+ decr = 0
+ } else {
+ decr = data.(int) - 1
+ }
+ fc.Put(key, decr, FileCacheEmbedExpiry)
+ return nil
+}
+
+// IsExist check value is exist.
+func (fc *FileCache) IsExist(key string) bool {
+ ret, _ := exists(fc.getCacheFileName(key))
+ return ret
+}
+
+// ClearAll will clean cached files.
+// not implemented.
+func (fc *FileCache) ClearAll() error {
+ return nil
+}
+
+// check file exist.
+func exists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+// FileGetContents Get bytes to file.
+// if non-exist, create this file.
+func FileGetContents(filename string) (data []byte, e error) {
+ return ioutil.ReadFile(filename)
+}
+
+// FilePutContents Put bytes to file.
+// if non-exist, create this file.
+func FilePutContents(filename string, content []byte) error {
+ return ioutil.WriteFile(filename, content, os.ModePerm)
+}
+
+// GobEncode Gob encodes file cache item.
+func GobEncode(data interface{}) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ enc := gob.NewEncoder(buf)
+ err := enc.Encode(data)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), err
+}
+
+// GobDecode Gob decodes file cache item.
+func GobDecode(data []byte, to *FileCacheItem) error {
+ buf := bytes.NewBuffer(data)
+ dec := gob.NewDecoder(buf)
+ return dec.Decode(&to)
+}
+
+func init() {
+ Register("file", NewFileCache)
+}
diff --git a/app/utils/cache/cache/memory.go b/app/utils/cache/cache/memory.go
new file mode 100644
index 0000000..0cc5015
--- /dev/null
+++ b/app/utils/cache/cache/memory.go
@@ -0,0 +1,239 @@
+package cache
+
+import (
+ "encoding/json"
+ "errors"
+ "sync"
+ "time"
+)
+
+var (
+ // DefaultEvery means the clock time of recycling the expired cache items in memory.
+ DefaultEvery = 60 // 1 minute
+)
+
+// MemoryItem store memory cache item.
+type MemoryItem struct {
+ val interface{}
+ createdTime time.Time
+ lifespan time.Duration
+}
+
+func (mi *MemoryItem) isExpire() bool {
+ // 0 means forever
+ if mi.lifespan == 0 {
+ return false
+ }
+ return time.Now().Sub(mi.createdTime) > mi.lifespan
+}
+
+// MemoryCache is Memory cache adapter.
+// it contains a RW locker for safe map storage.
+type MemoryCache struct {
+ sync.RWMutex
+ dur time.Duration
+ items map[string]*MemoryItem
+ Every int // run an expiration check Every clock time
+}
+
+// NewMemoryCache returns a new MemoryCache.
+func NewMemoryCache() Cache {
+ cache := MemoryCache{items: make(map[string]*MemoryItem)}
+ return &cache
+}
+
+// Get cache from memory.
+// if non-existed or expired, return nil.
+func (bc *MemoryCache) Get(name string) interface{} {
+ bc.RLock()
+ defer bc.RUnlock()
+ if itm, ok := bc.items[name]; ok {
+ if itm.isExpire() {
+ return nil
+ }
+ return itm.val
+ }
+ return nil
+}
+
+// GetMulti gets caches from memory.
+// if non-existed or expired, return nil.
+func (bc *MemoryCache) GetMulti(names []string) []interface{} {
+ var rc []interface{}
+ for _, name := range names {
+ rc = append(rc, bc.Get(name))
+ }
+ return rc
+}
+
+// Put cache to memory.
+// if lifespan is 0, it will be forever till restart.
+func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
+ bc.Lock()
+ defer bc.Unlock()
+ bc.items[name] = &MemoryItem{
+ val: value,
+ createdTime: time.Now(),
+ lifespan: lifespan,
+ }
+ return nil
+}
+
+// Delete cache in memory.
+func (bc *MemoryCache) Delete(name string) error {
+ bc.Lock()
+ defer bc.Unlock()
+ if _, ok := bc.items[name]; !ok {
+ return errors.New("key not exist")
+ }
+ delete(bc.items, name)
+ if _, ok := bc.items[name]; ok {
+ return errors.New("delete key error")
+ }
+ return nil
+}
+
+// Incr increase cache counter in memory.
+// it supports int,int32,int64,uint,uint32,uint64.
+func (bc *MemoryCache) Incr(key string) error {
+ bc.RLock()
+ defer bc.RUnlock()
+ itm, ok := bc.items[key]
+ if !ok {
+ return errors.New("key not exist")
+ }
+ switch itm.val.(type) {
+ case int:
+ itm.val = itm.val.(int) + 1
+ case int32:
+ itm.val = itm.val.(int32) + 1
+ case int64:
+ itm.val = itm.val.(int64) + 1
+ case uint:
+ itm.val = itm.val.(uint) + 1
+ case uint32:
+ itm.val = itm.val.(uint32) + 1
+ case uint64:
+ itm.val = itm.val.(uint64) + 1
+ default:
+ return errors.New("item val is not (u)int (u)int32 (u)int64")
+ }
+ return nil
+}
+
+// Decr decrease counter in memory.
+func (bc *MemoryCache) Decr(key string) error {
+ bc.RLock()
+ defer bc.RUnlock()
+ itm, ok := bc.items[key]
+ if !ok {
+ return errors.New("key not exist")
+ }
+ switch itm.val.(type) {
+ case int:
+ itm.val = itm.val.(int) - 1
+ case int64:
+ itm.val = itm.val.(int64) - 1
+ case int32:
+ itm.val = itm.val.(int32) - 1
+ case uint:
+ if itm.val.(uint) > 0 {
+ itm.val = itm.val.(uint) - 1
+ } else {
+ return errors.New("item val is less than 0")
+ }
+ case uint32:
+ if itm.val.(uint32) > 0 {
+ itm.val = itm.val.(uint32) - 1
+ } else {
+ return errors.New("item val is less than 0")
+ }
+ case uint64:
+ if itm.val.(uint64) > 0 {
+ itm.val = itm.val.(uint64) - 1
+ } else {
+ return errors.New("item val is less than 0")
+ }
+ default:
+ return errors.New("item val is not int int64 int32")
+ }
+ return nil
+}
+
+// IsExist check cache exist in memory.
+func (bc *MemoryCache) IsExist(name string) bool {
+ bc.RLock()
+ defer bc.RUnlock()
+ if v, ok := bc.items[name]; ok {
+ return !v.isExpire()
+ }
+ return false
+}
+
+// ClearAll will delete all cache in memory.
+func (bc *MemoryCache) ClearAll() error {
+ bc.Lock()
+ defer bc.Unlock()
+ bc.items = make(map[string]*MemoryItem)
+ return nil
+}
+
+// StartAndGC start memory cache. it will check expiration in every clock time.
+func (bc *MemoryCache) StartAndGC(config string) error {
+ var cf map[string]int
+ json.Unmarshal([]byte(config), &cf)
+ if _, ok := cf["interval"]; !ok {
+ cf = make(map[string]int)
+ cf["interval"] = DefaultEvery
+ }
+ dur := time.Duration(cf["interval"]) * time.Second
+ bc.Every = cf["interval"]
+ bc.dur = dur
+ go bc.vacuum()
+ return nil
+}
+
+// check expiration.
+func (bc *MemoryCache) vacuum() {
+ bc.RLock()
+ every := bc.Every
+ bc.RUnlock()
+
+ if every < 1 {
+ return
+ }
+ for {
+ <-time.After(bc.dur)
+ if bc.items == nil {
+ return
+ }
+ if keys := bc.expiredKeys(); len(keys) != 0 {
+ bc.clearItems(keys)
+ }
+ }
+}
+
+// expiredKeys returns key list which are expired.
+func (bc *MemoryCache) expiredKeys() (keys []string) {
+ bc.RLock()
+ defer bc.RUnlock()
+ for key, itm := range bc.items {
+ if itm.isExpire() {
+ keys = append(keys, key)
+ }
+ }
+ return
+}
+
+// clearItems removes all the items which key in keys.
+func (bc *MemoryCache) clearItems(keys []string) {
+ bc.Lock()
+ defer bc.Unlock()
+ for _, key := range keys {
+ delete(bc.items, key)
+ }
+}
+
+func init() {
+ Register("memory", NewMemoryCache)
+}
diff --git a/app/utils/cache/redis.go b/app/utils/cache/redis.go
new file mode 100644
index 0000000..4e5f047
--- /dev/null
+++ b/app/utils/cache/redis.go
@@ -0,0 +1,403 @@
+package cache
+
+import (
+ "encoding/json"
+ "errors"
+ "log"
+ "strings"
+ "time"
+
+ redigo "github.com/gomodule/redigo/redis"
+)
+
+// configuration
+type Config struct {
+ Server string
+ Password string
+ MaxIdle int // Maximum number of idle connections in the pool.
+
+ // Maximum number of connections allocated by the pool at a given time.
+ // When zero, there is no limit on the number of connections in the pool.
+ MaxActive int
+
+ // Close connections after remaining idle for this duration. If the value
+ // is zero, then idle connections are not closed. Applications should set
+ // the timeout to a value less than the server's timeout.
+ IdleTimeout time.Duration
+
+ // If Wait is true and the pool is at the MaxActive limit, then Get() waits
+ // for a connection to be returned to the pool before returning.
+ Wait bool
+ KeyPrefix string // prefix to all keys; example is "dev environment name"
+ KeyDelimiter string // delimiter to be used while appending keys; example is ":"
+ KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?"
+}
+
+var pool *redigo.Pool
+var conf *Config
+
+func NewRedis(addr string) {
+ if addr == "" {
+ panic("\nredis connect string cannot be empty\n")
+ }
+ pool = &redigo.Pool{
+ MaxIdle: redisMaxIdleConn,
+ IdleTimeout: redisIdleTTL,
+ MaxActive: redisMaxActive,
+ // MaxConnLifetime: redisDialTTL,
+ Wait: true,
+ Dial: func() (redigo.Conn, error) {
+ c, err := redigo.Dial("tcp", addr,
+ redigo.DialConnectTimeout(redisDialTTL),
+ redigo.DialReadTimeout(redisReadTTL),
+ redigo.DialWriteTimeout(redisWriteTTL),
+ )
+ if err != nil {
+ log.Println("Redis Dial failed: ", err)
+ return nil, err
+ }
+ return c, err
+ },
+ TestOnBorrow: func(c redigo.Conn, t time.Time) error {
+ _, err := c.Do("PING")
+ if err != nil {
+ log.Println("Unable to ping to redis server:", err)
+ }
+ return err
+ },
+ }
+ conn := pool.Get()
+ defer conn.Close()
+ if conn.Err() != nil {
+ println("\nredis connect " + addr + " error: " + conn.Err().Error())
+ } else {
+ println("\nredis connect " + addr + " success!\n")
+ }
+}
+
+func Do(cmd string, args ...interface{}) (reply interface{}, err error) {
+ conn := pool.Get()
+ defer conn.Close()
+ return conn.Do(cmd, args...)
+}
+
+func GetPool() *redigo.Pool {
+ return pool
+}
+
+func ParseKey(key string, vars []string) (string, error) {
+ arr := strings.Split(key, conf.KeyPlaceholder)
+ actualKey := ""
+ if len(arr) != len(vars)+1 {
+ return "", errors.New("redis/connection.go: Insufficient arguments to parse key")
+ } else {
+ for index, val := range arr {
+ if index == 0 {
+ actualKey = arr[index]
+ } else {
+ actualKey += vars[index-1] + val
+ }
+ }
+ }
+ return getPrefixedKey(actualKey), nil
+}
+
+func getPrefixedKey(key string) string {
+ return conf.KeyPrefix + conf.KeyDelimiter + key
+}
+func StripEnvKey(key string) string {
+ return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter)
+}
+func SplitKey(key string) []string {
+ return strings.Split(key, conf.KeyDelimiter)
+}
+func Expire(key string, ttl int) (interface{}, error) {
+ return Do("EXPIRE", key, ttl)
+}
+func Persist(key string) (interface{}, error) {
+ return Do("PERSIST", key)
+}
+
+func Del(key string) (interface{}, error) {
+ return Do("DEL", key)
+}
+func Set(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("SET", key, data)
+}
+func SetNX(key string, data interface{}) (interface{}, error) {
+ return Do("SETNX", key, data)
+}
+func SetEx(key string, data interface{}, ttl int) (interface{}, error) {
+ return Do("SETEX", key, ttl, data)
+}
+
+func SetJson(key string, data interface{}, ttl int) bool {
+ c, err := json.Marshal(data)
+ if err != nil {
+ return false
+ }
+ if ttl < 1 {
+ _, err = Set(key, c)
+ } else {
+ _, err = SetEx(key, c, ttl)
+ }
+ if err != nil {
+ return false
+ }
+ return true
+}
+
+func GetJson(key string, dst interface{}) error {
+ b, err := GetBytes(key)
+ if err != nil {
+ return err
+ }
+ if err = json.Unmarshal(b, dst); err != nil {
+ return err
+ }
+ return nil
+}
+
+func Get(key string) (interface{}, error) {
+ // get
+ return Do("GET", key)
+}
+func GetTTL(key string) (time.Duration, error) {
+ ttl, err := redigo.Int64(Do("TTL", key))
+ return time.Duration(ttl) * time.Second, err
+}
+func GetBytes(key string) ([]byte, error) {
+ return redigo.Bytes(Do("GET", key))
+}
+func GetString(key string) (string, error) {
+ return redigo.String(Do("GET", key))
+}
+func GetStringMap(key string) (map[string]string, error) {
+ return redigo.StringMap(Do("GET", key))
+}
+func GetInt(key string) (int, error) {
+ return redigo.Int(Do("GET", key))
+}
+func GetInt64(key string) (int64, error) {
+ return redigo.Int64(Do("GET", key))
+}
+func GetStringLength(key string) (int, error) {
+ return redigo.Int(Do("STRLEN", key))
+}
+func ZAdd(key string, score float64, data interface{}) (interface{}, error) {
+ return Do("ZADD", key, score, data)
+}
+func ZAddNX(key string, score float64, data interface{}) (interface{}, error) {
+ return Do("ZADD", key, "NX", score, data)
+}
+func ZRem(key string, data interface{}) (interface{}, error) {
+ return Do("ZREM", key, data)
+}
+func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) {
+ if withScores {
+ return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES"))
+ }
+ return redigo.Values(Do("ZRANGE", key, start, end))
+}
+func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) {
+ return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end))
+}
+func ZCard(setName string) (int64, error) {
+ return redigo.Int64(Do("ZCARD", setName))
+}
+func ZScan(setName string) (int64, error) {
+ return redigo.Int64(Do("ZCARD", setName))
+}
+func SAdd(setName string, data interface{}) (interface{}, error) {
+ return Do("SADD", setName, data)
+}
+func SCard(setName string) (int64, error) {
+ return redigo.Int64(Do("SCARD", setName))
+}
+func SIsMember(setName string, data interface{}) (bool, error) {
+ return redigo.Bool(Do("SISMEMBER", setName, data))
+}
+func SMembers(setName string) ([]string, error) {
+ return redigo.Strings(Do("SMEMBERS", setName))
+}
+func SRem(setName string, data interface{}) (interface{}, error) {
+ return Do("SREM", setName, data)
+}
+func HSet(key string, HKey string, data interface{}) (interface{}, error) {
+ return Do("HSET", key, HKey, data)
+}
+
+func HGet(key string, HKey string) (interface{}, error) {
+ return Do("HGET", key, HKey)
+}
+
+func HMGet(key string, hashKeys ...string) ([]interface{}, error) {
+ ret, err := Do("HMGET", key, hashKeys)
+ if err != nil {
+ return nil, err
+ }
+ reta, ok := ret.([]interface{})
+ if !ok {
+ return nil, errors.New("result not an array")
+ }
+ return reta, nil
+}
+
+func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) {
+ if len(hashKeys) == 0 || len(hashKeys) != len(vals) {
+ var ret interface{}
+ return ret, errors.New("bad length")
+ }
+ input := []interface{}{key}
+ for i, v := range hashKeys {
+ input = append(input, v, vals[i])
+ }
+ return Do("HMSET", input...)
+}
+
+func HGetString(key string, HKey string) (string, error) {
+ return redigo.String(Do("HGET", key, HKey))
+}
+func HGetFloat(key string, HKey string) (float64, error) {
+ f, err := redigo.Float64(Do("HGET", key, HKey))
+ return f, err
+}
+func HGetInt(key string, HKey string) (int, error) {
+ return redigo.Int(Do("HGET", key, HKey))
+}
+func HGetInt64(key string, HKey string) (int64, error) {
+ return redigo.Int64(Do("HGET", key, HKey))
+}
+func HGetBool(key string, HKey string) (bool, error) {
+ return redigo.Bool(Do("HGET", key, HKey))
+}
+func HDel(key string, HKey string) (interface{}, error) {
+ return Do("HDEL", key, HKey)
+}
+
+func HGetAll(key string) (map[string]interface{}, error) {
+ vals, err := redigo.Values(Do("HGETALL", key))
+ if err != nil {
+ return nil, err
+ }
+ num := len(vals) / 2
+ result := make(map[string]interface{}, num)
+ for i := 0; i < num; i++ {
+ key, _ := redigo.String(vals[2*i], nil)
+ result[key] = vals[2*i+1]
+ }
+ return result, nil
+}
+
+func FlushAll() bool {
+ res, _ := redigo.String(Do("FLUSHALL"))
+ if res == "" {
+ return false
+ }
+ return true
+}
+
+// NOTE: Use this in production environment with extreme care.
+// Read more here:https://redigo.io/commands/keys
+func Keys(pattern string) ([]string, error) {
+ return redigo.Strings(Do("KEYS", pattern))
+}
+
+func HKeys(key string) ([]string, error) {
+ return redigo.Strings(Do("HKEYS", key))
+}
+
+func Exists(key string) bool {
+ count, err := redigo.Int(Do("EXISTS", key))
+ if count == 0 || err != nil {
+ return false
+ }
+ return true
+}
+
+func Incr(key string) (int64, error) {
+ return redigo.Int64(Do("INCR", key))
+}
+
+func Decr(key string) (int64, error) {
+ return redigo.Int64(Do("DECR", key))
+}
+
+func IncrBy(key string, incBy int64) (int64, error) {
+ return redigo.Int64(Do("INCRBY", key, incBy))
+}
+
+func DecrBy(key string, decrBy int64) (int64, error) {
+ return redigo.Int64(Do("DECRBY", key))
+}
+
+func IncrByFloat(key string, incBy float64) (float64, error) {
+ return redigo.Float64(Do("INCRBYFLOAT", key, incBy))
+}
+
+func DecrByFloat(key string, decrBy float64) (float64, error) {
+ return redigo.Float64(Do("DECRBYFLOAT", key, decrBy))
+}
+
+// use for message queue
+func LPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("LPUSH", key, data)
+}
+
+func LPop(key string) (interface{}, error) {
+ return Do("LPOP", key)
+}
+
+func LPopString(key string) (string, error) {
+ return redigo.String(Do("LPOP", key))
+}
+func LPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("LPOP", key))
+ return f, err
+}
+func LPopInt(key string) (int, error) {
+ return redigo.Int(Do("LPOP", key))
+}
+func LPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("LPOP", key))
+}
+
+func RPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("RPUSH", key, data)
+}
+
+func RPop(key string) (interface{}, error) {
+ return Do("RPOP", key)
+}
+
+func RPopString(key string) (string, error) {
+ return redigo.String(Do("RPOP", key))
+}
+func RPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("RPOP", key))
+ return f, err
+}
+func RPopInt(key string) (int, error) {
+ return redigo.Int(Do("RPOP", key))
+}
+func RPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("RPOP", key))
+}
+
+func Scan(cursor int64, pattern string, count int64) (int64, []string, error) {
+ var items []string
+ var newCursor int64
+
+ values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count))
+ if err != nil {
+ return 0, nil, err
+ }
+ values, err = redigo.Scan(values, &newCursor, &items)
+ if err != nil {
+ return 0, nil, err
+ }
+ return newCursor, items, nil
+}
diff --git a/app/utils/cache/redis_cluster.go b/app/utils/cache/redis_cluster.go
new file mode 100644
index 0000000..901f30c
--- /dev/null
+++ b/app/utils/cache/redis_cluster.go
@@ -0,0 +1,622 @@
+package cache
+
+import (
+ "strconv"
+ "time"
+
+ "github.com/go-redis/redis"
+)
+
+var pools *redis.ClusterClient
+
+func NewRedisCluster(addrs []string) error {
+ opt := &redis.ClusterOptions{
+ Addrs: addrs,
+ PoolSize: redisPoolSize,
+ PoolTimeout: redisPoolTTL,
+ IdleTimeout: redisIdleTTL,
+ DialTimeout: redisDialTTL,
+ ReadTimeout: redisReadTTL,
+ WriteTimeout: redisWriteTTL,
+ }
+ pools = redis.NewClusterClient(opt)
+ if err := pools.Ping().Err(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func RCGet(key string) (interface{}, error) {
+ res, err := pools.Get(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCSet(key string, value interface{}) error {
+ err := pools.Set(key, value, 0).Err()
+ return convertError(err)
+}
+func RCGetSet(key string, value interface{}) (interface{}, error) {
+ res, err := pools.GetSet(key, value).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCSetNx(key string, value interface{}) (int64, error) {
+ res, err := pools.SetNX(key, value, 0).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func RCSetEx(key string, value interface{}, timeout int64) error {
+ _, err := pools.Set(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误
+func RCSetNxEx(key string, value interface{}, timeout int64) error {
+ res, err := pools.SetNX(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ if res {
+ return nil
+ }
+ return ErrNil
+}
+func RCMGet(keys ...string) ([]interface{}, error) {
+ res, err := pools.MGet(keys...).Result()
+ return res, convertError(err)
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func RCMSet(kvs map[string]interface{}) error {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return err
+ }
+ pairs = append(pairs, k, val)
+ }
+ return convertError(pools.MSet(pairs).Err())
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func RCMSetNX(kvs map[string]interface{}) (bool, error) {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return false, err
+ }
+ pairs = append(pairs, k, val)
+ }
+ res, err := pools.MSetNX(pairs).Result()
+ return res, convertError(err)
+}
+func RCExpireAt(key string, timestamp int64) (int64, error) {
+ res, err := pools.ExpireAt(key, time.Unix(timestamp, 0)).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func RCDel(keys ...string) (int64, error) {
+ args := make([]interface{}, 0, len(keys))
+ for _, key := range keys {
+ args = append(args, key)
+ }
+ res, err := pools.Del(keys...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCIncr(key string) (int64, error) {
+ res, err := pools.Incr(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCIncrBy(key string, delta int64) (int64, error) {
+ res, err := pools.IncrBy(key, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCExpire(key string, duration int64) (int64, error) {
+ res, err := pools.Expire(key, time.Duration(duration)*time.Second).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func RCExists(key string) (bool, error) {
+ res, err := pools.Exists(key).Result()
+ if err != nil {
+ return false, convertError(err)
+ }
+ if res > 0 {
+ return true, nil
+ }
+ return false, nil
+}
+func RCHGet(key string, field string) (interface{}, error) {
+ res, err := pools.HGet(key, field).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCHLen(key string) (int64, error) {
+ res, err := pools.HLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCHSet(key string, field string, val interface{}) error {
+ value, err := String(val, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ _, err = pools.HSet(key, field, value).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func RCHDel(key string, fields ...string) (int64, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ res, err := pools.HDel(key, fields...).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ return res, nil
+}
+
+func RCHMGet(key string, fields ...string) (interface{}, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ if len(fields) == 0 {
+ return nil, ErrNil
+ }
+ res, err := pools.HMGet(key, fields...).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCHMSet(key string, kvs ...interface{}) error {
+ if len(kvs) == 0 {
+ return nil
+ }
+ if len(kvs)%2 != 0 {
+ return ErrWrongArgsNum
+ }
+ var err error
+ v := map[string]interface{}{} // todo change
+ v["field"], err = String(kvs[0], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ v["value"], err = String(kvs[1], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs := make([]string, 0, len(kvs)-2)
+ if len(kvs) > 2 {
+ for _, kv := range kvs[2:] {
+ kvString, err := String(kv, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs = append(pairs, kvString)
+ }
+ }
+ v["paris"] = pairs
+ _, err = pools.HMSet(key, v).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+func RCHKeys(key string) ([]string, error) {
+ res, err := pools.HKeys(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCHVals(key string) ([]interface{}, error) {
+ res, err := pools.HVals(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ rs := make([]interface{}, 0, len(res))
+ for _, res := range res {
+ rs = append(rs, res)
+ }
+ return rs, nil
+}
+func RCHGetAll(key string) (map[string]string, error) {
+ vals, err := pools.HGetAll(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return vals, nil
+}
+func RCHIncrBy(key, field string, delta int64) (int64, error) {
+ res, err := pools.HIncrBy(key, field, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCZAdd(key string, kvs ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(kvs)+1)
+ args = append(args, key)
+ args = append(args, kvs...)
+ if len(kvs) == 0 {
+ return 0, nil
+ }
+ if len(kvs)%2 != 0 {
+ return 0, ErrWrongArgsNum
+ }
+ zs := make([]redis.Z, len(kvs)/2)
+ for i := 0; i < len(kvs); i += 2 {
+ idx := i / 2
+ score, err := Float64(kvs[i], nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ zs[idx].Score = score
+ zs[idx].Member = kvs[i+1]
+ }
+ res, err := pools.ZAdd(key, zs...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCZRem(key string, members ...string) (int64, error) {
+ args := make([]interface{}, 0, len(members))
+ args = append(args, key)
+ for _, member := range members {
+ args = append(args, member)
+ }
+ res, err := pools.ZRem(key, members).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, err
+}
+
+func RCZRange(key string, min, max int64, withScores bool) (interface{}, error) {
+ res := make([]interface{}, 0)
+ if withScores {
+ zs, err := pools.ZRangeWithScores(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, z := range zs {
+ res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64))
+ }
+ } else {
+ ms, err := pools.ZRange(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, m := range ms {
+ res = append(res, m)
+ }
+ }
+ return res, nil
+}
+func RCZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) {
+ opt := new(redis.ZRangeBy)
+ opt.Min = strconv.FormatInt(int64(min), 10)
+ opt.Max = strconv.FormatInt(int64(max), 10)
+ opt.Count = -1
+ opt.Offset = 0
+ vals, err := pools.ZRangeByScoreWithScores(key, *opt).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ res := make(map[string]int64, len(vals))
+ for _, val := range vals {
+ key, err := String(val.Member, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ res[key] = int64(val.Score)
+ }
+ return res, nil
+}
+func RCLRange(key string, start, stop int64) (interface{}, error) {
+ res, err := pools.LRange(key, start, stop).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCLSet(key string, index int, value interface{}) error {
+ err := pools.LSet(key, int64(index), value).Err()
+ return convertError(err)
+}
+func RCLLen(key string) (int64, error) {
+ res, err := pools.LLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCLRem(key string, count int, value interface{}) (int, error) {
+ val, _ := value.(string)
+ res, err := pools.LRem(key, int64(count), val).Result()
+ if err != nil {
+ return int(res), convertError(err)
+ }
+ return int(res), nil
+}
+func RCTTl(key string) (int64, error) {
+ duration, err := pools.TTL(key).Result()
+ if err != nil {
+ return int64(duration.Seconds()), convertError(err)
+ }
+ return int64(duration.Seconds()), nil
+}
+func RCLPop(key string) (interface{}, error) {
+ res, err := pools.LPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCRPop(key string) (interface{}, error) {
+ res, err := pools.RPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCBLPop(key string, timeout int) (interface{}, error) {
+ res, err := pools.BLPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, err
+ }
+ return res[1], nil
+}
+func RCBRPop(key string, timeout int) (interface{}, error) {
+ res, err := pools.BRPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, convertError(err)
+ }
+ return res[1], nil
+}
+func RCLPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ vals = append(vals, val)
+ }
+ _, err := pools.LPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func RCRPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ if err == ErrNil {
+ continue
+ }
+ return err
+ }
+ if val == "" {
+ continue
+ }
+ vals = append(vals, val)
+ }
+ _, err := pools.RPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func RCBRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) {
+ res, err := pools.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func RCRPopLPush(srcKey string, destKey string) (interface{}, error) {
+ res, err := pools.RPopLPush(srcKey, destKey).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCSAdd(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := pools.SAdd(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSPop(key string) ([]byte, error) {
+ res, err := pools.SPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCSIsMember(key string, member interface{}) (bool, error) {
+ m, _ := member.(string)
+ res, err := pools.SIsMember(key, m).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSRem(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := pools.SRem(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSMembers(key string) ([]string, error) {
+ res, err := pools.SMembers(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCScriptLoad(luaScript string) (interface{}, error) {
+ res, err := pools.ScriptLoad(luaScript).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCEvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, sha1, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := pools.EvalSha(sha1, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCEval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, luaScript, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := pools.Eval(luaScript, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCGetBit(key string, offset int64) (int64, error) {
+ res, err := pools.GetBit(key, offset).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSetBit(key string, offset uint32, value int) (int, error) {
+ res, err := pools.SetBit(key, int64(offset), value).Result()
+ return int(res), convertError(err)
+}
+func RCGetClient() *redis.ClusterClient {
+ return pools
+}
+func convertError(err error) error {
+ if err == redis.Nil {
+ // 为了兼容redis 2.x,这里不返回 ErrNil,ErrNil在调用redis_cluster_reply函数时才返回
+ return nil
+ }
+ return err
+}
diff --git a/app/utils/cache/redis_pool.go b/app/utils/cache/redis_pool.go
new file mode 100644
index 0000000..ca38b3f
--- /dev/null
+++ b/app/utils/cache/redis_pool.go
@@ -0,0 +1,324 @@
+package cache
+
+import (
+ "errors"
+ "log"
+ "strings"
+ "time"
+
+ redigo "github.com/gomodule/redigo/redis"
+)
+
+type RedisPool struct {
+ *redigo.Pool
+}
+
+func NewRedisPool(cfg *Config) *RedisPool {
+ return &RedisPool{&redigo.Pool{
+ MaxIdle: cfg.MaxIdle,
+ IdleTimeout: cfg.IdleTimeout,
+ MaxActive: cfg.MaxActive,
+ Wait: cfg.Wait,
+ Dial: func() (redigo.Conn, error) {
+ c, err := redigo.Dial("tcp", cfg.Server)
+ if err != nil {
+ log.Println("Redis Dial failed: ", err)
+ return nil, err
+ }
+ if cfg.Password != "" {
+ if _, err := c.Do("AUTH", cfg.Password); err != nil {
+ c.Close()
+ log.Println("Redis AUTH failed: ", err)
+ return nil, err
+ }
+ }
+ return c, err
+ },
+ TestOnBorrow: func(c redigo.Conn, t time.Time) error {
+ _, err := c.Do("PING")
+ if err != nil {
+ log.Println("Unable to ping to redis server:", err)
+ }
+ return err
+ },
+ }}
+}
+
+func (p *RedisPool) Do(cmd string, args ...interface{}) (reply interface{}, err error) {
+ conn := pool.Get()
+ defer conn.Close()
+ return conn.Do(cmd, args...)
+}
+
+func (p *RedisPool) GetPool() *redigo.Pool {
+ return pool
+}
+
+func (p *RedisPool) ParseKey(key string, vars []string) (string, error) {
+ arr := strings.Split(key, conf.KeyPlaceholder)
+ actualKey := ""
+ if len(arr) != len(vars)+1 {
+ return "", errors.New("redis/connection.go: Insufficient arguments to parse key")
+ } else {
+ for index, val := range arr {
+ if index == 0 {
+ actualKey = arr[index]
+ } else {
+ actualKey += vars[index-1] + val
+ }
+ }
+ }
+ return getPrefixedKey(actualKey), nil
+}
+
+func (p *RedisPool) getPrefixedKey(key string) string {
+ return conf.KeyPrefix + conf.KeyDelimiter + key
+}
+func (p *RedisPool) StripEnvKey(key string) string {
+ return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter)
+}
+func (p *RedisPool) SplitKey(key string) []string {
+ return strings.Split(key, conf.KeyDelimiter)
+}
+func (p *RedisPool) Expire(key string, ttl int) (interface{}, error) {
+ return Do("EXPIRE", key, ttl)
+}
+func (p *RedisPool) Persist(key string) (interface{}, error) {
+ return Do("PERSIST", key)
+}
+
+func (p *RedisPool) Del(key string) (interface{}, error) {
+ return Do("DEL", key)
+}
+func (p *RedisPool) Set(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("SET", key, data)
+}
+func (p *RedisPool) SetNX(key string, data interface{}) (interface{}, error) {
+ return Do("SETNX", key, data)
+}
+func (p *RedisPool) SetEx(key string, data interface{}, ttl int) (interface{}, error) {
+ return Do("SETEX", key, ttl, data)
+}
+func (p *RedisPool) Get(key string) (interface{}, error) {
+ // get
+ return Do("GET", key)
+}
+func (p *RedisPool) GetStringMap(key string) (map[string]string, error) {
+ // get
+ return redigo.StringMap(Do("GET", key))
+}
+
+func (p *RedisPool) GetTTL(key string) (time.Duration, error) {
+ ttl, err := redigo.Int64(Do("TTL", key))
+ return time.Duration(ttl) * time.Second, err
+}
+func (p *RedisPool) GetBytes(key string) ([]byte, error) {
+ return redigo.Bytes(Do("GET", key))
+}
+func (p *RedisPool) GetString(key string) (string, error) {
+ return redigo.String(Do("GET", key))
+}
+func (p *RedisPool) GetInt(key string) (int, error) {
+ return redigo.Int(Do("GET", key))
+}
+func (p *RedisPool) GetStringLength(key string) (int, error) {
+ return redigo.Int(Do("STRLEN", key))
+}
+func (p *RedisPool) ZAdd(key string, score float64, data interface{}) (interface{}, error) {
+ return Do("ZADD", key, score, data)
+}
+func (p *RedisPool) ZRem(key string, data interface{}) (interface{}, error) {
+ return Do("ZREM", key, data)
+}
+func (p *RedisPool) ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) {
+ if withScores {
+ return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES"))
+ }
+ return redigo.Values(Do("ZRANGE", key, start, end))
+}
+func (p *RedisPool) SAdd(setName string, data interface{}) (interface{}, error) {
+ return Do("SADD", setName, data)
+}
+func (p *RedisPool) SCard(setName string) (int64, error) {
+ return redigo.Int64(Do("SCARD", setName))
+}
+func (p *RedisPool) SIsMember(setName string, data interface{}) (bool, error) {
+ return redigo.Bool(Do("SISMEMBER", setName, data))
+}
+func (p *RedisPool) SMembers(setName string) ([]string, error) {
+ return redigo.Strings(Do("SMEMBERS", setName))
+}
+func (p *RedisPool) SRem(setName string, data interface{}) (interface{}, error) {
+ return Do("SREM", setName, data)
+}
+func (p *RedisPool) HSet(key string, HKey string, data interface{}) (interface{}, error) {
+ return Do("HSET", key, HKey, data)
+}
+
+func (p *RedisPool) HGet(key string, HKey string) (interface{}, error) {
+ return Do("HGET", key, HKey)
+}
+
+func (p *RedisPool) HMGet(key string, hashKeys ...string) ([]interface{}, error) {
+ ret, err := Do("HMGET", key, hashKeys)
+ if err != nil {
+ return nil, err
+ }
+ reta, ok := ret.([]interface{})
+ if !ok {
+ return nil, errors.New("result not an array")
+ }
+ return reta, nil
+}
+
+func (p *RedisPool) HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) {
+ if len(hashKeys) == 0 || len(hashKeys) != len(vals) {
+ var ret interface{}
+ return ret, errors.New("bad length")
+ }
+ input := []interface{}{key}
+ for i, v := range hashKeys {
+ input = append(input, v, vals[i])
+ }
+ return Do("HMSET", input...)
+}
+
+func (p *RedisPool) HGetString(key string, HKey string) (string, error) {
+ return redigo.String(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HGetFloat(key string, HKey string) (float64, error) {
+ f, err := redigo.Float64(Do("HGET", key, HKey))
+ return float64(f), err
+}
+func (p *RedisPool) HGetInt(key string, HKey string) (int, error) {
+ return redigo.Int(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HGetInt64(key string, HKey string) (int64, error) {
+ return redigo.Int64(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HGetBool(key string, HKey string) (bool, error) {
+ return redigo.Bool(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HDel(key string, HKey string) (interface{}, error) {
+ return Do("HDEL", key, HKey)
+}
+func (p *RedisPool) HGetAll(key string) (map[string]interface{}, error) {
+ vals, err := redigo.Values(Do("HGETALL", key))
+ if err != nil {
+ return nil, err
+ }
+ num := len(vals) / 2
+ result := make(map[string]interface{}, num)
+ for i := 0; i < num; i++ {
+ key, _ := redigo.String(vals[2*i], nil)
+ result[key] = vals[2*i+1]
+ }
+ return result, nil
+}
+
+// NOTE: Use this in production environment with extreme care.
+// Read more here:https://redigo.io/commands/keys
+func (p *RedisPool) Keys(pattern string) ([]string, error) {
+ return redigo.Strings(Do("KEYS", pattern))
+}
+
+func (p *RedisPool) HKeys(key string) ([]string, error) {
+ return redigo.Strings(Do("HKEYS", key))
+}
+
+func (p *RedisPool) Exists(key string) (bool, error) {
+ count, err := redigo.Int(Do("EXISTS", key))
+ if count == 0 {
+ return false, err
+ } else {
+ return true, err
+ }
+}
+
+func (p *RedisPool) Incr(key string) (int64, error) {
+ return redigo.Int64(Do("INCR", key))
+}
+
+func (p *RedisPool) Decr(key string) (int64, error) {
+ return redigo.Int64(Do("DECR", key))
+}
+
+func (p *RedisPool) IncrBy(key string, incBy int64) (int64, error) {
+ return redigo.Int64(Do("INCRBY", key, incBy))
+}
+
+func (p *RedisPool) DecrBy(key string, decrBy int64) (int64, error) {
+ return redigo.Int64(Do("DECRBY", key))
+}
+
+func (p *RedisPool) IncrByFloat(key string, incBy float64) (float64, error) {
+ return redigo.Float64(Do("INCRBYFLOAT", key, incBy))
+}
+
+func (p *RedisPool) DecrByFloat(key string, decrBy float64) (float64, error) {
+ return redigo.Float64(Do("DECRBYFLOAT", key, decrBy))
+}
+
+// use for message queue
+func (p *RedisPool) LPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("LPUSH", key, data)
+}
+
+func (p *RedisPool) LPop(key string) (interface{}, error) {
+ return Do("LPOP", key)
+}
+
+func (p *RedisPool) LPopString(key string) (string, error) {
+ return redigo.String(Do("LPOP", key))
+}
+func (p *RedisPool) LPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("LPOP", key))
+ return float64(f), err
+}
+func (p *RedisPool) LPopInt(key string) (int, error) {
+ return redigo.Int(Do("LPOP", key))
+}
+func (p *RedisPool) LPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("LPOP", key))
+}
+
+func (p *RedisPool) RPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("RPUSH", key, data)
+}
+
+func (p *RedisPool) RPop(key string) (interface{}, error) {
+ return Do("RPOP", key)
+}
+
+func (p *RedisPool) RPopString(key string) (string, error) {
+ return redigo.String(Do("RPOP", key))
+}
+func (p *RedisPool) RPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("RPOP", key))
+ return float64(f), err
+}
+func (p *RedisPool) RPopInt(key string) (int, error) {
+ return redigo.Int(Do("RPOP", key))
+}
+func (p *RedisPool) RPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("RPOP", key))
+}
+
+func (p *RedisPool) Scan(cursor int64, pattern string, count int64) (int64, []string, error) {
+ var items []string
+ var newCursor int64
+
+ values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count))
+ if err != nil {
+ return 0, nil, err
+ }
+ values, err = redigo.Scan(values, &newCursor, &items)
+ if err != nil {
+ return 0, nil, err
+ }
+
+ return newCursor, items, nil
+}
diff --git a/app/utils/cache/redis_pool_cluster.go b/app/utils/cache/redis_pool_cluster.go
new file mode 100644
index 0000000..cd1911b
--- /dev/null
+++ b/app/utils/cache/redis_pool_cluster.go
@@ -0,0 +1,617 @@
+package cache
+
+import (
+ "strconv"
+ "time"
+
+ "github.com/go-redis/redis"
+)
+
+type RedisClusterPool struct {
+ client *redis.ClusterClient
+}
+
+func NewRedisClusterPool(addrs []string) (*RedisClusterPool, error) {
+ opt := &redis.ClusterOptions{
+ Addrs: addrs,
+ PoolSize: 512,
+ PoolTimeout: 10 * time.Second,
+ IdleTimeout: 10 * time.Second,
+ DialTimeout: 10 * time.Second,
+ ReadTimeout: 3 * time.Second,
+ WriteTimeout: 3 * time.Second,
+ }
+ c := redis.NewClusterClient(opt)
+ if err := c.Ping().Err(); err != nil {
+ return nil, err
+ }
+ return &RedisClusterPool{client: c}, nil
+}
+
+func (p *RedisClusterPool) Get(key string) (interface{}, error) {
+ res, err := p.client.Get(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) Set(key string, value interface{}) error {
+ err := p.client.Set(key, value, 0).Err()
+ return convertError(err)
+}
+func (p *RedisClusterPool) GetSet(key string, value interface{}) (interface{}, error) {
+ res, err := p.client.GetSet(key, value).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) SetNx(key string, value interface{}) (int64, error) {
+ res, err := p.client.SetNX(key, value, 0).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func (p *RedisClusterPool) SetEx(key string, value interface{}, timeout int64) error {
+ _, err := p.client.Set(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误
+func (p *RedisClusterPool) SetNxEx(key string, value interface{}, timeout int64) error {
+ res, err := p.client.SetNX(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ if res {
+ return nil
+ }
+ return ErrNil
+}
+func (p *RedisClusterPool) MGet(keys ...string) ([]interface{}, error) {
+ res, err := p.client.MGet(keys...).Result()
+ return res, convertError(err)
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func (p *RedisClusterPool) MSet(kvs map[string]interface{}) error {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return err
+ }
+ pairs = append(pairs, k, val)
+ }
+ return convertError(p.client.MSet(pairs).Err())
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func (p *RedisClusterPool) MSetNX(kvs map[string]interface{}) (bool, error) {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return false, err
+ }
+ pairs = append(pairs, k, val)
+ }
+ res, err := p.client.MSetNX(pairs).Result()
+ return res, convertError(err)
+}
+func (p *RedisClusterPool) ExpireAt(key string, timestamp int64) (int64, error) {
+ res, err := p.client.ExpireAt(key, time.Unix(timestamp, 0)).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func (p *RedisClusterPool) Del(keys ...string) (int64, error) {
+ args := make([]interface{}, 0, len(keys))
+ for _, key := range keys {
+ args = append(args, key)
+ }
+ res, err := p.client.Del(keys...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) Incr(key string) (int64, error) {
+ res, err := p.client.Incr(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) IncrBy(key string, delta int64) (int64, error) {
+ res, err := p.client.IncrBy(key, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) Expire(key string, duration int64) (int64, error) {
+ res, err := p.client.Expire(key, time.Duration(duration)*time.Second).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func (p *RedisClusterPool) Exists(key string) (bool, error) { // todo (bool, error)
+ res, err := p.client.Exists(key).Result()
+ if err != nil {
+ return false, convertError(err)
+ }
+ if res > 0 {
+ return true, nil
+ }
+ return false, nil
+}
+func (p *RedisClusterPool) HGet(key string, field string) (interface{}, error) {
+ res, err := p.client.HGet(key, field).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) HLen(key string) (int64, error) {
+ res, err := p.client.HLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) HSet(key string, field string, val interface{}) error {
+ value, err := String(val, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ _, err = p.client.HSet(key, field, value).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func (p *RedisClusterPool) HDel(key string, fields ...string) (int64, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ res, err := p.client.HDel(key, fields...).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ return res, nil
+}
+
+func (p *RedisClusterPool) HMGet(key string, fields ...string) (interface{}, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ if len(fields) == 0 {
+ return nil, ErrNil
+ }
+ res, err := p.client.HMGet(key, fields...).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) HMSet(key string, kvs ...interface{}) error {
+ if len(kvs) == 0 {
+ return nil
+ }
+ if len(kvs)%2 != 0 {
+ return ErrWrongArgsNum
+ }
+ var err error
+ v := map[string]interface{}{} // todo change
+ v["field"], err = String(kvs[0], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ v["value"], err = String(kvs[1], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs := make([]string, 0, len(kvs)-2)
+ if len(kvs) > 2 {
+ for _, kv := range kvs[2:] {
+ kvString, err := String(kv, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs = append(pairs, kvString)
+ }
+ }
+ v["paris"] = pairs
+ _, err = p.client.HMSet(key, v).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+func (p *RedisClusterPool) HKeys(key string) ([]string, error) {
+ res, err := p.client.HKeys(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) HVals(key string) ([]interface{}, error) {
+ res, err := p.client.HVals(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ rs := make([]interface{}, 0, len(res))
+ for _, res := range res {
+ rs = append(rs, res)
+ }
+ return rs, nil
+}
+func (p *RedisClusterPool) HGetAll(key string) (map[string]string, error) {
+ vals, err := p.client.HGetAll(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return vals, nil
+}
+func (p *RedisClusterPool) HIncrBy(key, field string, delta int64) (int64, error) {
+ res, err := p.client.HIncrBy(key, field, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ZAdd(key string, kvs ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(kvs)+1)
+ args = append(args, key)
+ args = append(args, kvs...)
+ if len(kvs) == 0 {
+ return 0, nil
+ }
+ if len(kvs)%2 != 0 {
+ return 0, ErrWrongArgsNum
+ }
+ zs := make([]redis.Z, len(kvs)/2)
+ for i := 0; i < len(kvs); i += 2 {
+ idx := i / 2
+ score, err := Float64(kvs[i], nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ zs[idx].Score = score
+ zs[idx].Member = kvs[i+1]
+ }
+ res, err := p.client.ZAdd(key, zs...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ZRem(key string, members ...string) (int64, error) {
+ args := make([]interface{}, 0, len(members))
+ args = append(args, key)
+ for _, member := range members {
+ args = append(args, member)
+ }
+ res, err := p.client.ZRem(key, members).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, err
+}
+
+func (p *RedisClusterPool) ZRange(key string, min, max int64, withScores bool) (interface{}, error) {
+ res := make([]interface{}, 0)
+ if withScores {
+ zs, err := p.client.ZRangeWithScores(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, z := range zs {
+ res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64))
+ }
+ } else {
+ ms, err := p.client.ZRange(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, m := range ms {
+ res = append(res, m)
+ }
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) {
+ opt := new(redis.ZRangeBy)
+ opt.Min = strconv.FormatInt(int64(min), 10)
+ opt.Max = strconv.FormatInt(int64(max), 10)
+ opt.Count = -1
+ opt.Offset = 0
+ vals, err := p.client.ZRangeByScoreWithScores(key, *opt).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ res := make(map[string]int64, len(vals))
+ for _, val := range vals {
+ key, err := String(val.Member, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ res[key] = int64(val.Score)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) LRange(key string, start, stop int64) (interface{}, error) {
+ res, err := p.client.LRange(key, start, stop).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) LSet(key string, index int, value interface{}) error {
+ err := p.client.LSet(key, int64(index), value).Err()
+ return convertError(err)
+}
+func (p *RedisClusterPool) LLen(key string) (int64, error) {
+ res, err := p.client.LLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) LRem(key string, count int, value interface{}) (int, error) {
+ val, _ := value.(string)
+ res, err := p.client.LRem(key, int64(count), val).Result()
+ if err != nil {
+ return int(res), convertError(err)
+ }
+ return int(res), nil
+}
+func (p *RedisClusterPool) TTl(key string) (int64, error) {
+ duration, err := p.client.TTL(key).Result()
+ if err != nil {
+ return int64(duration.Seconds()), convertError(err)
+ }
+ return int64(duration.Seconds()), nil
+}
+func (p *RedisClusterPool) LPop(key string) (interface{}, error) {
+ res, err := p.client.LPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) RPop(key string) (interface{}, error) {
+ res, err := p.client.RPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) BLPop(key string, timeout int) (interface{}, error) {
+ res, err := p.client.BLPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, err
+ }
+ return res[1], nil
+}
+func (p *RedisClusterPool) BRPop(key string, timeout int) (interface{}, error) {
+ res, err := p.client.BRPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, convertError(err)
+ }
+ return res[1], nil
+}
+func (p *RedisClusterPool) LPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ vals = append(vals, val)
+ }
+ _, err := p.client.LPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func (p *RedisClusterPool) RPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ if err == ErrNil {
+ continue
+ }
+ return err
+ }
+ if val == "" {
+ continue
+ }
+ vals = append(vals, val)
+ }
+ _, err := p.client.RPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func (p *RedisClusterPool) BRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) {
+ res, err := p.client.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func (p *RedisClusterPool) RPopLPush(srcKey string, destKey string) (interface{}, error) {
+ res, err := p.client.RPopLPush(srcKey, destKey).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SAdd(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := p.client.SAdd(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SPop(key string) ([]byte, error) {
+ res, err := p.client.SPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) SIsMember(key string, member interface{}) (bool, error) {
+ m, _ := member.(string)
+ res, err := p.client.SIsMember(key, m).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SRem(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := p.client.SRem(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SMembers(key string) ([]string, error) {
+ res, err := p.client.SMembers(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ScriptLoad(luaScript string) (interface{}, error) {
+ res, err := p.client.ScriptLoad(luaScript).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) EvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, sha1, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := p.client.EvalSha(sha1, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) Eval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, luaScript, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := p.client.Eval(luaScript, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) GetBit(key string, offset int64) (int64, error) {
+ res, err := p.client.GetBit(key, offset).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SetBit(key string, offset uint32, value int) (int, error) {
+ res, err := p.client.SetBit(key, int64(offset), value).Result()
+ return int(res), convertError(err)
+}
+func (p *RedisClusterPool) GetClient() *redis.ClusterClient {
+ return pools
+}
diff --git a/app/utils/cachesecond/base.go b/app/utils/cachesecond/base.go
new file mode 100644
index 0000000..126c4d3
--- /dev/null
+++ b/app/utils/cachesecond/base.go
@@ -0,0 +1,421 @@
+package cachesecond
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "time"
+)
+
+const (
+ redisDialTTL = 10 * time.Second
+ redisReadTTL = 3 * time.Second
+ redisWriteTTL = 3 * time.Second
+ redisIdleTTL = 10 * time.Second
+ redisPoolTTL = 10 * time.Second
+ redisPoolSize int = 512
+ redisMaxIdleConn int = 64
+ redisMaxActive int = 512
+)
+
+var (
+ ErrNil = errors.New("nil return")
+ ErrWrongArgsNum = errors.New("args num error")
+ ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64")
+)
+
+// 以下为提供类型转换
+
+func Int(reply interface{}, err error) (int, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int:
+ return reply, nil
+ case int8:
+ return int(reply), nil
+ case int16:
+ return int(reply), nil
+ case int32:
+ return int(reply), nil
+ case int64:
+ x := int(reply)
+ if int64(x) != reply {
+ return 0, strconv.ErrRange
+ }
+ return x, nil
+ case uint:
+ n := int(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case uint8:
+ return int(reply), nil
+ case uint16:
+ return int(reply), nil
+ case uint32:
+ n := int(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case uint64:
+ n := int(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(data, 10, 0)
+ return int(n), err
+ case string:
+ if len(reply) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(reply, 10, 0)
+ return int(n), err
+ case nil:
+ return 0, ErrNil
+ case error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply)
+}
+
+func Int64(reply interface{}, err error) (int64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int:
+ return int64(reply), nil
+ case int8:
+ return int64(reply), nil
+ case int16:
+ return int64(reply), nil
+ case int32:
+ return int64(reply), nil
+ case int64:
+ return reply, nil
+ case uint:
+ n := int64(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case uint8:
+ return int64(reply), nil
+ case uint16:
+ return int64(reply), nil
+ case uint32:
+ return int64(reply), nil
+ case uint64:
+ n := int64(reply)
+ if n < 0 {
+ return 0, strconv.ErrRange
+ }
+ return n, nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(data, 10, 64)
+ return n, err
+ case string:
+ if len(reply) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseInt(reply, 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply)
+}
+
+func Uint64(reply interface{}, err error) (uint64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case uint:
+ return uint64(reply), nil
+ case uint8:
+ return uint64(reply), nil
+ case uint16:
+ return uint64(reply), nil
+ case uint32:
+ return uint64(reply), nil
+ case uint64:
+ return reply, nil
+ case int:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int8:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int16:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int32:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case int64:
+ if reply < 0 {
+ return 0, ErrNegativeInt
+ }
+ return uint64(reply), nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseUint(data, 10, 64)
+ return n, err
+ case string:
+ if len(reply) == 0 {
+ return 0, ErrNil
+ }
+
+ n, err := strconv.ParseUint(reply, 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply)
+}
+
+func Float64(reply interface{}, err error) (float64, error) {
+ if err != nil {
+ return 0, err
+ }
+
+ var value float64
+ err = nil
+ switch v := reply.(type) {
+ case float32:
+ value = float64(v)
+ case float64:
+ value = v
+ case int:
+ value = float64(v)
+ case int8:
+ value = float64(v)
+ case int16:
+ value = float64(v)
+ case int32:
+ value = float64(v)
+ case int64:
+ value = float64(v)
+ case uint:
+ value = float64(v)
+ case uint8:
+ value = float64(v)
+ case uint16:
+ value = float64(v)
+ case uint32:
+ value = float64(v)
+ case uint64:
+ value = float64(v)
+ case []byte:
+ data := string(v)
+ if len(data) == 0 {
+ return 0, ErrNil
+ }
+ value, err = strconv.ParseFloat(string(v), 64)
+ case string:
+ if len(v) == 0 {
+ return 0, ErrNil
+ }
+ value, err = strconv.ParseFloat(v, 64)
+ case nil:
+ err = ErrNil
+ case error:
+ err = v
+ default:
+ err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v)
+ }
+
+ return value, err
+}
+
+func Bool(reply interface{}, err error) (bool, error) {
+ if err != nil {
+ return false, err
+ }
+ switch reply := reply.(type) {
+ case bool:
+ return reply, nil
+ case int64:
+ return reply != 0, nil
+ case []byte:
+ data := string(reply)
+ if len(data) == 0 {
+ return false, ErrNil
+ }
+
+ return strconv.ParseBool(data)
+ case string:
+ if len(reply) == 0 {
+ return false, ErrNil
+ }
+
+ return strconv.ParseBool(reply)
+ case nil:
+ return false, ErrNil
+ case error:
+ return false, reply
+ }
+ return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply)
+}
+
+func Bytes(reply interface{}, err error) ([]byte, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ if len(reply) == 0 {
+ return nil, ErrNil
+ }
+ return reply, nil
+ case string:
+ data := []byte(reply)
+ if len(data) == 0 {
+ return nil, ErrNil
+ }
+ return data, nil
+ case nil:
+ return nil, ErrNil
+ case error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply)
+}
+
+func String(reply interface{}, err error) (string, error) {
+ if err != nil {
+ return "", err
+ }
+
+ value := ""
+ err = nil
+ switch v := reply.(type) {
+ case string:
+ if len(v) == 0 {
+ return "", ErrNil
+ }
+
+ value = v
+ case []byte:
+ if len(v) == 0 {
+ return "", ErrNil
+ }
+
+ value = string(v)
+ case int:
+ value = strconv.FormatInt(int64(v), 10)
+ case int8:
+ value = strconv.FormatInt(int64(v), 10)
+ case int16:
+ value = strconv.FormatInt(int64(v), 10)
+ case int32:
+ value = strconv.FormatInt(int64(v), 10)
+ case int64:
+ value = strconv.FormatInt(v, 10)
+ case uint:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint8:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint16:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint32:
+ value = strconv.FormatUint(uint64(v), 10)
+ case uint64:
+ value = strconv.FormatUint(v, 10)
+ case float32:
+ value = strconv.FormatFloat(float64(v), 'f', -1, 32)
+ case float64:
+ value = strconv.FormatFloat(v, 'f', -1, 64)
+ case bool:
+ value = strconv.FormatBool(v)
+ case nil:
+ err = ErrNil
+ case error:
+ err = v
+ default:
+ err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v)
+ }
+
+ return value, err
+}
+
+func Strings(reply interface{}, err error) ([]string, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ result := make([]string, len(reply))
+ for i := range reply {
+ if reply[i] == nil {
+ continue
+ }
+ switch subReply := reply[i].(type) {
+ case string:
+ result[i] = subReply
+ case []byte:
+ result[i] = string(subReply)
+ default:
+ return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i])
+ }
+ }
+ return result, nil
+ case []string:
+ return reply, nil
+ case nil:
+ return nil, ErrNil
+ case error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply)
+}
+
+func Values(reply interface{}, err error) ([]interface{}, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ return reply, nil
+ case nil:
+ return nil, ErrNil
+ case error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply)
+}
diff --git a/app/utils/cachesecond/cache/cache.go b/app/utils/cachesecond/cache/cache.go
new file mode 100644
index 0000000..e43c5f0
--- /dev/null
+++ b/app/utils/cachesecond/cache/cache.go
@@ -0,0 +1,107 @@
+package cache
+
+import (
+ "fmt"
+ "time"
+)
+
+var c Cache
+
+type Cache interface {
+ // get cached value by key.
+ Get(key string) interface{}
+ // GetMulti is a batch version of Get.
+ GetMulti(keys []string) []interface{}
+ // set cached value with key and expire time.
+ Put(key string, val interface{}, timeout time.Duration) error
+ // delete cached value by key.
+ Delete(key string) error
+ // increase cached int value by key, as a counter.
+ Incr(key string) error
+ // decrease cached int value by key, as a counter.
+ Decr(key string) error
+ // check if cached value exists or not.
+ IsExist(key string) bool
+ // clear all cache.
+ ClearAll() error
+ // start gc routine based on config string settings.
+ StartAndGC(config string) error
+}
+
+// Instance is a function create a new Cache Instance
+type Instance func() Cache
+
+var adapters = make(map[string]Instance)
+
+// Register makes a cache adapter available by the adapter name.
+// If Register is called twice with the same name or if driver is nil,
+// it panics.
+func Register(name string, adapter Instance) {
+ if adapter == nil {
+ panic("cache: Register adapter is nil")
+ }
+ if _, ok := adapters[name]; ok {
+ panic("cache: Register called twice for adapter " + name)
+ }
+ adapters[name] = adapter
+}
+
+// NewCache Create a new cache driver by adapter name and config string.
+// config need to be correct JSON as string: {"interval":360}.
+// it will start gc automatically.
+func NewCache(adapterName, config string) (adapter Cache, err error) {
+ instanceFunc, ok := adapters[adapterName]
+ if !ok {
+ err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
+ return
+ }
+ adapter = instanceFunc()
+ err = adapter.StartAndGC(config)
+ if err != nil {
+ adapter = nil
+ }
+ return
+}
+
+func InitCache(adapterName, config string) (err error) {
+ instanceFunc, ok := adapters[adapterName]
+ if !ok {
+ err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
+ return
+ }
+ c = instanceFunc()
+ err = c.StartAndGC(config)
+ if err != nil {
+ c = nil
+ }
+ return
+}
+
+func Get(key string) interface{} {
+ return c.Get(key)
+}
+
+func GetMulti(keys []string) []interface{} {
+ return c.GetMulti(keys)
+}
+func Put(key string, val interface{}, ttl time.Duration) error {
+ return c.Put(key, val, ttl)
+}
+func Delete(key string) error {
+ return c.Delete(key)
+}
+func Incr(key string) error {
+ return c.Incr(key)
+}
+func Decr(key string) error {
+ return c.Decr(key)
+}
+func IsExist(key string) bool {
+ return c.IsExist(key)
+}
+func ClearAll() error {
+ return c.ClearAll()
+}
+func StartAndGC(cfg string) error {
+ return c.StartAndGC(cfg)
+}
diff --git a/app/utils/cachesecond/cache/conv.go b/app/utils/cachesecond/cache/conv.go
new file mode 100644
index 0000000..6b700ae
--- /dev/null
+++ b/app/utils/cachesecond/cache/conv.go
@@ -0,0 +1,86 @@
+package cache
+
+import (
+ "fmt"
+ "strconv"
+)
+
+// GetString convert interface to string.
+func GetString(v interface{}) string {
+ switch result := v.(type) {
+ case string:
+ return result
+ case []byte:
+ return string(result)
+ default:
+ if v != nil {
+ return fmt.Sprint(result)
+ }
+ }
+ return ""
+}
+
+// GetInt convert interface to int.
+func GetInt(v interface{}) int {
+ switch result := v.(type) {
+ case int:
+ return result
+ case int32:
+ return int(result)
+ case int64:
+ return int(result)
+ default:
+ if d := GetString(v); d != "" {
+ value, _ := strconv.Atoi(d)
+ return value
+ }
+ }
+ return 0
+}
+
+// GetInt64 convert interface to int64.
+func GetInt64(v interface{}) int64 {
+ switch result := v.(type) {
+ case int:
+ return int64(result)
+ case int32:
+ return int64(result)
+ case int64:
+ return result
+ default:
+
+ if d := GetString(v); d != "" {
+ value, _ := strconv.ParseInt(d, 10, 64)
+ return value
+ }
+ }
+ return 0
+}
+
+// GetFloat64 convert interface to float64.
+func GetFloat64(v interface{}) float64 {
+ switch result := v.(type) {
+ case float64:
+ return result
+ default:
+ if d := GetString(v); d != "" {
+ value, _ := strconv.ParseFloat(d, 64)
+ return value
+ }
+ }
+ return 0
+}
+
+// GetBool convert interface to bool.
+func GetBool(v interface{}) bool {
+ switch result := v.(type) {
+ case bool:
+ return result
+ default:
+ if d := GetString(v); d != "" {
+ value, _ := strconv.ParseBool(d)
+ return value
+ }
+ }
+ return false
+}
diff --git a/app/utils/cachesecond/cache/file.go b/app/utils/cachesecond/cache/file.go
new file mode 100644
index 0000000..5c4e366
--- /dev/null
+++ b/app/utils/cachesecond/cache/file.go
@@ -0,0 +1,241 @@
+package cache
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/gob"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+ "time"
+)
+
+// FileCacheItem is basic unit of file cache adapter.
+// it contains data and expire time.
+type FileCacheItem struct {
+ Data interface{}
+ LastAccess time.Time
+ Expired time.Time
+}
+
+// FileCache Config
+var (
+ FileCachePath = "cache" // cache directory
+ FileCacheFileSuffix = ".bin" // cache file suffix
+ FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
+ FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
+)
+
+// FileCache is cache adapter for file storage.
+type FileCache struct {
+ CachePath string
+ FileSuffix string
+ DirectoryLevel int
+ EmbedExpiry int
+}
+
+// NewFileCache Create new file cache with no config.
+// the level and expiry need set in method StartAndGC as config string.
+func NewFileCache() Cache {
+ // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
+ return &FileCache{}
+}
+
+// StartAndGC will start and begin gc for file cache.
+// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
+func (fc *FileCache) StartAndGC(config string) error {
+
+ var cfg map[string]string
+ json.Unmarshal([]byte(config), &cfg)
+ if _, ok := cfg["CachePath"]; !ok {
+ cfg["CachePath"] = FileCachePath
+ }
+ if _, ok := cfg["FileSuffix"]; !ok {
+ cfg["FileSuffix"] = FileCacheFileSuffix
+ }
+ if _, ok := cfg["DirectoryLevel"]; !ok {
+ cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
+ }
+ if _, ok := cfg["EmbedExpiry"]; !ok {
+ cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
+ }
+ fc.CachePath = cfg["CachePath"]
+ fc.FileSuffix = cfg["FileSuffix"]
+ fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
+ fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
+
+ fc.Init()
+ return nil
+}
+
+// Init will make new dir for file cache if not exist.
+func (fc *FileCache) Init() {
+ if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
+ _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
+ }
+}
+
+// get cached file name. it's md5 encoded.
+func (fc *FileCache) getCacheFileName(key string) string {
+ m := md5.New()
+ io.WriteString(m, key)
+ keyMd5 := hex.EncodeToString(m.Sum(nil))
+ cachePath := fc.CachePath
+ switch fc.DirectoryLevel {
+ case 2:
+ cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
+ case 1:
+ cachePath = filepath.Join(cachePath, keyMd5[0:2])
+ }
+
+ if ok, _ := exists(cachePath); !ok { // todo : error handle
+ _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
+ }
+
+ return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
+}
+
+// Get value from file cache.
+// if non-exist or expired, return empty string.
+func (fc *FileCache) Get(key string) interface{} {
+ fileData, err := FileGetContents(fc.getCacheFileName(key))
+ if err != nil {
+ return ""
+ }
+ var to FileCacheItem
+ GobDecode(fileData, &to)
+ if to.Expired.Before(time.Now()) {
+ return ""
+ }
+ return to.Data
+}
+
+// GetMulti gets values from file cache.
+// if non-exist or expired, return empty string.
+func (fc *FileCache) GetMulti(keys []string) []interface{} {
+ var rc []interface{}
+ for _, key := range keys {
+ rc = append(rc, fc.Get(key))
+ }
+ return rc
+}
+
+// Put value into file cache.
+// timeout means how long to keep this file, unit of ms.
+// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
+func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
+ gob.Register(val)
+
+ item := FileCacheItem{Data: val}
+ if timeout == FileCacheEmbedExpiry {
+ item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
+ } else {
+ item.Expired = time.Now().Add(timeout)
+ }
+ item.LastAccess = time.Now()
+ data, err := GobEncode(item)
+ if err != nil {
+ return err
+ }
+ return FilePutContents(fc.getCacheFileName(key), data)
+}
+
+// Delete file cache value.
+func (fc *FileCache) Delete(key string) error {
+ filename := fc.getCacheFileName(key)
+ if ok, _ := exists(filename); ok {
+ return os.Remove(filename)
+ }
+ return nil
+}
+
+// Incr will increase cached int value.
+// fc value is saving forever unless Delete.
+func (fc *FileCache) Incr(key string) error {
+ data := fc.Get(key)
+ var incr int
+ if reflect.TypeOf(data).Name() != "int" {
+ incr = 0
+ } else {
+ incr = data.(int) + 1
+ }
+ fc.Put(key, incr, FileCacheEmbedExpiry)
+ return nil
+}
+
+// Decr will decrease cached int value.
+func (fc *FileCache) Decr(key string) error {
+ data := fc.Get(key)
+ var decr int
+ if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
+ decr = 0
+ } else {
+ decr = data.(int) - 1
+ }
+ fc.Put(key, decr, FileCacheEmbedExpiry)
+ return nil
+}
+
+// IsExist check value is exist.
+func (fc *FileCache) IsExist(key string) bool {
+ ret, _ := exists(fc.getCacheFileName(key))
+ return ret
+}
+
+// ClearAll will clean cached files.
+// not implemented.
+func (fc *FileCache) ClearAll() error {
+ return nil
+}
+
+// check file exist.
+func exists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+// FileGetContents Get bytes to file.
+// if non-exist, create this file.
+func FileGetContents(filename string) (data []byte, e error) {
+ return ioutil.ReadFile(filename)
+}
+
+// FilePutContents Put bytes to file.
+// if non-exist, create this file.
+func FilePutContents(filename string, content []byte) error {
+ return ioutil.WriteFile(filename, content, os.ModePerm)
+}
+
+// GobEncode Gob encodes file cache item.
+func GobEncode(data interface{}) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ enc := gob.NewEncoder(buf)
+ err := enc.Encode(data)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), err
+}
+
+// GobDecode Gob decodes file cache item.
+func GobDecode(data []byte, to *FileCacheItem) error {
+ buf := bytes.NewBuffer(data)
+ dec := gob.NewDecoder(buf)
+ return dec.Decode(&to)
+}
+
+func init() {
+ Register("file", NewFileCache)
+}
diff --git a/app/utils/cachesecond/cache/memory.go b/app/utils/cachesecond/cache/memory.go
new file mode 100644
index 0000000..0cc5015
--- /dev/null
+++ b/app/utils/cachesecond/cache/memory.go
@@ -0,0 +1,239 @@
+package cache
+
+import (
+ "encoding/json"
+ "errors"
+ "sync"
+ "time"
+)
+
+var (
+ // DefaultEvery means the clock time of recycling the expired cache items in memory.
+ DefaultEvery = 60 // 1 minute
+)
+
+// MemoryItem store memory cache item.
+type MemoryItem struct {
+ val interface{}
+ createdTime time.Time
+ lifespan time.Duration
+}
+
+func (mi *MemoryItem) isExpire() bool {
+ // 0 means forever
+ if mi.lifespan == 0 {
+ return false
+ }
+ return time.Now().Sub(mi.createdTime) > mi.lifespan
+}
+
+// MemoryCache is Memory cache adapter.
+// it contains a RW locker for safe map storage.
+type MemoryCache struct {
+ sync.RWMutex
+ dur time.Duration
+ items map[string]*MemoryItem
+ Every int // run an expiration check Every clock time
+}
+
+// NewMemoryCache returns a new MemoryCache.
+func NewMemoryCache() Cache {
+ cache := MemoryCache{items: make(map[string]*MemoryItem)}
+ return &cache
+}
+
+// Get cache from memory.
+// if non-existed or expired, return nil.
+func (bc *MemoryCache) Get(name string) interface{} {
+ bc.RLock()
+ defer bc.RUnlock()
+ if itm, ok := bc.items[name]; ok {
+ if itm.isExpire() {
+ return nil
+ }
+ return itm.val
+ }
+ return nil
+}
+
+// GetMulti gets caches from memory.
+// if non-existed or expired, return nil.
+func (bc *MemoryCache) GetMulti(names []string) []interface{} {
+ var rc []interface{}
+ for _, name := range names {
+ rc = append(rc, bc.Get(name))
+ }
+ return rc
+}
+
+// Put cache to memory.
+// if lifespan is 0, it will be forever till restart.
+func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
+ bc.Lock()
+ defer bc.Unlock()
+ bc.items[name] = &MemoryItem{
+ val: value,
+ createdTime: time.Now(),
+ lifespan: lifespan,
+ }
+ return nil
+}
+
+// Delete cache in memory.
+func (bc *MemoryCache) Delete(name string) error {
+ bc.Lock()
+ defer bc.Unlock()
+ if _, ok := bc.items[name]; !ok {
+ return errors.New("key not exist")
+ }
+ delete(bc.items, name)
+ if _, ok := bc.items[name]; ok {
+ return errors.New("delete key error")
+ }
+ return nil
+}
+
+// Incr increase cache counter in memory.
+// it supports int,int32,int64,uint,uint32,uint64.
+func (bc *MemoryCache) Incr(key string) error {
+ bc.RLock()
+ defer bc.RUnlock()
+ itm, ok := bc.items[key]
+ if !ok {
+ return errors.New("key not exist")
+ }
+ switch itm.val.(type) {
+ case int:
+ itm.val = itm.val.(int) + 1
+ case int32:
+ itm.val = itm.val.(int32) + 1
+ case int64:
+ itm.val = itm.val.(int64) + 1
+ case uint:
+ itm.val = itm.val.(uint) + 1
+ case uint32:
+ itm.val = itm.val.(uint32) + 1
+ case uint64:
+ itm.val = itm.val.(uint64) + 1
+ default:
+ return errors.New("item val is not (u)int (u)int32 (u)int64")
+ }
+ return nil
+}
+
+// Decr decrease counter in memory.
+func (bc *MemoryCache) Decr(key string) error {
+ bc.RLock()
+ defer bc.RUnlock()
+ itm, ok := bc.items[key]
+ if !ok {
+ return errors.New("key not exist")
+ }
+ switch itm.val.(type) {
+ case int:
+ itm.val = itm.val.(int) - 1
+ case int64:
+ itm.val = itm.val.(int64) - 1
+ case int32:
+ itm.val = itm.val.(int32) - 1
+ case uint:
+ if itm.val.(uint) > 0 {
+ itm.val = itm.val.(uint) - 1
+ } else {
+ return errors.New("item val is less than 0")
+ }
+ case uint32:
+ if itm.val.(uint32) > 0 {
+ itm.val = itm.val.(uint32) - 1
+ } else {
+ return errors.New("item val is less than 0")
+ }
+ case uint64:
+ if itm.val.(uint64) > 0 {
+ itm.val = itm.val.(uint64) - 1
+ } else {
+ return errors.New("item val is less than 0")
+ }
+ default:
+ return errors.New("item val is not int int64 int32")
+ }
+ return nil
+}
+
+// IsExist check cache exist in memory.
+func (bc *MemoryCache) IsExist(name string) bool {
+ bc.RLock()
+ defer bc.RUnlock()
+ if v, ok := bc.items[name]; ok {
+ return !v.isExpire()
+ }
+ return false
+}
+
+// ClearAll will delete all cache in memory.
+func (bc *MemoryCache) ClearAll() error {
+ bc.Lock()
+ defer bc.Unlock()
+ bc.items = make(map[string]*MemoryItem)
+ return nil
+}
+
+// StartAndGC start memory cache. it will check expiration in every clock time.
+func (bc *MemoryCache) StartAndGC(config string) error {
+ var cf map[string]int
+ json.Unmarshal([]byte(config), &cf)
+ if _, ok := cf["interval"]; !ok {
+ cf = make(map[string]int)
+ cf["interval"] = DefaultEvery
+ }
+ dur := time.Duration(cf["interval"]) * time.Second
+ bc.Every = cf["interval"]
+ bc.dur = dur
+ go bc.vacuum()
+ return nil
+}
+
+// check expiration.
+func (bc *MemoryCache) vacuum() {
+ bc.RLock()
+ every := bc.Every
+ bc.RUnlock()
+
+ if every < 1 {
+ return
+ }
+ for {
+ <-time.After(bc.dur)
+ if bc.items == nil {
+ return
+ }
+ if keys := bc.expiredKeys(); len(keys) != 0 {
+ bc.clearItems(keys)
+ }
+ }
+}
+
+// expiredKeys returns key list which are expired.
+func (bc *MemoryCache) expiredKeys() (keys []string) {
+ bc.RLock()
+ defer bc.RUnlock()
+ for key, itm := range bc.items {
+ if itm.isExpire() {
+ keys = append(keys, key)
+ }
+ }
+ return
+}
+
+// clearItems removes all the items which key in keys.
+func (bc *MemoryCache) clearItems(keys []string) {
+ bc.Lock()
+ defer bc.Unlock()
+ for _, key := range keys {
+ delete(bc.items, key)
+ }
+}
+
+func init() {
+ Register("memory", NewMemoryCache)
+}
diff --git a/app/utils/cachesecond/redis.go b/app/utils/cachesecond/redis.go
new file mode 100644
index 0000000..99c5247
--- /dev/null
+++ b/app/utils/cachesecond/redis.go
@@ -0,0 +1,406 @@
+package cachesecond
+
+import (
+ "encoding/json"
+ "errors"
+ "log"
+ "strings"
+ "time"
+
+ redigo "github.com/gomodule/redigo/redis"
+)
+
+// configuration
+type Config struct {
+ Server string
+ Password string
+ MaxIdle int // Maximum number of idle connections in the pool.
+
+ // Maximum number of connections allocated by the pool at a given time.
+ // When zero, there is no limit on the number of connections in the pool.
+ MaxActive int
+
+ // Close connections after remaining idle for this duration. If the value
+ // is zero, then idle connections are not closed. Applications should set
+ // the timeout to a value less than the server's timeout.
+ IdleTimeout time.Duration
+
+ // If Wait is true and the pool is at the MaxActive limit, then Get() waits
+ // for a connection to be returned to the pool before returning.
+ Wait bool
+ KeyPrefix string // prefix to all keys; example is "dev environment name"
+ KeyDelimiter string // delimiter to be used while appending keys; example is ":"
+ KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?"
+}
+
+var pool *redigo.Pool
+var conf *Config
+
+func NewRedis(addr, pwd string) {
+ if addr == "" {
+ panic("\nredis connect string cannot be empty\n")
+ }
+ pool = &redigo.Pool{
+ MaxIdle: redisMaxIdleConn,
+ IdleTimeout: redisIdleTTL,
+ MaxActive: redisMaxActive,
+ // MaxConnLifetime: redisDialTTL,
+ Wait: true,
+ Dial: func() (redigo.Conn, error) {
+ c, err := redigo.Dial("tcp", addr,
+ redigo.DialConnectTimeout(redisDialTTL),
+ redigo.DialReadTimeout(redisReadTTL),
+ redigo.DialWriteTimeout(redisWriteTTL),
+ )
+ if err != nil {
+ log.Println("Redis Dial failed: ", err)
+ return nil, err
+ }
+ if pwd != "" {
+ c.Send("auth", pwd)
+ }
+ return c, err
+ },
+ TestOnBorrow: func(c redigo.Conn, t time.Time) error {
+ _, err := c.Do("PING")
+ if err != nil {
+ log.Println("Unable to ping to redis server:", err)
+ }
+ return err
+ },
+ }
+ conn := pool.Get()
+ defer conn.Close()
+ if conn.Err() != nil {
+ println("\nredis connect " + addr + " error: " + conn.Err().Error())
+ } else {
+ println("\nredis connect " + addr + " success!\n")
+ }
+}
+
+func Do(cmd string, args ...interface{}) (reply interface{}, err error) {
+ conn := pool.Get()
+ defer conn.Close()
+ return conn.Do(cmd, args...)
+}
+
+func GetPool() *redigo.Pool {
+ return pool
+}
+
+func ParseKey(key string, vars []string) (string, error) {
+ arr := strings.Split(key, conf.KeyPlaceholder)
+ actualKey := ""
+ if len(arr) != len(vars)+1 {
+ return "", errors.New("redis/connection.go: Insufficient arguments to parse key")
+ } else {
+ for index, val := range arr {
+ if index == 0 {
+ actualKey = arr[index]
+ } else {
+ actualKey += vars[index-1] + val
+ }
+ }
+ }
+ return getPrefixedKey(actualKey), nil
+}
+
+func getPrefixedKey(key string) string {
+ return conf.KeyPrefix + conf.KeyDelimiter + key
+}
+func StripEnvKey(key string) string {
+ return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter)
+}
+func SplitKey(key string) []string {
+ return strings.Split(key, conf.KeyDelimiter)
+}
+func Expire(key string, ttl int) (interface{}, error) {
+ return Do("EXPIRE", key, ttl)
+}
+func Persist(key string) (interface{}, error) {
+ return Do("PERSIST", key)
+}
+
+func Del(key string) (interface{}, error) {
+ return Do("DEL", key)
+}
+func Set(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("SET", key, data)
+}
+func SetNX(key string, data interface{}) (interface{}, error) {
+ return Do("SETNX", key, data)
+}
+func SetEx(key string, data interface{}, ttl int) (interface{}, error) {
+ return Do("SETEX", key, ttl, data)
+}
+
+func SetJson(key string, data interface{}, ttl int) bool {
+ c, err := json.Marshal(data)
+ if err != nil {
+ return false
+ }
+ if ttl < 1 {
+ _, err = Set(key, c)
+ } else {
+ _, err = SetEx(key, c, ttl)
+ }
+ if err != nil {
+ return false
+ }
+ return true
+}
+
+func GetJson(key string, dst interface{}) error {
+ b, err := GetBytes(key)
+ if err != nil {
+ return err
+ }
+ if err = json.Unmarshal(b, dst); err != nil {
+ return err
+ }
+ return nil
+}
+
+func Get(key string) (interface{}, error) {
+ // get
+ return Do("GET", key)
+}
+func GetTTL(key string) (time.Duration, error) {
+ ttl, err := redigo.Int64(Do("TTL", key))
+ return time.Duration(ttl) * time.Second, err
+}
+func GetBytes(key string) ([]byte, error) {
+ return redigo.Bytes(Do("GET", key))
+}
+func GetString(key string) (string, error) {
+ return redigo.String(Do("GET", key))
+}
+func GetStringMap(key string) (map[string]string, error) {
+ return redigo.StringMap(Do("GET", key))
+}
+func GetInt(key string) (int, error) {
+ return redigo.Int(Do("GET", key))
+}
+func GetInt64(key string) (int64, error) {
+ return redigo.Int64(Do("GET", key))
+}
+func GetStringLength(key string) (int, error) {
+ return redigo.Int(Do("STRLEN", key))
+}
+func ZAdd(key string, score float64, data interface{}) (interface{}, error) {
+ return Do("ZADD", key, score, data)
+}
+func ZAddNX(key string, score float64, data interface{}) (interface{}, error) {
+ return Do("ZADD", key, "NX", score, data)
+}
+func ZRem(key string, data interface{}) (interface{}, error) {
+ return Do("ZREM", key, data)
+}
+func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) {
+ if withScores {
+ return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES"))
+ }
+ return redigo.Values(Do("ZRANGE", key, start, end))
+}
+func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) {
+ return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end))
+}
+func ZCard(setName string) (int64, error) {
+ return redigo.Int64(Do("ZCARD", setName))
+}
+func ZScan(setName string) (int64, error) {
+ return redigo.Int64(Do("ZCARD", setName))
+}
+func SAdd(setName string, data interface{}) (interface{}, error) {
+ return Do("SADD", setName, data)
+}
+func SCard(setName string) (int64, error) {
+ return redigo.Int64(Do("SCARD", setName))
+}
+func SIsMember(setName string, data interface{}) (bool, error) {
+ return redigo.Bool(Do("SISMEMBER", setName, data))
+}
+func SMembers(setName string) ([]string, error) {
+ return redigo.Strings(Do("SMEMBERS", setName))
+}
+func SRem(setName string, data interface{}) (interface{}, error) {
+ return Do("SREM", setName, data)
+}
+func HSet(key string, HKey string, data interface{}) (interface{}, error) {
+ return Do("HSET", key, HKey, data)
+}
+
+func HGet(key string, HKey string) (interface{}, error) {
+ return Do("HGET", key, HKey)
+}
+
+func HMGet(key string, hashKeys ...string) ([]interface{}, error) {
+ ret, err := Do("HMGET", key, hashKeys)
+ if err != nil {
+ return nil, err
+ }
+ reta, ok := ret.([]interface{})
+ if !ok {
+ return nil, errors.New("result not an array")
+ }
+ return reta, nil
+}
+
+func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) {
+ if len(hashKeys) == 0 || len(hashKeys) != len(vals) {
+ var ret interface{}
+ return ret, errors.New("bad length")
+ }
+ input := []interface{}{key}
+ for i, v := range hashKeys {
+ input = append(input, v, vals[i])
+ }
+ return Do("HMSET", input...)
+}
+
+func HGetString(key string, HKey string) (string, error) {
+ return redigo.String(Do("HGET", key, HKey))
+}
+func HGetFloat(key string, HKey string) (float64, error) {
+ f, err := redigo.Float64(Do("HGET", key, HKey))
+ return f, err
+}
+func HGetInt(key string, HKey string) (int, error) {
+ return redigo.Int(Do("HGET", key, HKey))
+}
+func HGetInt64(key string, HKey string) (int64, error) {
+ return redigo.Int64(Do("HGET", key, HKey))
+}
+func HGetBool(key string, HKey string) (bool, error) {
+ return redigo.Bool(Do("HGET", key, HKey))
+}
+func HDel(key string, HKey string) (interface{}, error) {
+ return Do("HDEL", key, HKey)
+}
+
+func HGetAll(key string) (map[string]interface{}, error) {
+ vals, err := redigo.Values(Do("HGETALL", key))
+ if err != nil {
+ return nil, err
+ }
+ num := len(vals) / 2
+ result := make(map[string]interface{}, num)
+ for i := 0; i < num; i++ {
+ key, _ := redigo.String(vals[2*i], nil)
+ result[key] = vals[2*i+1]
+ }
+ return result, nil
+}
+
+func FlushAll() bool {
+ res, _ := redigo.String(Do("FLUSHALL"))
+ if res == "" {
+ return false
+ }
+ return true
+}
+
+// NOTE: Use this in production environment with extreme care.
+// Read more here:https://redigo.io/commands/keys
+func Keys(pattern string) ([]string, error) {
+ return redigo.Strings(Do("KEYS", pattern))
+}
+
+func HKeys(key string) ([]string, error) {
+ return redigo.Strings(Do("HKEYS", key))
+}
+
+func Exists(key string) bool {
+ count, err := redigo.Int(Do("EXISTS", key))
+ if count == 0 || err != nil {
+ return false
+ }
+ return true
+}
+
+func Incr(key string) (int64, error) {
+ return redigo.Int64(Do("INCR", key))
+}
+
+func Decr(key string) (int64, error) {
+ return redigo.Int64(Do("DECR", key))
+}
+
+func IncrBy(key string, incBy int64) (int64, error) {
+ return redigo.Int64(Do("INCRBY", key, incBy))
+}
+
+func DecrBy(key string, decrBy int64) (int64, error) {
+ return redigo.Int64(Do("DECRBY", key))
+}
+
+func IncrByFloat(key string, incBy float64) (float64, error) {
+ return redigo.Float64(Do("INCRBYFLOAT", key, incBy))
+}
+
+func DecrByFloat(key string, decrBy float64) (float64, error) {
+ return redigo.Float64(Do("DECRBYFLOAT", key, decrBy))
+}
+
+// use for message queue
+func LPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("LPUSH", key, data)
+}
+
+func LPop(key string) (interface{}, error) {
+ return Do("LPOP", key)
+}
+
+func LPopString(key string) (string, error) {
+ return redigo.String(Do("LPOP", key))
+}
+func LPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("LPOP", key))
+ return f, err
+}
+func LPopInt(key string) (int, error) {
+ return redigo.Int(Do("LPOP", key))
+}
+func LPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("LPOP", key))
+}
+
+func RPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("RPUSH", key, data)
+}
+
+func RPop(key string) (interface{}, error) {
+ return Do("RPOP", key)
+}
+
+func RPopString(key string) (string, error) {
+ return redigo.String(Do("RPOP", key))
+}
+func RPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("RPOP", key))
+ return f, err
+}
+func RPopInt(key string) (int, error) {
+ return redigo.Int(Do("RPOP", key))
+}
+func RPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("RPOP", key))
+}
+
+func Scan(cursor int64, pattern string, count int64) (int64, []string, error) {
+ var items []string
+ var newCursor int64
+
+ values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count))
+ if err != nil {
+ return 0, nil, err
+ }
+ values, err = redigo.Scan(values, &newCursor, &items)
+ if err != nil {
+ return 0, nil, err
+ }
+ return newCursor, items, nil
+}
diff --git a/app/utils/cachesecond/redis_cluster.go b/app/utils/cachesecond/redis_cluster.go
new file mode 100644
index 0000000..c86bf55
--- /dev/null
+++ b/app/utils/cachesecond/redis_cluster.go
@@ -0,0 +1,622 @@
+package cachesecond
+
+import (
+ "strconv"
+ "time"
+
+ "github.com/go-redis/redis"
+)
+
+var pools *redis.ClusterClient
+
+func NewRedisCluster(addrs []string) error {
+ opt := &redis.ClusterOptions{
+ Addrs: addrs,
+ PoolSize: redisPoolSize,
+ PoolTimeout: redisPoolTTL,
+ IdleTimeout: redisIdleTTL,
+ DialTimeout: redisDialTTL,
+ ReadTimeout: redisReadTTL,
+ WriteTimeout: redisWriteTTL,
+ }
+ pools = redis.NewClusterClient(opt)
+ if err := pools.Ping().Err(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func RCGet(key string) (interface{}, error) {
+ res, err := pools.Get(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCSet(key string, value interface{}) error {
+ err := pools.Set(key, value, 0).Err()
+ return convertError(err)
+}
+func RCGetSet(key string, value interface{}) (interface{}, error) {
+ res, err := pools.GetSet(key, value).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCSetNx(key string, value interface{}) (int64, error) {
+ res, err := pools.SetNX(key, value, 0).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func RCSetEx(key string, value interface{}, timeout int64) error {
+ _, err := pools.Set(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误
+func RCSetNxEx(key string, value interface{}, timeout int64) error {
+ res, err := pools.SetNX(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ if res {
+ return nil
+ }
+ return ErrNil
+}
+func RCMGet(keys ...string) ([]interface{}, error) {
+ res, err := pools.MGet(keys...).Result()
+ return res, convertError(err)
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func RCMSet(kvs map[string]interface{}) error {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return err
+ }
+ pairs = append(pairs, k, val)
+ }
+ return convertError(pools.MSet(pairs).Err())
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func RCMSetNX(kvs map[string]interface{}) (bool, error) {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return false, err
+ }
+ pairs = append(pairs, k, val)
+ }
+ res, err := pools.MSetNX(pairs).Result()
+ return res, convertError(err)
+}
+func RCExpireAt(key string, timestamp int64) (int64, error) {
+ res, err := pools.ExpireAt(key, time.Unix(timestamp, 0)).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func RCDel(keys ...string) (int64, error) {
+ args := make([]interface{}, 0, len(keys))
+ for _, key := range keys {
+ args = append(args, key)
+ }
+ res, err := pools.Del(keys...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCIncr(key string) (int64, error) {
+ res, err := pools.Incr(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCIncrBy(key string, delta int64) (int64, error) {
+ res, err := pools.IncrBy(key, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCExpire(key string, duration int64) (int64, error) {
+ res, err := pools.Expire(key, time.Duration(duration)*time.Second).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func RCExists(key string) (bool, error) {
+ res, err := pools.Exists(key).Result()
+ if err != nil {
+ return false, convertError(err)
+ }
+ if res > 0 {
+ return true, nil
+ }
+ return false, nil
+}
+func RCHGet(key string, field string) (interface{}, error) {
+ res, err := pools.HGet(key, field).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCHLen(key string) (int64, error) {
+ res, err := pools.HLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCHSet(key string, field string, val interface{}) error {
+ value, err := String(val, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ _, err = pools.HSet(key, field, value).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func RCHDel(key string, fields ...string) (int64, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ res, err := pools.HDel(key, fields...).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ return res, nil
+}
+
+func RCHMGet(key string, fields ...string) (interface{}, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ if len(fields) == 0 {
+ return nil, ErrNil
+ }
+ res, err := pools.HMGet(key, fields...).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCHMSet(key string, kvs ...interface{}) error {
+ if len(kvs) == 0 {
+ return nil
+ }
+ if len(kvs)%2 != 0 {
+ return ErrWrongArgsNum
+ }
+ var err error
+ v := map[string]interface{}{} // todo change
+ v["field"], err = String(kvs[0], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ v["value"], err = String(kvs[1], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs := make([]string, 0, len(kvs)-2)
+ if len(kvs) > 2 {
+ for _, kv := range kvs[2:] {
+ kvString, err := String(kv, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs = append(pairs, kvString)
+ }
+ }
+ v["paris"] = pairs
+ _, err = pools.HMSet(key, v).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+func RCHKeys(key string) ([]string, error) {
+ res, err := pools.HKeys(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCHVals(key string) ([]interface{}, error) {
+ res, err := pools.HVals(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ rs := make([]interface{}, 0, len(res))
+ for _, res := range res {
+ rs = append(rs, res)
+ }
+ return rs, nil
+}
+func RCHGetAll(key string) (map[string]string, error) {
+ vals, err := pools.HGetAll(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return vals, nil
+}
+func RCHIncrBy(key, field string, delta int64) (int64, error) {
+ res, err := pools.HIncrBy(key, field, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCZAdd(key string, kvs ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(kvs)+1)
+ args = append(args, key)
+ args = append(args, kvs...)
+ if len(kvs) == 0 {
+ return 0, nil
+ }
+ if len(kvs)%2 != 0 {
+ return 0, ErrWrongArgsNum
+ }
+ zs := make([]redis.Z, len(kvs)/2)
+ for i := 0; i < len(kvs); i += 2 {
+ idx := i / 2
+ score, err := Float64(kvs[i], nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ zs[idx].Score = score
+ zs[idx].Member = kvs[i+1]
+ }
+ res, err := pools.ZAdd(key, zs...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCZRem(key string, members ...string) (int64, error) {
+ args := make([]interface{}, 0, len(members))
+ args = append(args, key)
+ for _, member := range members {
+ args = append(args, member)
+ }
+ res, err := pools.ZRem(key, members).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, err
+}
+
+func RCZRange(key string, min, max int64, withScores bool) (interface{}, error) {
+ res := make([]interface{}, 0)
+ if withScores {
+ zs, err := pools.ZRangeWithScores(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, z := range zs {
+ res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64))
+ }
+ } else {
+ ms, err := pools.ZRange(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, m := range ms {
+ res = append(res, m)
+ }
+ }
+ return res, nil
+}
+func RCZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) {
+ opt := new(redis.ZRangeBy)
+ opt.Min = strconv.FormatInt(int64(min), 10)
+ opt.Max = strconv.FormatInt(int64(max), 10)
+ opt.Count = -1
+ opt.Offset = 0
+ vals, err := pools.ZRangeByScoreWithScores(key, *opt).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ res := make(map[string]int64, len(vals))
+ for _, val := range vals {
+ key, err := String(val.Member, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ res[key] = int64(val.Score)
+ }
+ return res, nil
+}
+func RCLRange(key string, start, stop int64) (interface{}, error) {
+ res, err := pools.LRange(key, start, stop).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCLSet(key string, index int, value interface{}) error {
+ err := pools.LSet(key, int64(index), value).Err()
+ return convertError(err)
+}
+func RCLLen(key string) (int64, error) {
+ res, err := pools.LLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCLRem(key string, count int, value interface{}) (int, error) {
+ val, _ := value.(string)
+ res, err := pools.LRem(key, int64(count), val).Result()
+ if err != nil {
+ return int(res), convertError(err)
+ }
+ return int(res), nil
+}
+func RCTTl(key string) (int64, error) {
+ duration, err := pools.TTL(key).Result()
+ if err != nil {
+ return int64(duration.Seconds()), convertError(err)
+ }
+ return int64(duration.Seconds()), nil
+}
+func RCLPop(key string) (interface{}, error) {
+ res, err := pools.LPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCRPop(key string) (interface{}, error) {
+ res, err := pools.RPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCBLPop(key string, timeout int) (interface{}, error) {
+ res, err := pools.BLPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, err
+ }
+ return res[1], nil
+}
+func RCBRPop(key string, timeout int) (interface{}, error) {
+ res, err := pools.BRPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, convertError(err)
+ }
+ return res[1], nil
+}
+func RCLPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ vals = append(vals, val)
+ }
+ _, err := pools.LPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func RCRPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ if err == ErrNil {
+ continue
+ }
+ return err
+ }
+ if val == "" {
+ continue
+ }
+ vals = append(vals, val)
+ }
+ _, err := pools.RPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func RCBRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) {
+ res, err := pools.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func RCRPopLPush(srcKey string, destKey string) (interface{}, error) {
+ res, err := pools.RPopLPush(srcKey, destKey).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCSAdd(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := pools.SAdd(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSPop(key string) ([]byte, error) {
+ res, err := pools.SPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func RCSIsMember(key string, member interface{}) (bool, error) {
+ m, _ := member.(string)
+ res, err := pools.SIsMember(key, m).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSRem(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := pools.SRem(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSMembers(key string) ([]string, error) {
+ res, err := pools.SMembers(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCScriptLoad(luaScript string) (interface{}, error) {
+ res, err := pools.ScriptLoad(luaScript).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCEvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, sha1, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := pools.EvalSha(sha1, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCEval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, luaScript, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := pools.Eval(luaScript, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func RCGetBit(key string, offset int64) (int64, error) {
+ res, err := pools.GetBit(key, offset).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func RCSetBit(key string, offset uint32, value int) (int, error) {
+ res, err := pools.SetBit(key, int64(offset), value).Result()
+ return int(res), convertError(err)
+}
+func RCGetClient() *redis.ClusterClient {
+ return pools
+}
+func convertError(err error) error {
+ if err == redis.Nil {
+ // 为了兼容redis 2.x,这里不返回 ErrNil,ErrNil在调用redis_cluster_reply函数时才返回
+ return nil
+ }
+ return err
+}
diff --git a/app/utils/cachesecond/redis_pool.go b/app/utils/cachesecond/redis_pool.go
new file mode 100644
index 0000000..501aa27
--- /dev/null
+++ b/app/utils/cachesecond/redis_pool.go
@@ -0,0 +1,324 @@
+package cachesecond
+
+import (
+ "errors"
+ "log"
+ "strings"
+ "time"
+
+ redigo "github.com/gomodule/redigo/redis"
+)
+
+type RedisPool struct {
+ *redigo.Pool
+}
+
+func NewRedisPool(cfg *Config) *RedisPool {
+ return &RedisPool{&redigo.Pool{
+ MaxIdle: cfg.MaxIdle,
+ IdleTimeout: cfg.IdleTimeout,
+ MaxActive: cfg.MaxActive,
+ Wait: cfg.Wait,
+ Dial: func() (redigo.Conn, error) {
+ c, err := redigo.Dial("tcp", cfg.Server)
+ if err != nil {
+ log.Println("Redis Dial failed: ", err)
+ return nil, err
+ }
+ if cfg.Password != "" {
+ if _, err := c.Do("AUTH", cfg.Password); err != nil {
+ c.Close()
+ log.Println("Redis AUTH failed: ", err)
+ return nil, err
+ }
+ }
+ return c, err
+ },
+ TestOnBorrow: func(c redigo.Conn, t time.Time) error {
+ _, err := c.Do("PING")
+ if err != nil {
+ log.Println("Unable to ping to redis server:", err)
+ }
+ return err
+ },
+ }}
+}
+
+func (p *RedisPool) Do(cmd string, args ...interface{}) (reply interface{}, err error) {
+ conn := pool.Get()
+ defer conn.Close()
+ return conn.Do(cmd, args...)
+}
+
+func (p *RedisPool) GetPool() *redigo.Pool {
+ return pool
+}
+
+func (p *RedisPool) ParseKey(key string, vars []string) (string, error) {
+ arr := strings.Split(key, conf.KeyPlaceholder)
+ actualKey := ""
+ if len(arr) != len(vars)+1 {
+ return "", errors.New("redis/connection.go: Insufficient arguments to parse key")
+ } else {
+ for index, val := range arr {
+ if index == 0 {
+ actualKey = arr[index]
+ } else {
+ actualKey += vars[index-1] + val
+ }
+ }
+ }
+ return getPrefixedKey(actualKey), nil
+}
+
+func (p *RedisPool) getPrefixedKey(key string) string {
+ return conf.KeyPrefix + conf.KeyDelimiter + key
+}
+func (p *RedisPool) StripEnvKey(key string) string {
+ return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter)
+}
+func (p *RedisPool) SplitKey(key string) []string {
+ return strings.Split(key, conf.KeyDelimiter)
+}
+func (p *RedisPool) Expire(key string, ttl int) (interface{}, error) {
+ return Do("EXPIRE", key, ttl)
+}
+func (p *RedisPool) Persist(key string) (interface{}, error) {
+ return Do("PERSIST", key)
+}
+
+func (p *RedisPool) Del(key string) (interface{}, error) {
+ return Do("DEL", key)
+}
+func (p *RedisPool) Set(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("SET", key, data)
+}
+func (p *RedisPool) SetNX(key string, data interface{}) (interface{}, error) {
+ return Do("SETNX", key, data)
+}
+func (p *RedisPool) SetEx(key string, data interface{}, ttl int) (interface{}, error) {
+ return Do("SETEX", key, ttl, data)
+}
+func (p *RedisPool) Get(key string) (interface{}, error) {
+ // get
+ return Do("GET", key)
+}
+func (p *RedisPool) GetStringMap(key string) (map[string]string, error) {
+ // get
+ return redigo.StringMap(Do("GET", key))
+}
+
+func (p *RedisPool) GetTTL(key string) (time.Duration, error) {
+ ttl, err := redigo.Int64(Do("TTL", key))
+ return time.Duration(ttl) * time.Second, err
+}
+func (p *RedisPool) GetBytes(key string) ([]byte, error) {
+ return redigo.Bytes(Do("GET", key))
+}
+func (p *RedisPool) GetString(key string) (string, error) {
+ return redigo.String(Do("GET", key))
+}
+func (p *RedisPool) GetInt(key string) (int, error) {
+ return redigo.Int(Do("GET", key))
+}
+func (p *RedisPool) GetStringLength(key string) (int, error) {
+ return redigo.Int(Do("STRLEN", key))
+}
+func (p *RedisPool) ZAdd(key string, score float64, data interface{}) (interface{}, error) {
+ return Do("ZADD", key, score, data)
+}
+func (p *RedisPool) ZRem(key string, data interface{}) (interface{}, error) {
+ return Do("ZREM", key, data)
+}
+func (p *RedisPool) ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) {
+ if withScores {
+ return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES"))
+ }
+ return redigo.Values(Do("ZRANGE", key, start, end))
+}
+func (p *RedisPool) SAdd(setName string, data interface{}) (interface{}, error) {
+ return Do("SADD", setName, data)
+}
+func (p *RedisPool) SCard(setName string) (int64, error) {
+ return redigo.Int64(Do("SCARD", setName))
+}
+func (p *RedisPool) SIsMember(setName string, data interface{}) (bool, error) {
+ return redigo.Bool(Do("SISMEMBER", setName, data))
+}
+func (p *RedisPool) SMembers(setName string) ([]string, error) {
+ return redigo.Strings(Do("SMEMBERS", setName))
+}
+func (p *RedisPool) SRem(setName string, data interface{}) (interface{}, error) {
+ return Do("SREM", setName, data)
+}
+func (p *RedisPool) HSet(key string, HKey string, data interface{}) (interface{}, error) {
+ return Do("HSET", key, HKey, data)
+}
+
+func (p *RedisPool) HGet(key string, HKey string) (interface{}, error) {
+ return Do("HGET", key, HKey)
+}
+
+func (p *RedisPool) HMGet(key string, hashKeys ...string) ([]interface{}, error) {
+ ret, err := Do("HMGET", key, hashKeys)
+ if err != nil {
+ return nil, err
+ }
+ reta, ok := ret.([]interface{})
+ if !ok {
+ return nil, errors.New("result not an array")
+ }
+ return reta, nil
+}
+
+func (p *RedisPool) HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) {
+ if len(hashKeys) == 0 || len(hashKeys) != len(vals) {
+ var ret interface{}
+ return ret, errors.New("bad length")
+ }
+ input := []interface{}{key}
+ for i, v := range hashKeys {
+ input = append(input, v, vals[i])
+ }
+ return Do("HMSET", input...)
+}
+
+func (p *RedisPool) HGetString(key string, HKey string) (string, error) {
+ return redigo.String(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HGetFloat(key string, HKey string) (float64, error) {
+ f, err := redigo.Float64(Do("HGET", key, HKey))
+ return float64(f), err
+}
+func (p *RedisPool) HGetInt(key string, HKey string) (int, error) {
+ return redigo.Int(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HGetInt64(key string, HKey string) (int64, error) {
+ return redigo.Int64(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HGetBool(key string, HKey string) (bool, error) {
+ return redigo.Bool(Do("HGET", key, HKey))
+}
+func (p *RedisPool) HDel(key string, HKey string) (interface{}, error) {
+ return Do("HDEL", key, HKey)
+}
+func (p *RedisPool) HGetAll(key string) (map[string]interface{}, error) {
+ vals, err := redigo.Values(Do("HGETALL", key))
+ if err != nil {
+ return nil, err
+ }
+ num := len(vals) / 2
+ result := make(map[string]interface{}, num)
+ for i := 0; i < num; i++ {
+ key, _ := redigo.String(vals[2*i], nil)
+ result[key] = vals[2*i+1]
+ }
+ return result, nil
+}
+
+// NOTE: Use this in production environment with extreme care.
+// Read more here:https://redigo.io/commands/keys
+func (p *RedisPool) Keys(pattern string) ([]string, error) {
+ return redigo.Strings(Do("KEYS", pattern))
+}
+
+func (p *RedisPool) HKeys(key string) ([]string, error) {
+ return redigo.Strings(Do("HKEYS", key))
+}
+
+func (p *RedisPool) Exists(key string) (bool, error) {
+ count, err := redigo.Int(Do("EXISTS", key))
+ if count == 0 {
+ return false, err
+ } else {
+ return true, err
+ }
+}
+
+func (p *RedisPool) Incr(key string) (int64, error) {
+ return redigo.Int64(Do("INCR", key))
+}
+
+func (p *RedisPool) Decr(key string) (int64, error) {
+ return redigo.Int64(Do("DECR", key))
+}
+
+func (p *RedisPool) IncrBy(key string, incBy int64) (int64, error) {
+ return redigo.Int64(Do("INCRBY", key, incBy))
+}
+
+func (p *RedisPool) DecrBy(key string, decrBy int64) (int64, error) {
+ return redigo.Int64(Do("DECRBY", key))
+}
+
+func (p *RedisPool) IncrByFloat(key string, incBy float64) (float64, error) {
+ return redigo.Float64(Do("INCRBYFLOAT", key, incBy))
+}
+
+func (p *RedisPool) DecrByFloat(key string, decrBy float64) (float64, error) {
+ return redigo.Float64(Do("DECRBYFLOAT", key, decrBy))
+}
+
+// use for message queue
+func (p *RedisPool) LPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("LPUSH", key, data)
+}
+
+func (p *RedisPool) LPop(key string) (interface{}, error) {
+ return Do("LPOP", key)
+}
+
+func (p *RedisPool) LPopString(key string) (string, error) {
+ return redigo.String(Do("LPOP", key))
+}
+func (p *RedisPool) LPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("LPOP", key))
+ return float64(f), err
+}
+func (p *RedisPool) LPopInt(key string) (int, error) {
+ return redigo.Int(Do("LPOP", key))
+}
+func (p *RedisPool) LPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("LPOP", key))
+}
+
+func (p *RedisPool) RPush(key string, data interface{}) (interface{}, error) {
+ // set
+ return Do("RPUSH", key, data)
+}
+
+func (p *RedisPool) RPop(key string) (interface{}, error) {
+ return Do("RPOP", key)
+}
+
+func (p *RedisPool) RPopString(key string) (string, error) {
+ return redigo.String(Do("RPOP", key))
+}
+func (p *RedisPool) RPopFloat(key string) (float64, error) {
+ f, err := redigo.Float64(Do("RPOP", key))
+ return float64(f), err
+}
+func (p *RedisPool) RPopInt(key string) (int, error) {
+ return redigo.Int(Do("RPOP", key))
+}
+func (p *RedisPool) RPopInt64(key string) (int64, error) {
+ return redigo.Int64(Do("RPOP", key))
+}
+
+func (p *RedisPool) Scan(cursor int64, pattern string, count int64) (int64, []string, error) {
+ var items []string
+ var newCursor int64
+
+ values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count))
+ if err != nil {
+ return 0, nil, err
+ }
+ values, err = redigo.Scan(values, &newCursor, &items)
+ if err != nil {
+ return 0, nil, err
+ }
+
+ return newCursor, items, nil
+}
diff --git a/app/utils/cachesecond/redis_pool_cluster.go b/app/utils/cachesecond/redis_pool_cluster.go
new file mode 100644
index 0000000..30ea8ac
--- /dev/null
+++ b/app/utils/cachesecond/redis_pool_cluster.go
@@ -0,0 +1,617 @@
+package cachesecond
+
+import (
+ "strconv"
+ "time"
+
+ "github.com/go-redis/redis"
+)
+
+type RedisClusterPool struct {
+ client *redis.ClusterClient
+}
+
+func NewRedisClusterPool(addrs []string) (*RedisClusterPool, error) {
+ opt := &redis.ClusterOptions{
+ Addrs: addrs,
+ PoolSize: 512,
+ PoolTimeout: 10 * time.Second,
+ IdleTimeout: 10 * time.Second,
+ DialTimeout: 10 * time.Second,
+ ReadTimeout: 3 * time.Second,
+ WriteTimeout: 3 * time.Second,
+ }
+ c := redis.NewClusterClient(opt)
+ if err := c.Ping().Err(); err != nil {
+ return nil, err
+ }
+ return &RedisClusterPool{client: c}, nil
+}
+
+func (p *RedisClusterPool) Get(key string) (interface{}, error) {
+ res, err := p.client.Get(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) Set(key string, value interface{}) error {
+ err := p.client.Set(key, value, 0).Err()
+ return convertError(err)
+}
+func (p *RedisClusterPool) GetSet(key string, value interface{}) (interface{}, error) {
+ res, err := p.client.GetSet(key, value).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) SetNx(key string, value interface{}) (int64, error) {
+ res, err := p.client.SetNX(key, value, 0).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func (p *RedisClusterPool) SetEx(key string, value interface{}, timeout int64) error {
+ _, err := p.client.Set(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误
+func (p *RedisClusterPool) SetNxEx(key string, value interface{}, timeout int64) error {
+ res, err := p.client.SetNX(key, value, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ if res {
+ return nil
+ }
+ return ErrNil
+}
+func (p *RedisClusterPool) MGet(keys ...string) ([]interface{}, error) {
+ res, err := p.client.MGet(keys...).Result()
+ return res, convertError(err)
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func (p *RedisClusterPool) MSet(kvs map[string]interface{}) error {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return err
+ }
+ pairs = append(pairs, k, val)
+ }
+ return convertError(p.client.MSet(pairs).Err())
+}
+
+// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test}
+func (p *RedisClusterPool) MSetNX(kvs map[string]interface{}) (bool, error) {
+ pairs := make([]string, 0, len(kvs)*2)
+ for k, v := range kvs {
+ val, err := String(v, nil)
+ if err != nil {
+ return false, err
+ }
+ pairs = append(pairs, k, val)
+ }
+ res, err := p.client.MSetNX(pairs).Result()
+ return res, convertError(err)
+}
+func (p *RedisClusterPool) ExpireAt(key string, timestamp int64) (int64, error) {
+ res, err := p.client.ExpireAt(key, time.Unix(timestamp, 0)).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func (p *RedisClusterPool) Del(keys ...string) (int64, error) {
+ args := make([]interface{}, 0, len(keys))
+ for _, key := range keys {
+ args = append(args, key)
+ }
+ res, err := p.client.Del(keys...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) Incr(key string) (int64, error) {
+ res, err := p.client.Incr(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) IncrBy(key string, delta int64) (int64, error) {
+ res, err := p.client.IncrBy(key, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) Expire(key string, duration int64) (int64, error) {
+ res, err := p.client.Expire(key, time.Duration(duration)*time.Second).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ if res {
+ return 1, nil
+ }
+ return 0, nil
+}
+func (p *RedisClusterPool) Exists(key string) (bool, error) { // todo (bool, error)
+ res, err := p.client.Exists(key).Result()
+ if err != nil {
+ return false, convertError(err)
+ }
+ if res > 0 {
+ return true, nil
+ }
+ return false, nil
+}
+func (p *RedisClusterPool) HGet(key string, field string) (interface{}, error) {
+ res, err := p.client.HGet(key, field).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) HLen(key string) (int64, error) {
+ res, err := p.client.HLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) HSet(key string, field string, val interface{}) error {
+ value, err := String(val, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ _, err = p.client.HSet(key, field, value).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func (p *RedisClusterPool) HDel(key string, fields ...string) (int64, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ res, err := p.client.HDel(key, fields...).Result()
+ if err != nil {
+ return 0, convertError(err)
+ }
+ return res, nil
+}
+
+func (p *RedisClusterPool) HMGet(key string, fields ...string) (interface{}, error) {
+ args := make([]interface{}, 0, len(fields)+1)
+ args = append(args, key)
+ for _, field := range fields {
+ args = append(args, field)
+ }
+ if len(fields) == 0 {
+ return nil, ErrNil
+ }
+ res, err := p.client.HMGet(key, fields...).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) HMSet(key string, kvs ...interface{}) error {
+ if len(kvs) == 0 {
+ return nil
+ }
+ if len(kvs)%2 != 0 {
+ return ErrWrongArgsNum
+ }
+ var err error
+ v := map[string]interface{}{} // todo change
+ v["field"], err = String(kvs[0], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ v["value"], err = String(kvs[1], nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs := make([]string, 0, len(kvs)-2)
+ if len(kvs) > 2 {
+ for _, kv := range kvs[2:] {
+ kvString, err := String(kv, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ pairs = append(pairs, kvString)
+ }
+ }
+ v["paris"] = pairs
+ _, err = p.client.HMSet(key, v).Result()
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+func (p *RedisClusterPool) HKeys(key string) ([]string, error) {
+ res, err := p.client.HKeys(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) HVals(key string) ([]interface{}, error) {
+ res, err := p.client.HVals(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ rs := make([]interface{}, 0, len(res))
+ for _, res := range res {
+ rs = append(rs, res)
+ }
+ return rs, nil
+}
+func (p *RedisClusterPool) HGetAll(key string) (map[string]string, error) {
+ vals, err := p.client.HGetAll(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return vals, nil
+}
+func (p *RedisClusterPool) HIncrBy(key, field string, delta int64) (int64, error) {
+ res, err := p.client.HIncrBy(key, field, delta).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ZAdd(key string, kvs ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(kvs)+1)
+ args = append(args, key)
+ args = append(args, kvs...)
+ if len(kvs) == 0 {
+ return 0, nil
+ }
+ if len(kvs)%2 != 0 {
+ return 0, ErrWrongArgsNum
+ }
+ zs := make([]redis.Z, len(kvs)/2)
+ for i := 0; i < len(kvs); i += 2 {
+ idx := i / 2
+ score, err := Float64(kvs[i], nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ zs[idx].Score = score
+ zs[idx].Member = kvs[i+1]
+ }
+ res, err := p.client.ZAdd(key, zs...).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ZRem(key string, members ...string) (int64, error) {
+ args := make([]interface{}, 0, len(members))
+ args = append(args, key)
+ for _, member := range members {
+ args = append(args, member)
+ }
+ res, err := p.client.ZRem(key, members).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, err
+}
+
+func (p *RedisClusterPool) ZRange(key string, min, max int64, withScores bool) (interface{}, error) {
+ res := make([]interface{}, 0)
+ if withScores {
+ zs, err := p.client.ZRangeWithScores(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, z := range zs {
+ res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64))
+ }
+ } else {
+ ms, err := p.client.ZRange(key, min, max).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ for _, m := range ms {
+ res = append(res, m)
+ }
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) {
+ opt := new(redis.ZRangeBy)
+ opt.Min = strconv.FormatInt(int64(min), 10)
+ opt.Max = strconv.FormatInt(int64(max), 10)
+ opt.Count = -1
+ opt.Offset = 0
+ vals, err := p.client.ZRangeByScoreWithScores(key, *opt).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ res := make(map[string]int64, len(vals))
+ for _, val := range vals {
+ key, err := String(val.Member, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ res[key] = int64(val.Score)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) LRange(key string, start, stop int64) (interface{}, error) {
+ res, err := p.client.LRange(key, start, stop).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) LSet(key string, index int, value interface{}) error {
+ err := p.client.LSet(key, int64(index), value).Err()
+ return convertError(err)
+}
+func (p *RedisClusterPool) LLen(key string) (int64, error) {
+ res, err := p.client.LLen(key).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) LRem(key string, count int, value interface{}) (int, error) {
+ val, _ := value.(string)
+ res, err := p.client.LRem(key, int64(count), val).Result()
+ if err != nil {
+ return int(res), convertError(err)
+ }
+ return int(res), nil
+}
+func (p *RedisClusterPool) TTl(key string) (int64, error) {
+ duration, err := p.client.TTL(key).Result()
+ if err != nil {
+ return int64(duration.Seconds()), convertError(err)
+ }
+ return int64(duration.Seconds()), nil
+}
+func (p *RedisClusterPool) LPop(key string) (interface{}, error) {
+ res, err := p.client.LPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) RPop(key string) (interface{}, error) {
+ res, err := p.client.RPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) BLPop(key string, timeout int) (interface{}, error) {
+ res, err := p.client.BLPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, err
+ }
+ return res[1], nil
+}
+func (p *RedisClusterPool) BRPop(key string, timeout int) (interface{}, error) {
+ res, err := p.client.BRPop(time.Duration(timeout)*time.Second, key).Result()
+ if err != nil {
+ // 兼容redis 2.x
+ if err == redis.Nil {
+ return nil, ErrNil
+ }
+ return nil, convertError(err)
+ }
+ return res[1], nil
+}
+func (p *RedisClusterPool) LPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ return err
+ }
+ vals = append(vals, val)
+ }
+ _, err := p.client.LPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+func (p *RedisClusterPool) RPush(key string, value ...interface{}) error {
+ args := make([]interface{}, 0, len(value)+1)
+ args = append(args, key)
+ args = append(args, value...)
+ vals := make([]string, 0, len(value))
+ for _, v := range value {
+ val, err := String(v, nil)
+ if err != nil && err != ErrNil {
+ if err == ErrNil {
+ continue
+ }
+ return err
+ }
+ if val == "" {
+ continue
+ }
+ vals = append(vals, val)
+ }
+ _, err := p.client.RPush(key, vals).Result() // todo ...
+ if err != nil {
+ return convertError(err)
+ }
+ return nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func (p *RedisClusterPool) BRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) {
+ res, err := p.client.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+
+// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test}
+func (p *RedisClusterPool) RPopLPush(srcKey string, destKey string) (interface{}, error) {
+ res, err := p.client.RPopLPush(srcKey, destKey).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SAdd(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := p.client.SAdd(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SPop(key string) ([]byte, error) {
+ res, err := p.client.SPop(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return []byte(res), nil
+}
+func (p *RedisClusterPool) SIsMember(key string, member interface{}) (bool, error) {
+ m, _ := member.(string)
+ res, err := p.client.SIsMember(key, m).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SRem(key string, members ...interface{}) (int64, error) {
+ args := make([]interface{}, 0, len(members)+1)
+ args = append(args, key)
+ args = append(args, members...)
+ ms := make([]string, 0, len(members))
+ for _, member := range members {
+ m, err := String(member, nil)
+ if err != nil && err != ErrNil {
+ return 0, err
+ }
+ ms = append(ms, m)
+ }
+ res, err := p.client.SRem(key, ms).Result() // todo ...
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SMembers(key string) ([]string, error) {
+ res, err := p.client.SMembers(key).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) ScriptLoad(luaScript string) (interface{}, error) {
+ res, err := p.client.ScriptLoad(luaScript).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) EvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, sha1, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := p.client.EvalSha(sha1, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) Eval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) {
+ vals := make([]interface{}, 0, len(keysArgs)+2)
+ vals = append(vals, luaScript, numberKeys)
+ vals = append(vals, keysArgs...)
+ keys := make([]string, 0, numberKeys)
+ args := make([]string, 0, len(keysArgs)-numberKeys)
+ for i, value := range keysArgs {
+ val, err := String(value, nil)
+ if err != nil && err != ErrNil {
+ return nil, err
+ }
+ if i < numberKeys {
+ keys = append(keys, val)
+ } else {
+ args = append(args, val)
+ }
+ }
+ res, err := p.client.Eval(luaScript, keys, args).Result()
+ if err != nil {
+ return nil, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) GetBit(key string, offset int64) (int64, error) {
+ res, err := p.client.GetBit(key, offset).Result()
+ if err != nil {
+ return res, convertError(err)
+ }
+ return res, nil
+}
+func (p *RedisClusterPool) SetBit(key string, offset uint32, value int) (int, error) {
+ res, err := p.client.SetBit(key, int64(offset), value).Result()
+ return int(res), convertError(err)
+}
+func (p *RedisClusterPool) GetClient() *redis.ClusterClient {
+ return pools
+}
diff --git a/app/utils/convert.go b/app/utils/convert.go
new file mode 100644
index 0000000..4b88b99
--- /dev/null
+++ b/app/utils/convert.go
@@ -0,0 +1,434 @@
+package utils
+
+import (
+ "code.fnuoos.com/go_rely_warehouse/zyos_go_coupon.git/utils"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "math"
+ "strconv"
+ "strings"
+)
+
+func ToString(raw interface{}, e error) (res string) {
+ if e != nil {
+ return ""
+ }
+ return AnyToString(raw)
+}
+
+func ToInt64(raw interface{}, e error) int64 {
+ if e != nil {
+ return 0
+ }
+ return AnyToInt64(raw)
+}
+
+func Float64ToStrPrec4(f float64) string {
+ return strconv.FormatFloat(f, 'f', 4, 64)
+}
+func AnyToBool(raw interface{}) bool {
+ switch i := raw.(type) {
+ case float32, float64, int, int64, uint, uint8, uint16, uint32, uint64, int8, int16, int32:
+ return i != 0
+ case []byte:
+ return i != nil
+ case string:
+ if i == "false" {
+ return false
+ }
+ return i != ""
+ case error:
+ return false
+ case nil:
+ return true
+ }
+ val := fmt.Sprint(raw)
+ val = strings.TrimLeft(val, "&")
+ if strings.TrimLeft(val, "{}") == "" {
+ return false
+ }
+ if strings.TrimLeft(val, "[]") == "" {
+ return false
+ }
+ // ptr type
+ b, err := json.Marshal(raw)
+ if err != nil {
+ return false
+ }
+ if strings.TrimLeft(string(b), "\"\"") == "" {
+ return false
+ }
+ if strings.TrimLeft(string(b), "{}") == "" {
+ return false
+ }
+ return true
+}
+
+func AnyToInt64(raw interface{}) int64 {
+ switch i := raw.(type) {
+ case string:
+ res, _ := strconv.ParseInt(i, 10, 64)
+ return res
+ case []byte:
+ return BytesToInt64(i)
+ case int:
+ return int64(i)
+ case int64:
+ return i
+ case uint:
+ return int64(i)
+ case uint8:
+ return int64(i)
+ case uint16:
+ return int64(i)
+ case uint32:
+ return int64(i)
+ case uint64:
+ return int64(i)
+ case int8:
+ return int64(i)
+ case int16:
+ return int64(i)
+ case int32:
+ return int64(i)
+ case float32:
+ return int64(i)
+ case float64:
+ return int64(i)
+ case error:
+ return 0
+ case bool:
+ if i {
+ return 1
+ }
+ return 0
+ }
+ return 0
+}
+
+func AnyToString(raw interface{}) string {
+ switch i := raw.(type) {
+ case []byte:
+ return string(i)
+ case int:
+ return strconv.FormatInt(int64(i), 10)
+ case int64:
+ return strconv.FormatInt(i, 10)
+ case float32:
+ return Float64ToStr(float64(i))
+ case float64:
+ return Float64ToStr(i)
+ case uint:
+ return strconv.FormatInt(int64(i), 10)
+ case uint8:
+ return strconv.FormatInt(int64(i), 10)
+ case uint16:
+ return strconv.FormatInt(int64(i), 10)
+ case uint32:
+ return strconv.FormatInt(int64(i), 10)
+ case uint64:
+ return strconv.FormatInt(int64(i), 10)
+ case int8:
+ return strconv.FormatInt(int64(i), 10)
+ case int16:
+ return strconv.FormatInt(int64(i), 10)
+ case int32:
+ return strconv.FormatInt(int64(i), 10)
+ case string:
+ return i
+ case error:
+ return i.Error()
+ case bool:
+ return strconv.FormatBool(i)
+ }
+ return fmt.Sprintf("%#v", raw)
+}
+
+func AnyToFloat64(raw interface{}) float64 {
+ switch i := raw.(type) {
+ case []byte:
+ f, _ := strconv.ParseFloat(string(i), 64)
+ return f
+ case int:
+ return float64(i)
+ case int64:
+ return float64(i)
+ case float32:
+ return float64(i)
+ case float64:
+ return i
+ case uint:
+ return float64(i)
+ case uint8:
+ return float64(i)
+ case uint16:
+ return float64(i)
+ case uint32:
+ return float64(i)
+ case uint64:
+ return float64(i)
+ case int8:
+ return float64(i)
+ case int16:
+ return float64(i)
+ case int32:
+ return float64(i)
+ case string:
+ f, _ := strconv.ParseFloat(i, 64)
+ return f
+ case bool:
+ if i {
+ return 1
+ }
+ }
+ return 0
+}
+
+func ToByte(raw interface{}, e error) []byte {
+ if e != nil {
+ return []byte{}
+ }
+ switch i := raw.(type) {
+ case string:
+ return []byte(i)
+ case int:
+ return Int64ToBytes(int64(i))
+ case int64:
+ return Int64ToBytes(i)
+ case float32:
+ return Float32ToByte(i)
+ case float64:
+ return Float64ToByte(i)
+ case uint:
+ return Int64ToBytes(int64(i))
+ case uint8:
+ return Int64ToBytes(int64(i))
+ case uint16:
+ return Int64ToBytes(int64(i))
+ case uint32:
+ return Int64ToBytes(int64(i))
+ case uint64:
+ return Int64ToBytes(int64(i))
+ case int8:
+ return Int64ToBytes(int64(i))
+ case int16:
+ return Int64ToBytes(int64(i))
+ case int32:
+ return Int64ToBytes(int64(i))
+ case []byte:
+ return i
+ case error:
+ return []byte(i.Error())
+ case bool:
+ if i {
+ return []byte("true")
+ }
+ return []byte("false")
+ }
+ return []byte(fmt.Sprintf("%#v", raw))
+}
+
+func Int64ToBytes(i int64) []byte {
+ var buf = make([]byte, 8)
+ binary.BigEndian.PutUint64(buf, uint64(i))
+ return buf
+}
+
+func BytesToInt64(buf []byte) int64 {
+ return int64(binary.BigEndian.Uint64(buf))
+}
+
+func StrToInt(s string) int {
+ res, _ := strconv.Atoi(s)
+ return res
+}
+
+func StrToInt64(s string) int64 {
+ res, _ := strconv.ParseInt(s, 10, 64)
+ return res
+}
+
+func Float32ToByte(float float32) []byte {
+ bits := math.Float32bits(float)
+ bytes := make([]byte, 4)
+ binary.LittleEndian.PutUint32(bytes, bits)
+
+ return bytes
+}
+
+func ByteToFloat32(bytes []byte) float32 {
+ bits := binary.LittleEndian.Uint32(bytes)
+ return math.Float32frombits(bits)
+}
+
+func Float64ToByte(float float64) []byte {
+ bits := math.Float64bits(float)
+ bytes := make([]byte, 8)
+ binary.LittleEndian.PutUint64(bytes, bits)
+ return bytes
+}
+
+func ByteToFloat64(bytes []byte) float64 {
+ bits := binary.LittleEndian.Uint64(bytes)
+ return math.Float64frombits(bits)
+}
+
+func Float64ToStr(f float64) string {
+ return strconv.FormatFloat(f, 'f', 2, 64)
+}
+func Float64ToStrPrec1(f float64) string {
+ return strconv.FormatFloat(f, 'f', 1, 64)
+}
+func Float64ToStrByPrec(f float64, prec int) string {
+ return strconv.FormatFloat(f, 'f', prec, 64)
+}
+func Float32ToStrByPrec(f float32, prec int) string {
+ return Float64ToStrByPrec(float64(f), prec)
+}
+func GetFloatByOne(count, num, mul float64, prec int) string {
+
+ return Float64ToStrByPrec(float64(int(float64(count)/num*mul))/mul, prec)
+}
+
+func Float32ToStr(f float32) string {
+ return Float64ToStr(float64(f))
+}
+
+func StrToFloat64(s string) float64 {
+ res, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return 0
+ }
+ return res
+}
+func StrToFormatByType(c *gin.Context, s string, types string) string {
+ commPrec := "2"
+ if types == "commission" {
+ commPrec = c.GetString("commission_prec")
+ }
+ if types == "integral" {
+ commPrec = c.GetString("integral_prec")
+ }
+ if s == "" {
+ s = "0"
+ }
+ s = StrToFormat(c, s, utils.StrToInt(commPrec))
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 && c.GetString("is_show_point") != "1" {
+ if utils.StrToFloat64(ex[1]) == 0 {
+ s = ex[0]
+ } else {
+ val := utils.Float64ToStrByPrec(utils.StrToFloat64(ex[1]), 0)
+ keyMax := 0
+ for i := 0; i < len(val); i++ {
+ ch := string(val[i])
+ fmt.Println(utils.StrToInt(ch))
+ if utils.StrToInt(ch) > 0 {
+ keyMax = i
+ }
+ }
+ valNew := val[0 : keyMax+1]
+ s = ex[0] + "." + strings.ReplaceAll(ex[1], val, valNew)
+ }
+ }
+ return s
+}
+func StrToFormat(c *gin.Context, s string, prec int) string {
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 {
+ if StrToFloat64(ex[1]) == 0 && c.GetString("is_show_point") != "1" { //小数点后面为空就是不要小数点了
+ return ex[0]
+ }
+ //看取多少位
+ str := ex[1]
+ str1 := str
+ if prec < len(str) {
+ str1 = str[0:prec]
+ } else {
+ for i := 0; i < prec-len(str); i++ {
+ str1 += "0"
+ }
+ }
+ if prec > 0 {
+ return ex[0] + "." + str1
+ } else {
+ return ex[0]
+ }
+ }
+ return s
+}
+func StrToFormatEg(s string, prec int, is_show_point string) string {
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 {
+ if StrToFloat64(ex[1]) == 0 && is_show_point != "1" { //小数点后面为空就是不要小数点了
+ return ex[0]
+ }
+ //看取多少位
+ str := ex[1]
+ str1 := str
+ if prec < len(str) {
+ str1 = str[0:prec]
+ } else {
+ for i := 0; i < prec-len(str); i++ {
+ str1 += "0"
+ }
+ }
+ if prec > 0 {
+ return ex[0] + "." + str1
+ } else {
+ return ex[0]
+ }
+ }
+ return s
+}
+
+func StrToFloat32(s string) float32 {
+ res, err := strconv.ParseFloat(s, 32)
+ if err != nil {
+ return 0
+ }
+ return float32(res)
+}
+
+func StrToBool(s string) bool {
+ b, _ := strconv.ParseBool(s)
+ return b
+}
+
+func BoolToStr(b bool) string {
+ if b {
+ return "true"
+ }
+ return "false"
+}
+
+func FloatToInt64(f float64) int64 {
+ return int64(f)
+}
+
+func IntToStr(i int) string {
+ return strconv.Itoa(i)
+}
+
+func Int64ToStr(i int64) string {
+ return strconv.FormatInt(i, 10)
+}
+
+func IntToFloat64(i int) float64 {
+ s := strconv.Itoa(i)
+ res, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return 0
+ }
+ return res
+}
+func Int64ToFloat64(i int64) float64 {
+ s := strconv.FormatInt(i, 10)
+ res, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return 0
+ }
+ return res
+}
diff --git a/app/utils/crypto.go b/app/utils/crypto.go
new file mode 100644
index 0000000..56289c5
--- /dev/null
+++ b/app/utils/crypto.go
@@ -0,0 +1,19 @@
+package utils
+
+import (
+ "crypto/md5"
+ "encoding/base64"
+ "fmt"
+)
+
+func GetMd5(raw []byte) string {
+ h := md5.New()
+ h.Write(raw)
+ return fmt.Sprintf("%x", h.Sum(nil))
+}
+
+func GetBase64Md5(raw []byte) string {
+ h := md5.New()
+ h.Write(raw)
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
diff --git a/app/utils/curl.go b/app/utils/curl.go
new file mode 100644
index 0000000..0a45607
--- /dev/null
+++ b/app/utils/curl.go
@@ -0,0 +1,209 @@
+package utils
+
+import (
+ "bytes"
+ "crypto/tls"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "sort"
+ "strings"
+ "time"
+)
+
+var CurlDebug bool
+
+func CurlGet(router string, header map[string]string) ([]byte, error) {
+ return curl(http.MethodGet, router, nil, header)
+}
+func CurlGetJson(router string, body interface{}, header map[string]string) ([]byte, error) {
+ return curl_new(http.MethodGet, router, body, header)
+}
+
+// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string
+func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) {
+ return curl(http.MethodPost, router, body, header)
+}
+
+func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) {
+ return curl(http.MethodPut, router, body, header)
+}
+
+// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string
+func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) {
+ return curl(http.MethodPatch, router, body, header)
+}
+
+// CurlDelete is curl delete
+func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) {
+ return curl(http.MethodDelete, router, body, header)
+}
+
+func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) {
+ var reqBody io.Reader
+ contentType := "application/json"
+ switch v := body.(type) {
+ case string:
+ reqBody = strings.NewReader(v)
+ case []byte:
+ reqBody = bytes.NewReader(v)
+ case map[string]string:
+ val := url.Values{}
+ for k, v := range v {
+ val.Set(k, v)
+ }
+ reqBody = strings.NewReader(val.Encode())
+ contentType = "application/x-www-form-urlencoded"
+ case map[string]interface{}:
+ val := url.Values{}
+ for k, v := range v {
+ val.Set(k, v.(string))
+ }
+ reqBody = strings.NewReader(val.Encode())
+ contentType = "application/x-www-form-urlencoded"
+ }
+ if header == nil {
+ header = map[string]string{"Content-Type": contentType}
+ }
+ if _, ok := header["Content-Type"]; !ok {
+ header["Content-Type"] = contentType
+ }
+ resp, er := CurlReq(method, router, reqBody, header)
+ if er != nil {
+ return nil, er
+ }
+ res, err := ioutil.ReadAll(resp.Body)
+ if CurlDebug {
+ blob := SerializeStr(body)
+ if contentType != "application/json" {
+ blob = HttpBuild(body)
+ }
+ fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n",
+ router,
+ time.Now().Format("2006-01-02 15:04:05.000"),
+ method,
+ contentType,
+ HttpBuildQuery(header),
+ blob,
+ err,
+ SerializeStr(resp.Header),
+ string(res),
+ )
+ }
+ resp.Body.Close()
+ return res, err
+}
+
+func curl_new(method, router string, body interface{}, header map[string]string) ([]byte, error) {
+ var reqBody io.Reader
+ contentType := "application/json"
+
+ if header == nil {
+ header = map[string]string{"Content-Type": contentType}
+ }
+ if _, ok := header["Content-Type"]; !ok {
+ header["Content-Type"] = contentType
+ }
+ resp, er := CurlReq(method, router, reqBody, header)
+ if er != nil {
+ return nil, er
+ }
+ res, err := ioutil.ReadAll(resp.Body)
+ if CurlDebug {
+ blob := SerializeStr(body)
+ if contentType != "application/json" {
+ blob = HttpBuild(body)
+ }
+ fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n",
+ router,
+ time.Now().Format("2006-01-02 15:04:05.000"),
+ method,
+ contentType,
+ HttpBuildQuery(header),
+ blob,
+ err,
+ SerializeStr(resp.Header),
+ string(res),
+ )
+ }
+ resp.Body.Close()
+ return res, err
+}
+
+func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) {
+ req, _ := http.NewRequest(method, router, reqBody)
+ if header != nil {
+ for k, v := range header {
+ req.Header.Set(k, v)
+ }
+ }
+ // 绕过github等可能因为特征码返回503问题
+ // https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/
+ defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009,
+ 0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a}
+ client := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...),
+ },
+ },
+ // 获取301重定向
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ return http.ErrUseLastResponse
+ },
+ }
+ return client.Do(req)
+}
+
+// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序 a=123&b=321
+func HttpBuildQuery(args map[string]string, sortAsc ...bool) string {
+ str := ""
+ if len(args) == 0 {
+ return str
+ }
+ if len(sortAsc) > 0 {
+ keys := make([]string, 0, len(args))
+ for k := range args {
+ keys = append(keys, k)
+ }
+ if sortAsc[0] {
+ sort.Strings(keys)
+ } else {
+ sort.Sort(sort.Reverse(sort.StringSlice(keys)))
+ }
+ for _, k := range keys {
+ str += "&" + k + "=" + args[k]
+ }
+ } else {
+ for k, v := range args {
+ str += "&" + k + "=" + v
+ }
+ }
+ return str[1:]
+}
+
+func HttpBuild(body interface{}, sortAsc ...bool) string {
+ params := map[string]string{}
+ if args, ok := body.(map[string]interface{}); ok {
+ for k, v := range args {
+ params[k] = AnyToString(v)
+ }
+ return HttpBuildQuery(params, sortAsc...)
+ }
+ if args, ok := body.(map[string]string); ok {
+ for k, v := range args {
+ params[k] = AnyToString(v)
+ }
+ return HttpBuildQuery(params, sortAsc...)
+ }
+ if args, ok := body.(map[string]int); ok {
+ for k, v := range args {
+ params[k] = AnyToString(v)
+ }
+ return HttpBuildQuery(params, sortAsc...)
+ }
+ return AnyToString(body)
+}
diff --git a/app/utils/debug.go b/app/utils/debug.go
new file mode 100644
index 0000000..bb2e9d3
--- /dev/null
+++ b/app/utils/debug.go
@@ -0,0 +1,25 @@
+package utils
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "time"
+)
+
+func Debug(args ...interface{}) {
+ s := ""
+ l := len(args)
+ if l < 1 {
+ fmt.Println("please input some data")
+ os.Exit(0)
+ }
+ i := 1
+ for _, v := range args {
+ s += fmt.Sprintf("【"+strconv.Itoa(i)+"】: %#v\n", v)
+ i++
+ }
+ s = "******************** 【DEBUG - " + time.Now().Format("2006-01-02 15:04:05") + "】 ********************\n" + s + "******************** 【DEBUG - END】 ********************\n"
+ fmt.Println(s)
+ os.Exit(0)
+}
diff --git a/app/utils/distance.go b/app/utils/distance.go
new file mode 100644
index 0000000..2a6923b
--- /dev/null
+++ b/app/utils/distance.go
@@ -0,0 +1,16 @@
+package utils
+
+import "math"
+
+//返回单位为:千米
+func GetDistance(lat1, lat2, lng1, lng2 float64) float64 {
+ radius := 6371000.0 //6378137.0
+ rad := math.Pi / 180.0
+ lat1 = lat1 * rad
+ lng1 = lng1 * rad
+ lat2 = lat2 * rad
+ lng2 = lng2 * rad
+ theta := lng2 - lng1
+ dist := math.Acos(math.Sin(lat1)*math.Sin(lat2) + math.Cos(lat1)*math.Cos(lat2)*math.Cos(theta))
+ return dist * radius / 1000
+}
diff --git a/app/utils/duplicate.go b/app/utils/duplicate.go
new file mode 100644
index 0000000..17cea88
--- /dev/null
+++ b/app/utils/duplicate.go
@@ -0,0 +1,37 @@
+package utils
+
+func RemoveDuplicateString(elms []string) []string {
+ res := make([]string, 0, len(elms))
+ temp := map[string]struct{}{}
+ for _, item := range elms {
+ if _, ok := temp[item]; !ok {
+ temp[item] = struct{}{}
+ res = append(res, item)
+ }
+ }
+ return res
+}
+
+func RemoveDuplicateInt(elms []int) []int {
+ res := make([]int, 0, len(elms))
+ temp := map[int]struct{}{}
+ for _, item := range elms {
+ if _, ok := temp[item]; !ok {
+ temp[item] = struct{}{}
+ res = append(res, item)
+ }
+ }
+ return res
+}
+
+func RemoveDuplicateInt64(elms []int64) []int64 {
+ res := make([]int64, 0, len(elms))
+ temp := map[int64]struct{}{}
+ for _, item := range elms {
+ if _, ok := temp[item]; !ok {
+ temp[item] = struct{}{}
+ res = append(res, item)
+ }
+ }
+ return res
+}
diff --git a/app/utils/file.go b/app/utils/file.go
new file mode 100644
index 0000000..93ed08f
--- /dev/null
+++ b/app/utils/file.go
@@ -0,0 +1,22 @@
+package utils
+
+import (
+ "os"
+ "path"
+ "strings"
+ "time"
+)
+
+// 获取文件后缀
+func FileExt(fname string) string {
+ return strings.ToLower(strings.TrimLeft(path.Ext(fname), "."))
+}
+
+func FilePutContents(fileName string, content string) {
+ fd, _ := os.OpenFile("./tmp/"+fileName+".log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
+ fd_time := time.Now().Format("2006-01-02 15:04:05")
+ fd_content := strings.Join([]string{"[", fd_time, "] ", content, "\n"}, "")
+ buf := []byte(fd_content)
+ fd.Write(buf)
+ fd.Close()
+}
diff --git a/app/utils/file_and_dir.go b/app/utils/file_and_dir.go
new file mode 100644
index 0000000..93141f9
--- /dev/null
+++ b/app/utils/file_and_dir.go
@@ -0,0 +1,29 @@
+package utils
+
+import "os"
+
+// 判断所给路径文件、文件夹是否存在
+func Exists(path string) bool {
+ _, err := os.Stat(path) //os.Stat获取文件信息
+ if err != nil {
+ if os.IsExist(err) {
+ return true
+ }
+ return false
+ }
+ return true
+}
+
+// 判断所给路径是否为文件夹
+func IsDir(path string) bool {
+ s, err := os.Stat(path)
+ if err != nil {
+ return false
+ }
+ return s.IsDir()
+}
+
+// 判断所给路径是否为文件
+func IsFile(path string) bool {
+ return !IsDir(path)
+}
diff --git a/app/utils/format.go b/app/utils/format.go
new file mode 100644
index 0000000..55fa4ac
--- /dev/null
+++ b/app/utils/format.go
@@ -0,0 +1,197 @@
+package utils
+
+import (
+ "code.fnuoos.com/go_rely_warehouse/zyos_go_coupon.git/utils"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "math"
+ "strings"
+)
+
+func CouponFormat(c *gin.Context, data string) string {
+ switch data {
+ case "0.00", "0", "":
+ return ""
+ default:
+ return GetPrec(c, data, "2")
+ }
+}
+func GetPrec(c *gin.Context, sum, commPrec string) string {
+ if sum == "" {
+ sum = "0"
+ }
+ sum = StrToFormat(c, sum, utils.StrToInt(commPrec))
+ ex := strings.Split(sum, ".")
+ if len(ex) == 2 && c.GetString("is_show_point") != "1" {
+ if utils.StrToFloat64(ex[1]) == 0 {
+ sum = ex[0]
+ } else {
+ val := utils.Float64ToStrByPrec(utils.StrToFloat64(ex[1]), 0)
+ keyMax := 0
+ for i := 0; i < len(val); i++ {
+ ch := string(val[i])
+ fmt.Println(utils.StrToInt(ch))
+ if utils.StrToInt(ch) > 0 {
+ keyMax = i
+ }
+ }
+ valNew := val[0 : keyMax+1]
+ sum = ex[0] + "." + strings.ReplaceAll(ex[1], val, valNew)
+ }
+ }
+ return sum
+}
+
+func CommissionFormat(data string) string {
+ if StrToFloat64(data) > 0 {
+ return data
+ }
+
+ return ""
+}
+
+func HideString(src string, hLen int) string {
+ str := []rune(src)
+ if hLen == 0 {
+ hLen = 4
+ }
+ hideStr := ""
+ for i := 0; i < hLen; i++ {
+ hideStr += "*"
+ }
+ hideLen := len(str) / 2
+ showLen := len(str) - hideLen
+ if hideLen == 0 || showLen == 0 {
+ return hideStr
+ }
+ subLen := showLen / 2
+ if subLen == 0 {
+ return string(str[:showLen]) + hideStr
+ }
+ s := string(str[:subLen])
+ s += hideStr
+ s += string(str[len(str)-subLen:])
+ return s
+}
+
+// SaleCountFormat is 格式化销量
+func SaleCountFormat(s string) string {
+ s = strings.ReplaceAll(s, "+", "")
+ if strings.Contains(s, "万") {
+ s = strings.ReplaceAll(s, "万", "")
+ s = Float64ToStrByPrec(StrToFloat64(s)*10000, 0)
+ }
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 && StrToInt(ex[1]) == 0 {
+ s = ex[0]
+ }
+ if utils.StrToInt(s) > 0 {
+ s = Comm(s)
+ return "已售" + s
+ }
+ return "已售0"
+}
+func SaleCountFormat2(s string) string {
+ s = strings.ReplaceAll(s, "+", "")
+ if strings.Contains(s, "万") {
+ s = strings.ReplaceAll(s, "万", "")
+ s = Float64ToStrByPrec(StrToFloat64(s)*10000, 0)
+ }
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 && StrToInt(ex[1]) == 0 {
+ s = ex[0]
+ }
+ if utils.StrToInt(s) > 0 {
+ s = Comm(s)
+
+ return s
+ }
+ return "0"
+}
+func SaleCountFormat1(s string) string {
+ s = strings.ReplaceAll(s, "+", "")
+ if strings.Contains(s, "万") {
+ s = strings.ReplaceAll(s, "万", "")
+ s = Float64ToStrByPrec(StrToFloat64(s)*10000, 0)
+ }
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 && StrToInt(ex[1]) == 0 {
+ s = ex[0]
+ }
+ if utils.StrToInt(s) > 0 {
+ s = Comm(s)
+ return s
+ }
+ return "0"
+}
+func Comm(s string) string {
+ ex := strings.Split(s, ".")
+ if len(ex) == 2 && StrToInt(ex[1]) == 0 {
+ s = ex[0]
+ }
+ if utils.StrToInt(s) >= 10000 {
+ num := FloatFormat(StrToFloat64(s)/10000, 2)
+ numStr := Float64ToStr(num)
+ ex := strings.Split(numStr, ".")
+ if len(ex) == 2 {
+ if utils.StrToFloat64(ex[1]) == 0 {
+ numStr = ex[0]
+ } else {
+ val := utils.Float64ToStrByPrec(utils.StrToFloat64(ex[1]), 0)
+ keyMax := 0
+ for i := 0; i < len(val); i++ {
+ ch := string(val[i])
+ fmt.Println(utils.StrToInt(ch))
+ if utils.StrToInt(ch) > 0 {
+ keyMax = i
+ }
+ }
+ valNew := val[0 : keyMax+1]
+ numStr = ex[0] + "." + strings.ReplaceAll(ex[1], val, valNew)
+ }
+ }
+ s = numStr + "万"
+ }
+ //if StrToInt(s) >= 10000000 {
+ // num := FloatFormat(StrToFloat64(s)/10000000, 2)
+ // numStr := Float64ToStr(num)
+ // ex := strings.Split(numStr, ".")
+ // if len(ex) == 2 {
+ // if utils.StrToFloat64(ex[1]) == 0 {
+ // numStr = ex[0]
+ // } else {
+ // val := utils.Float64ToStrByPrec(utils.StrToFloat64(ex[1]), 0)
+ // keyMax := 0
+ // for i := 0; i < len(val); i++ {
+ // ch := string(val[i])
+ // fmt.Println(utils.StrToInt(ch))
+ // if utils.StrToInt(ch) > 0 {
+ // keyMax = i
+ // }
+ // }
+ // valNew := val[0 : keyMax+1]
+ // numStr = ex[0] + "." + strings.ReplaceAll(ex[1], val, valNew)
+ // }
+ // }
+ // s = numStr + "千万"
+ //}
+ return s
+}
+func CommissionFormat1(numStr string) string {
+ split := strings.Split(numStr, ".")
+ if len(split) == 2 {
+ if split[1] == "00" {
+ numStr = strings.ReplaceAll(numStr, ".00", "")
+ }
+ }
+ return numStr
+}
+
+// 小数格式化
+func FloatFormat(f float64, i int) float64 {
+ if i > 14 {
+ return f
+ }
+ p := math.Pow10(i)
+ return float64(int64((f+0.000000000000009)*p)) / p
+}
diff --git a/app/utils/json.go b/app/utils/json.go
new file mode 100644
index 0000000..2905833
--- /dev/null
+++ b/app/utils/json.go
@@ -0,0 +1,37 @@
+package utils
+
+import (
+ "bytes"
+ "encoding/json"
+ "regexp"
+)
+
+func JsonMarshal(interface{}) {
+
+}
+
+// 不科学计数法
+func JsonDecode(data []byte, v interface{}) error {
+ d := json.NewDecoder(bytes.NewReader(data))
+ d.UseNumber()
+ return d.Decode(v)
+}
+
+// json字符串驼峰命名格式 转为 下划线命名格式
+// c :json字符串
+func MarshalJSONCamelCase2JsonSnakeCase(c string) []byte {
+ // Regexp definitions
+ var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`)
+ var wordBarrierRegex = regexp.MustCompile(`(\w)([A-Z])`)
+ marshalled := []byte(c)
+ converted := keyMatchRegex.ReplaceAllFunc(
+ marshalled,
+ func(match []byte) []byte {
+ return bytes.ToLower(wordBarrierRegex.ReplaceAll(
+ match,
+ []byte(`${1}_${2}`),
+ ))
+ },
+ )
+ return converted
+}
diff --git a/app/utils/logx/log.go b/app/utils/logx/log.go
new file mode 100644
index 0000000..ca11223
--- /dev/null
+++ b/app/utils/logx/log.go
@@ -0,0 +1,245 @@
+package logx
+
+import (
+ "os"
+ "strings"
+ "time"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+type LogConfig struct {
+ AppName string `yaml:"app_name" json:"app_name" toml:"app_name"`
+ Level string `yaml:"level" json:"level" toml:"level"`
+ StacktraceLevel string `yaml:"stacktrace_level" json:"stacktrace_level" toml:"stacktrace_level"`
+ IsStdOut bool `yaml:"is_stdout" json:"is_stdout" toml:"is_stdout"`
+ TimeFormat string `yaml:"time_format" json:"time_format" toml:"time_format"` // second, milli, nano, standard, iso,
+ Encoding string `yaml:"encoding" json:"encoding" toml:"encoding"` // console, json
+ Skip int `yaml:"skip" json:"skip" toml:"skip"`
+
+ IsFileOut bool `yaml:"is_file_out" json:"is_file_out" toml:"is_file_out"`
+ FileDir string `yaml:"file_dir" json:"file_dir" toml:"file_dir"`
+ FileName string `yaml:"file_name" json:"file_name" toml:"file_name"`
+ FileMaxSize int `yaml:"file_max_size" json:"file_max_size" toml:"file_max_size"`
+ FileMaxAge int `yaml:"file_max_age" json:"file_max_age" toml:"file_max_age"`
+}
+
+var (
+ l *LogX = defaultLogger()
+ conf *LogConfig
+)
+
+// default logger setting
+func defaultLogger() *LogX {
+ conf = &LogConfig{
+ Level: "debug",
+ StacktraceLevel: "error",
+ IsStdOut: true,
+ TimeFormat: "standard",
+ Encoding: "console",
+ Skip: 2,
+ }
+ writers := []zapcore.WriteSyncer{os.Stdout}
+ lg, lv := newZapLogger(setLogLevel(conf.Level), setLogLevel(conf.StacktraceLevel), conf.Encoding, conf.TimeFormat, conf.Skip, zapcore.NewMultiWriteSyncer(writers...))
+ zap.RedirectStdLog(lg)
+ return &LogX{logger: lg, atomLevel: lv}
+}
+
+// initial standard log, if you don't init, it will use default logger setting
+func InitDefaultLogger(cfg *LogConfig) {
+ var writers []zapcore.WriteSyncer
+ if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) {
+ writers = append(writers, os.Stdout)
+ }
+ if cfg.IsFileOut {
+ writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge))
+ }
+
+ lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...))
+ zap.RedirectStdLog(lg)
+ if cfg.AppName != "" {
+ lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称
+ }
+ l = &LogX{logger: lg, atomLevel: lv}
+}
+
+// create a new logger
+func NewLogger(cfg *LogConfig) *LogX {
+ var writers []zapcore.WriteSyncer
+ if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) {
+ writers = append(writers, os.Stdout)
+ }
+ if cfg.IsFileOut {
+ writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge))
+ }
+
+ lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...))
+ zap.RedirectStdLog(lg)
+ if cfg.AppName != "" {
+ lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称
+ }
+ return &LogX{logger: lg, atomLevel: lv}
+}
+
+// create a new zaplog logger
+func newZapLogger(level, stacktrace zapcore.Level, encoding, timeType string, skip int, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) {
+ encCfg := zapcore.EncoderConfig{
+ TimeKey: "T",
+ LevelKey: "L",
+ NameKey: "N",
+ CallerKey: "C",
+ MessageKey: "M",
+ StacktraceKey: "S",
+ LineEnding: zapcore.DefaultLineEnding,
+ EncodeCaller: zapcore.ShortCallerEncoder,
+ EncodeDuration: zapcore.NanosDurationEncoder,
+ EncodeLevel: zapcore.LowercaseLevelEncoder,
+ }
+ setTimeFormat(timeType, &encCfg) // set time type
+ atmLvl := zap.NewAtomicLevel() // set level
+ atmLvl.SetLevel(level)
+ encoder := zapcore.NewJSONEncoder(encCfg) // 确定encoder格式
+ if encoding == "console" {
+ encoder = zapcore.NewConsoleEncoder(encCfg)
+ }
+ return zap.New(zapcore.NewCore(encoder, output, atmLvl), zap.AddCaller(), zap.AddStacktrace(stacktrace), zap.AddCallerSkip(skip)), &atmLvl
+}
+
+// set log level
+func setLogLevel(lvl string) zapcore.Level {
+ switch strings.ToLower(lvl) {
+ case "panic":
+ return zapcore.PanicLevel
+ case "fatal":
+ return zapcore.FatalLevel
+ case "error":
+ return zapcore.ErrorLevel
+ case "warn", "warning":
+ return zapcore.WarnLevel
+ case "info":
+ return zapcore.InfoLevel
+ default:
+ return zapcore.DebugLevel
+ }
+}
+
+// set time format
+func setTimeFormat(timeType string, z *zapcore.EncoderConfig) {
+ switch strings.ToLower(timeType) {
+ case "iso": // iso8601 standard
+ z.EncodeTime = zapcore.ISO8601TimeEncoder
+ case "sec": // only for unix second, without millisecond
+ z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+ enc.AppendInt64(t.Unix())
+ }
+ case "second": // unix second, with millisecond
+ z.EncodeTime = zapcore.EpochTimeEncoder
+ case "milli", "millisecond": // millisecond
+ z.EncodeTime = zapcore.EpochMillisTimeEncoder
+ case "nano", "nanosecond": // nanosecond
+ z.EncodeTime = zapcore.EpochNanosTimeEncoder
+ default: // standard format
+ z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+ enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
+ }
+ }
+}
+
+func GetLevel() string {
+ switch l.atomLevel.Level() {
+ case zapcore.PanicLevel:
+ return "panic"
+ case zapcore.FatalLevel:
+ return "fatal"
+ case zapcore.ErrorLevel:
+ return "error"
+ case zapcore.WarnLevel:
+ return "warn"
+ case zapcore.InfoLevel:
+ return "info"
+ default:
+ return "debug"
+ }
+}
+
+func SetLevel(lvl string) {
+ l.atomLevel.SetLevel(setLogLevel(lvl))
+}
+
+// temporary add call skip
+func AddCallerSkip(skip int) *LogX {
+ l.logger.WithOptions(zap.AddCallerSkip(skip))
+ return l
+}
+
+// permanent add call skip
+func AddDepth(skip int) *LogX {
+ l.logger = l.logger.WithOptions(zap.AddCallerSkip(skip))
+ return l
+}
+
+// permanent add options
+func AddOptions(opts ...zap.Option) *LogX {
+ l.logger = l.logger.WithOptions(opts...)
+ return l
+}
+
+func AddField(k string, v interface{}) {
+ l.logger.With(zap.Any(k, v))
+}
+
+func AddFields(fields map[string]interface{}) *LogX {
+ for k, v := range fields {
+ l.logger.With(zap.Any(k, v))
+ }
+ return l
+}
+
+// Normal log
+func Debug(e interface{}, args ...interface{}) error {
+ return l.Debug(e, args...)
+}
+func Info(e interface{}, args ...interface{}) error {
+ return l.Info(e, args...)
+}
+func Warn(e interface{}, args ...interface{}) error {
+ return l.Warn(e, args...)
+}
+func Error(e interface{}, args ...interface{}) error {
+ return l.Error(e, args...)
+}
+func Panic(e interface{}, args ...interface{}) error {
+ return l.Panic(e, args...)
+}
+func Fatal(e interface{}, args ...interface{}) error {
+ return l.Fatal(e, args...)
+}
+
+// Format logs
+func Debugf(format string, args ...interface{}) error {
+ return l.Debugf(format, args...)
+}
+func Infof(format string, args ...interface{}) error {
+ return l.Infof(format, args...)
+}
+func Warnf(format string, args ...interface{}) error {
+ return l.Warnf(format, args...)
+}
+func Errorf(format string, args ...interface{}) error {
+ return l.Errorf(format, args...)
+}
+func Panicf(format string, args ...interface{}) error {
+ return l.Panicf(format, args...)
+}
+func Fatalf(format string, args ...interface{}) error {
+ return l.Fatalf(format, args...)
+}
+
+func formatFieldMap(m FieldMap) []Field {
+ var res []Field
+ for k, v := range m {
+ res = append(res, zap.Any(k, v))
+ }
+ return res
+}
diff --git a/app/utils/logx/output.go b/app/utils/logx/output.go
new file mode 100644
index 0000000..ef33f0b
--- /dev/null
+++ b/app/utils/logx/output.go
@@ -0,0 +1,105 @@
+package logx
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "path/filepath"
+ "time"
+
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+// output interface
+type WriteSyncer interface {
+ io.Writer
+ Sync() error
+}
+
+// split writer
+func NewRollingFile(dir, filename string, maxSize, MaxAge int) WriteSyncer {
+ s, err := os.Stat(dir)
+ if err != nil || !s.IsDir() {
+ os.RemoveAll(dir)
+ if err := os.MkdirAll(dir, 0766); err != nil {
+ panic(err)
+ }
+ }
+ return newLumberjackWriteSyncer(&lumberjack.Logger{
+ Filename: filepath.Join(dir, filename),
+ MaxSize: maxSize, // megabytes, MB
+ MaxAge: MaxAge, // days
+ LocalTime: true,
+ Compress: false,
+ })
+}
+
+type lumberjackWriteSyncer struct {
+ *lumberjack.Logger
+ buf *bytes.Buffer
+ logChan chan []byte
+ closeChan chan interface{}
+ maxSize int
+}
+
+func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer {
+ ws := &lumberjackWriteSyncer{
+ Logger: l,
+ buf: bytes.NewBuffer([]byte{}),
+ logChan: make(chan []byte, 5000),
+ closeChan: make(chan interface{}),
+ maxSize: 1024,
+ }
+ go ws.run()
+ return ws
+}
+
+func (l *lumberjackWriteSyncer) run() {
+ ticker := time.NewTicker(1 * time.Second)
+
+ for {
+ select {
+ case <-ticker.C:
+ if l.buf.Len() > 0 {
+ l.sync()
+ }
+ case bs := <-l.logChan:
+ _, err := l.buf.Write(bs)
+ if err != nil {
+ continue
+ }
+ if l.buf.Len() > l.maxSize {
+ l.sync()
+ }
+ case <-l.closeChan:
+ l.sync()
+ return
+ }
+ }
+}
+
+func (l *lumberjackWriteSyncer) Stop() {
+ close(l.closeChan)
+}
+
+func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) {
+ b := make([]byte, len(bs))
+ for i, c := range bs {
+ b[i] = c
+ }
+ l.logChan <- b
+ return 0, nil
+}
+
+func (l *lumberjackWriteSyncer) Sync() error {
+ return nil
+}
+
+func (l *lumberjackWriteSyncer) sync() error {
+ defer l.buf.Reset()
+ _, err := l.Logger.Write(l.buf.Bytes())
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/app/utils/logx/sugar.go b/app/utils/logx/sugar.go
new file mode 100644
index 0000000..ab380fc
--- /dev/null
+++ b/app/utils/logx/sugar.go
@@ -0,0 +1,192 @@
+package logx
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+
+ "go.uber.org/zap"
+)
+
+type LogX struct {
+ logger *zap.Logger
+ atomLevel *zap.AtomicLevel
+}
+
+type Field = zap.Field
+type FieldMap map[string]interface{}
+
+// 判断其他类型--start
+func getFields(msg string, format bool, args ...interface{}) (string, []Field) {
+ var str []interface{}
+ var fields []zap.Field
+ if len(args) > 0 {
+ for _, v := range args {
+ if f, ok := v.(Field); ok {
+ fields = append(fields, f)
+ } else if f, ok := v.(FieldMap); ok {
+ fields = append(fields, formatFieldMap(f)...)
+ } else {
+ str = append(str, AnyToString(v))
+ }
+ }
+ if format {
+ return fmt.Sprintf(msg, str...), fields
+ }
+ str = append([]interface{}{msg}, str...)
+ return fmt.Sprintln(str...), fields
+ }
+ return msg, []Field{}
+}
+
+func (l *LogX) Debug(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.Debug(msg, field...)
+ }
+ return e
+}
+func (l *LogX) Info(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.Info(msg, field...)
+ }
+ return e
+}
+func (l *LogX) Warn(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.Warn(msg, field...)
+ }
+ return e
+}
+func (l *LogX) Error(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.Error(msg, field...)
+ }
+ return e
+}
+func (l *LogX) DPanic(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.DPanic(msg, field...)
+ }
+ return e
+}
+func (l *LogX) Panic(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.Panic(msg, field...)
+ }
+ return e
+}
+func (l *LogX) Fatal(s interface{}, args ...interface{}) error {
+ es, e := checkErr(s)
+ if es != "" {
+ msg, field := getFields(es, false, args...)
+ l.logger.Fatal(msg, field...)
+ }
+ return e
+}
+
+func checkErr(s interface{}) (string, error) {
+ switch e := s.(type) {
+ case error:
+ return e.Error(), e
+ case string:
+ return e, errors.New(e)
+ case []byte:
+ return string(e), nil
+ default:
+ return "", nil
+ }
+}
+
+func (l *LogX) LogError(err error) error {
+ return l.Error(err.Error())
+}
+
+func (l *LogX) Debugf(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.Debug(s, f...)
+ return errors.New(s)
+}
+
+func (l *LogX) Infof(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.Info(s, f...)
+ return errors.New(s)
+}
+
+func (l *LogX) Warnf(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.Warn(s, f...)
+ return errors.New(s)
+}
+
+func (l *LogX) Errorf(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.Error(s, f...)
+ return errors.New(s)
+}
+
+func (l *LogX) DPanicf(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.DPanic(s, f...)
+ return errors.New(s)
+}
+
+func (l *LogX) Panicf(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.Panic(s, f...)
+ return errors.New(s)
+}
+
+func (l *LogX) Fatalf(msg string, args ...interface{}) error {
+ s, f := getFields(msg, true, args...)
+ l.logger.Fatal(s, f...)
+ return errors.New(s)
+}
+
+func AnyToString(raw interface{}) string {
+ switch i := raw.(type) {
+ case []byte:
+ return string(i)
+ case int:
+ return strconv.FormatInt(int64(i), 10)
+ case int64:
+ return strconv.FormatInt(i, 10)
+ case float32:
+ return strconv.FormatFloat(float64(i), 'f', 2, 64)
+ case float64:
+ return strconv.FormatFloat(i, 'f', 2, 64)
+ case uint:
+ return strconv.FormatInt(int64(i), 10)
+ case uint8:
+ return strconv.FormatInt(int64(i), 10)
+ case uint16:
+ return strconv.FormatInt(int64(i), 10)
+ case uint32:
+ return strconv.FormatInt(int64(i), 10)
+ case uint64:
+ return strconv.FormatInt(int64(i), 10)
+ case int8:
+ return strconv.FormatInt(int64(i), 10)
+ case int16:
+ return strconv.FormatInt(int64(i), 10)
+ case int32:
+ return strconv.FormatInt(int64(i), 10)
+ case string:
+ return i
+ case error:
+ return i.Error()
+ }
+ return fmt.Sprintf("%#v", raw)
+}
diff --git a/app/utils/map_and_struct.go b/app/utils/map_and_struct.go
new file mode 100644
index 0000000..34904ce
--- /dev/null
+++ b/app/utils/map_and_struct.go
@@ -0,0 +1,341 @@
+package utils
+
+import (
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+func Map2Struct(vals map[string]interface{}, dst interface{}) (err error) {
+ return Map2StructByTag(vals, dst, "json")
+}
+
+func Map2StructByTag(vals map[string]interface{}, dst interface{}, structTag string) (err error) {
+ defer func() {
+ e := recover()
+ if e != nil {
+ if v, ok := e.(error); ok {
+ err = fmt.Errorf("Panic: %v", v.Error())
+ } else {
+ err = fmt.Errorf("Panic: %v", e)
+ }
+ }
+ }()
+
+ pt := reflect.TypeOf(dst)
+ pv := reflect.ValueOf(dst)
+
+ if pv.Kind() != reflect.Ptr || pv.Elem().Kind() != reflect.Struct {
+ return fmt.Errorf("not a pointer of struct")
+ }
+
+ var f reflect.StructField
+ var ft reflect.Type
+ var fv reflect.Value
+
+ for i := 0; i < pt.Elem().NumField(); i++ {
+ f = pt.Elem().Field(i)
+ fv = pv.Elem().Field(i)
+ ft = f.Type
+
+ if f.Anonymous || !fv.CanSet() {
+ continue
+ }
+
+ tag := f.Tag.Get(structTag)
+
+ name, option := parseTag(tag)
+
+ if name == "-" {
+ continue
+ }
+
+ if name == "" {
+ name = strings.ToLower(f.Name)
+ }
+ val, ok := vals[name]
+
+ if !ok {
+ if option == "required" {
+ return fmt.Errorf("'%v' not found", name)
+ }
+ if len(option) != 0 {
+ val = option // default value
+ } else {
+ //fv.Set(reflect.Zero(ft)) // TODO set zero value or just ignore it?
+ continue
+ }
+ }
+
+ // convert or set value to field
+ vv := reflect.ValueOf(val)
+ vt := reflect.TypeOf(val)
+
+ if vt.Kind() != reflect.String {
+ // try to assign and convert
+ if vt.AssignableTo(ft) {
+ fv.Set(vv)
+ continue
+ }
+
+ if vt.ConvertibleTo(ft) {
+ fv.Set(vv.Convert(ft))
+ continue
+ }
+
+ return fmt.Errorf("value type not match: field=%v(%v) value=%v(%v)", f.Name, ft.Kind(), val, vt.Kind())
+ }
+ s := strings.TrimSpace(vv.String())
+ if len(s) == 0 && option == "required" {
+ return fmt.Errorf("value of required argument can't not be empty")
+ }
+ fk := ft.Kind()
+
+ // convert string to value
+ if fk == reflect.Ptr && ft.Elem().Kind() == reflect.String {
+ fv.Set(reflect.ValueOf(&s))
+ continue
+ }
+ if fk == reflect.Ptr || fk == reflect.Struct {
+ err = convertJsonValue(s, name, fv)
+ } else if fk == reflect.Slice {
+ err = convertSlice(s, f.Name, ft, fv)
+ } else {
+ err = convertValue(fk, s, f.Name, fv)
+ }
+
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
+ return nil
+}
+
+func Struct2Map(s interface{}) map[string]interface{} {
+ return Struct2MapByTag(s, "json")
+}
+func Struct2MapByTag(s interface{}, tagName string) map[string]interface{} {
+ t := reflect.TypeOf(s)
+ v := reflect.ValueOf(s)
+
+ if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct {
+ t = t.Elem()
+ v = v.Elem()
+ }
+
+ if v.Kind() != reflect.Struct {
+ return nil
+ }
+
+ m := make(map[string]interface{})
+
+ for i := 0; i < t.NumField(); i++ {
+ fv := v.Field(i)
+ ft := t.Field(i)
+
+ if !fv.CanInterface() {
+ continue
+ }
+
+ if ft.PkgPath != "" { // unexported
+ continue
+ }
+
+ var name string
+ var option string
+ tag := ft.Tag.Get(tagName)
+ if tag != "" {
+ ts := strings.Split(tag, ",")
+ if len(ts) == 1 {
+ name = ts[0]
+ } else if len(ts) > 1 {
+ name = ts[0]
+ option = ts[1]
+ }
+ if name == "-" {
+ continue // skip this field
+ }
+ if name == "" {
+ name = strings.ToLower(ft.Name)
+ }
+ if option == "omitempty" {
+ if isEmpty(&fv) {
+ continue // skip empty field
+ }
+ }
+ } else {
+ name = strings.ToLower(ft.Name)
+ }
+
+ if ft.Anonymous && fv.Kind() == reflect.Ptr && fv.IsNil() {
+ continue
+ }
+ if (ft.Anonymous && fv.Kind() == reflect.Struct) ||
+ (ft.Anonymous && fv.Kind() == reflect.Ptr && fv.Elem().Kind() == reflect.Struct) {
+
+ // embedded struct
+ embedded := Struct2MapByTag(fv.Interface(), tagName)
+ for embName, embValue := range embedded {
+ m[embName] = embValue
+ }
+ } else if option == "string" {
+ kind := fv.Kind()
+ if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 {
+ m[name] = strconv.FormatInt(fv.Int(), 10)
+ } else if kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 {
+ m[name] = strconv.FormatUint(fv.Uint(), 10)
+ } else if kind == reflect.Float32 || kind == reflect.Float64 {
+ m[name] = strconv.FormatFloat(fv.Float(), 'f', 2, 64)
+ } else {
+ m[name] = fv.Interface()
+ }
+ } else {
+ m[name] = fv.Interface()
+ }
+ }
+
+ return m
+}
+
+func isEmpty(v *reflect.Value) bool {
+ k := v.Kind()
+ if k == reflect.Bool {
+ return v.Bool() == false
+ } else if reflect.Int < k && k < reflect.Int64 {
+ return v.Int() == 0
+ } else if reflect.Uint < k && k < reflect.Uintptr {
+ return v.Uint() == 0
+ } else if k == reflect.Float32 || k == reflect.Float64 {
+ return v.Float() == 0
+ } else if k == reflect.Array || k == reflect.Map || k == reflect.Slice || k == reflect.String {
+ return v.Len() == 0
+ } else if k == reflect.Interface || k == reflect.Ptr {
+ return v.IsNil()
+ }
+ return false
+}
+
+func convertSlice(s string, name string, ft reflect.Type, fv reflect.Value) error {
+ var err error
+ et := ft.Elem()
+
+ if et.Kind() == reflect.Ptr || et.Kind() == reflect.Struct {
+ return convertJsonValue(s, name, fv)
+ }
+
+ ss := strings.Split(s, ",")
+
+ if len(s) == 0 || len(ss) == 0 {
+ return nil
+ }
+
+ fs := reflect.MakeSlice(ft, 0, len(ss))
+
+ for _, si := range ss {
+ ev := reflect.New(et).Elem()
+
+ err = convertValue(et.Kind(), si, name, ev)
+ if err != nil {
+ return err
+ }
+ fs = reflect.Append(fs, ev)
+ }
+
+ fv.Set(fs)
+
+ return nil
+}
+
+func convertJsonValue(s string, name string, fv reflect.Value) error {
+ var err error
+ d := StringToSlice(s)
+
+ if fv.Kind() == reflect.Ptr {
+ if fv.IsNil() {
+ fv.Set(reflect.New(fv.Type().Elem()))
+ }
+ } else {
+ fv = fv.Addr()
+ }
+
+ err = json.Unmarshal(d, fv.Interface())
+
+ if err != nil {
+ return fmt.Errorf("invalid json '%v': %v, %v", name, err.Error(), s)
+ }
+
+ return nil
+}
+
+func convertValue(kind reflect.Kind, s string, name string, fv reflect.Value) error {
+ if !fv.CanAddr() {
+ return fmt.Errorf("can not addr: %v", name)
+ }
+
+ if kind == reflect.String {
+ fv.SetString(s)
+ return nil
+ }
+
+ if kind == reflect.Bool {
+ switch s {
+ case "true":
+ fv.SetBool(true)
+ case "false":
+ fv.SetBool(false)
+ case "1":
+ fv.SetBool(true)
+ case "0":
+ fv.SetBool(false)
+ default:
+ return fmt.Errorf("invalid bool: %v value=%v", name, s)
+ }
+ return nil
+ }
+
+ if reflect.Int <= kind && kind <= reflect.Int64 {
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return fmt.Errorf("invalid int: %v value=%v", name, s)
+ }
+ fv.SetInt(i)
+
+ } else if reflect.Uint <= kind && kind <= reflect.Uint64 {
+ i, err := strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return fmt.Errorf("invalid int: %v value=%v", name, s)
+ }
+ fv.SetUint(i)
+
+ } else if reflect.Float32 == kind || kind == reflect.Float64 {
+ i, err := strconv.ParseFloat(s, 64)
+
+ if err != nil {
+ return fmt.Errorf("invalid float: %v value=%v", name, s)
+ }
+
+ fv.SetFloat(i)
+ } else {
+ // not support or just ignore it?
+ // return fmt.Errorf("type not support: field=%v(%v) value=%v(%v)", name, ft.Kind(), val, vt.Kind())
+ }
+ return nil
+}
+
+func parseTag(tag string) (string, string) {
+ tags := strings.Split(tag, ",")
+
+ if len(tags) <= 0 {
+ return "", ""
+ }
+
+ if len(tags) == 1 {
+ return tags[0], ""
+ }
+
+ return tags[0], tags[1]
+}
diff --git a/app/utils/map_to_map.go b/app/utils/map_to_map.go
new file mode 100644
index 0000000..3a68bad
--- /dev/null
+++ b/app/utils/map_to_map.go
@@ -0,0 +1,112 @@
+package utils
+
+import (
+ "math"
+)
+
+// WGS84坐标系:即地球坐标系,国际上通用的坐标系。
+// GCJ02坐标系:即火星坐标系,WGS84坐标系经加密后的坐标系。Google Maps,高德在用。
+// BD09坐标系:即百度坐标系,GCJ02坐标系经加密后的坐标系。
+
+const (
+ X_PI = math.Pi * 3000.0 / 180.0
+ OFFSET = 0.00669342162296594323
+ AXIS = 6378245.0
+)
+
+//BD09toGCJ02 百度坐标系->火星坐标系
+func BD09toGCJ02(lon, lat float64) (float64, float64) {
+ x := lon - 0.0065
+ y := lat - 0.006
+
+ z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*X_PI)
+ theta := math.Atan2(y, x) - 0.000003*math.Cos(x*X_PI)
+
+ gLon := z * math.Cos(theta)
+ gLat := z * math.Sin(theta)
+
+ return gLon, gLat
+}
+
+//GCJ02toBD09 火星坐标系->百度坐标系
+func GCJ02toBD09(lon, lat float64) (float64, float64) {
+ z := math.Sqrt(lon*lon+lat*lat) + 0.00002*math.Sin(lat*X_PI)
+ theta := math.Atan2(lat, lon) + 0.000003*math.Cos(lon*X_PI)
+
+ bdLon := z*math.Cos(theta) + 0.0065
+ bdLat := z*math.Sin(theta) + 0.006
+
+ return bdLon, bdLat
+}
+
+//WGS84toGCJ02 WGS84坐标系->火星坐标系
+func WGS84toGCJ02(lon, lat float64) (float64, float64) {
+ if isOutOFChina(lon, lat) {
+ return lon, lat
+ }
+
+ mgLon, mgLat := delta(lon, lat)
+
+ return mgLon, mgLat
+}
+
+//GCJ02toWGS84 火星坐标系->WGS84坐标系
+func GCJ02toWGS84(lon, lat float64) (float64, float64) {
+ if isOutOFChina(lon, lat) {
+ return lon, lat
+ }
+
+ mgLon, mgLat := delta(lon, lat)
+
+ return lon*2 - mgLon, lat*2 - mgLat
+}
+
+//BD09toWGS84 百度坐标系->WGS84坐标系
+func BD09toWGS84(lon, lat float64) (float64, float64) {
+ lon, lat = BD09toGCJ02(lon, lat)
+ return GCJ02toWGS84(lon, lat)
+}
+
+//WGS84toBD09 WGS84坐标系->百度坐标系
+func WGS84toBD09(lon, lat float64) (float64, float64) {
+ lon, lat = WGS84toGCJ02(lon, lat)
+ return GCJ02toBD09(lon, lat)
+}
+
+func delta(lon, lat float64) (float64, float64) {
+ dlat := transformlat(lon-105.0, lat-35.0)
+ dlon := transformlng(lon-105.0, lat-35.0)
+
+ radlat := lat / 180.0 * math.Pi
+ magic := math.Sin(radlat)
+ magic = 1 - OFFSET*magic*magic
+ sqrtmagic := math.Sqrt(magic)
+
+ dlat = (dlat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtmagic) * math.Pi)
+ dlon = (dlon * 180.0) / (AXIS / sqrtmagic * math.Cos(radlat) * math.Pi)
+
+ mgLat := lat + dlat
+ mgLon := lon + dlon
+
+ return mgLon, mgLat
+}
+
+func transformlat(lon, lat float64) float64 {
+ var ret = -100.0 + 2.0*lon + 3.0*lat + 0.2*lat*lat + 0.1*lon*lat + 0.2*math.Sqrt(math.Abs(lon))
+ ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
+ ret += (20.0*math.Sin(lat*math.Pi) + 40.0*math.Sin(lat/3.0*math.Pi)) * 2.0 / 3.0
+ ret += (160.0*math.Sin(lat/12.0*math.Pi) + 320*math.Sin(lat*math.Pi/30.0)) * 2.0 / 3.0
+ return ret
+}
+
+func transformlng(lon, lat float64) float64 {
+ var ret = 300.0 + lon + 2.0*lat + 0.1*lon*lon + 0.1*lon*lat + 0.1*math.Sqrt(math.Abs(lon))
+ ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
+ ret += (20.0*math.Sin(lon*math.Pi) + 40.0*math.Sin(lon/3.0*math.Pi)) * 2.0 / 3.0
+ ret += (150.0*math.Sin(lon/12.0*math.Pi) + 300.0*math.Sin(lon/30.0*math.Pi)) * 2.0 / 3.0
+ return ret
+}
+
+func isOutOFChina(lon, lat float64) bool {
+ return !(lon > 73.66 && lon < 135.05 && lat > 3.86 && lat < 53.55)
+}
diff --git a/app/utils/md5.go b/app/utils/md5.go
new file mode 100644
index 0000000..52c108d
--- /dev/null
+++ b/app/utils/md5.go
@@ -0,0 +1,12 @@
+package utils
+
+import (
+ "crypto/md5"
+ "encoding/hex"
+)
+
+func Md5(str string) string {
+ h := md5.New()
+ h.Write([]byte(str))
+ return hex.EncodeToString(h.Sum(nil))
+}
diff --git a/app/utils/open_platform.go b/app/utils/open_platform.go
new file mode 100644
index 0000000..b4f79da
--- /dev/null
+++ b/app/utils/open_platform.go
@@ -0,0 +1,135 @@
+package utils
+
+import (
+ "applet/app/cfg"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "sort"
+ "time"
+)
+
+// 调用开放平台的封装
+type OpenPlatformReqClient struct {
+ AppKey string
+ AppSecret string
+ Method string
+ Version string
+ Timestamp string
+ Nonce string
+ Sign string
+ BizData map[string]interface{}
+ params map[string]string
+ ReturnData ReturnDataResp
+}
+
+type ReturnDataResp struct {
+ Code int `json:"code"`
+ Msg string `json:"msg"`
+ Data interface{} `json:"data"`
+}
+
+func NewOpenPlatformReqClient(appKey, method, version, appSecret string, bizData map[string]interface{}) (*OpenPlatformReqClient, error) {
+ if appKey == "" || appSecret == "" || method == "" || version == "" {
+ return nil, errors.New("appKey,method,version not allow empty")
+ }
+ nowStr := AnyToString(time.Now().Unix())
+ nonce := UUIDString()
+ return &OpenPlatformReqClient{
+ AppKey: appKey,
+ AppSecret: appSecret,
+ Method: method,
+ Version: version,
+ Nonce: nonce,
+ Timestamp: nowStr,
+ params: map[string]string{"app_key": appKey, "method": method, "version": version, "timestamp": nowStr, "nonce": nonce},
+ BizData: bizData,
+ }, nil
+}
+
+func (client *OpenPlatformReqClient) CurlOpen() error {
+ if client.params == nil {
+ return errors.New("params not allow empty")
+ }
+ url := cfg.ZhiosOpen.URL + "/api/open/gw"
+ fmt.Printf("%#v\n", string(Serialize(client.params)))
+ resp, err := CurlPost(url, Serialize(client.params), nil)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(resp, &client.ReturnData)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+/*func (client *OpenPlatformReqClient) CreateParams() *OpenPlatformReqClient {
+ client.params["timestamp"] = client.Timestamp
+ client.params["nonce"] = client.Nonce
+ //client.params["biz_data"] = SerializeStr(client.BizData)
+ return client
+}*/
+
+func (client *OpenPlatformReqClient) SetBizDataToParams() *OpenPlatformReqClient {
+ client.params["biz_data"] = SerializeStr(client.BizData)
+ return client
+}
+
+func (client *OpenPlatformReqClient) SetParams(key, value string) *OpenPlatformReqClient {
+ client.params[key] = value
+ return client
+}
+
+func (client *OpenPlatformReqClient) GetParams(key string) string {
+ return client.params[key]
+}
+
+func (client *OpenPlatformReqClient) CreateSign() *OpenPlatformReqClient {
+ /*if client.BizData != nil {
+ for key := range client.BizData {
+ client.params[key] = AnyToString2(client.BizData[key])
+ }
+ }*/
+ var keys []string
+ for key := range client.params {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ str := ""
+ for _, key := range keys {
+ str += fmt.Sprintf("%v=%v&", key, client.params[key])
+ }
+ str = client.AppSecret + str[:len(str)-1] + client.AppSecret
+ fmt.Printf("sign: %s\n", str)
+ client.Sign = Md5(str)
+ client.params["sign"] = client.Sign
+ if client.BizData != nil {
+ for key := range client.BizData {
+ if _, ok := client.params[key]; ok {
+ delete(client.params, key)
+ }
+ }
+ }
+ return client
+}
+
+func (client *OpenPlatformReqClient) VerifySign(sign string) bool {
+ if sign == "" || client.Sign == "" {
+ return false
+ }
+ if client.Sign == sign {
+ return true
+ }
+ return false
+}
+
+func (client *OpenPlatformReqClient) ResetNonce() *OpenPlatformReqClient {
+ client.Nonce = UUIDString()
+ return client
+}
+
+func (client *OpenPlatformReqClient) ResetTimestamp() *OpenPlatformReqClient {
+ client.Timestamp = AnyToString(time.Now().Unix())
+ return client
+}
diff --git a/app/utils/qrcode/decodeFile.go b/app/utils/qrcode/decodeFile.go
new file mode 100644
index 0000000..f50fb28
--- /dev/null
+++ b/app/utils/qrcode/decodeFile.go
@@ -0,0 +1,33 @@
+package qrcode
+
+import (
+ "image"
+ _ "image/jpeg"
+ _ "image/png"
+ "os"
+
+ "github.com/makiuchi-d/gozxing"
+ "github.com/makiuchi-d/gozxing/qrcode"
+)
+
+func DecodeFile(fi string) (string, error) {
+ file, err := os.Open(fi)
+ if err != nil {
+ return "", err
+ }
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return "", err
+ }
+ // prepare BinaryBitmap
+ bmp, err := gozxing.NewBinaryBitmapFromImage(img)
+ if err != nil {
+ return "", err
+ }
+ // decode image
+ result, err := qrcode.NewQRCodeReader().Decode(bmp, nil)
+ if err != nil {
+ return "", err
+ }
+ return result.String(), nil
+}
diff --git a/app/utils/qrcode/getBase64.go b/app/utils/qrcode/getBase64.go
new file mode 100644
index 0000000..2d0fe75
--- /dev/null
+++ b/app/utils/qrcode/getBase64.go
@@ -0,0 +1,55 @@
+package qrcode
+
+// 生成登录二维码图片, 方便在网页上显示
+
+import (
+ "bytes"
+ "encoding/base64"
+ "image/jpeg"
+ "image/png"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/boombuler/barcode"
+ "github.com/boombuler/barcode/qr"
+)
+
+func GetJPGBase64(content string, edges ...int) string {
+ edgeLen := 300
+ if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 {
+ edgeLen = edges[0]
+ }
+ img, _ := qr.Encode(content, qr.L, qr.Unicode)
+ img, _ = barcode.Scale(img, edgeLen, edgeLen)
+
+ emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区
+ jpeg.Encode(emptyBuff, img, nil)
+ dist := make([]byte, 50000) // 开辟存储空间
+ base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64
+ return "data:image/png;base64," + string(dist) // 输出图片base64(type = []byte)
+}
+
+func GetPNGBase64(content string, edges ...int) string {
+ edgeLen := 300
+ if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 {
+ edgeLen = edges[0]
+ }
+ img, _ := qr.Encode(content, qr.L, qr.Unicode)
+ img, _ = barcode.Scale(img, edgeLen, edgeLen)
+
+ emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区
+ png.Encode(emptyBuff, img)
+ dist := make([]byte, 50000) // 开辟存储空间
+ base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64
+ return string(dist) // 输出图片base64(type = []byte)
+}
+func GetFileBase64(content string) string {
+ res, err := http.Get(content)
+ if err != nil {
+ return ""
+ }
+ defer res.Body.Close()
+ data, _ := ioutil.ReadAll(res.Body)
+ imageBase64 := base64.StdEncoding.EncodeToString(data)
+ return imageBase64
+}
diff --git a/app/utils/qrcode/saveFile.go b/app/utils/qrcode/saveFile.go
new file mode 100644
index 0000000..4854783
--- /dev/null
+++ b/app/utils/qrcode/saveFile.go
@@ -0,0 +1,85 @@
+package qrcode
+
+// 生成登录二维码图片
+
+import (
+ "errors"
+ "image"
+ "image/jpeg"
+ "image/png"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/boombuler/barcode"
+ "github.com/boombuler/barcode/qr"
+)
+
+func SaveJpegFile(filePath, content string, edges ...int) error {
+ edgeLen := 300
+ if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 {
+ edgeLen = edges[0]
+ }
+ img, _ := qr.Encode(content, qr.L, qr.Unicode)
+ img, _ = barcode.Scale(img, edgeLen, edgeLen)
+
+ return writeFile(filePath, img, "jpg")
+}
+
+func SavePngFile(filePath, content string, edges ...int) error {
+ edgeLen := 300
+ if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 {
+ edgeLen = edges[0]
+ }
+ img, _ := qr.Encode(content, qr.L, qr.Unicode)
+ img, _ = barcode.Scale(img, edgeLen, edgeLen)
+
+ return writeFile(filePath, img, "png")
+}
+
+func writeFile(filePath string, img image.Image, format string) error {
+ if err := createDir(filePath); err != nil {
+ return err
+ }
+ file, err := os.Create(filePath)
+ defer file.Close()
+ if err != nil {
+ return err
+ }
+ switch strings.ToLower(format) {
+ case "png":
+ err = png.Encode(file, img)
+ break
+ case "jpg":
+ err = jpeg.Encode(file, img, nil)
+ default:
+ return errors.New("format not accept")
+ }
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func createDir(filePath string) error {
+ var err error
+ // filePath, _ = filepath.Abs(filePath)
+ dirPath := filepath.Dir(filePath)
+ dirInfo, err := os.Stat(dirPath)
+ if err != nil {
+ if !os.IsExist(err) {
+ err = os.MkdirAll(dirPath, 0777)
+ if err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
+ } else {
+ if dirInfo.IsDir() {
+ return nil
+ }
+ return errors.New("directory is a file")
+ }
+ return nil
+}
diff --git a/app/utils/qrcode/writeWeb.go b/app/utils/qrcode/writeWeb.go
new file mode 100644
index 0000000..57e1e92
--- /dev/null
+++ b/app/utils/qrcode/writeWeb.go
@@ -0,0 +1,39 @@
+package qrcode
+
+import (
+ "bytes"
+ "image/jpeg"
+ "image/png"
+ "net/http"
+
+ "github.com/boombuler/barcode"
+ "github.com/boombuler/barcode/qr"
+)
+
+func WritePng(w http.ResponseWriter, content string, edges ...int) error {
+ edgeLen := 300
+ if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 {
+ edgeLen = edges[0]
+ }
+ img, _ := qr.Encode(content, qr.L, qr.Unicode)
+ img, _ = barcode.Scale(img, edgeLen, edgeLen)
+ buff := bytes.NewBuffer(nil)
+ png.Encode(buff, img)
+ w.Header().Set("Content-Type", "image/png")
+ _, err := w.Write(buff.Bytes())
+ return err
+}
+
+func WriteJpg(w http.ResponseWriter, content string, edges ...int) error {
+ edgeLen := 300
+ if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 {
+ edgeLen = edges[0]
+ }
+ img, _ := qr.Encode(content, qr.L, qr.Unicode)
+ img, _ = barcode.Scale(img, edgeLen, edgeLen)
+ buff := bytes.NewBuffer(nil)
+ jpeg.Encode(buff, img, nil)
+ w.Header().Set("Content-Type", "image/jpg")
+ _, err := w.Write(buff.Bytes())
+ return err
+}
diff --git a/app/utils/rand.go b/app/utils/rand.go
new file mode 100644
index 0000000..fd4bf25
--- /dev/null
+++ b/app/utils/rand.go
@@ -0,0 +1,77 @@
+package utils
+
+import (
+ crand "crypto/rand"
+ "fmt"
+ "math"
+ "math/big"
+ "math/rand"
+ "time"
+)
+
+func RandString(l int, c ...string) string {
+ var (
+ chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ str string
+ num *big.Int
+ )
+ if len(c) > 0 {
+ chars = c[0]
+ }
+ chrLen := int64(len(chars))
+ for len(str) < l {
+ num, _ = crand.Int(crand.Reader, big.NewInt(chrLen))
+ str += string(chars[num.Int64()])
+ }
+ return str
+}
+
+func RandNum() string {
+ seed := time.Now().UnixNano() + rand.Int63()
+ return fmt.Sprintf("%05v", rand.New(rand.NewSource(seed)).Int31n(1000000))
+}
+
+// x的y次方
+func RandPow(l int) string {
+ var i = "1"
+ for j := 0; j < l; j++ {
+ i += "0"
+ }
+ k := StrToInt64(i)
+ n := rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(k)
+ ls := "%0" + IntToStr(l) + "v"
+ str := fmt.Sprintf(ls, n)
+ //min := int(math.Pow10(l - 1))
+ //max := int(math.Pow10(l) - 1)
+ return str
+}
+func RandInt(min, max int) int {
+ if min >= max || min == 0 || max == 0 {
+ if max == 0 {
+ max = min
+ }
+ return max
+ }
+ return rand.Intn(max-min) + min
+}
+func RandInt1(min, max int) int {
+ rand.Seed(time.Now().UnixNano())
+ return rand.Intn(max-min+1) + min
+}
+
+func CalculateDistance(lat1, lng1, lat2, lng2 float64) float64 {
+ radius := 6371.0 // 地球半径(单位:公里)
+ dLat := DegToRad(lat2 - lat1)
+ dLng := DegToRad(lng2 - lng1)
+ a := math.Sin(dLat/2)*math.Sin(dLat/2) + math.Cos(DegToRad(lat1))*math.Cos(DegToRad(lat2))*math.Sin(dLng/2)*math.Sin(dLng/2)
+ c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
+ distance := radius * c
+
+ return distance
+}
+
+// 将角度转换为弧度
+func DegToRad(deg float64) float64 {
+ return deg * (math.Pi / 180)
+
+}
diff --git a/app/utils/redis.go b/app/utils/redis.go
new file mode 100644
index 0000000..6080e6e
--- /dev/null
+++ b/app/utils/redis.go
@@ -0,0 +1,32 @@
+package utils
+
+import (
+ "applet/app/utils/cache"
+ "fmt"
+ "github.com/gin-gonic/gin"
+)
+
+func ClearRedis(c *gin.Context) {
+ var str = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
+ key := fmt.Sprintf("%s:cfg_cache", c.GetString("mid"))
+ cache.Del(key)
+ key2 := fmt.Sprintf("%s:virtual_coin_cfg", c.GetString("mid"))
+ cache.Del(key2)
+ for _, v := range str {
+ key1 := fmt.Sprintf("%s:cfg_cache:%s", c.GetString("mid"), v)
+ cache.Del(key1)
+ }
+
+}
+func ClearRedisDb(dbname string) {
+ var str = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
+ key := fmt.Sprintf("%s:cfg_cache", dbname)
+ cache.Del(key)
+ key2 := fmt.Sprintf("%s:virtual_coin_cfg", dbname)
+ cache.Del(key2)
+ for _, v := range str {
+ key1 := fmt.Sprintf("%s:cfg_cache:%s", dbname, v)
+ cache.Del(key1)
+ }
+
+}
diff --git a/app/utils/rpc_client.go b/app/utils/rpc_client.go
new file mode 100644
index 0000000..4fc392a
--- /dev/null
+++ b/app/utils/rpc_client.go
@@ -0,0 +1,60 @@
+package utils
+
+import (
+ "applet/pkg/pb"
+ "context"
+ "fmt"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+ "strconv"
+ "time"
+)
+
+func GetBusinessIntClient(url, port string) pb.BusinessIntClient {
+ target := fmt.Sprintf("%s:%s", url, port)
+ conn, err := grpc.Dial(target, grpc.WithInsecure())
+ if err != nil {
+ fmt.Println(err)
+ return nil
+ }
+ return pb.NewBusinessIntClient(conn)
+}
+
+func GetBusinessExtClient(url, port string) pb.BusinessExtClient {
+ target := fmt.Sprintf("%s:%s", url, port)
+ conn, err := grpc.Dial(target, grpc.WithInsecure())
+ //defer conn.Close()
+ if err != nil {
+ fmt.Println(err)
+ return nil
+ }
+ return pb.NewBusinessExtClient(conn)
+}
+
+func GetLogicExtClient(url, port string) pb.LogicExtClient {
+ target := fmt.Sprintf("%s:%s", url, port)
+ conn, err := grpc.Dial(target, grpc.WithInsecure())
+ if err != nil {
+ fmt.Println(err)
+ return nil
+ }
+ return pb.NewLogicExtClient(conn)
+}
+
+func GetCtx(token, userId, deviceId, masterId string) context.Context {
+ if userId == "" {
+ userId = "1"
+ }
+ if deviceId == "" {
+ deviceId = "1"
+ }
+ if token == "" {
+ token = "0"
+ }
+ return metadata.NewOutgoingContext(context.TODO(), metadata.Pairs(
+ "user_id", userId,
+ "device_id", deviceId,
+ "token", token,
+ "master_id", masterId,
+ "request_id", strconv.FormatInt(time.Now().UnixNano(), 10)))
+}
diff --git a/app/utils/rsa.go b/app/utils/rsa.go
new file mode 100644
index 0000000..02f0386
--- /dev/null
+++ b/app/utils/rsa.go
@@ -0,0 +1,231 @@
+package utils
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+)
+
+// 生成私钥文件 TODO 未指定路径
+func RsaKeyGen(bits int) error {
+ privateKey, err := rsa.GenerateKey(rand.Reader, bits)
+ if err != nil {
+ return err
+ }
+ derStream := x509.MarshalPKCS1PrivateKey(privateKey)
+ block := &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: derStream,
+ }
+ priFile, err := os.Create("private.pem")
+ if err != nil {
+ return err
+ }
+ err = pem.Encode(priFile, block)
+ priFile.Close()
+ if err != nil {
+ return err
+ }
+ // 生成公钥文件
+ publicKey := &privateKey.PublicKey
+ derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
+ if err != nil {
+ return err
+ }
+ block = &pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: derPkix,
+ }
+ pubFile, err := os.Create("public.pem")
+ if err != nil {
+ return err
+ }
+ err = pem.Encode(pubFile, block)
+ pubFile.Close()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// 生成私钥文件, 返回 privateKey , publicKey, error
+func RsaKeyGenText(bits int) (string, string, error) { // bits 字节位 1024/2048
+ privateKey, err := rsa.GenerateKey(rand.Reader, bits)
+ if err != nil {
+ return "", "", err
+ }
+ derStream := x509.MarshalPKCS1PrivateKey(privateKey)
+ block := &pem.Block{
+ Type: "RSA PRIVATE KEY",
+ Bytes: derStream,
+ }
+ priBuff := bytes.NewBuffer(nil)
+ err = pem.Encode(priBuff, block)
+ if err != nil {
+ return "", "", err
+ }
+ // 生成公钥文件
+ publicKey := &privateKey.PublicKey
+ derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
+ if err != nil {
+ return "", "", err
+ }
+ block = &pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: derPkix,
+ }
+ pubBuff := bytes.NewBuffer(nil)
+ err = pem.Encode(pubBuff, block)
+ if err != nil {
+ return "", "", err
+ }
+ return priBuff.String(), pubBuff.String(), nil
+}
+
+// 加密
+func RsaEncrypt(rawData, publicKey []byte) ([]byte, error) {
+ block, _ := pem.Decode(publicKey)
+ if block == nil {
+ return nil, errors.New("public key error")
+ }
+ pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ return nil, err
+ }
+ pub := pubInterface.(*rsa.PublicKey)
+ return rsa.EncryptPKCS1v15(rand.Reader, pub, rawData)
+}
+
+// 公钥加密
+func RsaEncrypts(data, keyBytes []byte) []byte {
+ //解密pem格式的公钥
+ block, _ := pem.Decode(keyBytes)
+ if block == nil {
+ panic(errors.New("public key error"))
+ }
+ // 解析公钥
+ pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ panic(err)
+ }
+ // 类型断言
+ pub := pubInterface.(*rsa.PublicKey)
+ //加密
+ ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data)
+ if err != nil {
+ panic(err)
+ }
+ return ciphertext
+}
+
+// 解密
+func RsaDecrypt(cipherText, privateKey []byte) ([]byte, error) {
+ block, _ := pem.Decode(privateKey)
+ if block == nil {
+ return nil, errors.New("private key error")
+ }
+ priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return nil, err
+ }
+ return rsa.DecryptPKCS1v15(rand.Reader, priv, cipherText)
+}
+func SyRsaEncrypt(signStr, signature, privateKeyBytes string) error {
+ h := sha256.New()
+ h.Write([]byte(signStr))
+ d := h.Sum(nil)
+ sign, _ := base64.StdEncoding.DecodeString(signature)
+ pub, err := ParsePKIXPublicKey(privateKeyBytes)
+ err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, d, sign)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+func FormatPublicKey(publicKey string) (pKey string) {
+ var buffer strings.Builder
+ buffer.WriteString("-----BEGIN PUBLIC KEY-----\n")
+ rawLen := 64
+ keyLen := len(publicKey)
+ raws := keyLen / rawLen
+ temp := keyLen % rawLen
+ if temp > 0 {
+ raws++
+ }
+ start := 0
+ end := start + rawLen
+ for i := 0; i < raws; i++ {
+ if i == raws-1 {
+ buffer.WriteString(publicKey[start:])
+ } else {
+ buffer.WriteString(publicKey[start:end])
+ }
+ buffer.WriteByte('\n')
+ start += rawLen
+ end = start + rawLen
+ }
+ buffer.WriteString("-----END PUBLIC KEY-----\n")
+ pKey = buffer.String()
+ return
+}
+
+func ParsePKIXPublicKey(privateKey string) (*rsa.PublicKey, error) {
+
+ privateKey = FormatPublicKey(privateKey)
+ // 2、解码私钥字节,生成加密对象
+ block, _ := pem.Decode([]byte(privateKey))
+ if block == nil {
+
+ return nil, errors.New("私钥信息错误!")
+ }
+ // 3、解析DER编码的私钥,生成私钥对象
+ priKey, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+
+ return nil, err
+ }
+ return priKey.(*rsa.PublicKey), nil
+}
+
+// 从证书获取公钥
+func OpensslPemGetPublic(pathOrString string) (interface{}, error) {
+ var certPem []byte
+ var err error
+ if IsFile(pathOrString) && Exists(pathOrString) {
+ certPem, err = ioutil.ReadFile(pathOrString)
+ if err != nil {
+ return nil, err
+ }
+ if string(certPem) == "" {
+ return nil, errors.New("empty pem file")
+ }
+ } else {
+ if pathOrString == "" {
+ return nil, errors.New("empty pem string")
+ }
+ certPem = StringToSlice(pathOrString)
+ }
+ block, rest := pem.Decode(certPem)
+ if block == nil || block.Type != "PUBLIC KEY" {
+ //log.Fatal("failed to decode PEM block containing public key")
+ return nil, errors.New("failed to decode PEM block containing public key")
+ }
+ pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("Got a %T, with remaining data: %q", pub, rest)
+ return pub, nil
+}
diff --git a/app/utils/serialize.go b/app/utils/serialize.go
new file mode 100644
index 0000000..1ac4d80
--- /dev/null
+++ b/app/utils/serialize.go
@@ -0,0 +1,23 @@
+package utils
+
+import (
+ "encoding/json"
+)
+
+func Serialize(data interface{}) []byte {
+ res, err := json.Marshal(data)
+ if err != nil {
+ return []byte{}
+ }
+ return res
+}
+
+func Unserialize(b []byte, dst interface{}) {
+ if err := json.Unmarshal(b, dst); err != nil {
+ dst = nil
+ }
+}
+
+func SerializeStr(data interface{}, arg ...interface{}) string {
+ return string(Serialize(data))
+}
diff --git a/app/utils/shuffle.go b/app/utils/shuffle.go
new file mode 100644
index 0000000..2c845a8
--- /dev/null
+++ b/app/utils/shuffle.go
@@ -0,0 +1,48 @@
+package utils
+
+import (
+ "math/rand"
+ "time"
+)
+
+// 打乱随机字符串
+func ShuffleString(s *string) {
+ if len(*s) > 1 {
+ b := []byte(*s)
+ rand.Seed(time.Now().UnixNano())
+ rand.Shuffle(len(b), func(x, y int) {
+ b[x], b[y] = b[y], b[x]
+ })
+ *s = string(b)
+ }
+}
+
+// 打乱随机slice
+func ShuffleSliceBytes(b []byte) {
+ if len(b) > 1 {
+ rand.Seed(time.Now().UnixNano())
+ rand.Shuffle(len(b), func(x, y int) {
+ b[x], b[y] = b[y], b[x]
+ })
+ }
+}
+
+// 打乱slice int
+func ShuffleSliceInt(i []int) {
+ if len(i) > 1 {
+ rand.Seed(time.Now().UnixNano())
+ rand.Shuffle(len(i), func(x, y int) {
+ i[x], i[y] = i[y], i[x]
+ })
+ }
+}
+
+// 打乱slice interface
+func ShuffleSliceInterface(i []interface{}) {
+ if len(i) > 1 {
+ rand.Seed(time.Now().UnixNano())
+ rand.Shuffle(len(i), func(x, y int) {
+ i[x], i[y] = i[y], i[x]
+ })
+ }
+}
diff --git a/app/utils/sign_check.go b/app/utils/sign_check.go
new file mode 100644
index 0000000..5c80ab1
--- /dev/null
+++ b/app/utils/sign_check.go
@@ -0,0 +1,216 @@
+package utils
+
+import (
+ "applet/app/cfg"
+ "applet/app/utils/logx"
+ "fmt"
+ "github.com/forgoer/openssl"
+ "github.com/gin-gonic/gin"
+ "github.com/syyongx/php2go"
+ "strings"
+ "time"
+)
+
+var publicKey = []byte(`-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFQD7RL2tDNuwdg0jTfV0zjAzh
+WoCWfGrcNiucy2XUHZZU2oGhHv1N10qu3XayTDD4pu4sJ73biKwqR6ZN7IS4Sfon
+vrzaXGvrTG4kmdo3XrbrkzmyBHDLTsJvv6pyS2HPl9QPSvKDN0iJ66+KN8QjBpw1
+FNIGe7xbDaJPY733/QIDAQAB
+-----END PUBLIC KEY-----`)
+
+var privateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCFQD7RL2tDNuwdg0jTfV0zjAzhWoCWfGrcNiucy2XUHZZU2oGh
+Hv1N10qu3XayTDD4pu4sJ73biKwqR6ZN7IS4SfonvrzaXGvrTG4kmdo3Xrbrkzmy
+BHDLTsJvv6pyS2HPl9QPSvKDN0iJ66+KN8QjBpw1FNIGe7xbDaJPY733/QIDAQAB
+AoGADi14wY8XDY7Bbp5yWDZFfV+QW0Xi2qAgSo/k8gjeK8R+I0cgdcEzWF3oz1Q2
+9d+PclVokAAmfj47e0AmXLImqMCSEzi1jDBUFIRoJk9WE1YstE94mrCgV0FW+N/u
++L6OgZcjmF+9dHKprnpaUGQuUV5fF8j0qp8S2Jfs3Sw+dOECQQCQnHALzFjmXXIR
+Ez3VSK4ZoYgDIrrpzNst5Hh6AMDNZcG3CrCxlQrgqjgTzBSr3ZSavvkfYRj42STk
+TqyX1tQFAkEA6+O6UENoUTk2lG7iO/ta7cdIULnkTGwQqvkgLIUjk6w8E3sBTIfw
+rerTEmquw5F42HHE+FMrRat06ZN57lENmQJAYgUHlZevcoZIePZ35Qfcqpbo4Gc8
+Fpm6vwKr/tZf2Vlt0qo2VkhWFS6L0C92m4AX6EQmDHT+Pj7BWNdS+aCuGQJBAOkq
+NKPZvWdr8jNOV3mKvxqB/U0uMigIOYGGtvLKt5vkh42J7ILFbHW8w95UbWMKjDUG
+X/hF3WQEUo//Imsa2yECQHSZIpJxiTRueoDiyRt0LH+jdbYFUu/6D0UIYXhFvP/p
+EZX+hfCfUnNYX59UVpRjSZ66g0CbCjuBPOhmOD+hDeQ=
+-----END RSA PRIVATE KEY-----`)
+
+func GetApiVersion(c *gin.Context) int {
+ var apiVersion = c.GetHeader("apiVersion")
+ if StrToInt(apiVersion) == 0 { //没有版本号先不校验
+ apiVersion = c.GetHeader("Apiversion")
+ }
+ if StrToInt(apiVersion) == 0 { //没有版本号先不校验
+ apiVersion = c.GetHeader("api_version")
+ }
+ if StrToInt(apiVersion) == 0 { //没有版本号先不校验
+ apiVersion = c.GetString("apiVersion")
+ }
+ if StrToInt(apiVersion) == 0 {
+ platform := c.GetHeader("platform")
+ if InArr(platform, []string{"ios", "android"}) == false && c.GetString("h5_applet_must_sign") == "1" {
+ apiVersion = "1"
+ }
+ if InArr(platform, []string{"android"}) && c.GetString("android_must_sign") == "1" {
+ apiVersion = "1"
+ }
+ if InArr(platform, []string{"ios"}) && c.GetString("ios_must_sign") == "1" {
+ apiVersion = "1"
+ }
+ }
+ if c.GetString("api_version") == "1" && cfg.Prd {
+ apiVersion = "1"
+ }
+ if (strings.Contains(c.Request.Host, "zhios-app") || strings.Contains(c.Request.Host, "api.zhios.cn")) && apiVersion == "1" {
+ apiVersion = "0"
+ c.Set("api_version", "0")
+ }
+
+ //if InArr(c.GetHeader("platform"), []string{"ios", "android"}) {
+ // apiVersion = "0"
+ //}
+ var uri = c.Request.RequestURI
+ if InArr(c.GetHeader("platform"), []string{"ios", "android", "pc"}) { //不用签名的接口
+ var filterList = []string{
+ "/api/v1/appcheck",
+ "/api/v1/app/guide",
+ "/api/v1/new/config.json",
+ "pub.flutter.web_download_page",
+ }
+ for _, v := range filterList {
+ if strings.Contains(uri, v) {
+ apiVersion = "0"
+ }
+ }
+ }
+ return StrToInt(apiVersion)
+}
+func CheckUri(c *gin.Context) int {
+ apiVersion := "1"
+ //var uri = c.Request.RequestURI
+ if InArr(c.GetHeader("platform"), []string{"ios", "android"}) { //不用签名的接口
+ //var filterList = []string{
+ // "/api/v1/appcheck",
+ // "/api/v1/app/guide",
+ // "/api/v1/new/config.json",
+ // "api/v1/rec",
+ // "api/v1/custom/mod/",
+ // "api/v1/mod/",
+ // "api/v1/s/",
+ //}
+ //for _, v := range filterList {
+ // if strings.Contains(uri, v) {
+ // apiVersion = "0"
+ // }
+ //}
+ apiVersion = "0"
+ }
+ return StrToInt(apiVersion)
+}
+
+// 签名校验
+func SignCheck(c *gin.Context) bool {
+ var apiVersion = GetApiVersion(c)
+ if apiVersion == 0 { //没有版本号先不校验
+ return true
+ }
+ //1.通过rsa 解析出 aes
+ var key = c.GetHeader("key")
+
+ //拼接对应参数
+ var uri = c.Request.RequestURI
+ var query = GetQueryParam(uri)
+ fmt.Println(query)
+ query["timestamp"] = c.GetHeader("timestamp")
+ query["nonce"] = c.GetHeader("nonce")
+ query["key"] = key
+ token := c.GetHeader("Authorization")
+ if token != "" {
+ // 按空格分割
+ parts := strings.SplitN(token, " ", 2)
+ if len(parts) == 2 && parts[0] == "Bearer" {
+ token = parts[1]
+ }
+ }
+ query["token"] = token
+ //2.query参数按照 ASCII 码从小到大排序
+ str := JoinStringsInASCII(query, "&", false, false, "")
+ //3.拼上密钥
+ secret := ""
+ if InArr(c.GetHeader("platform"), []string{"android", "ios"}) {
+ secret = c.GetString("app_api_secret_key")
+ } else if c.GetHeader("platform") == "wap" {
+ secret = c.GetString("h5_api_secret_key")
+ } else {
+ secret = c.GetString("applet_api_secret_key")
+ }
+
+ str = fmt.Sprintf("%s&secret=%s", str, secret)
+ fmt.Println(str)
+ //4.md5加密 转小写
+ sign := strings.ToLower(Md5(str))
+ //5.判断跟前端传来的sign是否一致
+ if sign != c.GetHeader("sign") {
+ return false
+ }
+
+ if StrToInt64(query["timestamp"])/1000 < time.Now().Unix()-300 {
+ fmt.Println("============" + query["timestamp"])
+ return false
+ }
+ //if query["nonce"] != "" {
+ // //TODO s
+ // getString, err := cache.GetString(query["nonce"])
+ // if err != nil {
+ // fmt.Println("nonce", err)
+ // }
+ // if getString != "" {
+ // fmt.Println("nonce", "============"+getString)
+ // return false
+ // } else {
+ // cache.SetEx(query["nonce"], "1", 300)
+ // }
+ //}
+ return true
+}
+
+func ResultAes(c *gin.Context, raw []byte) string {
+ var key = c.GetHeader("key")
+ base, _ := php2go.Base64Decode(key)
+ aes, err := RsaDecrypt([]byte(base), privateKey)
+ if err != nil {
+ logx.Info(err)
+ return ""
+ }
+ fmt.Println("============aes============")
+ fmt.Println(string(aes))
+ fmt.Println(string(raw))
+ str, _ := openssl.AesECBEncrypt(raw, aes, openssl.PKCS7_PADDING)
+ value := php2go.Base64Encode(string(str))
+ fmt.Println(value)
+
+ return value
+}
+
+func ResultAesDecrypt(c *gin.Context, raw string) string {
+ var key = c.GetHeader("key")
+ if key == "" {
+ key = c.GetHeader("Key")
+ }
+ fmt.Println("验签", key)
+ base, _ := php2go.Base64Decode(key)
+ aes, err := RsaDecrypt([]byte(base), privateKey)
+ if err != nil {
+ logx.Info(err)
+ return ""
+ }
+ raw = strings.ReplaceAll(raw, "\"", "")
+ fmt.Println(raw)
+ value1, _ := php2go.Base64Decode(raw)
+ if value1 == "" {
+ return ""
+ }
+ str1, _ := openssl.AesECBDecrypt([]byte(value1), aes, openssl.PKCS7_PADDING)
+ fmt.Println("==========解码=========")
+ fmt.Println(string(str1))
+ return string(str1)
+}
diff --git a/app/utils/slice.go b/app/utils/slice.go
new file mode 100644
index 0000000..fd86081
--- /dev/null
+++ b/app/utils/slice.go
@@ -0,0 +1,13 @@
+package utils
+
+// ContainsString is 字符串是否包含在字符串切片里
+func ContainsString(array []string, val string) (index int) {
+ index = -1
+ for i := 0; i < len(array); i++ {
+ if array[i] == val {
+ index = i
+ return
+ }
+ }
+ return
+}
diff --git a/app/utils/slice_and_string.go b/app/utils/slice_and_string.go
new file mode 100644
index 0000000..3ae6946
--- /dev/null
+++ b/app/utils/slice_and_string.go
@@ -0,0 +1,47 @@
+package utils
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "unsafe"
+)
+
+// string与slice互转,零copy省内存
+
+// zero copy to change slice to string
+func Slice2String(b []byte) (s string) {
+ pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
+ pString := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ pString.Data = pBytes.Data
+ pString.Len = pBytes.Len
+ return
+}
+
+// no copy to change string to slice
+func StringToSlice(s string) (b []byte) {
+ pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
+ pString := (*reflect.StringHeader)(unsafe.Pointer(&s))
+ pBytes.Data = pString.Data
+ pBytes.Len = pString.Len
+ pBytes.Cap = pString.Len
+ return
+}
+
+// 任意slice合并
+func SliceJoin(sep string, elems ...interface{}) string {
+ l := len(elems)
+ if l == 0 {
+ return ""
+ }
+ if l == 1 {
+ s := fmt.Sprint(elems[0])
+ sLen := len(s) - 1
+ if s[0] == '[' && s[sLen] == ']' {
+ return strings.Replace(s[1:sLen], " ", sep, -1)
+ }
+ return s
+ }
+ sep = strings.Replace(fmt.Sprint(elems), " ", sep, -1)
+ return sep[1 : len(sep)-1]
+}
diff --git a/app/utils/string.go b/app/utils/string.go
new file mode 100644
index 0000000..adae62e
--- /dev/null
+++ b/app/utils/string.go
@@ -0,0 +1,223 @@
+package utils
+
+import (
+ "code.fnuoos.com/go_rely_warehouse/zyos_go_order_relate_rule.git/lib/comm_plan"
+ "fmt"
+ "github.com/syyongx/php2go"
+ "math/rand"
+ "reflect"
+ "regexp"
+ "sort"
+ "strings"
+ "unicode"
+)
+
+func Implode(glue string, args ...interface{}) string {
+ data := make([]string, len(args))
+ for i, s := range args {
+ data[i] = fmt.Sprint(s)
+ }
+ return strings.Join(data, glue)
+}
+func RremoveChinese(s string) string {
+ // 正则表达式匹配中文字符
+ re := regexp.MustCompile("[\u4e00-\u9fa5]")
+ // 将所有中文字符替换为空字符串
+ s = re.ReplaceAllString(s, "")
+ return s
+}
+
+// 字符串是否在数组里
+func InArr(target string, strArray []string) bool {
+ for _, element := range strArray {
+ if target == element {
+ return true
+ }
+ }
+ return false
+}
+func ContainsDigitOrLetter(s string) bool {
+ reg, err := regexp.Compile("[0-9a-zA-Z]")
+ if err != nil {
+ // 处理正则表达式编译错误
+ return false
+ }
+ return reg.MatchString(s)
+}
+
+// 生成指定长度的字符串
+func RandStringBytes(n int) string {
+ const letterBytes = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ b := make([]byte, n)
+ for i := range b {
+ b[i] = letterBytes[rand.Intn(len(letterBytes))]
+ }
+ return string(b)
+}
+
+// 把数组的值放到key里
+func ArrayColumn(array interface{}, key string) (result map[string]interface{}, err error) {
+ result = make(map[string]interface{})
+ t := reflect.TypeOf(array)
+ v := reflect.ValueOf(array)
+ if t.Kind() != reflect.Slice {
+ return nil, nil
+ }
+ if v.Len() == 0 {
+ return nil, nil
+ }
+ for i := 0; i < v.Len(); i++ {
+ indexv := v.Index(i)
+ if indexv.Type().Kind() != reflect.Struct {
+ return nil, nil
+ }
+ mapKeyInterface := indexv.FieldByName(key)
+ if mapKeyInterface.Kind() == reflect.Invalid {
+ return nil, nil
+ }
+ mapKeyString, err := InterfaceToString(mapKeyInterface.Interface())
+ if err != nil {
+ return nil, err
+ }
+ result[mapKeyString] = indexv.Interface()
+ }
+ return result, err
+}
+
+// 转string
+func InterfaceToString(v interface{}) (result string, err error) {
+ switch reflect.TypeOf(v).Kind() {
+ case reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
+ result = fmt.Sprintf("%v", v)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ result = fmt.Sprintf("%v", v)
+ case reflect.String:
+ result = v.(string)
+ default:
+ err = nil
+ }
+ return result, err
+}
+
+func HideTrueName(name string) string {
+ res := "**"
+ if name != "" {
+ runs := []rune(name)
+ leng := len(runs)
+ if leng <= 3 {
+ res = string(runs[0:1]) + res
+ } else if leng < 5 {
+ res = string(runs[0:2]) + res
+ } else if leng < 10 {
+ res = string(runs[0:2]) + "***" + string(runs[leng-2:leng])
+ } else if leng < 16 {
+ res = string(runs[0:3]) + "****" + string(runs[leng-3:leng])
+ } else {
+ res = string(runs[0:4]) + "*****" + string(runs[leng-4:leng])
+ }
+ }
+ return res
+}
+
+// 是否有中文
+func IsChineseChar(str string) bool {
+ for _, r := range str {
+ if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
+ return true
+ }
+ }
+ return false
+}
+
+// 清空前面是0的
+func LeadZeros(str string) string {
+ bytes := []byte(str)
+ var index int
+ for i, b := range bytes {
+ if b != byte(48) {
+ index = i
+ break
+ }
+ }
+ i := bytes[index:len(bytes)]
+ return string(i)
+}
+func GetQueryParam(uri string) map[string]string {
+ //根据问号分割路由还是query参数
+ uriList := strings.Split(uri, "?")
+ var query = make(map[string]string, 0)
+ //有参数才处理
+ if len(uriList) == 2 {
+ //分割query参数
+ var queryList = strings.Split(uriList[1], "&")
+ if len(queryList) > 0 {
+ //key value 分别赋值
+ for _, v := range queryList {
+ var valueList = strings.Split(v, "=")
+ if len(valueList) == 2 {
+ value, _ := php2go.URLDecode(valueList[1])
+ if value == "" {
+ value = valueList[1]
+ }
+ query[valueList[0]] = value
+ }
+ }
+ }
+ }
+ return query
+}
+
+// JoinStringsInASCII 按照规则,参数名ASCII码从小到大排序后拼接
+// data 待拼接的数据
+// sep 连接符
+// onlyValues 是否只包含参数值,true则不包含参数名,否则参数名和参数值均有
+// includeEmpty 是否包含空值,true则包含空值,否则不包含,注意此参数不影响参数名的存在
+// exceptKeys 被排除的参数名,不参与排序及拼接
+func JoinStringsInASCII(data map[string]string, sep string, onlyValues, includeEmpty bool, exceptKeys ...string) string {
+ var list []string
+ var keyList []string
+ m := make(map[string]int)
+ if len(exceptKeys) > 0 {
+ for _, except := range exceptKeys {
+ m[except] = 1
+ }
+ }
+ for k := range data {
+ if _, ok := m[k]; ok {
+ continue
+ }
+ value := data[k]
+ if !includeEmpty && value == "" {
+ continue
+ }
+ if onlyValues {
+ keyList = append(keyList, k)
+ } else {
+ list = append(list, fmt.Sprintf("%s=%s", k, value))
+ }
+ }
+ if onlyValues {
+ sort.Strings(keyList)
+ for _, v := range keyList {
+ list = append(list, AnyToString(data[v]))
+ }
+ } else {
+ sort.Strings(list)
+ }
+ return strings.Join(list, sep)
+}
+
+// 手机号中间4位替换为*号
+func FormatMobileStar(mobile string) string {
+ if len(mobile) <= 10 {
+ return mobile
+ }
+ return mobile[:3] + "****" + mobile[7:]
+}
+func ConvertList2Map(a []*comm_plan.VirtualCoinCommission) (b map[string]float64) {
+ b = make(map[string]float64)
+ for _, i := range a {
+ b[i.Cid] = i.Val
+ }
+ return b
+}
diff --git a/app/utils/struct2UrlParams.go b/app/utils/struct2UrlParams.go
new file mode 100644
index 0000000..7f8ac05
--- /dev/null
+++ b/app/utils/struct2UrlParams.go
@@ -0,0 +1,43 @@
+package utils
+
+import "sort"
+
+func Struct2UrlParams(obj interface{}) string {
+ var str = ""
+ mapVal := Struct2Map(obj)
+ var keys []string
+ for key := range mapVal {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ str += key + "=" + AnyToString(mapVal[key]) + "&"
+ }
+ /*t := reflect.TypeOf(origin)
+ v := reflect.ValueOf(origin)
+
+ for i := 0; i < t.NumField(); i++ {
+ tag := strings.ToLower(t.Field(i).Tag.Get("json"))
+ if tag != "sign" {
+ switch v.Field(i).Kind(){
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ str += tag + "=" + utils.Int64ToStr(v.Field(i).Int()) + "&"
+ break
+ case reflect.String:
+ str += tag + "=" + v.Field(i).String() + "&"
+ break
+ case reflect.Bool:
+ str += tag + "=" + utils.BoolToStr(v.Field(i).Bool()) + "&"
+ break
+ case reflect.Float32, reflect.Float64:
+ str += tag + "=" + utils.Float64ToStr(v.Field(i).Float())
+ break
+ case reflect.Array, reflect.Struct, reflect.Slice:
+ str += ToUrlKeyValue(v.Field(i).Interface())
+ default:
+ break
+ }
+ }
+ }*/
+ return str
+}
diff --git a/app/utils/time.go b/app/utils/time.go
new file mode 100644
index 0000000..9c0f6f8
--- /dev/null
+++ b/app/utils/time.go
@@ -0,0 +1,239 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+func StrToTime(s string) (int64, error) {
+ // delete all not int characters
+ if s == "" {
+ return time.Now().Unix(), nil
+ }
+ r := make([]rune, 14)
+ l := 0
+ // 过滤除数字以外的字符
+ for _, v := range s {
+ if '0' <= v && v <= '9' {
+ r[l] = v
+ l++
+ if l == 14 {
+ break
+ }
+ }
+ }
+ for l < 14 {
+ r[l] = '0' // 补0
+ l++
+ }
+ t, err := time.Parse("20060102150405", string(r))
+ if err != nil {
+ return 0, err
+ }
+ return t.Unix(), nil
+}
+
+func TimeToStr(unixSecTime interface{}, layout ...string) string {
+ i := AnyToInt64(unixSecTime)
+ if i == 0 {
+ return ""
+ }
+ f := "2006-01-02 15:04:05"
+ if len(layout) > 0 {
+ f = layout[0]
+ }
+ return time.Unix(i, 0).Format(f)
+}
+
+func FormatNanoUnix() string {
+ return strings.Replace(time.Now().Format("20060102150405.0000000"), ".", "", 1)
+}
+
+func TimeParse(format, src string) (time.Time, error) {
+ return time.ParseInLocation(format, src, time.Local)
+}
+
+func TimeParseStd(src string) time.Time {
+ t, _ := TimeParse("2006-01-02 15:04:05", src)
+ return t
+}
+
+func TimeStdParseUnix(src string) int64 {
+ t, err := TimeParse("2006-01-02 15:04:05", src)
+ if err != nil {
+ return 0
+ }
+ return t.Unix()
+}
+
+// 获取一个当前时间 时间间隔 时间戳
+func GetTimeInterval(unit string, amount int) (startTime, endTime int64) {
+ t := time.Now()
+ nowTime := t.Unix()
+ tmpTime := int64(0)
+ switch unit {
+ case "years":
+ tmpTime = time.Date(t.Year()+amount, t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix()
+ case "months":
+ tmpTime = time.Date(t.Year(), t.Month()+time.Month(amount), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix()
+ case "days":
+ tmpTime = time.Date(t.Year(), t.Month(), t.Day()+amount, t.Hour(), 0, 0, 0, t.Location()).Unix()
+ case "hours":
+ tmpTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour()+amount, 0, 0, 0, t.Location()).Unix()
+ }
+ if amount > 0 {
+ startTime = nowTime
+ endTime = tmpTime
+ } else {
+ startTime = tmpTime
+ endTime = nowTime
+ }
+ return
+}
+
+// 几天前
+func TimeInterval(newTime int) string {
+ now := time.Now().Unix()
+ newTime64 := AnyToInt64(newTime)
+ if newTime64 >= now {
+ return "刚刚"
+ }
+ interval := now - newTime64
+ switch {
+ case interval < 60:
+ return AnyToString(interval) + "秒前"
+ case interval < 60*60:
+ return AnyToString(interval/60) + "分前"
+ case interval < 60*60*24:
+ return AnyToString(interval/60/60) + "小时前"
+ case interval < 60*60*24*30:
+ return AnyToString(interval/60/60/24) + "天前"
+ case interval < 60*60*24*30*12:
+ return AnyToString(interval/60/60/24/30) + "月前"
+ default:
+ return AnyToString(interval/60/60/24/30/12) + "年前"
+ }
+}
+
+// 时分秒字符串转时间戳,传入示例:8:40 or 8:40:10
+func HmsToUnix(str string) (int64, error) {
+ t := time.Now()
+ arr := strings.Split(str, ":")
+ if len(arr) < 2 {
+ return 0, errors.New("Time format error")
+ }
+ h, _ := strconv.Atoi(arr[0])
+ m, _ := strconv.Atoi(arr[1])
+ s := 0
+ if len(arr) == 3 {
+ s, _ = strconv.Atoi(arr[3])
+ }
+ formatted1 := fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), h, m, s)
+ res, err := time.ParseInLocation("20060102150405", formatted1, time.Local)
+ if err != nil {
+ return 0, err
+ } else {
+ return res.Unix(), nil
+ }
+}
+
+// 获取特定时间范围
+func GetTimeRange(s string) map[string]int64 {
+ t := time.Now()
+ var stime, etime time.Time
+ switch s {
+ case "today":
+ stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
+ etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location())
+ case "yesterday":
+ stime = time.Date(t.Year(), t.Month(), t.Day()-1, 0, 0, 0, 0, t.Location())
+ etime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
+ case "within_seven_days":
+ // 明天 0点
+ etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location())
+ stime = time.Unix(etime.Unix()-7*86400, 0)
+
+ case "within_fifteen_days":
+ // 明天 0点
+ etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location())
+ // 前14天0点
+ stime = time.Unix(etime.Unix()-15*86400, 0)
+ case "current_month":
+ stime = GetFirstDateOfMonth(t)
+ etime = time.Now()
+ case "last_month":
+ etime = GetFirstDateOfMonth(t)
+ monthTimes := TimeStdParseUnix(etime.Format("2006-01-02 15:04:05")) - 86400
+ times, _ := UnixToTime(Int64ToStr(monthTimes))
+ stime = GetFirstDateOfMonth(times)
+ }
+
+ return map[string]int64{
+ "start": stime.Unix(),
+ "end": etime.Unix(),
+ }
+}
+
+/**
+获取本周周一的日期
+*/
+func GetFirstDateOfWeek() (weekMonday string) {
+ now := time.Now()
+
+ offset := int(time.Monday - now.Weekday())
+ if offset > 0 {
+ offset = -6
+ }
+
+ weekStartDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
+ weekMonday = weekStartDate.Format("2006-01-02")
+ return
+}
+
+/**
+获取上周的周一日期
+*/
+func GetLastWeekFirstDate() (weekMonday string) {
+ thisWeekMonday := GetFirstDateOfWeek()
+ TimeMonday, _ := time.Parse("2006-01-02", thisWeekMonday)
+ lastWeekMonday := TimeMonday.AddDate(0, 0, -7)
+ weekMonday = lastWeekMonday.Format("2006-01-02")
+ return
+}
+
+//时间戳转时间格式
+func UnixToTime(e string) (datatime time.Time, err error) {
+ data, err := strconv.ParseInt(e, 10, 64)
+ datatime = time.Unix(data, 0)
+ return
+}
+
+//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。
+func GetFirstDateOfMonth(d time.Time) time.Time {
+ d = d.AddDate(0, 0, -d.Day()+1)
+ return GetZeroTime(d)
+}
+
+//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。
+func GetLastDateOfMonth(d time.Time) time.Time {
+ return GetFirstDateOfMonth(d).AddDate(0, 1, -1)
+}
+
+//获取某一天的0点时间
+func GetZeroTime(d time.Time) time.Time {
+ return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
+}
+
+//获取当月某天的某个时间的时间
+func GetDayToTime(day, timeStr string) string {
+ if timeStr == "" {
+ timeStr = "00:00:00"
+ }
+ year := time.Now().Year()
+ month := time.Now().Format("01")
+ times := fmt.Sprintf("%s-%s-%s %s", IntToStr(year), month, day, timeStr)
+ return times
+}
diff --git a/app/utils/translate.go b/app/utils/translate.go
new file mode 100644
index 0000000..dbcb00d
--- /dev/null
+++ b/app/utils/translate.go
@@ -0,0 +1,110 @@
+package utils
+
+import (
+ "applet/app/cfg"
+ "applet/app/utils/cache"
+ "encoding/json"
+ "github.com/gin-gonic/gin"
+ "github.com/go-creed/sat"
+ "strings"
+)
+
+func ReadReverse(c *gin.Context, str string) string {
+ if c.GetString("translate_open") == "zh_Hant_" { //繁体先不改
+ sat.InitDefaultDict(sat.SetPath(cfg.WxappletFilepath.URL + "/" + "sat.txt")) //使用自定义词库
+ sat := sat.DefaultDict()
+ res := sat.ReadReverse(str)
+ list := strings.Split(res, "http")
+ imgList := []string{".png", ".jpg", ".jpeg", ".gif"}
+ for _, v := range list {
+ for _, v1 := range imgList {
+ if strings.Contains(v, v1) { //判断是不是有图片 有图片就截取 替换简繁体
+ strs := strings.Split(v, v1)
+ if len(strs) > 0 {
+ oldStr := strs[0]
+ newStr := sat.Read(oldStr)
+ res = strings.ReplaceAll(res, oldStr, newStr)
+ }
+ }
+ }
+ }
+ return res
+ }
+ if c.GetString("translate_open") != "zh_Hant_" { //除了繁体,其他都走这里
+ //简体---其他语言
+ cTouString, err := cache.GetString("multi_language_c_to_" + c.GetString("translate_open"))
+ if err != nil {
+ return str
+ }
+ var cTou = make(map[string]string)
+ json.Unmarshal([]byte(cTouString), &cTou)
+ if len(cTou) == 0 {
+ return str
+ }
+ //其他语言--简体
+ getString1, err1 := cache.GetString("multi_language_" + c.GetString("translate_open") + "_to_c")
+ if err1 != nil {
+ return str
+ }
+ var uToc = make(map[string]string)
+ json.Unmarshal([]byte(getString1), &uToc)
+ if len(uToc) == 0 {
+ return str
+ }
+ res := str
+ for k, v := range cTou {
+ res = strings.ReplaceAll(res, k, v)
+ }
+ list := strings.Split(res, "http")
+ imgList := []string{".png", ".jpg", ".jpeg", ".gif"}
+ for _, v := range list {
+ for _, v1 := range imgList {
+ if strings.Contains(v, v1) { //判断是不是有图片 有图片就截取 替换简繁体
+ strs := strings.Split(v, v1)
+ if len(strs) > 0 {
+ oldStr := strs[0]
+ newStr := oldStr
+ for k2, v2 := range uToc {
+ newStr = strings.ReplaceAll(oldStr, k2, v2)
+ }
+ res = strings.ReplaceAll(res, oldStr, newStr)
+ }
+ }
+ }
+ }
+ return res
+ }
+ return str
+
+}
+
+func ReadReverse1(str, types string) string {
+ res := map[string]map[string]string{}
+ err := cache.GetJson("multi_language", &res)
+ if err != nil {
+ return str
+ }
+ for k, v := range res {
+ str = strings.ReplaceAll(str, k, v[types])
+ }
+ resStr := str
+ list := strings.Split(resStr, "http")
+ imgList := []string{".png", ".jpg", ".jpeg", ".gif"}
+ for _, v := range list {
+ for _, v1 := range imgList {
+ if strings.Contains(v, v1) { //判断是不是有图片 有图片就截取 替换简繁体
+ strs := strings.Split(v, v1)
+ if len(strs) > 0 {
+ oldStr := strs[0]
+ for k2, v2 := range res {
+ if v2[types] == oldStr {
+ resStr = strings.ReplaceAll(resStr, oldStr, k2)
+ }
+ }
+ //res = strings.ReplaceAll(res, oldStr, newStr)
+ }
+ }
+ }
+ }
+ return resStr
+}
diff --git a/app/utils/trim_html.go b/app/utils/trim_html.go
new file mode 100644
index 0000000..f796f32
--- /dev/null
+++ b/app/utils/trim_html.go
@@ -0,0 +1,38 @@
+package utils
+
+import (
+ "regexp"
+ "strings"
+)
+
+func TrimHtml(src string) string {
+ re, _ := regexp.Compile("<[\\S\\s]+?>")
+ src = re.ReplaceAllStringFunc(src, strings.ToLower)
+
+ re, _ = regexp.Compile("