From 77e192b6c092eb231a4d44bb44bd8efa84b64a61 Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Fri, 21 Aug 2026 08:54:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tmp/init-check-data/db/kra.db | Bin 0 -> 471040 bytes .tmp/init-check-data/kra-init.db | Bin 0 -> 520192 bytes .tmp/init-check/config.yaml | 35 ++ .tmp/init-check/db/kra.db | Bin 0 -> 503808 bytes .tmp/init-smoke/config.yaml | 30 ++ app/system/README.md | 32 +- app/system/cmd/main.go | 6 +- app/system/cmd/wire.go | 4 + app/system/cmd/wire_gen.go | 26 +- app/system/internal/app/catalog.go | 7 +- .../internal/{module => app}/definition.go | 2 +- app/system/internal/biz/payment.go | 59 ++- app/system/internal/biz/payment_test.go | 18 + app/system/internal/conf/conf.pb.go | 349 +++++++++++++++--- app/system/internal/conf/conf.proto | 27 ++ app/system/internal/data/config_store.go | 28 +- app/system/internal/data/config_watch.go | 2 + app/system/internal/data/data.go | 10 + .../internal/data/initialization_backend.go | 38 ++ .../internal/data/integration_config.go | 83 +++++ .../internal/data/integration_config_test.go | 25 +- .../internal/data/repository/runtime.go | 18 +- app/system/internal/data/websocket_config.go | 96 +++++ .../internal/data/websocket_config_test.go | 50 +++ .../internal/initialize/configuration.go | 22 +- .../configuration_test.go} | 5 +- app/system/internal/integration/README.md | 31 ++ app/system/internal/integration/mq/emqx.go | 154 ++++++++ .../internal/integration/mq/emqx_test.go | 56 +++ app/system/internal/integration/provider.go | 8 + .../internal/integration/websocket/server.go | 260 +++++++++++++ .../security/{adminauth => }/token.go | 2 +- app/system/internal/server/gin.go | 34 +- app/system/internal/server/gin_test.go | 8 +- .../handler/announcement.go | 49 ++- .../{transport => server}/handler/api.go | 136 ++++--- .../handler/api_token.go | 26 +- .../{transport => server}/handler/audit.go | 136 ++++--- .../handler/authority.go | 90 +++-- .../handler/dictionary.go | 135 ++++--- .../{transport => server}/handler/email.go | 14 +- .../{transport => server}/handler/export.go | 93 +++-- .../handler/export_test.go | 0 app/system/internal/server/handler/http.go | 25 ++ .../{transport => server}/handler/media.go | 148 ++++---- .../{transport => server}/handler/menu.go | 108 +++--- .../handler/navigation.go | 10 +- .../handler/organization.go | 144 ++++---- .../handler/parameter.go | 42 +-- .../{transport => server}/handler/payment.go | 113 ++++-- .../handler/payment_test.go | 4 +- .../handler/permission.go | 24 +- .../internal/server/handler/provider.go | 11 + .../{transport => server}/handler/public.go | 56 ++- .../{transport => server}/handler/query.go | 0 .../{transport => server}/handler/session.go | 7 +- .../{transport => server}/handler/set.go | 0 .../handler/system_config.go | 34 +- .../{transport => server}/handler/task.go | 70 ++-- .../{transport => server}/handler/user.go | 144 ++++---- .../{transport => server}/handler/version.go | 60 ++- .../{transport => server}/httpx/cookie.go | 0 .../{transport => server}/httpx/response.go | 0 .../middleware/access.go | 7 +- .../middleware/access_log.go | 7 +- .../middleware/access_log_test.go | 0 .../middleware/access_test.go | 0 .../{transport => server}/middleware/audit.go | 5 +- .../{transport => server}/middleware/auth.go | 11 +- .../middleware/capture.go | 0 .../{transport => server}/middleware/cors.go | 0 .../middleware/cors_test.go | 0 .../middleware/error_audit.go | 6 +- .../middleware/error_audit_test.go | 0 app/system/internal/server/middleware/http.go | 18 + .../middleware/rate_limit.go | 10 +- .../middleware/rate_limit_test.go | 0 .../middleware/recovery.go | 0 .../middleware/request.go | 0 .../internal/server/providers_system.go | 4 +- app/system/internal/server/router/aliases.go | 26 ++ .../router/announcement.go | 4 +- .../{transport => server}/router/api.go | 4 +- .../{transport => server}/router/api_token.go | 4 +- .../{transport => server}/router/audit.go | 4 +- .../{transport => server}/router/authority.go | 4 +- .../router/dictionary.go | 4 +- .../{transport => server}/router/email.go | 4 +- .../{transport => server}/router/export.go | 4 +- .../{transport => server}/router/media.go | 4 +- .../{transport => server}/router/menu.go | 4 +- .../router/organization.go | 4 +- .../{transport => server}/router/parameter.go | 4 +- .../{transport => server}/router/payment.go | 7 +- .../router/permission.go | 4 +- app/system/internal/server/router/provider.go | 6 + .../{transport => server}/router/public.go | 4 +- app/system/internal/server/router/routes.go | 42 +++ .../router/system_config.go | 4 +- .../{transport => server}/router/task.go | 4 +- .../{transport => server}/router/user.go | 8 +- .../{transport => server}/router/version.go | 4 +- app/system/internal/server/routes.go | 44 --- app/system/internal/server/swagger.go | 4 +- app/system/internal/service/announcement.go | 17 +- app/system/internal/service/api.go | 39 +- app/system/internal/service/api_token.go | 11 +- app/system/internal/service/audit.go | 41 +- app/system/internal/service/audit_error.go | 23 +- app/system/internal/service/audit_log_file.go | 21 +- app/system/internal/service/authentication.go | 9 +- app/system/internal/service/authority.go | 25 +- app/system/internal/service/department.go | 25 +- app/system/internal/service/dictionary.go | 65 ++-- .../internal/service/dictionary_import.go | 17 +- .../{ => service}/dto/announcement.go | 0 app/system/internal/{ => service}/dto/api.go | 0 .../internal/{ => service}/dto/audit.go | 0 .../internal/{ => service}/dto/audit_test.go | 0 .../{ => service}/dto/authentication.go | 0 .../internal/{ => service}/dto/authority.go | 0 .../internal/{ => service}/dto/email.go | 0 .../internal/{ => service}/dto/export.go | 0 .../internal/{ => service}/dto/media.go | 0 app/system/internal/{ => service}/dto/menu.go | 0 .../{ => service}/dto/organization.go | 0 .../internal/{ => service}/dto/payment.go | 5 + .../internal/{ => service}/dto/permission.go | 0 .../internal/{ => service}/dto/settings.go | 0 .../internal/{ => service}/dto/system.go | 0 .../{ => service}/dto/system_config.go | 0 .../internal/{ => service}/dto/system_init.go | 0 app/system/internal/{ => service}/dto/task.go | 0 .../internal/{ => service}/dto/version.go | 0 app/system/internal/service/dto_alias.go | 162 ++++++++ app/system/internal/service/export.go | 31 +- app/system/internal/service/media.go | 37 +- app/system/internal/service/media_upload.go | 10 +- app/system/internal/service/menu.go | 65 ++-- app/system/internal/service/menu_test.go | 7 +- app/system/internal/service/parameter.go | 21 +- app/system/internal/service/payment.go | 47 ++- app/system/internal/service/position.go | 21 +- .../metadata.go => service/route_metadata.go} | 4 +- app/system/internal/service/security.go | 11 +- app/system/internal/service/system_config.go | 4 +- app/system/internal/service/system_info.go | 16 +- app/system/internal/service/system_init.go | 8 +- app/system/internal/service/task.go | 29 +- app/system/internal/service/user.go | 27 +- .../internal/service/user_conversion.go | 25 +- app/system/internal/service/version.go | 19 +- app/system/internal/utils/configutil/json.go | 16 - docs/system-pkg-audit.md | 104 +++--- docs/system-structure-audit.md | 57 +++ go.mod | 4 +- go.sum | 4 + pkg/logging/source.go | 6 + pkg/logging/zap_test.go | 24 ++ pkg/mq/mq.go | 58 +++ pkg/mq/mqtt.go | 126 +++++++ pkg/mq/mqtt_test.go | 57 +++ pkg/websocket/melody.go | 236 ++++++++++++ pkg/websocket/melody_test.go | 50 +++ web/src/api/payment.js | 12 + web/src/view/systemTools/payment/config.vue | 41 +- web/src/view/systemTools/payment/orders.vue | 71 ++-- 167 files changed, 3657 insertions(+), 1528 deletions(-) create mode 100644 .tmp/init-check-data/db/kra.db create mode 100644 .tmp/init-check-data/kra-init.db create mode 100644 .tmp/init-check/config.yaml create mode 100644 .tmp/init-check/db/kra.db create mode 100644 .tmp/init-smoke/config.yaml rename app/system/internal/{module => app}/definition.go (98%) create mode 100644 app/system/internal/data/websocket_config.go create mode 100644 app/system/internal/data/websocket_config_test.go rename app/system/internal/{utils/configutil/json_test.go => initialize/configuration_test.go} (80%) create mode 100644 app/system/internal/integration/mq/emqx.go create mode 100644 app/system/internal/integration/mq/emqx_test.go create mode 100644 app/system/internal/integration/websocket/server.go rename app/system/internal/security/{adminauth => }/token.go (99%) rename app/system/internal/{transport => server}/handler/announcement.go (63%) rename app/system/internal/{transport => server}/handler/api.go (58%) rename app/system/internal/{transport => server}/handler/api_token.go (59%) rename app/system/internal/{transport => server}/handler/audit.go (66%) rename app/system/internal/{transport => server}/handler/authority.go (56%) rename app/system/internal/{transport => server}/handler/dictionary.go (61%) rename app/system/internal/{transport => server}/handler/email.go (63%) rename app/system/internal/{transport => server}/handler/export.go (71%) rename app/system/internal/{transport => server}/handler/export_test.go (100%) create mode 100644 app/system/internal/server/handler/http.go rename app/system/internal/{transport => server}/handler/media.go (60%) rename app/system/internal/{transport => server}/handler/menu.go (53%) rename app/system/internal/{transport => server}/handler/navigation.go (60%) rename app/system/internal/{transport => server}/handler/organization.go (56%) rename app/system/internal/{transport => server}/handler/parameter.go (59%) rename app/system/internal/{transport => server}/handler/payment.go (52%) rename app/system/internal/{transport => server}/handler/payment_test.go (87%) rename app/system/internal/{transport => server}/handler/permission.go (62%) create mode 100644 app/system/internal/server/handler/provider.go rename app/system/internal/{transport => server}/handler/public.go (70%) rename app/system/internal/{transport => server}/handler/query.go (100%) rename app/system/internal/{transport => server}/handler/session.go (72%) rename app/system/internal/{transport => server}/handler/set.go (100%) rename app/system/internal/{transport => server}/handler/system_config.go (61%) rename app/system/internal/{transport => server}/handler/task.go (63%) rename app/system/internal/{transport => server}/handler/user.go (54%) rename app/system/internal/{transport => server}/handler/version.go (67%) rename app/system/internal/{transport => server}/httpx/cookie.go (100%) rename app/system/internal/{transport => server}/httpx/response.go (100%) rename app/system/internal/{transport => server}/middleware/access.go (86%) rename app/system/internal/{transport => server}/middleware/access_log.go (95%) rename app/system/internal/{transport => server}/middleware/access_log_test.go (100%) rename app/system/internal/{transport => server}/middleware/access_test.go (100%) rename app/system/internal/{transport => server}/middleware/audit.go (93%) rename app/system/internal/{transport => server}/middleware/auth.go (81%) rename app/system/internal/{transport => server}/middleware/capture.go (100%) rename app/system/internal/{transport => server}/middleware/cors.go (100%) rename app/system/internal/{transport => server}/middleware/cors_test.go (100%) rename app/system/internal/{transport => server}/middleware/error_audit.go (94%) rename app/system/internal/{transport => server}/middleware/error_audit_test.go (100%) create mode 100644 app/system/internal/server/middleware/http.go rename app/system/internal/{transport => server}/middleware/rate_limit.go (80%) rename app/system/internal/{transport => server}/middleware/rate_limit_test.go (100%) rename app/system/internal/{transport => server}/middleware/recovery.go (100%) rename app/system/internal/{transport => server}/middleware/request.go (100%) create mode 100644 app/system/internal/server/router/aliases.go rename app/system/internal/{transport => server}/router/announcement.go (78%) rename app/system/internal/{transport => server}/router/api.go (92%) rename app/system/internal/{transport => server}/router/api_token.go (66%) rename app/system/internal/{transport => server}/router/audit.go (91%) rename app/system/internal/{transport => server}/router/authority.go (80%) rename app/system/internal/{transport => server}/router/dictionary.go (90%) rename app/system/internal/{transport => server}/router/email.go (59%) rename app/system/internal/{transport => server}/router/export.go (86%) rename app/system/internal/{transport => server}/router/media.go (88%) rename app/system/internal/{transport => server}/router/menu.go (82%) rename app/system/internal/{transport => server}/router/organization.go (89%) rename app/system/internal/{transport => server}/router/parameter.go (77%) rename app/system/internal/{transport => server}/router/payment.go (56%) rename app/system/internal/{transport => server}/router/permission.go (68%) create mode 100644 app/system/internal/server/router/provider.go rename app/system/internal/{transport => server}/router/public.go (83%) create mode 100644 app/system/internal/server/router/routes.go rename app/system/internal/{transport => server}/router/system_config.go (77%) rename app/system/internal/{transport => server}/router/task.go (82%) rename app/system/internal/{transport => server}/router/user.go (73%) rename app/system/internal/{transport => server}/router/version.go (78%) delete mode 100644 app/system/internal/server/routes.go rename app/system/internal/{ => service}/dto/announcement.go (100%) rename app/system/internal/{ => service}/dto/api.go (100%) rename app/system/internal/{ => service}/dto/audit.go (100%) rename app/system/internal/{ => service}/dto/audit_test.go (100%) rename app/system/internal/{ => service}/dto/authentication.go (100%) rename app/system/internal/{ => service}/dto/authority.go (100%) rename app/system/internal/{ => service}/dto/email.go (100%) rename app/system/internal/{ => service}/dto/export.go (100%) rename app/system/internal/{ => service}/dto/media.go (100%) rename app/system/internal/{ => service}/dto/menu.go (100%) rename app/system/internal/{ => service}/dto/organization.go (100%) rename app/system/internal/{ => service}/dto/payment.go (98%) rename app/system/internal/{ => service}/dto/permission.go (100%) rename app/system/internal/{ => service}/dto/settings.go (100%) rename app/system/internal/{ => service}/dto/system.go (100%) rename app/system/internal/{ => service}/dto/system_config.go (100%) rename app/system/internal/{ => service}/dto/system_init.go (100%) rename app/system/internal/{ => service}/dto/task.go (100%) rename app/system/internal/{ => service}/dto/version.go (100%) create mode 100644 app/system/internal/service/dto_alias.go rename app/system/internal/{routeinfo/metadata.go => service/route_metadata.go} (99%) delete mode 100644 app/system/internal/utils/configutil/json.go create mode 100644 docs/system-structure-audit.md create mode 100644 pkg/mq/mq.go create mode 100644 pkg/mq/mqtt.go create mode 100644 pkg/mq/mqtt_test.go create mode 100644 pkg/websocket/melody.go create mode 100644 pkg/websocket/melody_test.go diff --git a/.tmp/init-check-data/db/kra.db b/.tmp/init-check-data/db/kra.db new file mode 100644 index 0000000000000000000000000000000000000000..4caf312d9ded63aa05df738958221f548000278b GIT binary patch literal 471040 zcmeI*eQX@*eIIZRNpVO@?$vjDHfj-a7&df6}yQI!%-Er|H&U?G_JoC)+e15-qW}baH-@bXxaEZKP*)`pj zC%c~N5`?bL%5qm%*B1SsL;n~48T3hS_=5gh;GVa7y4Cf{%8&NZ(tU*=vN~&ppA`OY z;m-^IqwvGQrGdW~_=Wy!;&=L<-S@TLpY-ZIH@d$i{8$+7`exy;f(FgQqIBV?;2ku~ z3fV9!8>+MEsCwOS)C#E*msC{U4L3b|=~iiWu_VvUUoO2ZYb|PP^1{5Vh5KkHwYVWo z@$McHrK3lM?IBlRu9C1;`0vBbBP`CoeyzkcBA=v3(`3VR$ttns8@J}J&)&KtUoG8{ zXO|Wi=H}^s*Guz@MXhWTJ?OP2SLid>sF9-9sI%v6Ec{lkV_e<3?yhOFOE%mh?YdL8 zjk;@CX1IyksH?WssD~SB#9gy!=hz;K_sTP(^v*@WyO{u8)*YhOh}lrtr*0dS9&AG!VbyJ@0pX;PU^JV{^XD- zDT?q_F`|jm(2rOAYB)<07e3KE^-tO=xlgLmR9w{PsYOgz<&@dBcnUS?bQh-Wno+3` zI+L1>8nKNsovp1}-7@L^VGYN!>GS41U)1!nYuqO)-^My2bycs@&0ISbvZ6PtuAlDQ zX4i15(ZP*!Jc*a97M+kOJzv?@O~+tUy&9i&iZ^&bl;%ze-swcfgz04vKJfhcLQ977 zb0&Y^IBB@@RMu>b?-!+0r-b*9L|MaCP96O~HgCAfEOCDNYh~ErCLjNHm*;o~KBjp& zL@(I7>Xa=u|Ap86%cVDFm#!_!BTfiA2}=^t?16$vZdRVQNT+u7Mf(-#LzA= ze)?Ptyvl33eRA@wLht#){x|RC>bm1Ru2!5t(q*?n$q3ezj}s$r@Qab3fVjv01S&A(@@H5^x^gEUtO zy_XC})SOTi@1;I^eY+rdx1+@H&mCL$=e2dZRju3BeS;2^MUO9})8mS<>WIx)F3r!~ zTuR*Q^OX}V#ks>*X|_jC4OUsBSB2(*=_=s(K2f?&yEz+mvqf*zz|X(u%xSzYKecme zh7Il1*XHpw#yk{aMr@ThbDYa&-26^n?AZ0doXN59#LLdL!j4k6Azepcu-=q&?=jg)`g+4rYXox=c7QWO~ z_)mp@UHBG#f>TaszQk3Sj4A{R{ZDjv&vL~R>^%UN)aV=2>@A5=q5C>lI!pACO)_|>65 z9C~kPs!CxPo8ThXQ&-OnjKHoR8?|84=bE5lQ;aBKxAJLz8 zdt{(%Vd3E6{;h*-gr~>Oot_w%$IhLf8b5#L>?`9F<7Xx(rcTifV`Dp3sNEyAdm9f& zo8Jr>Js*4lWOUiK)`>lOesr`rTCN(w_eg`6;kUPfSK&9Ef>-fZKu6C{jd&b~+G zIP}Y#qvz>6FNAKiOp}ydmHhyKen)Wh{P@@-@15t_sE!@!-zwyGdi>n<)S1aPqv9(( zAj^(bUMFsiRISQpc6jV-utAUYYSnt624DDcM$f-VnUH|W<@9Y?2M~d84Eqi`s@oWD}*A1HvgYC&;OTS7-R*a2c15Maq zkj>ntKlmxLujtWgj=NcvHP@}vi*<9_W?RY?Q>|F^>)mF!{n6V8`~Az-K>lqKe^J_ToXx9N)hg@N zhmY<&<+psKKfmR%Gt)B@Gi@&Be1+zxUXE@p6N5HgXy5e2#Kd&F`-CyBLbGZ5uR81X zTf_Zb*RPMX==Ipl^x1Q5F5-N7v*D_~u2=PkkKP&b`+PK?V{B$>d}{1mYmP*PX4CYp z%rNQmhmUR_@Oyo*Rpa9`Gi}?B%bQK#yGNDxw^JTFH#2$e%$YVl<|{Ottyqms!+iK? zdC+hAREz$eJ3BEk-mb%Zg+$Y*SLyxRY2QQ}@J(7CV`6e@yj}0(^5zj95c<0Xmpj7U zet#6lTa04*+*!KJXmbd@Lh~3-qh2R=Fo4gCe)G??YW~c*vuE4o;xtzwY<|V1nb_yI z+bR>M$Hyny%J3DM?UvUJQ*YFqhmXFv&u{!#i%y@VC;fDr1*_5LJny{CQabOM|>+a{i8xeBS)Cur;4ZCjr@H$C2V0^loz zGF5{@b~HO1HMuCwZhj6zf^d?@a4jL zh2Q?Tlc0YPfB*y_009U<00Izz00bZafsb0Cx2IniVGqxFaIrbp2hiBQtVfJu{ zJv_r6p6=}#5Dv0uPqBvrdl+U9L+s%Id)Ut&2HC>^d+28mB75j#5Bu0dFMH@=58XXI z{R95`f9MBYLw`N=gTjvs-yi<7!rv~?|1x%Abol$j-z{_v|8C)4;R}V&6#h-&w}&4L zPYsU@Ys0@eJTUzA;m-~I$wzI7<3j)f5P$##AOHafKmY;|fI!#&-h+pQ;FrxKhkB14 z5mLVgetx)DIxIv#qdju4cSI8SAErKcq<7>*xFzw6P3h_0kt1PA@KeTTrQVSj!%f_8 z{0=|cJ90eS9{>355WhM8RoF9pC;XpFJ3Hv zjn43)-jSn1^!4}Pw>3khm?{M$Pv9MC~`-pw~*64Q)y?I+FdibprnD&GIj-Z=r z9vO%T*jD!afBJv=hd&5F00Izz00bZa0SG_<0uX=z1UguNeg2R2e+LH`LxTVWAOHaf zKmY;|fB*y_009V40j&R#1`vP%1Rwwb2tWV=5P$##AOL~R7r^?z^J9z=LI45~fB*y_ z009U<00Izz00i*+f8+oJAOHafKmY;|fB*y_009U$}v8@GzXNe;e(=d@uIN(YLr2Fok{K(t3RF1u)N*1Cz)}{ zzH*6P)ZEQF2@`FdY=%$o>(xecyW&YtiPGex;4MWUdV{8Q%a#Yo|h1^hVY7)1BMw8g4Z@xKWNL@p9Fo6EdaeE8Dv17;LIn z3>)0!Ppf@wY0~eo>mCgBr>^0v#H|4JTJ;pzTqbwR4UXmMGgN zL{U0+O!(^2DDC|2M@Q(-rdq&4lZ=}f+9k$MpNoN4c}=%ZPM%fhJzv=W=Dl29cbo^7 z-MmvPYNk1qggCr)q&&^F? zdWBv!Ow}Q-Ynah7!)~hi_pG&sz!rQ5Wdvr#u&^hOQ*{Cm!v#{2S9JEvyY&`y1A9#2zl^!Bl-_D+!U_o{|VXsGTr zh*>61^5won`lkv{vU9OW_f)oC>Y)p|3&J+L@C0k0oQ}4D=uI5Mzcb6(`Db)H;0Mr8 zXnB5NQJ!DAc8v>Xx*#MBFZ0np(fqukR~@=43o@iRcZUrt?+KzbJuP_8L`qJ1jh<6# z%~%cc*uO!_Z0yboW$J`J{<+DoBK>(vQRce)q`T8Xm!Fo+K+;LT@?ZPQ?@W{GIEz?j za}Ql%_p?*8qNZ}biC z`^DZr>OI&qOsoD5+iy321Aid&NT*K=+mmirSNCs8cV`6eZsL@x81xRxGIe{CE)ZQh zFVTx$yeYU8aT~h21WlTm5#Ep25?pDs1vcL7*3()3Y^BiOPu-vOLcXdmvpdyb9_Cg| zMUAf137wLfMUJbx4TrlEOx=X0#+$elrp)Sb%D@;#=!_kXs!PKwQp4d+0%jK8t!MU1S>r?r8ky|%jeEYZT z&9?)&t=!RRuCRc_?ZnUMNuYS2`lKkmMh8-kX6m>C*G#evu0Y8~H@5*6=42yrwZz%z zr%$BEuSpcI_vPTdh{>q4>;_S-Tfv&ozosO&oXmDFSt0C~zctg5u;pYo4!k5vQ&WO> zA=wR1g$=@Qk=u5nMOU~g?K=}!Om?UHMNul95WJTYxfpgmy+Vq%e=%r7S-`J4z-q?r}B^`XOz{WAUNT+YIBp2yi{U`~zZKFH)BAE!>zHusi@`d0nm7XXSxN6A>#W|i$2d?r* zmJr#C41MQ}EmK^}rq{^e1}K#&)#hTC-UT&p#xhs3MD{42d^DIq*CVKS7G8GE)Vs`V zi=5?3?!v_4EtwfK`j8;*A)N-IH$PQ+jO1+g>?cHNfxh27lbFpCJEXHZS$HfzrIM9$ zb30ijmEK-`#JAhtAK4}Avph>~c_C|;tniU#8-II~>^B!c#e4O6QF`%3Ve4go$YtGG zrtfvwjVj@u9!(CCD|w93)%lyT`(t6g-ye^jO+?Qoqi1KLXH&s5<8pC%n<;g0sYmBAu+k#FAS^k_g!fZO#(*VI-$sOfG5TZ2|oLe{aVRy@vn< zAOHafKmY;|fB*y_009W>MgX7x??wrAAOHafKmY;|fB*y_009U<00Mhk0H6Qw?f9Yh z5P$##AOHafKmY;|fB*y_0D;{IVEw-vCDefc1Rwwb2tWV=5P$##AOHaf>}>&j|9@}C z550!~1Rwwb2tWV=5P$##AOHaf>_z~e|L;Z#bszu%2tWV=5P$##AOHafKmY=JTLA0- zy&XUF9s&@600bZa0SG_<0uX=z1R$^*0et^|H%h1j0SG_<0uX=z1Rwwb2tWV=5ZK!S zSpV>C0SG_<0uX=z1Rwwb2tZ&r0{HxYH%h1j0SG_<0uX=z z1Rwwb2tWV=5ZK!S`22rw#}B=S00bZa0SG_<0uX=z1Rwwb2<%1xpa1Vh33VU<0SG_< z0uX=z1Rwwb2tWV=ds_hO|GgbQ^d15bfB*y_009U<00Izz00bbg8v*wH|AJ8I8vf0p z9}UeOF!ulX;2#do4gB(eu+Q!Nk3E0X^M&qxwD^sIi|tzzUXZR_5w@pXeYr}UO-Cgg zb<1{Dm(=Q2-6d+-GAo8_SZ3S8iSyEPgI zJEBWA+@iK(Tea|c*{U{bX86jg6I*vJJ9w>lFPsyl+f#x!YnTtHfN*Yvf!|#U}R}hE3E)ozB~uY&L4dHp)>) zSM;hwuNJJaux8bIu*;eiW0lPiyJ*v?ld_@H$)-hJf38u9$u6)vRC+*@-g=NlJ4;Q1 zX3AE>%t4!E6|D05j)!4&+%=mxYgU!J1c$MmYBp18(CeUorlvE59f^t3+XP0x!?3uT z6@8OlRnpDP(B@VwyG&G*JWv}Bu~nL#<|c%L2@B}EaoCjD6dP;bwhQnb&|Q_#RX@wjxG0$r`UM^l;hSXUiBTfooIHA>n0nXOgbhDpQxjs zvE0pUQPUgln&n@s5}SR?@WMw&=r-ut(3~-fT5vW|bsClUKG!BS-Cl3T>HaIC^xAR3 zI})Wk8+Ki{^%|e*@y3b#@MQLgLlYFG!s0ER7Nz6Ig{?*umY`mIr15MxnoY@1@J^q< zZKPMH(E7e!Z6t0&!o2@vQIy`A5xltsXvHYAiBGpT=`7&tMm3F_t?+6-bhcto)V?Iw4--->}cF~qmtkDE4%M2-&mX@x!p}2izdFZb@x-EG&3W-zZ9iScGuK? zP4Ls%bjt4fM9=c7zD%OkL?WT+f|8ig$})3aJ9H@>UG$3nvX~o6VuGM&3^83bv8_t( zv*Sj~l)BFDhS(}r!SDauzpsH(2tWV=5P$##AOHafKmY;|fIue*;QRlb5Jiju0uX=z z1Rwwb2tWV=5P$##Akba_pZ~Y_K`8_v009U<00Izz00bZa0SG{#69n-2eFIUXMglx?4SOLv5B*%$IhLe7?;P+ou8UGKQ{Tw%-GcA^w{(%x?yZ= z#|pzw^>zL48~vYtVc!d1V4wdBg}>>d|KJY-5P$##AOHafKmY;|fB*y_0D%q^=;`Si z!0-P%Ft8XB1Rwwb2tWV=5P$##AOHafKp-W6-~XpT@Dc(LfB*y_009U<00Izz00bb= zfdcsazXOAcAwd8F5P$##AOHafKmY;|fB*zi0{Hwt1%j6lfB*y_009U<00Izz00bZa zfesYF=l>lTR166M5P$##AOHafKmY;|fB*y_kP^V>|0xi>ga8B}009U<00Izz00bZa z0SI)U0M`E<7*q@i0uX=z1Rwwb2tWV=5P$##AdnKk_y1EMcnJXrKmY;|fB*y_009U< z00I!`KmoS?e_FWO)&2Jh=Z2l3cMp8~z`j9e;ClnF_W!c@pzp`~zSR4ld%n{>DSU@k zdUJ5P9YIgy;wiy9Y?u|YVN^C$XVXz#qed#Kt2^tCYU(wT+II8OtvWxp}(#_0s%eQ7hX-cj?%4SCcFBnQOl(N*9l%0f=o| zwpt-o5>#g0@wYAH4?SH!9=cyb2OYPiDfV3JW7_0igE+2gRH9?D-sf-!$BgS|kI9uO z)z6923?1iCBG5q_uC12|f5hwUju_U?9xp0Uwnk<|>B<#hdnyVp*{EB#tGc9CuTprm zY?&3qH7wH~XRE@C&7N?9Z`qLcQ=|b+_@dURvy|YH!{5q9E=U=FYcvj)crMv+i`t59 z)xzgxtJQ_e< z?}ax+>GqW1%_dTnhDKNQa+x@eTD4Z4)Y+LXzM7xH>1tdnaXzOjrB2_iT1k|qriAw& zMd3^D!6KR7_GUJy=_*e&zBG&`Js&sgBn*wK+pC0bV)JphEl6E1ehr7%bgqbI+oGm7 z+%?NK+|A@>r%dw=~LI;weYAF;_5~-$rP{eGM!8m!BY|mL6g8R^HL!$ zn9EOsxI%9Dk69qo5%R(N%`dl{Hap4mUGmDph#ciICMmb)T9^7V2VozU-3p?7p0|X!7C+V6J?ERld@%3(r0U?{7QbL zGWBu;mZ_DBn>RYUyH3U_%A%QR_Kn?kGHxfHR5Gp6PIC1W?-QRDrJGdp;trDG1e@E% z(r&vLw$i?XQ7vV=@R}%1PYYkU80CIuM7ce>oIMjVJ3i49B0d3H&p+X*(Y&K9YV4N9 zEN`kcdgY2XCQgmqlV{ngY=-BKO`Pb|OP@zYJ{4MANGs&N;oqhO4JzI%pAn^ZE(+ew zM8c$RkKVQDEE(U!uo&f4xs;zcc~!aA;_S(*m_7~1FN)G#+To?0JIvi)wd(QfkI`eU zxgERA*HyOqK27h{FACeoqO{K&X{$bdHk2p(x0yy&i;OOiaCsNQS4i(*utYqxbIquS{VES(pnRXPY`mxJJ}+K%Egk2?yk z;oS{`-$B9m|J%Kzfieg{00Izz00bZa0SG_<0uX>eX9!^Z-x+blC?Eg<2tWV=5P$## zAOHafKmY>m1n~R+b~-4700bZa0SG_<0uX=z1Rwwb2y})3KL78GIARnKfB*y_009U< z00Izz00bZafp!8||F_dY83Z5z0SG_<0uX=z1Rwwb2tc4S1hD?^j5uNx5P$##AOHaf zKmY;|fB*y_0D*P_`2Bx79h5-;0uX=z1Rwwb2tWV=5P$##Izs@T|93_lF$xGk00Izz z00bZa0SG_<0uX>eI{|$D-%bZ*5P$##AOHafKmY;|fB*y_0D;aBz~}#+5l4&y0uX=z z1Rwwb2tWV=5P$##Aka<#pZ~YhK^X)f009U<00Izz00bZa0SG{#GX(JYe`mxIqksSe zAOHafKmY;|fB*y_009WJ6Ttevoes(%009U<00Izz00bZa0SG_<0-Yg%^?zr?5u<2v3)c3vaf7yLO`2D^GTIsu8V_jY19qIb3f;VND6|!MeHdJTRQFYU_8fKZ)i0L|N zg;a@4Dyr^g%2qDjD$OpI5^4q%QtS#U7x*mN4{FRBhM}^ zF3io-Ca#y}7mHfiCVDVDO|H;qu2CaJtx;#s*`WEYF$KT1*5{NexgV_sWn^F zlrP^ArAzeC$6FkF#i$aM^*qsny3@g<-SQ6~7b{y!Z;R526TDoUqL z314~WM>N$y>gA1OR8yr-G_HIAE5s?=M%^_mGbDEEq#QnfV7P0lL&}y}p;0j#HDVj( zqDD4Ixj}=Yx|{VV7iz>^vnoxuAb@q<)@yXfH*YV@7qvClt*Z^2#ycqWUk7_7Hmwo8 zLhQs|%T^^y0e?5WTD2aib=$a4r!l@~CYxsI%-HO!LN6zuxOgm`9>lh3@4fDKM5#n4 zg_on;NuK@je3;m-XR{|!&RtUSr-is(P4On)5~aJZ3Eonw6T0i_WjY<1u3DypT(#^? zuHoQ-Id$%~>Re7;u06h^IW=k0CpumIJe|p36Sh{l$t-8Lmc9I9@^olf?;|@g>iitz zy}2Yx##Ov6LX`}qf(vF=~RlM0- zqEx2d`mVhb_dzWW@b=^PJ-i+AZ&Y`nq3&UIW$TMKMd|8Q;r&;+^Sx!qc4+vmX1^1+ zw=Em{$Yy|YRd*bEH_F|fa2GFEUkxu>-0ex~CZ|U4Lc&^p+5{KAkTtx0SG?zMh|Q3~=(><4tKtn_7o|CR ze|@?|^joK;b8Dx-^ZVw--ASb)zW?9u-4T>Q00Izz00bZa0SG_<0uX=z1Uf?i>;KM( zBSrxM2tWV=5P$##AOHafKmY;|XeWT*|F_dY83Z5z0SG_<0uX=z1Rwwb2tc4S1n~KP zXT%YsfB*y_009U<00Izz00bZa0SL4c!1}+P4$2?^0SG_<0uX=z1Rwwb2tWV=ogskr ze`mxIqksSeAOHafKmY;|fB*y_009WJ6Ttevoes(%009U<00Izz00bZa0SG_<0-Yg% z|NdWR#1W%_00bZa0SG_<0uX=z1Rwwb2(%Nx=l|_=PzC`AKmY;|fB*y_009U<00I!` z3;}%p-x+blC?Eg<2tWV=5P$##AOHafKmY>m1n~KPI~|lk00Izz00bZa0SG_<0uX=z z1Uf?i>;KM(BSrxM2tWV=5P$##AOHafKmY;|XeWU6e>)wNK>z{}fB*y_009U<00Izz z00cTi0Kfn5j5uNx5P$##AOHafKmY;|fB*y_0D*P_`24?}4$2?^0SG_<0uX=z1Rwwb z2tWV=ogskre`mxIqksSeAOHafKmY;|fB*y_009WJ6TtWX+v%VT0uX=z1Rwwb2tWV= z5P$##AkY~C`24>!;)qc|00Izz00bZa0SG_<0uX=z1lkGU^Z#}_D1!h5AOHafKmY;| zfB*y_009VehQQGN|JLlDsrO zcXO#E&&^*hy)A3IYekb6=4CBzOFOBB$J2_@Q`JABYFe7|WgjcFO5bvRrX0qI8jV;BcD`m{xr7_1zqtjw}n&JTM!zcc3gvbM&Ye z@{U@z%oW3~>8@d!s!@rL{qAleliE9{Bk3B7cW_yhZqn{t%Bd3tM< zwX#b#TzP(BQJ!DAb}i0ptyMvs@P3V=Mp^7l8rxuxqPE;{43jvH>TcFarkSWT+<`l` z(^!6&lxgqNebP5;#B|k~RY^A}UoO2dyL4?)9;L$|wy9S~i<)H{s|J0l>NTrj()L2U zaG23vV@0i19ZI1alx-JH&@i$&Xt&kNy0f%su8=q zrqe?_)?N;A-74|pxEs@Q!?uZ8-poq|ik&PDqo^*^Q((PfJuuaElRj#)*`U2O%2AR< zkzdiP4m~?)==?4P^K$lJArN0))K(hR6{A{hw`X!{PG@vb%FfDNq*t@4=G5|dJF(fD zolSIhNY2EWq2|q8>23!z47&nXEj>EnY4f%o9;T|(>6P4Pr)zkI7d3xlb1!v`rhl@w zZCiG@g{O6`bwXnp^uMW)*mj@Z1kejhn-hFIJdG1`V)G!GSuqYvUUfg*RiZmYr6-tC zdHQ_(fa*>qIhH0haI_Rk4Nt&l1S=IKn?O7_B(sl)9bT$VC5 z=>1E440_#usEX37uL|2Q`xm85^~?s6yhLTnwV%zEit96XRdWqGnS`l9Q`@Bp7rtyb zbb{cnnRIq=UA?@<;^%1co3|I{74P|-dN$8Xl7Y;>w%gIE1yYf4v_ z&@D|)3fmX`JjvNRXVY>rS8{4R(QJta7v2ga2btc=Pf?EH4Od0!3caO_!$Ie=%o-%M z^l(~+q~*>z_mk0m8h2-=)ZZ1Q3$#B6qx8sWGTwAAo2PMMv~;F9zEOZqD5P$##AOHafKmY;| zfB*y_@Bsy|{{Mh**bM>@fB*y_009U<00Izz00bbghXkSvmaM%q35P$##AOHafKmY;|fB*y_u!jWxKSlde82|tP literal 0 HcmV?d00001 diff --git a/.tmp/init-check-data/kra-init.db b/.tmp/init-check-data/kra-init.db new file mode 100644 index 0000000000000000000000000000000000000000..91113584774888276e65bb3302a8d6a2e9604f14 GIT binary patch literal 520192 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 Jfl|We{{VNU;9UR! literal 0 HcmV?d00001 diff --git a/.tmp/init-check/config.yaml b/.tmp/init-check/config.yaml new file mode 100644 index 0000000..63b5408 --- /dev/null +++ b/.tmp/init-check/config.yaml @@ -0,0 +1,35 @@ +server: + http: + network: tcp + addr: 127.0.0.1:18001 + timeout: 60s +data: + database: + driver: sqlite + path: .tmp/init-check-data + name: kra-init + source: .tmp\init-check-data\kra-init.db + prefix: "" + log_mode: silent + config: "" + host: "" + password: "" + port: "" + user: "" + redis: + addr: "" +admin: + router_prefix: "" + jwt: + signing_key: 7bd3e9cb-e201-4982-9af9-2ea209a80179 + expires_time: 3600s + buffer_time: 300s + issuer: kra + system: + disable_auto_migrate: false + local: + store_path: .tmp/init-check-data/uploads + path_prefix: uploads/file + app: + app_id: kra + env: test diff --git a/.tmp/init-check/db/kra.db b/.tmp/init-check/db/kra.db new file mode 100644 index 0000000000000000000000000000000000000000..4a2df9b266b3074fe5d3f118ec4e8bf32f45fdba GIT binary patch literal 503808 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 Xsj1QNW-H`eL;z8Xs72InZ`A&Oc#9~c literal 0 HcmV?d00001 diff --git a/.tmp/init-smoke/config.yaml b/.tmp/init-smoke/config.yaml new file mode 100644 index 0000000..dfeed99 --- /dev/null +++ b/.tmp/init-smoke/config.yaml @@ -0,0 +1,30 @@ +server: + http: + network: tcp + addr: 127.0.0.1:18000 + timeout: 60s +data: + database: + driver: sqlite + path: .tmp/init-smoke + name: fresh + source: "" + prefix: "" + log_mode: silent + redis: + addr: "" +admin: + router_prefix: "" + jwt: + signing_key: init-test-secret + expires_time: 3600s + buffer_time: 300s + issuer: kra + system: + disable_auto_migrate: false + local: + store_path: .tmp/init-smoke/uploads + path_prefix: uploads/file + app: + app_id: kra + env: test diff --git a/app/system/README.md b/app/system/README.md index 0bb148a..b2c4254 100644 --- a/app/system/README.md +++ b/app/system/README.md @@ -1,21 +1,35 @@ # System Module -系统模块承载当前管理后台的完整业务边界: +系统模块承载当前管理后台的完整业务边界。`internal` 顶层只保留有明确 +生命周期或分层职责的包: +- `app`:组合根、system 模块定义和任务/路由运行时组合 - `biz`:系统领域对象、用例和仓储接口 +- `conf`:配置 proto 与运行时配置解析 - `data`:数据库生命周期、系统仓储、系统表和支付持久化 -- `service`:请求 DTO 与领域用例的转换 -- `transport`:Gin server、middleware、handler、router、HTTP 响应工具 -- `integration`:Redis、邮件、对象存储和支付渠道适配器 - `initialize`:数据库首次初始化和系统种子数据编排 -- `worker`:定时任务执行与调度 +- `integration`:Redis、邮件、对象存储、支付、WebSocket 和 EMQX 适配器 - `security`:后台 JWT 等安全实现 -- `utils`:只服务于系统模块的无状态工具 +- `server`:Gin server 组合与生命周期;横切 HTTP 代码按子包维护: + `server/handler`、`server/middleware`、`server/router`、`server/httpx` +- `service`:应用服务、DTO 与领域对象转换和路由元数据;DTO 集中在 + `service/dto`,根包中的 `dto_aliases.go` 只负责兼容旧调用方 +- `worker`:定时任务执行与调度 + +目录代表边界,模块文件按资源命名。DTO、handler、中间件、路由和 HTTP +响应工具分别放在独立子包中,避免 `service`/`server` 根目录堆积几十个 +文件,同时不把只有一两个文件的业务逻辑再拆成新包。单文件的 module +定义并入 `app`,JWT 实现集中在 `security`,protobuf JSON 统一使用 +`pkg/protoutil`。 + +`internal/app` 只有 `catalog.go` 和 `definition.go` 是有意保留的组合根: +前者组装模块 catalog、任务注册和运行时,后者声明 system 的迁移、管理面 +和定时任务。它们不是可以下沉到 `service` 或 `data` 的业务文件。 系统表统一使用 `sys_` 前缀;业务表应由新业务模块自行命名和迁移,不要混入 本目录。 -system 通过 `Definition()` 提供自己的迁移、支付菜单/API 和默认任务,通过 -`worker.TaskMethods` 提供依赖系统用例的任务实现,通过 `transport/server.Routes` -提供路由。应用组合根消费这些公共协议;新增业务不需要修改 system 的初始化、 +system 通过 `Definition()` 提供迁移、支付菜单/API 和默认任务,通过 +`worker.TaskMethods` 提供依赖系统用例的任务实现,通过 `server/router.Routes` 提供 +路由。应用组合根消费这些公共协议;新增业务不需要修改 system 的初始化、 worker、路由或数据层。 diff --git a/app/system/cmd/main.go b/app/system/cmd/main.go index 1d3216e..a0178d6 100644 --- a/app/system/cmd/main.go +++ b/app/system/cmd/main.go @@ -10,10 +10,10 @@ import ( "strings" "kra/app/system/internal/conf" - "kra/app/system/internal/dto" "kra/app/system/internal/service" "kra/app/system/internal/worker" "kra/pkg/logging" + "kra/pkg/mq" "github.com/go-kratos/kratos/v3" "github.com/go-kratos/kratos/v3/config" @@ -40,10 +40,10 @@ func init() { flag.StringVar(&flagconf, "conf", "../configs", "config path, eg: -conf config.yaml") } -func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *service.AuditRecorder, loggerControl *logging.ReloadableLogger) *kratos.App { +func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *service.AuditRecorder, loggerControl *logging.ReloadableLogger, _ mq.Client) *kratos.App { if audit != nil && loggerControl != nil { loggerControl.SetErrorSink(logging.ErrorSinkFunc(func(ctx context.Context, entry logging.ErrorEntry) error { - return audit.CreateErrorRequest(ctx, &dto.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID}) + return audit.CreateErrorRequest(ctx, &service.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID}) })) } return kratos.New( diff --git a/app/system/cmd/wire.go b/app/system/cmd/wire.go index b9763f4..8cdbe98 100644 --- a/app/system/cmd/wire.go +++ b/app/system/cmd/wire.go @@ -16,6 +16,8 @@ import ( "kra/app/system/internal/integration" "kra/app/system/internal/integration/cache" "kra/app/system/internal/server" + "kra/app/system/internal/server/handler" + "kra/app/system/internal/server/router" "kra/app/system/internal/service" "kra/app/system/internal/worker" "kra/pkg/logging" @@ -29,6 +31,8 @@ import ( func wireApp(*conf.Server, *conf.Runtime, *slog.Logger, *logging.ReloadableLogger, string) (*kratos.App, func(), error) { panic(wire.Build( server.ProviderSet, + handler.ProviderSet, + router.ProviderSet, worker.ProviderSet, app.Catalog, app.TaskRegistry, diff --git a/app/system/cmd/wire_gen.go b/app/system/cmd/wire_gen.go index f4c8d9f..80cb70c 100644 --- a/app/system/cmd/wire_gen.go +++ b/app/system/cmd/wire_gen.go @@ -17,10 +17,13 @@ import ( "kra/app/system/internal/initialize" "kra/app/system/internal/integration/cache" "kra/app/system/internal/integration/email" + "kra/app/system/internal/integration/mq" "kra/app/system/internal/integration/storage" + "kra/app/system/internal/integration/websocket" "kra/app/system/internal/server" + "kra/app/system/internal/server/handler" + "kra/app/system/internal/server/router" "kra/app/system/internal/service" - "kra/app/system/internal/transport/handler" "kra/app/system/internal/worker" "kra/pkg/logging" "log/slog" @@ -143,14 +146,27 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger user := handler.NewUser(userService, authService) navigation := handler.NewNavigation(userService) session := handler.NewSession(tokenService) - set := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session) - routes := server.NewRoutes(set) + v := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session) + routes := router.NewRoutes(v) taskMethods := worker.NewTaskMethods(taskUsecase, mediaUsecase, runtime) moduleRuntime := app.Runtime(routes, taskMethods, registry) - engine := server.NewGinEngineWithRuntime(runtime, accessControlService, authService, securityService, auditRecorder, logger, string2, moduleRuntime) + websocketServer, cleanup2, err := websocket.New(runtime) + if err != nil { + cleanup() + return nil, nil, err + } + engine := server.NewGinEngineWithRuntime(runtime, accessControlService, authService, securityService, auditRecorder, logger, string2, moduleRuntime, websocketServer) httpServer := server.NewGinServer(confServer, engine) - kratosApp := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger) + mqReloadable, cleanup3, err := mq.New(runtime, logger) + if err != nil { + cleanup2() + cleanup() + return nil, nil, err + } + kratosApp := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger, mqReloadable) return kratosApp, func() { + cleanup3() + cleanup2() cleanup() }, nil } diff --git a/app/system/internal/app/catalog.go b/app/system/internal/app/catalog.go index 96407da..a327eac 100644 --- a/app/system/internal/app/catalog.go +++ b/app/system/internal/app/catalog.go @@ -4,8 +4,7 @@ package app import ( - systemmodule "kra/app/system/internal/module" - systemserver "kra/app/system/internal/server" + systemrouter "kra/app/system/internal/server/router" systemworker "kra/app/system/internal/worker" "kra/pkg/module" platformtask "kra/pkg/task" @@ -15,7 +14,7 @@ import ( // module means adding one Definition here; system initialization and runtime // code consume the catalog without knowing that module's implementation. func Catalog() module.Catalog { - return module.Catalog{Definitions: []module.Definition{systemmodule.Definition()}} + return module.Catalog{Definitions: []module.Definition{Definition()}} } // TaskRegistry builds the process-wide registry from dependency-free module @@ -28,7 +27,7 @@ func TaskRegistry(catalog module.Catalog) *platformtask.Registry { } // Runtime composes HTTP route contributors from the enabled modules. -func Runtime(systemRoutes *systemserver.Routes, systemTasks *systemworker.TaskMethods, registry *platformtask.Registry) *module.Runtime { +func Runtime(systemRoutes *systemrouter.Routes, systemTasks *systemworker.TaskMethods, registry *platformtask.Registry) *module.Runtime { platformtask.Apply(registry, systemTasks) return module.NewRuntime(systemRoutes) } diff --git a/app/system/internal/module/definition.go b/app/system/internal/app/definition.go similarity index 98% rename from app/system/internal/module/definition.go rename to app/system/internal/app/definition.go index e4cc09e..2a0191b 100644 --- a/app/system/internal/module/definition.go +++ b/app/system/internal/app/definition.go @@ -1,4 +1,4 @@ -package system +package app import ( datapayment "kra/app/system/internal/data/payment" diff --git a/app/system/internal/biz/payment.go b/app/system/internal/biz/payment.go index 05e1822..7e0c8dd 100644 --- a/app/system/internal/biz/payment.go +++ b/app/system/internal/biz/payment.go @@ -493,6 +493,30 @@ func (uc *PaymentUsecase) Refund(ctx context.Context, provider, tradeNo string, return uc.refundWithOrder(ctx, provider, tradeNo, amount) } +// Fulfill retries delivery for an already-paid order. It is intentionally +// idempotent: the repository lease prevents concurrent attempts and a +// succeeded order is returned as a duplicate without invoking the handler. +func (uc *PaymentUsecase) Fulfill(ctx context.Context, provider, tradeNo string) (*PaymentResult, error) { + if !validPaymentText(provider, 64) || !validPaymentText(tradeNo, 128) { + return nil, errors.New("发货参数不完整") + } + if uc.orders == nil { + return nil, errors.New("支付订单仓储未接入") + } + order, err := uc.orders.FindPaymentOrder(ctx, provider, tradeNo) + if err != nil { + return nil, err + } + result := paymentResultFromOrder(order) + if result == nil { + return nil, ErrPaymentOrderNotFound + } + if order.PaymentStatus != PaymentStatusPaid { + return nil, ErrPaymentOrderState + } + return uc.fulfillOrder(ctx, order, result) +} + // Callback verifies the notification in the adapter, then always performs a // provider-side query before it can dispatch fulfillment. Callback payloads // are never trusted for amount, currency, or final payment state. @@ -671,10 +695,21 @@ func (uc *PaymentUsecase) reconcilePaymentResult(ctx context.Context, result, ca return result, nil } + fulfilled, err := uc.fulfillOrder(ctx, order, result) + if err != nil { + return fail(err) + } + return fulfilled, nil +} + +func (uc *PaymentUsecase) fulfillOrder(ctx context.Context, order *PaymentOrder, result *PaymentResult) (*PaymentResult, error) { + if order == nil || result == nil { + return nil, errors.New("支付发货订单为空") + } order, token, duplicate, err := uc.orders.BeginPaymentFulfillment(ctx, result.Provider, result.TradeNo, 10*time.Minute) if err != nil { uc.logger.Error(ctx, "payment.order.fulfillment_begin_failed", "provider", result.Provider, "trade_no", result.TradeNo, "error", err) - return fail(err) + return nil, err } if duplicate { result.Duplicate = true @@ -685,29 +720,23 @@ func (uc *PaymentUsecase) reconcilePaymentResult(ctx context.Context, result, ca if handler == nil { err = fmt.Errorf("支付发货 handler 未注册: %s", order.BusinessType) _, _ = uc.orders.CompletePaymentFulfillment(ctx, result.Provider, result.TradeNo, token, false, err.Error()) - uc.logger.Error(ctx, "payment.order.fulfillment_handler_missing", "provider", result.Provider, "trade_no", result.TradeNo, "business_type", order.BusinessType, "error", err) - return fail(err) + return nil, err } - confirmation := &PaymentConfirmation{ - ID: order.ConfirmationID, Provider: order.Provider, TradeNo: order.TradeNo, - ProviderTradeNo: result.ProviderTradeNo, BusinessType: order.BusinessType, - BusinessID: order.BusinessID, Subject: order.Subject, Amount: order.Amount, - OriginalAmount: order.OriginalAmount, PayerPaidAmount: order.PayerPaidAmount, - CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount, + confirmation := &PaymentConfirmation{ID: order.ConfirmationID, Provider: order.Provider, TradeNo: order.TradeNo, + ProviderTradeNo: result.ProviderTradeNo, BusinessType: order.BusinessType, BusinessID: order.BusinessID, + Subject: order.Subject, Amount: order.Amount, OriginalAmount: order.OriginalAmount, + PayerPaidAmount: order.PayerPaidAmount, CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount, DiscountAmount: order.DiscountAmount, ProviderDiscountAmount: order.ProviderDiscountAmount, MerchantDiscountAmount: order.MerchantDiscountAmount, SettlementAmount: order.SettlementAmount, Currency: order.Currency, PayerCurrency: order.PayerCurrency, AmountBreakdownKnown: order.AmountBreakdownKnown, - Payload: result.Payload, CreatedAt: time.Now().UTC(), - } + Payload: result.Payload, CreatedAt: time.Now().UTC()} if err = handler.Fulfill(ctx, confirmation); err != nil { _, _ = uc.orders.CompletePaymentFulfillment(ctx, result.Provider, result.TradeNo, token, false, err.Error()) - uc.logger.Error(ctx, "payment.order.fulfillment_failed", "provider", result.Provider, "trade_no", result.TradeNo, "confirmation_id", confirmation.ID, "error", err) - return fail(err) + return nil, err } order, err = uc.orders.CompletePaymentFulfillment(ctx, result.Provider, result.TradeNo, token, true, "") if err != nil { - uc.logger.Error(ctx, "payment.order.fulfillment_record_failed", "provider", result.Provider, "trade_no", result.TradeNo, "confirmation_id", confirmation.ID, "error", err) - return fail(err) + return nil, err } result.Status = "fulfilled" return attachOrderResult(result, order), nil diff --git a/app/system/internal/biz/payment_test.go b/app/system/internal/biz/payment_test.go index cd217b2..2c94193 100644 --- a/app/system/internal/biz/payment_test.go +++ b/app/system/internal/biz/payment_test.go @@ -330,6 +330,24 @@ func TestPersistentPaymentCallbackFulfillsAndPersistsDuplicate(t *testing.T) { } } +func TestPaymentFulfillRetriesFailedOrder(t *testing.T) { + orders := &paymentOrderRepoStub{order: &PaymentOrder{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: PaymentStatusPaid, FulfillmentStatus: FulfillmentStatusFailed, RefundStatus: RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111"}} + uc := NewPaymentUsecase(&paymentRepoStub{}, orders, slog.New(slog.NewTextHandler(io.Discard, nil))) + handler := &paymentFulfillmentStub{} + registry := NewPaymentFulfillmentRegistry() + if err := registry.Register(handler); err != nil { + t.Fatal(err) + } + uc.SetFulfillmentRegistry(registry) + result, err := uc.Fulfill(context.Background(), PaymentAlipay, "order-1") + if err != nil { + t.Fatal(err) + } + if result.Status != "fulfilled" || handler.calls != 1 || orders.order.FulfillmentStatus != FulfillmentStatusSucceeded { + t.Fatalf("fulfillment retry failed: result=%+v calls=%d status=%s", result, handler.calls, orders.order.FulfillmentStatus) + } +} + func TestPaymentConfirmationAcceptsProviderDiscount(t *testing.T) { intent := &PaymentIntent{Provider: PaymentAlipay, TradeNo: "T1", Amount: 1000, Currency: "CNY", BusinessType: "item", BusinessID: "I1"} result := &PaymentResult{Provider: PaymentAlipay, TradeNo: "T1", ProviderTradeNo: "P1", Status: "success", Amount: 1000, PayerPaidAmount: 800, CashPaidAmount: 800, DiscountAmount: 200, ProviderDiscountAmount: 150, MerchantDiscountAmount: 50, SettlementAmount: 950, Currency: "CNY", AmountBreakdownKnown: true} diff --git a/app/system/internal/conf/conf.pb.go b/app/system/internal/conf/conf.pb.go index 49fdb40..5e1f470 100644 --- a/app/system/internal/conf/conf.pb.go +++ b/app/system/internal/conf/conf.pb.go @@ -212,13 +212,15 @@ type AdminBackend struct { Email *AdminBackend_Email `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` Storage *AdminBackend_Storage `protobuf:"bytes,6,opt,name=storage,proto3" json:"storage,omitempty"` // ConfigPath is populated by the entrypoint and is not required in YAML. - ConfigPath string `protobuf:"bytes,7,opt,name=config_path,json=configPath,proto3" json:"config_path,omitempty"` - Media *AdminBackend_Media `protobuf:"bytes,8,opt,name=media,proto3" json:"media,omitempty"` - DiskList []*AdminBackend_Disk `protobuf:"bytes,9,rep,name=disk_list,json=diskList,proto3" json:"disk_list,omitempty"` - System *AdminBackend_System `protobuf:"bytes,10,opt,name=system,proto3" json:"system,omitempty"` - Zap *AdminBackend_Zap `protobuf:"bytes,11,opt,name=zap,proto3" json:"zap,omitempty"` - Cors *AdminBackend_CORS `protobuf:"bytes,12,opt,name=cors,proto3" json:"cors,omitempty"` - App *AdminBackend_App `protobuf:"bytes,13,opt,name=app,proto3" json:"app,omitempty"` + ConfigPath string `protobuf:"bytes,7,opt,name=config_path,json=configPath,proto3" json:"config_path,omitempty"` + Media *AdminBackend_Media `protobuf:"bytes,8,opt,name=media,proto3" json:"media,omitempty"` + DiskList []*AdminBackend_Disk `protobuf:"bytes,9,rep,name=disk_list,json=diskList,proto3" json:"disk_list,omitempty"` + System *AdminBackend_System `protobuf:"bytes,10,opt,name=system,proto3" json:"system,omitempty"` + Zap *AdminBackend_Zap `protobuf:"bytes,11,opt,name=zap,proto3" json:"zap,omitempty"` + Cors *AdminBackend_CORS `protobuf:"bytes,12,opt,name=cors,proto3" json:"cors,omitempty"` + App *AdminBackend_App `protobuf:"bytes,13,opt,name=app,proto3" json:"app,omitempty"` + Websocket *AdminBackend_WebSocket `protobuf:"bytes,14,opt,name=websocket,proto3" json:"websocket,omitempty"` + Mq *AdminBackend_MQ `protobuf:"bytes,15,opt,name=mq,proto3" json:"mq,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -344,6 +346,20 @@ func (x *AdminBackend) GetApp() *AdminBackend_App { return nil } +func (x *AdminBackend) GetWebsocket() *AdminBackend_WebSocket { + if x != nil { + return x.Websocket + } + return nil +} + +func (x *AdminBackend) GetMq() *AdminBackend_MQ { + if x != nil { + return x.Mq + } + return nil +} + type Server_HTTP struct { state protoimpl.MessageState `protogen:"open.v1"` Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` @@ -1704,6 +1720,216 @@ func (x *AdminBackend_App) GetEnv() string { return "" } +type AdminBackend_WebSocket struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + AllowOrigins []string `protobuf:"bytes,3,rep,name=allow_origins,json=allowOrigins,proto3" json:"allow_origins,omitempty"` + MaxMessageSize int64 `protobuf:"varint,4,opt,name=max_message_size,json=maxMessageSize,proto3" json:"max_message_size,omitempty"` + WriteWait *durationpb.Duration `protobuf:"bytes,5,opt,name=write_wait,json=writeWait,proto3" json:"write_wait,omitempty"` + PongWait *durationpb.Duration `protobuf:"bytes,6,opt,name=pong_wait,json=pongWait,proto3" json:"pong_wait,omitempty"` + PingPeriod *durationpb.Duration `protobuf:"bytes,7,opt,name=ping_period,json=pingPeriod,proto3" json:"ping_period,omitempty"` + MessageBufferSize int32 `protobuf:"varint,8,opt,name=message_buffer_size,json=messageBufferSize,proto3" json:"message_buffer_size,omitempty"` + ConcurrentMessageHandling bool `protobuf:"varint,9,opt,name=concurrent_message_handling,json=concurrentMessageHandling,proto3" json:"concurrent_message_handling,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminBackend_WebSocket) Reset() { + *x = AdminBackend_WebSocket{} + mi := &file_conf_conf_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminBackend_WebSocket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminBackend_WebSocket) ProtoMessage() {} + +func (x *AdminBackend_WebSocket) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminBackend_WebSocket.ProtoReflect.Descriptor instead. +func (*AdminBackend_WebSocket) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{3, 11} +} + +func (x *AdminBackend_WebSocket) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *AdminBackend_WebSocket) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *AdminBackend_WebSocket) GetAllowOrigins() []string { + if x != nil { + return x.AllowOrigins + } + return nil +} + +func (x *AdminBackend_WebSocket) GetMaxMessageSize() int64 { + if x != nil { + return x.MaxMessageSize + } + return 0 +} + +func (x *AdminBackend_WebSocket) GetWriteWait() *durationpb.Duration { + if x != nil { + return x.WriteWait + } + return nil +} + +func (x *AdminBackend_WebSocket) GetPongWait() *durationpb.Duration { + if x != nil { + return x.PongWait + } + return nil +} + +func (x *AdminBackend_WebSocket) GetPingPeriod() *durationpb.Duration { + if x != nil { + return x.PingPeriod + } + return nil +} + +func (x *AdminBackend_WebSocket) GetMessageBufferSize() int32 { + if x != nil { + return x.MessageBufferSize + } + return 0 +} + +func (x *AdminBackend_WebSocket) GetConcurrentMessageHandling() bool { + if x != nil { + return x.ConcurrentMessageHandling + } + return false +} + +// MQ config is persisted in sys_integration_configs (kind=mq/provider=emqx). +// The bootstrap fields are retained as a one-time migration source. +type AdminBackend_MQ struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Broker string `protobuf:"bytes,2,opt,name=broker,proto3" json:"broker,omitempty"` + ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,5,opt,name=password,proto3" json:"password,omitempty"` + KeepAlive int32 `protobuf:"varint,6,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"` + CleanSession bool `protobuf:"varint,7,opt,name=clean_session,json=cleanSession,proto3" json:"clean_session,omitempty"` + ConnectTimeout int32 `protobuf:"varint,8,opt,name=connect_timeout,json=connectTimeout,proto3" json:"connect_timeout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminBackend_MQ) Reset() { + *x = AdminBackend_MQ{} + mi := &file_conf_conf_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminBackend_MQ) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminBackend_MQ) ProtoMessage() {} + +func (x *AdminBackend_MQ) ProtoReflect() protoreflect.Message { + mi := &file_conf_conf_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminBackend_MQ.ProtoReflect.Descriptor instead. +func (*AdminBackend_MQ) Descriptor() ([]byte, []int) { + return file_conf_conf_proto_rawDescGZIP(), []int{3, 12} +} + +func (x *AdminBackend_MQ) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *AdminBackend_MQ) GetBroker() string { + if x != nil { + return x.Broker + } + return "" +} + +func (x *AdminBackend_MQ) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *AdminBackend_MQ) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *AdminBackend_MQ) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *AdminBackend_MQ) GetKeepAlive() int32 { + if x != nil { + return x.KeepAlive + } + return 0 +} + +func (x *AdminBackend_MQ) GetCleanSession() bool { + if x != nil { + return x.CleanSession + } + return false +} + +func (x *AdminBackend_MQ) GetConnectTimeout() int32 { + if x != nil { + return x.ConnectTimeout + } + return 0 +} + type AdminBackend_Storage struct { state protoimpl.MessageState `protogen:"open.v1"` Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` @@ -1720,7 +1946,7 @@ type AdminBackend_Storage struct { func (x *AdminBackend_Storage) Reset() { *x = AdminBackend_Storage{} - mi := &file_conf_conf_proto_msgTypes[20] + mi := &file_conf_conf_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1732,7 +1958,7 @@ func (x *AdminBackend_Storage) String() string { func (*AdminBackend_Storage) ProtoMessage() {} func (x *AdminBackend_Storage) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[20] + mi := &file_conf_conf_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1745,7 +1971,7 @@ func (x *AdminBackend_Storage) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminBackend_Storage.ProtoReflect.Descriptor instead. func (*AdminBackend_Storage) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{3, 11} + return file_conf_conf_proto_rawDescGZIP(), []int{3, 13} } func (x *AdminBackend_Storage) GetType() string { @@ -1819,7 +2045,7 @@ type AdminBackend_Qiniu struct { func (x *AdminBackend_Qiniu) Reset() { *x = AdminBackend_Qiniu{} - mi := &file_conf_conf_proto_msgTypes[21] + mi := &file_conf_conf_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1831,7 +2057,7 @@ func (x *AdminBackend_Qiniu) String() string { func (*AdminBackend_Qiniu) ProtoMessage() {} func (x *AdminBackend_Qiniu) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[21] + mi := &file_conf_conf_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1844,7 +2070,7 @@ func (x *AdminBackend_Qiniu) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminBackend_Qiniu.ProtoReflect.Descriptor instead. func (*AdminBackend_Qiniu) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{3, 12} + return file_conf_conf_proto_rawDescGZIP(), []int{3, 14} } func (x *AdminBackend_Qiniu) GetZone() string { @@ -1916,7 +2142,7 @@ type AdminBackend_ObjectStore struct { func (x *AdminBackend_ObjectStore) Reset() { *x = AdminBackend_ObjectStore{} - mi := &file_conf_conf_proto_msgTypes[22] + mi := &file_conf_conf_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1928,7 +2154,7 @@ func (x *AdminBackend_ObjectStore) String() string { func (*AdminBackend_ObjectStore) ProtoMessage() {} func (x *AdminBackend_ObjectStore) ProtoReflect() protoreflect.Message { - mi := &file_conf_conf_proto_msgTypes[22] + mi := &file_conf_conf_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1941,7 +2167,7 @@ func (x *AdminBackend_ObjectStore) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminBackend_ObjectStore.ProtoReflect.Descriptor instead. func (*AdminBackend_ObjectStore) Descriptor() ([]byte, []int) { - return file_conf_conf_proto_rawDescGZIP(), []int{3, 13} + return file_conf_conf_proto_rawDescGZIP(), []int{3, 15} } func (x *AdminBackend_ObjectStore) GetEndpoint() string { @@ -2086,7 +2312,7 @@ const file_conf_conf_proto_rawDesc = "" + "\x12connect_timeout_ms\x18\n" + " \x01(\x03R\x10connectTimeoutMs\x12\x15\n" + "\x06is_zap\x18\v \x01(\bR\x05isZap\x120\n" + - "\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xad\x1c\n" + + "\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xc0\"\n" + "\fAdminBackend\x12#\n" + "\rrouter_prefix\x18\x01 \x01(\tR\frouterPrefix\x12.\n" + "\x03jwt\x18\x02 \x01(\v2\x1c.kratos.api.AdminBackend.JWTR\x03jwt\x12:\n" + @@ -2102,7 +2328,9 @@ const file_conf_conf_proto_rawDesc = "" + " \x01(\v2\x1f.kratos.api.AdminBackend.SystemR\x06system\x12.\n" + "\x03zap\x18\v \x01(\v2\x1c.kratos.api.AdminBackend.ZapR\x03zap\x121\n" + "\x04cors\x18\f \x01(\v2\x1d.kratos.api.AdminBackend.CORSR\x04cors\x12.\n" + - "\x03app\x18\r \x01(\v2\x1c.kratos.api.AdminBackend.AppR\x03app\x1a\xb8\x01\n" + + "\x03app\x18\r \x01(\v2\x1c.kratos.api.AdminBackend.AppR\x03app\x12@\n" + + "\twebsocket\x18\x0e \x01(\v2\".kratos.api.AdminBackend.WebSocketR\twebsocket\x12+\n" + + "\x02mq\x18\x0f \x01(\v2\x1b.kratos.api.AdminBackend.MQR\x02mq\x1a\xb8\x01\n" + "\x03JWT\x12\x1f\n" + "\vsigning_key\x18\x01 \x01(\tR\n" + "signingKey\x12<\n" + @@ -2175,7 +2403,29 @@ const file_conf_conf_proto_rawDesc = "" + "\x03App\x12\x12\n" + "\x04node\x18\x01 \x01(\tR\x04node\x12\x15\n" + "\x06app_id\x18\x02 \x01(\tR\x05appId\x12\x10\n" + - "\x03env\x18\x03 \x01(\tR\x03env\x1a\xe8\x03\n" + + "\x03env\x18\x03 \x01(\tR\x03env\x1a\xa6\x03\n" + + "\tWebSocket\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x12\n" + + "\x04path\x18\x02 \x01(\tR\x04path\x12#\n" + + "\rallow_origins\x18\x03 \x03(\tR\fallowOrigins\x12(\n" + + "\x10max_message_size\x18\x04 \x01(\x03R\x0emaxMessageSize\x128\n" + + "\n" + + "write_wait\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\twriteWait\x126\n" + + "\tpong_wait\x18\x06 \x01(\v2\x19.google.protobuf.DurationR\bpongWait\x12:\n" + + "\vping_period\x18\a \x01(\v2\x19.google.protobuf.DurationR\n" + + "pingPeriod\x12.\n" + + "\x13message_buffer_size\x18\b \x01(\x05R\x11messageBufferSize\x12>\n" + + "\x1bconcurrent_message_handling\x18\t \x01(\bR\x19concurrentMessageHandling\x1a\xf8\x01\n" + + "\x02MQ\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x16\n" + + "\x06broker\x18\x02 \x01(\tR\x06broker\x12\x1b\n" + + "\tclient_id\x18\x03 \x01(\tR\bclientId\x12\x1a\n" + + "\busername\x18\x04 \x01(\tR\busername\x12\x1a\n" + + "\bpassword\x18\x05 \x01(\tR\bpassword\x12\x1d\n" + + "\n" + + "keep_alive\x18\x06 \x01(\x05R\tkeepAlive\x12#\n" + + "\rclean_session\x18\a \x01(\bR\fcleanSession\x12'\n" + + "\x0fconnect_timeout\x18\b \x01(\x05R\x0econnectTimeout\x1a\xe8\x03\n" + "\aStorage\x12\x12\n" + "\x04type\x18\x01 \x01(\tR\x04type\x124\n" + "\x05qiniu\x18\x02 \x01(\v2\x1e.kratos.api.AdminBackend.QiniuR\x05qiniu\x12C\n" + @@ -2227,7 +2477,7 @@ func file_conf_conf_proto_rawDescGZIP() []byte { return file_conf_conf_proto_rawDescData } -var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_conf_conf_proto_goTypes = []any{ (*Bootstrap)(nil), // 0: kratos.api.Bootstrap (*Server)(nil), // 1: kratos.api.Server @@ -2249,10 +2499,12 @@ var file_conf_conf_proto_goTypes = []any{ (*AdminBackend_CORS)(nil), // 17: kratos.api.AdminBackend.CORS (*AdminBackend_CORSRule)(nil), // 18: kratos.api.AdminBackend.CORSRule (*AdminBackend_App)(nil), // 19: kratos.api.AdminBackend.App - (*AdminBackend_Storage)(nil), // 20: kratos.api.AdminBackend.Storage - (*AdminBackend_Qiniu)(nil), // 21: kratos.api.AdminBackend.Qiniu - (*AdminBackend_ObjectStore)(nil), // 22: kratos.api.AdminBackend.ObjectStore - (*durationpb.Duration)(nil), // 23: google.protobuf.Duration + (*AdminBackend_WebSocket)(nil), // 20: kratos.api.AdminBackend.WebSocket + (*AdminBackend_MQ)(nil), // 21: kratos.api.AdminBackend.MQ + (*AdminBackend_Storage)(nil), // 22: kratos.api.AdminBackend.Storage + (*AdminBackend_Qiniu)(nil), // 23: kratos.api.AdminBackend.Qiniu + (*AdminBackend_ObjectStore)(nil), // 24: kratos.api.AdminBackend.ObjectStore + (*durationpb.Duration)(nil), // 25: google.protobuf.Duration } var file_conf_conf_proto_depIdxs = []int32{ 1, // 0: kratos.api.Bootstrap.server:type_name -> kratos.api.Server @@ -2268,33 +2520,38 @@ var file_conf_conf_proto_depIdxs = []int32{ 10, // 10: kratos.api.AdminBackend.captcha:type_name -> kratos.api.AdminBackend.Captcha 11, // 11: kratos.api.AdminBackend.local:type_name -> kratos.api.AdminBackend.Local 12, // 12: kratos.api.AdminBackend.email:type_name -> kratos.api.AdminBackend.Email - 20, // 13: kratos.api.AdminBackend.storage:type_name -> kratos.api.AdminBackend.Storage + 22, // 13: kratos.api.AdminBackend.storage:type_name -> kratos.api.AdminBackend.Storage 13, // 14: kratos.api.AdminBackend.media:type_name -> kratos.api.AdminBackend.Media 14, // 15: kratos.api.AdminBackend.disk_list:type_name -> kratos.api.AdminBackend.Disk 15, // 16: kratos.api.AdminBackend.system:type_name -> kratos.api.AdminBackend.System 16, // 17: kratos.api.AdminBackend.zap:type_name -> kratos.api.AdminBackend.Zap 17, // 18: kratos.api.AdminBackend.cors:type_name -> kratos.api.AdminBackend.CORS 19, // 19: kratos.api.AdminBackend.app:type_name -> kratos.api.AdminBackend.App - 23, // 20: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration - 23, // 21: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration - 23, // 22: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration - 7, // 23: kratos.api.Data.Mongo.hosts:type_name -> kratos.api.Data.MongoHost - 23, // 24: kratos.api.AdminBackend.JWT.expires_time:type_name -> google.protobuf.Duration - 23, // 25: kratos.api.AdminBackend.JWT.buffer_time:type_name -> google.protobuf.Duration - 23, // 26: kratos.api.AdminBackend.Captcha.store_expiration:type_name -> google.protobuf.Duration - 18, // 27: kratos.api.AdminBackend.CORS.whitelist:type_name -> kratos.api.AdminBackend.CORSRule - 21, // 28: kratos.api.AdminBackend.Storage.qiniu:type_name -> kratos.api.AdminBackend.Qiniu - 22, // 29: kratos.api.AdminBackend.Storage.aliyun_oss:type_name -> kratos.api.AdminBackend.ObjectStore - 22, // 30: kratos.api.AdminBackend.Storage.huawei_obs:type_name -> kratos.api.AdminBackend.ObjectStore - 22, // 31: kratos.api.AdminBackend.Storage.tencent_cos:type_name -> kratos.api.AdminBackend.ObjectStore - 22, // 32: kratos.api.AdminBackend.Storage.aws_s3:type_name -> kratos.api.AdminBackend.ObjectStore - 22, // 33: kratos.api.AdminBackend.Storage.cloudflare_r2:type_name -> kratos.api.AdminBackend.ObjectStore - 22, // 34: kratos.api.AdminBackend.Storage.minio:type_name -> kratos.api.AdminBackend.ObjectStore - 35, // [35:35] is the sub-list for method output_type - 35, // [35:35] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 20, // 20: kratos.api.AdminBackend.websocket:type_name -> kratos.api.AdminBackend.WebSocket + 21, // 21: kratos.api.AdminBackend.mq:type_name -> kratos.api.AdminBackend.MQ + 25, // 22: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration + 25, // 23: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration + 25, // 24: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration + 7, // 25: kratos.api.Data.Mongo.hosts:type_name -> kratos.api.Data.MongoHost + 25, // 26: kratos.api.AdminBackend.JWT.expires_time:type_name -> google.protobuf.Duration + 25, // 27: kratos.api.AdminBackend.JWT.buffer_time:type_name -> google.protobuf.Duration + 25, // 28: kratos.api.AdminBackend.Captcha.store_expiration:type_name -> google.protobuf.Duration + 18, // 29: kratos.api.AdminBackend.CORS.whitelist:type_name -> kratos.api.AdminBackend.CORSRule + 25, // 30: kratos.api.AdminBackend.WebSocket.write_wait:type_name -> google.protobuf.Duration + 25, // 31: kratos.api.AdminBackend.WebSocket.pong_wait:type_name -> google.protobuf.Duration + 25, // 32: kratos.api.AdminBackend.WebSocket.ping_period:type_name -> google.protobuf.Duration + 23, // 33: kratos.api.AdminBackend.Storage.qiniu:type_name -> kratos.api.AdminBackend.Qiniu + 24, // 34: kratos.api.AdminBackend.Storage.aliyun_oss:type_name -> kratos.api.AdminBackend.ObjectStore + 24, // 35: kratos.api.AdminBackend.Storage.huawei_obs:type_name -> kratos.api.AdminBackend.ObjectStore + 24, // 36: kratos.api.AdminBackend.Storage.tencent_cos:type_name -> kratos.api.AdminBackend.ObjectStore + 24, // 37: kratos.api.AdminBackend.Storage.aws_s3:type_name -> kratos.api.AdminBackend.ObjectStore + 24, // 38: kratos.api.AdminBackend.Storage.cloudflare_r2:type_name -> kratos.api.AdminBackend.ObjectStore + 24, // 39: kratos.api.AdminBackend.Storage.minio:type_name -> kratos.api.AdminBackend.ObjectStore + 40, // [40:40] is the sub-list for method output_type + 40, // [40:40] is the sub-list for method input_type + 40, // [40:40] is the sub-list for extension type_name + 40, // [40:40] is the sub-list for extension extendee + 0, // [0:40] is the sub-list for field type_name } func init() { file_conf_conf_proto_init() } @@ -2308,7 +2565,7 @@ func file_conf_conf_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_conf_conf_proto_rawDesc), len(file_conf_conf_proto_rawDesc)), NumEnums: 0, - NumMessages: 23, + NumMessages: 25, NumExtensions: 0, NumServices: 0, }, diff --git a/app/system/internal/conf/conf.proto b/app/system/internal/conf/conf.proto index 571e583..0568b58 100644 --- a/app/system/internal/conf/conf.proto +++ b/app/system/internal/conf/conf.proto @@ -93,6 +93,8 @@ message AdminBackend { Zap zap = 11; CORS cors = 12; App app = 13; + WebSocket websocket = 14; + MQ mq = 15; message JWT { string signing_key = 1; @@ -179,6 +181,31 @@ message AdminBackend { string env = 3; } + message WebSocket { + bool enabled = 1; + string path = 2; + repeated string allow_origins = 3; + int64 max_message_size = 4; + google.protobuf.Duration write_wait = 5; + google.protobuf.Duration pong_wait = 6; + google.protobuf.Duration ping_period = 7; + int32 message_buffer_size = 8; + bool concurrent_message_handling = 9; + } + + // MQ config is persisted in sys_integration_configs (kind=mq/provider=emqx). + // The bootstrap fields are retained as a one-time migration source. + message MQ { + bool enabled = 1; + string broker = 2; + string client_id = 3; + string username = 4; + string password = 5; + int32 keep_alive = 6; + bool clean_session = 7; + int32 connect_timeout = 8; + } + message Storage { string type = 1; Qiniu qiniu = 2; diff --git a/app/system/internal/data/config_store.go b/app/system/internal/data/config_store.go index 13f6a1a..16ac8a9 100644 --- a/app/system/internal/data/config_store.go +++ b/app/system/internal/data/config_store.go @@ -182,6 +182,8 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con fileAdmin := cloneAdminConfig(adminConfig) fileAdmin.Storage = nil fileAdmin.Email = nil + fileAdmin.Websocket = nil + fileAdmin.Mq = nil adminValue, err := protoMap(fileAdmin) if err != nil { return err @@ -194,6 +196,8 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con } deleteYAMLMapping(&document, "admin", "storage") deleteYAMLMapping(&document, "admin", "email") + deleteYAMLMapping(&document, "admin", "websocket") + deleteYAMLMapping(&document, "admin", "mq") if adminConfig.System != nil { if err = setServerHTTPPort(&document, adminConfig.System.Addr); err != nil { return err @@ -253,6 +257,8 @@ func (d *Data) persistDatabaseConfig(database *conf.Data_Database, signingKey st } deleteYAMLMapping(&document, "admin", "storage") deleteYAMLMapping(&document, "admin", "email") + deleteYAMLMapping(&document, "admin", "websocket") + deleteYAMLMapping(&document, "admin", "mq") return writeConfigDocument(configPath, &document) } @@ -271,11 +277,13 @@ func (d *Data) removeIntegrationConfigFromFile() error { if err = yaml.Unmarshal(raw, &document); err != nil { return err } - if yamlMappingValue(&document, "admin", "storage") == nil && yamlMappingValue(&document, "admin", "email") == nil { + if yamlMappingValue(&document, "admin", "storage") == nil && yamlMappingValue(&document, "admin", "email") == nil && yamlMappingValue(&document, "admin", "websocket") == nil && yamlMappingValue(&document, "admin", "mq") == nil { return nil } deleteYAMLMapping(&document, "admin", "storage") deleteYAMLMapping(&document, "admin", "email") + deleteYAMLMapping(&document, "admin", "websocket") + deleteYAMLMapping(&document, "admin", "mq") return writeConfigDocument(configPath, &document) } @@ -357,6 +365,8 @@ func (d *Data) reloadConfig(ctx context.Context) error { } legacyStorage := next.Admin.Storage legacyEmail := next.Admin.Email + legacyWebSocket := next.Admin.Websocket + legacyMQ := next.Admin.Mq currentAdmin := d.runtime.Admin() if legacyStorage == nil { if currentAdmin != nil { @@ -366,6 +376,12 @@ func (d *Data) reloadConfig(ctx context.Context) error { if legacyEmail == nil && currentAdmin != nil { legacyEmail = currentAdmin.Email } + if legacyWebSocket == nil && currentAdmin != nil { + legacyWebSocket = currentAdmin.Websocket + } + if legacyMQ == nil && currentAdmin != nil { + legacyMQ = currentAdmin.Mq + } storageConfig, err := resolveStorageIntegrationConfig(candidateDB.WithContext(ctx), legacyStorage) if err != nil { return fmt.Errorf("reload storage configuration: %w", err) @@ -376,6 +392,16 @@ func (d *Data) reloadConfig(ctx context.Context) error { return fmt.Errorf("reload email configuration: %w", err) } next.Admin.Email = emailConfig + websocketConfig, err := resolveWebSocketIntegrationConfig(candidateDB.WithContext(ctx), legacyWebSocket) + if err != nil { + return fmt.Errorf("reload websocket configuration: %w", err) + } + next.Admin.Websocket = websocketConfig + mqConfig, err := resolveMQIntegrationConfig(candidateDB.WithContext(ctx), legacyMQ) + if err != nil { + return fmt.Errorf("reload mq configuration: %w", err) + } + next.Admin.Mq = mqConfig candidateStorage, err := storage.New(next.Admin) if err != nil { return fmt.Errorf("reload storage: %w", err) diff --git a/app/system/internal/data/config_watch.go b/app/system/internal/data/config_watch.go index f679566..1917280 100644 --- a/app/system/internal/data/config_watch.go +++ b/app/system/internal/data/config_watch.go @@ -58,6 +58,8 @@ func (d *Data) watchConfig() func() { if current := d.runtime.Admin(); current != nil { next.Admin.Storage = current.Storage next.Admin.Email = current.Email + next.Admin.Mq = current.Mq + next.Admin.Websocket = current.Websocket } next.Admin.ConfigPath = absolute d.runtime.Replace(next.Data, next.Admin) diff --git a/app/system/internal/data/data.go b/app/system/internal/data/data.go index ee7d3a5..9e54d7a 100644 --- a/app/system/internal/data/data.go +++ b/app/system/internal/data/data.go @@ -207,6 +207,16 @@ func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *stor } admin.Storage = storageConfig admin.Email = emailConfig + websocketConfig, websocketErr := resolveWebSocketIntegrationConfig(db, admin.Websocket) + if websocketErr != nil { + return nil, nil, fmt.Errorf("load websocket integration configuration: %w", websocketErr) + } + admin.Websocket = websocketConfig + mqConfig, mqErr := resolveMQIntegrationConfig(db, admin.Mq) + if mqErr != nil { + return nil, nil, fmt.Errorf("load mq integration configuration: %w", mqErr) + } + admin.Mq = mqConfig runtime.Replace(c, admin) activeStorage, storageErr := storage.New(admin) if storageErr != nil { diff --git a/app/system/internal/data/initialization_backend.go b/app/system/internal/data/initialization_backend.go index ec5b409..528254f 100644 --- a/app/system/internal/data/initialization_backend.go +++ b/app/system/internal/data/initialization_backend.go @@ -69,6 +69,12 @@ func (d *Data) PersistAdminConfig(ctx context.Context, raw []byte) error { if next.Email == nil { next.Email = currentAdmin.Email } + if next.Websocket == nil { + next.Websocket = currentAdmin.Websocket + } + if next.Mq == nil { + next.Mq = currentAdmin.Mq + } next.ConfigPath = currentAdmin.ConfigPath candidateStorage, err := storage.New(next) if err != nil { @@ -80,6 +86,12 @@ func (d *Data) PersistAdminConfig(ctx context.Context, raw []byte) error { if err := d.persistEmailIntegrationConfig(ctx, next.Email); err != nil { return err } + if err := d.PersistWebSocketConfig(ctx, next.Websocket); err != nil { + return err + } + if err := d.persistMQIntegrationConfig(ctx, next.Mq); err != nil { + return err + } if err := d.persistConfigValues(currentData, next); err != nil { return err } @@ -108,6 +120,12 @@ func (d *Data) PersistRuntimeConfig(ctx context.Context, dataRaw, adminRaw []byt if nextAdmin.Email == nil { nextAdmin.Email = currentAdmin.Email } + if nextAdmin.Websocket == nil { + nextAdmin.Websocket = currentAdmin.Websocket + } + if nextAdmin.Mq == nil { + nextAdmin.Mq = currentAdmin.Mq + } nextAdmin.ConfigPath = currentAdmin.ConfigPath candidateStorage, err := storage.New(nextAdmin) if err != nil { @@ -119,6 +137,12 @@ func (d *Data) PersistRuntimeConfig(ctx context.Context, dataRaw, adminRaw []byt if err := d.persistEmailIntegrationConfig(ctx, nextAdmin.Email); err != nil { return err } + if err := d.PersistWebSocketConfig(ctx, nextAdmin.Websocket); err != nil { + return err + } + if err := d.persistMQIntegrationConfig(ctx, nextAdmin.Mq); err != nil { + return err + } if err := d.persistConfigValues(nextData, nextAdmin); err != nil { return err } @@ -205,6 +229,14 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *biz.DatabaseConfig if err != nil { return fmt.Errorf("initialize email integration configuration: %w", err) } + var legacyWebSocket *conf.AdminBackend_WebSocket + if currentAdmin != nil { + legacyWebSocket = currentAdmin.Websocket + } + websocketConfig, err := resolveWebSocketIntegrationConfig(candidate.WithContext(ctx), legacyWebSocket) + if err != nil { + return fmt.Errorf("initialize websocket integration configuration: %w", err) + } signingKey := uuid.NewString() if err := d.persistDatabaseConfig(config, signingKey); err != nil { return fmt.Errorf("persist database configuration: %w", err) @@ -219,7 +251,13 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *biz.DatabaseConfig } currentAdmin.Jwt.SigningKey = signingKey currentAdmin.Storage = storageConfig + currentAdmin.Websocket = websocketConfig currentAdmin.Email = emailConfig + mqConfig, err := resolveMQIntegrationConfig(candidate.WithContext(ctx), currentAdmin.Mq) + if err != nil { + return fmt.Errorf("initialize mq integration configuration: %w", err) + } + currentAdmin.Mq = mqConfig d.runtime.Replace(currentData, currentAdmin) activated = true return nil diff --git a/app/system/internal/data/integration_config.go b/app/system/internal/data/integration_config.go index 771d963..55ee235 100644 --- a/app/system/internal/data/integration_config.go +++ b/app/system/internal/data/integration_config.go @@ -19,6 +19,7 @@ const ( integrationKindStorage = "storage" integrationKindEmail = "email" integrationKindPayment = "payment" + integrationKindMQ = "mq" ) // integrationConfigPO stores credentials and provider-specific options for @@ -34,6 +35,88 @@ type integrationConfigPO struct { Config string `gorm:"type:text;not null"` } +func defaultMQIntegrationConfig() *conf.AdminBackend_MQ { + return &conf.AdminBackend_MQ{CleanSession: true, KeepAlive: 30, ConnectTimeout: 10} +} + +func saveMQIntegrationConfig(db *gorm.DB, config *conf.AdminBackend_MQ) error { + if config == nil { + config = defaultMQIntegrationConfig() + } + raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(config) + if err != nil { + return fmt.Errorf("encode emqx integration configuration: %w", err) + } + enabled := config.Enabled && strings.TrimSpace(config.Broker) != "" + clean := db.Session(&gorm.Session{NewDB: true}) + var row integrationConfigPO + err = clean.Where("kind = ? AND provider = ?", integrationKindMQ, "emqx").First(&row).Error + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return clean.Create(&integrationConfigPO{Kind: integrationKindMQ, Provider: "emqx", Enabled: enabled, Config: string(raw)}).Error + case err != nil: + return err + default: + return clean.Model(&row).Updates(map[string]any{"enabled": enabled, "config": string(raw)}).Error + } +} + +func loadMQIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_MQ, bool, error) { + var row integrationConfigPO + err := db.Session(&gorm.Session{NewDB: true}).Where("kind = ? AND provider = ?", integrationKindMQ, "emqx").First(&row).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + if err != nil { + return nil, false, err + } + if !json.Valid([]byte(row.Config)) { + return nil, false, errors.New("invalid emqx integration configuration") + } + config := defaultMQIntegrationConfig() + if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(row.Config), config); err != nil { + return nil, false, fmt.Errorf("decode emqx integration configuration: %w", err) + } + config.Enabled = row.Enabled + return config, true, nil +} + +func resolveMQIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_MQ) (*conf.AdminBackend_MQ, error) { + clean := db.Session(&gorm.Session{NewDB: true}) + if !clean.Migrator().HasTable(&integrationConfigPO{}) { + if legacy == nil { + return defaultMQIntegrationConfig(), nil + } + return proto.Clone(legacy).(*conf.AdminBackend_MQ), nil + } + loaded, found, err := loadMQIntegrationConfig(clean) + if err != nil { + return nil, err + } + if found { + return loaded, nil + } + if legacy == nil { + legacy = defaultMQIntegrationConfig() + } + if err = saveMQIntegrationConfig(clean, legacy); err != nil { + return nil, err + } + loaded, _, err = loadMQIntegrationConfig(clean) + return loaded, err +} + +func (d *Data) persistMQIntegrationConfig(ctx context.Context, config *conf.AdminBackend_MQ) error { + if !d.databaseReady.Load() { + return errors.New("database is not initialized") + } + db := d.gormDB.WithContext(ctx) + if !db.Migrator().HasTable(&integrationConfigPO{}) { + return errors.New("integration configuration table does not exist") + } + return saveMQIntegrationConfig(db, config) +} + func (integrationConfigPO) TableName() string { return "sys_integration_configs" } var storageProviderNames = []string{ diff --git a/app/system/internal/data/integration_config_test.go b/app/system/internal/data/integration_config_test.go index c6959e5..1cc057b 100644 --- a/app/system/internal/data/integration_config_test.go +++ b/app/system/internal/data/integration_config_test.go @@ -123,6 +123,25 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) { } } +func TestMQIntegrationConfigRoundTrip(t *testing.T) { + db := openIntegrationConfigTestDB(t) + legacy := &conf.AdminBackend_MQ{Enabled: true, Broker: "mqtt://emqx.example.com:1883", ClientId: "system", Username: "app", Password: "secret", KeepAlive: 45, CleanSession: true, ConnectTimeout: 12} + if err := saveMQIntegrationConfig(db, legacy); err != nil { + t.Fatal(err) + } + loaded, found, err := loadMQIntegrationConfig(db) + if err != nil || !found { + t.Fatalf("found=%v err=%v", found, err) + } + if loaded.Broker != legacy.Broker || loaded.Password != legacy.Password || loaded.KeepAlive != 45 { + t.Fatalf("loaded mq = %#v", loaded) + } + loaded, err = resolveMQIntegrationConfig(db, &conf.AdminBackend_MQ{Broker: "must-not-replace"}) + if err != nil || loaded.Broker != legacy.Broker { + t.Fatalf("database mq was replaced: %#v err=%v", loaded, err) + } +} + func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) { db := openIntegrationConfigTestDB(t) legacy := &conf.AdminBackend_Storage{ @@ -186,7 +205,7 @@ func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) { func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") - input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n extension_key: retained\n") + input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n mq:\n enabled: true\n broker: tcp://localhost:1883\n password: legacy-mq-secret\n extension_key: retained\n") if err := os.WriteFile(path, input, 0o600); err != nil { t.Fatal(err) } @@ -199,6 +218,7 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) { Qiniu: &conf.AdminBackend_Qiniu{SecretKey: "database-only-secret"}, }, Email: &conf.AdminBackend_Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"}, + Mq: &conf.AdminBackend_MQ{Enabled: true, Broker: "tcp://emqx:1883", Password: "database-only-mq-secret"}, } if err := d.persistConfigValues(&conf.Data{}, admin); err != nil { t.Fatal(err) @@ -222,6 +242,9 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) { if _, exists := adminValue["email"]; exists { t.Fatalf("email remained in YAML: %s", raw) } + if _, exists := adminValue["mq"]; exists { + t.Fatalf("mq remained in YAML: %s", raw) + } if adminValue["extension_key"] != "retained" { t.Fatalf("extension key was not retained: %#v", adminValue) } diff --git a/app/system/internal/data/repository/runtime.go b/app/system/internal/data/repository/runtime.go index 9ae2d48..3b1b3cd 100644 --- a/app/system/internal/data/repository/runtime.go +++ b/app/system/internal/data/repository/runtime.go @@ -8,7 +8,7 @@ import ( "kra/app/system/internal/biz" "kra/app/system/internal/conf" - "kra/app/system/internal/security/adminauth" + "kra/app/system/internal/security" jwt "github.com/golang-jwt/jwt/v5" ) @@ -95,7 +95,7 @@ func (i *tokenIssuer) IssueToken(user *biz.User, authorityID uint, mustChangePas if user.PasswordUpdatedAt != nil { passwordVersion = user.PasswordUpdatedAt.UnixNano() } - token, claims, err := adminauth.Generate(settings.SigningKey, settings.Issuer, expires, settings.Buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, mustChangePassword, passwordVersion) + token, claims, err := security.Generate(settings.SigningKey, settings.Issuer, expires, settings.Buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, mustChangePassword, passwordVersion) if err != nil { return nil, err } @@ -107,7 +107,7 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b return nil, errors.New("nil JWT claims") } settings := i.settings.JWTSettings() - claims := &adminauth.Claims{ + claims := &security.Claims{ UUID: source.UUID, ID: source.ID, Username: source.Username, NickName: source.NickName, AuthorityID: authorityID, BufferTime: int64(source.BufferTime / time.Second), UserType: source.UserType, MustChangePwd: source.MustChangePwd, PasswordVersion: source.PasswordVersion, @@ -116,7 +116,7 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b IssuedAt: jwt.NewNumericDate(source.IssuedAt), NotBefore: jwt.NewNumericDate(source.NotBefore), ExpiresAt: jwt.NewNumericDate(source.ExpiresAt), }, } - token, err := adminauth.Sign(settings.SigningKey, claims) + token, err := security.Sign(settings.SigningKey, claims) if err != nil { return nil, err } @@ -124,16 +124,16 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b } func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) { - claims, err := adminauth.Parse(token, i.settings.JWTSettings().SigningKey) + claims, err := security.Parse(token, i.settings.JWTSettings().SigningKey) if err != nil { switch { - case errors.Is(err, adminauth.ErrTokenExpired): + case errors.Is(err, security.ErrTokenExpired): return nil, biz.ErrTokenExpired - case errors.Is(err, adminauth.ErrTokenMalformed): + case errors.Is(err, security.ErrTokenMalformed): return nil, biz.ErrTokenMalformed - case errors.Is(err, adminauth.ErrTokenSignatureInvalid): + case errors.Is(err, security.ErrTokenSignatureInvalid): return nil, biz.ErrTokenSignatureInvalid - case errors.Is(err, adminauth.ErrTokenNotValidYet): + case errors.Is(err, security.ErrTokenNotValidYet): return nil, biz.ErrTokenNotValidYet default: return nil, biz.ErrTokenInvalid diff --git a/app/system/internal/data/websocket_config.go b/app/system/internal/data/websocket_config.go new file mode 100644 index 0000000..58e1253 --- /dev/null +++ b/app/system/internal/data/websocket_config.go @@ -0,0 +1,96 @@ +package data + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "kra/app/system/internal/conf" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "gorm.io/gorm" +) + +const integrationKindWebSocket = "websocket" + +// saveWebSocketIntegrationConfig persists the Melody settings in the shared +// integration table. It intentionally lives separately from storage/payment +// persistence so adding another transport does not expand their API surface. +func saveWebSocketIntegrationConfig(db *gorm.DB, config *conf.AdminBackend_WebSocket) error { + if config == nil { + config = &conf.AdminBackend_WebSocket{} + } + raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(config) + if err != nil { + return fmt.Errorf("encode websocket integration configuration: %w", err) + } + clean := db.Session(&gorm.Session{NewDB: true}) + var current integrationConfigPO + err = clean.Where("kind = ? AND provider = ?", integrationKindWebSocket, "melody").First(¤t).Error + switch { + case errors.Is(err, gorm.ErrRecordNotFound): + return clean.Create(&integrationConfigPO{Kind: integrationKindWebSocket, Provider: "melody", Enabled: config.Enabled, Config: string(raw)}).Error + case err != nil: + return err + default: + return clean.Model(¤t).Updates(map[string]any{"enabled": config.Enabled, "config": string(raw)}).Error + } +} + +func loadWebSocketIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_WebSocket, bool, error) { + var row integrationConfigPO + err := db.Session(&gorm.Session{NewDB: true}).Where("kind = ? AND provider = ?", integrationKindWebSocket, "melody").First(&row).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + if err != nil { + return nil, false, err + } + if !json.Valid([]byte(row.Config)) { + return nil, false, errors.New("invalid websocket integration configuration") + } + config := &conf.AdminBackend_WebSocket{} + if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(row.Config), config); err != nil { + return nil, false, fmt.Errorf("decode websocket integration configuration: %w", err) + } + config.Enabled = row.Enabled + return config, true, nil +} + +func resolveWebSocketIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_WebSocket) (*conf.AdminBackend_WebSocket, error) { + clean := db.Session(&gorm.Session{NewDB: true}) + if !clean.Migrator().HasTable(&integrationConfigPO{}) { + if legacy == nil { + return &conf.AdminBackend_WebSocket{}, nil + } + return proto.Clone(legacy).(*conf.AdminBackend_WebSocket), nil + } + config, found, err := loadWebSocketIntegrationConfig(clean) + if err != nil { + return nil, err + } + if found { + return config, nil + } + if legacy == nil { + legacy = &conf.AdminBackend_WebSocket{} + } + if err = saveWebSocketIntegrationConfig(clean, legacy); err != nil { + return nil, err + } + config, _, err = loadWebSocketIntegrationConfig(clean) + return config, err +} + +func (d *Data) PersistWebSocketConfig(ctx context.Context, config *conf.AdminBackend_WebSocket) error { + if !d.databaseReady.Load() { + return errors.New("database is not initialized") + } + db := d.gormDB.WithContext(ctx) + if !db.Migrator().HasTable(&integrationConfigPO{}) { + return errors.New("integration configuration table does not exist") + } + return saveWebSocketIntegrationConfig(db, config) +} diff --git a/app/system/internal/data/websocket_config_test.go b/app/system/internal/data/websocket_config_test.go new file mode 100644 index 0000000..6df39ec --- /dev/null +++ b/app/system/internal/data/websocket_config_test.go @@ -0,0 +1,50 @@ +package data + +import ( + "testing" + "time" + + "kra/app/system/internal/conf" + + "google.golang.org/protobuf/types/known/durationpb" +) + +func TestWebSocketIntegrationConfigRoundTrip(t *testing.T) { + db := openIntegrationConfigTestDB(t) + want := &conf.AdminBackend_WebSocket{ + Enabled: true, Path: "/events", AllowOrigins: []string{"https://admin.example.com"}, + MaxMessageSize: 4096, WriteWait: durationpb.New(3 * time.Second), + PongWait: durationpb.New(20 * time.Second), PingPeriod: durationpb.New(15 * time.Second), + MessageBufferSize: 32, ConcurrentMessageHandling: true, + } + if err := saveWebSocketIntegrationConfig(db, want); err != nil { + t.Fatal(err) + } + got, found, err := loadWebSocketIntegrationConfig(db) + if err != nil { + t.Fatal(err) + } + if !found { + t.Fatal("websocket integration configuration was not found") + } + if !got.Enabled || got.Path != want.Path || got.MaxMessageSize != want.MaxMessageSize || got.MessageBufferSize != want.MessageBufferSize { + t.Fatalf("loaded websocket config = %#v", got) + } + if len(got.AllowOrigins) != 1 || got.AllowOrigins[0] != want.AllowOrigins[0] { + t.Fatalf("allow origins = %v", got.AllowOrigins) + } +} + +func TestResolveWebSocketIntegrationConfigPrefersDatabase(t *testing.T) { + db := openIntegrationConfigTestDB(t) + if err := saveWebSocketIntegrationConfig(db, &conf.AdminBackend_WebSocket{Enabled: true, Path: "/database"}); err != nil { + t.Fatal(err) + } + got, err := resolveWebSocketIntegrationConfig(db, &conf.AdminBackend_WebSocket{Path: "/legacy"}) + if err != nil { + t.Fatal(err) + } + if got.Path != "/database" || !got.Enabled { + t.Fatalf("resolved websocket config = %#v", got) + } +} diff --git a/app/system/internal/initialize/configuration.go b/app/system/internal/initialize/configuration.go index 4ae9c9b..49ab1f7 100644 --- a/app/system/internal/initialize/configuration.go +++ b/app/system/internal/initialize/configuration.go @@ -5,7 +5,7 @@ import ( "encoding/json" "kra/app/system/internal/conf" - "kra/app/system/internal/utils/configutil" + "kra/pkg/protoutil" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" @@ -89,6 +89,16 @@ func (r *Repo) ConfigurationJSON() (json.RawMessage, error) { if adminConfig.App != nil { admin["app"] = adminConfig.App } + if adminConfig.Websocket != nil { + admin["websocket"] = adminConfig.Websocket + } + if adminConfig.Mq != nil { + mqConfig := proto.Clone(adminConfig.Mq).(*conf.AdminBackend_MQ) + if mqConfig.Password != "" { + mqConfig.Password = "******" + } + admin["mq"] = mqConfig + } } // Never mask secrets on the live runtime object. ConfigurationJSON is a // read-only operation; mutating dataConfig here would replace the actual @@ -105,6 +115,9 @@ func (r *Repo) ConfigurationJSON() (json.RawMessage, error) { safeAdmin.Email.Secret = "******" } maskStorageSecrets(safeAdmin.Storage) + if safeAdmin.Mq != nil && safeAdmin.Mq.Password != "" { + safeAdmin.Mq.Password = "******" + } } dataMap := map[string]any{} if safeData != nil { @@ -137,12 +150,12 @@ func (r *Repo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) e } options := protojson.UnmarshalOptions{DiscardUnknown: true} if len(value.Data) > 0 && string(value.Data) != "null" { - if err := configutil.MergeProtoJSON(nextData, value.Data, options); err != nil { + if err := protoutil.MergeProtoJSON(nextData, value.Data, options); err != nil { return err } } if len(value.Admin) > 0 && string(value.Admin) != "null" { - if err := configutil.MergeProtoJSON(nextAdmin, value.Admin, options); err != nil { + if err := protoutil.MergeProtoJSON(nextAdmin, value.Admin, options); err != nil { return err } } @@ -307,6 +320,9 @@ func preserveAdminSecrets(next, current *conf.AdminBackend) { next.Email.Secret = current.Email.Secret } preserveStorageSecrets(next.Storage, current.Storage) + if next.Mq != nil && current.Mq != nil && maskedSecret(next.Mq.Password) { + next.Mq.Password = current.Mq.Password + } } func maskedSecret(value string) bool { return value == "" || value == "******" } diff --git a/app/system/internal/utils/configutil/json_test.go b/app/system/internal/initialize/configuration_test.go similarity index 80% rename from app/system/internal/utils/configutil/json_test.go rename to app/system/internal/initialize/configuration_test.go index 9335b6f..081d55a 100644 --- a/app/system/internal/utils/configutil/json_test.go +++ b/app/system/internal/initialize/configuration_test.go @@ -1,10 +1,11 @@ -package configutil +package initialize import ( "encoding/json" "testing" "kra/app/system/internal/conf" + "kra/pkg/protoutil" "google.golang.org/protobuf/encoding/protojson" ) @@ -16,7 +17,7 @@ func TestMergeProtoJSONPreservesOmittedFieldsAndNormalizesKeys(t *testing.T) { } patch := json.RawMessage(`{"router_prefix":"/admin","system":{"iplimit_count":9}}`) - if err := MergeProtoJSON(target, patch, protojson.UnmarshalOptions{DiscardUnknown: true}); err != nil { + if err := protoutil.MergeProtoJSON(target, patch, protojson.UnmarshalOptions{DiscardUnknown: true}); err != nil { t.Fatal(err) } if target.RouterPrefix != "/admin" || target.System.IplimitCount != 9 { diff --git a/app/system/internal/integration/README.md b/app/system/internal/integration/README.md index 840dc57..d5a8576 100644 --- a/app/system/internal/integration/README.md +++ b/app/system/internal/integration/README.md @@ -8,8 +8,11 @@ state, or translate provider-specific protocols into `biz` interfaces. - `cache`: Redis-backed cache with an in-memory fallback. - `email`: SMTP email repository. +- `mq`: reloadable EMQX/MQTT client exposed through the shared `pkg/mq` interface. - `payment`: payment-channel SDKs and callback/signature handling. - `storage`: local and object-storage implementations of `biz.FileStorage`. +- `websocket`: reloadable Melody endpoint exposed through the shared + `pkg/websocket` wrapper. These packages are not `utils`: they perform I/O and own external dependency lifecycles. Their constructors are exposed through `ProviderSet`; `cmd` binds @@ -18,3 +21,31 @@ the `cache.RedisProvider` implementation to the shared `data.Data` container. Stateless protocol helpers that do not own clients live in `pkg/paymentkit`. They are intentionally small and dependency light, while provider adapters remain here. + +## Shared WebSocket and MQ APIs + +Business modules depend on `websocket.Hub` and `mq.Client` from the shared +`pkg/websocket` and `pkg/mq` packages; they do not construct +Melody or Paho clients and do not read system configuration directly. The +system integration packages own runtime refresh and shutdown. Registered +WebSocket handlers and MQTT subscriptions are retained when database-backed +configuration replaces a live client. + +```go +ws.OnMessage(func(session *websocket.Session, payload []byte) { + _ = ws.Send(session, payload) +}) +_ = ws.Broadcast([]byte(`{"type":"system.ready"}`)) + +_ = mq.PublishJSON(ctx, mqClient, "orders/paid", event, mq.AtLeastOnce, false) +_ = mqClient.Subscribe(ctx, "orders/+/paid", mq.AtLeastOnce, handler) +``` + +Integration configuration is stored in `sys_integration_configs`: + +- WebSocket: `kind=websocket`, `provider=melody` +- EMQX: `kind=mq`, `provider=emqx` + +The YAML values are only migration/bootstrap inputs. After the integration +table exists, the database is authoritative and runtime updates are applied +without restarting the process. The WebSocket public path defaults to `/ws`. diff --git a/app/system/internal/integration/mq/emqx.go b/app/system/internal/integration/mq/emqx.go new file mode 100644 index 0000000..f45d327 --- /dev/null +++ b/app/system/internal/integration/mq/emqx.go @@ -0,0 +1,154 @@ +package mq + +import ( + "context" + "log/slog" + "sync" + "time" + + "kra/app/system/internal/conf" + "kra/pkg/mq" +) + +// Reloadable follows the runtime snapshot and keeps a single shared EMQX +// connection for all modules in this process. +type Reloadable struct { + mu sync.RWMutex + opMu sync.Mutex + current mq.Client + subscriptions map[string]subscription + stop func() + logger *slog.Logger + closed bool +} + +type subscription struct { + qos byte + handler mq.Handler +} + +func New(runtime *conf.Runtime, logger *slog.Logger) (*Reloadable, func(), error) { + if logger == nil { + logger = slog.Default() + } + r := &Reloadable{logger: logger, subscriptions: make(map[string]subscription)} + if runtime != nil { + var config *conf.AdminBackend_MQ + if admin := runtime.Admin(); admin != nil { + config = admin.GetMq() + } + r.replace(config) + r.stop = runtime.Subscribe(func(_ *conf.Data, admin *conf.AdminBackend) { + if admin != nil { + r.replace(admin.GetMq()) + } + }) + } + cleanup := func() { + if r.stop != nil { + r.stop() + } + _ = r.Close() + } + return r, cleanup, nil +} + +func (r *Reloadable) replace(config *conf.AdminBackend_MQ) { + r.opMu.Lock() + defer r.opMu.Unlock() + if r.closed { + return + } + if config == nil { + config = &conf.AdminBackend_MQ{} + } + cfg := mq.Config{Enabled: config.Enabled, Broker: config.Broker, ClientID: config.ClientId, Username: config.Username, Password: config.Password, CleanSession: config.CleanSession} + if config.KeepAlive > 0 { + cfg.KeepAlive = time.Duration(config.KeepAlive) * time.Second + } + if config.ConnectTimeout > 0 { + cfg.ConnectTimeout = time.Duration(config.ConnectTimeout) * time.Second + } + client, err := mq.NewMQTT(cfg) + if err != nil { + r.logger.Warn("emqx unavailable", "mod", "mq", "error", err) + return + } + if config.Enabled { + if err = r.restoreSubscriptions(context.Background(), client); err != nil { + _ = client.Close() + r.logger.Warn("restore emqx subscriptions failed", "mod", "mq", "error", err) + return + } + } + r.mu.Lock() + old := r.current + r.current = client + r.mu.Unlock() + if old != nil { + _ = old.Close() + } +} + +func (r *Reloadable) restoreSubscriptions(ctx context.Context, client mq.Client) error { + for topic, item := range r.subscriptions { + if err := client.Subscribe(ctx, topic, item.qos, item.handler); err != nil { + return err + } + } + return nil +} + +func (r *Reloadable) client() mq.Client { r.mu.RLock(); defer r.mu.RUnlock(); return r.current } +func (r *Reloadable) Publish(ctx context.Context, topic string, payload []byte, qos byte, retain bool) error { + c := r.client() + if c == nil { + return mq.ErrUnavailable + } + return c.Publish(ctx, topic, payload, qos, retain) +} +func (r *Reloadable) Subscribe(ctx context.Context, topic string, qos byte, handler mq.Handler) error { + r.opMu.Lock() + defer r.opMu.Unlock() + c := r.client() + if c == nil { + return mq.ErrUnavailable + } + if err := c.Subscribe(ctx, topic, qos, handler); err != nil { + return err + } + r.subscriptions[topic] = subscription{qos: qos, handler: handler} + return nil +} +func (r *Reloadable) Unsubscribe(ctx context.Context, topics ...string) error { + r.opMu.Lock() + defer r.opMu.Unlock() + c := r.client() + if c == nil { + return mq.ErrUnavailable + } + if err := c.Unsubscribe(ctx, topics...); err != nil { + return err + } + for _, topic := range topics { + delete(r.subscriptions, topic) + } + return nil +} +func (r *Reloadable) Connected() bool { c := r.client(); return c != nil && c.Connected() } +func (r *Reloadable) Close() error { + r.opMu.Lock() + defer r.opMu.Unlock() + if r.closed { + return nil + } + r.closed = true + r.mu.Lock() + old := r.current + r.current = nil + r.mu.Unlock() + if old != nil { + return old.Close() + } + return nil +} diff --git a/app/system/internal/integration/mq/emqx_test.go b/app/system/internal/integration/mq/emqx_test.go new file mode 100644 index 0000000..06b9b8a --- /dev/null +++ b/app/system/internal/integration/mq/emqx_test.go @@ -0,0 +1,56 @@ +package mq + +import ( + "context" + "testing" + + platformmq "kra/pkg/mq" +) + +type fakeClient struct { + subscribed []string + unsubscribed []string +} + +func (*fakeClient) Publish(context.Context, string, []byte, byte, bool) error { return nil } +func (f *fakeClient) Subscribe(_ context.Context, topic string, _ byte, _ platformmq.Handler) error { + f.subscribed = append(f.subscribed, topic) + return nil +} +func (f *fakeClient) Unsubscribe(_ context.Context, topics ...string) error { + f.unsubscribed = append(f.unsubscribed, topics...) + return nil +} +func (*fakeClient) Connected() bool { return true } +func (*fakeClient) Close() error { return nil } + +func TestReloadableTracksSubscriptions(t *testing.T) { + client := &fakeClient{} + r := &Reloadable{current: client, subscriptions: make(map[string]subscription)} + handler := func(context.Context, platformmq.Message) {} + if err := r.Subscribe(context.Background(), "orders/+/paid", platformmq.AtLeastOnce, handler); err != nil { + t.Fatal(err) + } + if _, ok := r.subscriptions["orders/+/paid"]; !ok { + t.Fatal("subscription was not retained for configuration reload") + } + if err := r.Unsubscribe(context.Background(), "orders/+/paid"); err != nil { + t.Fatal(err) + } + if _, ok := r.subscriptions["orders/+/paid"]; ok { + t.Fatal("unsubscribed topic remained in the reload registry") + } +} + +func TestReloadableRestoresSubscriptions(t *testing.T) { + client := &fakeClient{} + r := &Reloadable{subscriptions: map[string]subscription{ + "orders/+/paid": {qos: platformmq.AtLeastOnce, handler: func(context.Context, platformmq.Message) {}}, + }} + if err := r.restoreSubscriptions(context.Background(), client); err != nil { + t.Fatal(err) + } + if len(client.subscribed) != 1 || client.subscribed[0] != "orders/+/paid" { + t.Fatalf("restored subscriptions = %v", client.subscribed) + } +} diff --git a/app/system/internal/integration/provider.go b/app/system/internal/integration/provider.go index b5422ff..12f2094 100644 --- a/app/system/internal/integration/provider.go +++ b/app/system/internal/integration/provider.go @@ -7,7 +7,11 @@ import ( "kra/app/system/internal/biz" "kra/app/system/internal/integration/cache" "kra/app/system/internal/integration/email" + mqintegration "kra/app/system/internal/integration/mq" "kra/app/system/internal/integration/storage" + websocketintegration "kra/app/system/internal/integration/websocket" + "kra/pkg/mq" + platformws "kra/pkg/websocket" "github.com/google/wire" ) @@ -16,5 +20,9 @@ var ProviderSet = wire.NewSet( cache.New, email.NewEmailRepo, storage.NewFileStorage, + mqintegration.New, + wire.Bind(new(mq.Client), new(*mqintegration.Reloadable)), + websocketintegration.New, + wire.Bind(new(platformws.Hub), new(*websocketintegration.Server)), wire.Bind(new(biz.FileStorage), new(*storage.Reloadable)), ) diff --git a/app/system/internal/integration/websocket/server.go b/app/system/internal/integration/websocket/server.go new file mode 100644 index 0000000..8e0f8e1 --- /dev/null +++ b/app/system/internal/integration/websocket/server.go @@ -0,0 +1,260 @@ +package websocket + +import ( + "errors" + "net/http" + "strings" + "sync" + "time" + + melody "github.com/olahol/melody" + "kra/app/system/internal/conf" + platformws "kra/pkg/websocket" +) + +// Server is the system-owned WebSocket endpoint. Business modules can attach +// handlers and publish messages without depending on Gin or Melody directly. +type Server struct { + mu sync.RWMutex + current *platformws.Server + path string + messageHandlers []func(*melody.Session, []byte) + binaryHandlers []func(*melody.Session, []byte) + connectHandlers []func(*melody.Session) + disconnectHandlers []func(*melody.Session) + closed bool +} + +func New(runtime *conf.Runtime) (*Server, func(), error) { + s := &Server{} + s.Replace(runtime) + var unsubscribe func() + if runtime != nil { + unsubscribe = runtime.Subscribe(func(_ *conf.Data, _ *conf.AdminBackend) { s.Replace(runtime) }) + } + return s, func() { + if unsubscribe != nil { + unsubscribe() + } + s.mu.Lock() + s.closed = true + current := s.current + s.current = nil + s.mu.Unlock() + if current != nil { + _ = current.Close() + } + }, nil +} + +func (s *Server) Replace(runtime *conf.Runtime) { + var config *conf.AdminBackend_WebSocket + if runtime != nil && runtime.Admin() != nil { + config = runtime.Admin().Websocket + } + if config == nil { + config = &conf.AdminBackend_WebSocket{} + } + s.mu.Lock() + if s.closed { + s.mu.Unlock() + return + } + previous := s.current + if !config.Enabled { + s.current = nil + s.path = "" + s.mu.Unlock() + if previous != nil { + _ = previous.Close() + } + return + } + next := platformws.New(platformws.Config{ + WriteWait: duration(config.WriteWait, 10*time.Second), + PongWait: duration(config.PongWait, 60*time.Second), + PingPeriod: duration(config.PingPeriod, 54*time.Second), + MaxMessageSize: config.MaxMessageSize, + MessageBufferSize: int(config.MessageBufferSize), + ConcurrentMessageHandling: config.ConcurrentMessageHandling, + AllowOrigins: config.AllowOrigins, + }) + for _, handler := range s.messageHandlers { + next.OnMessage(handler) + } + for _, handler := range s.binaryHandlers { + next.OnBinaryMessage(handler) + } + for _, handler := range s.connectHandlers { + next.OnConnect(handler) + } + for _, handler := range s.disconnectHandlers { + next.OnDisconnect(handler) + } + s.path = strings.TrimSpace(config.Path) + if s.path == "" { + s.path = "/ws" + } else if !strings.HasPrefix(s.path, "/") { + s.path = "/" + s.path + } + s.current = next + s.mu.Unlock() + if previous != nil { + _ = previous.Close() + } +} + +func duration(value interface{ AsDuration() time.Duration }, fallback time.Duration) time.Duration { + if value == nil { + return fallback + } + if result := value.AsDuration(); result > 0 { + return result + } + return fallback +} + +func (s *Server) Enabled() bool { + if s == nil { + return false + } + s.mu.RLock() + defer s.mu.RUnlock() + return s.current != nil +} +func (s *Server) Path() string { + if s == nil { + return "" + } + s.mu.RLock() + defer s.mu.RUnlock() + return s.path +} +func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) error { + if s == nil { + return errors.New("websocket server is disabled") + } + s.mu.RLock() + current := s.current + s.mu.RUnlock() + if current == nil { + return errors.New("websocket server is disabled") + } + return current.HandleRequest(w, r) +} +func (s *Server) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]any) error { + current, err := s.active() + if err != nil { + return err + } + return current.HandleRequestWithKeys(w, r, keys) +} +func (s *Server) Broadcast(message []byte) error { + if s == nil { + return errors.New("websocket server is disabled") + } + s.mu.RLock() + current := s.current + s.mu.RUnlock() + if current == nil { + return errors.New("websocket server is disabled") + } + return current.Broadcast(message) +} +func (s *Server) BroadcastBinary(message []byte) error { + if s == nil { + return errors.New("websocket server is disabled") + } + s.mu.RLock() + current := s.current + s.mu.RUnlock() + if current == nil { + return errors.New("websocket server is disabled") + } + return current.BroadcastBinary(message) +} +func (s *Server) Sessions() ([]*melody.Session, error) { + current, err := s.active() + if err != nil { + return nil, err + } + return current.Sessions() +} +func (s *Server) Len() int { + current, err := s.active() + if err != nil { + return 0 + } + return current.Len() +} +func (s *Server) Send(session *melody.Session, message []byte) error { + current, err := s.active() + if err != nil { + return err + } + return current.Send(session, message) +} +func (s *Server) SendBinary(session *melody.Session, message []byte) error { + current, err := s.active() + if err != nil { + return err + } + return current.SendBinary(session, message) +} +func (s *Server) active() (*platformws.Server, error) { + if s == nil { + return nil, errors.New("websocket server is disabled") + } + s.mu.RLock() + current := s.current + s.mu.RUnlock() + if current == nil { + return nil, errors.New("websocket server is disabled") + } + return current, nil +} +func (s *Server) OnMessage(handler func(*melody.Session, []byte)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.messageHandlers = append(s.messageHandlers, handler) + current := s.current + if current != nil { + current.OnMessage(handler) + } + s.mu.Unlock() +} +func (s *Server) OnBinaryMessage(handler func(*melody.Session, []byte)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.binaryHandlers = append(s.binaryHandlers, handler) + if s.current != nil { + s.current.OnBinaryMessage(handler) + } + s.mu.Unlock() +} +func (s *Server) OnConnect(handler func(*melody.Session)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.connectHandlers = append(s.connectHandlers, handler) + if s.current != nil { + s.current.OnConnect(handler) + } + s.mu.Unlock() +} +func (s *Server) OnDisconnect(handler func(*melody.Session)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.disconnectHandlers = append(s.disconnectHandlers, handler) + if s.current != nil { + s.current.OnDisconnect(handler) + } + s.mu.Unlock() +} diff --git a/app/system/internal/security/adminauth/token.go b/app/system/internal/security/token.go similarity index 99% rename from app/system/internal/security/adminauth/token.go rename to app/system/internal/security/token.go index 386b098..ce235d0 100644 --- a/app/system/internal/security/adminauth/token.go +++ b/app/system/internal/security/token.go @@ -1,4 +1,4 @@ -package adminauth +package security import ( "errors" diff --git a/app/system/internal/server/gin.go b/app/system/internal/server/gin.go index 11b4e20..9a1edde 100644 --- a/app/system/internal/server/gin.go +++ b/app/system/internal/server/gin.go @@ -11,10 +11,12 @@ import ( "time" "kra/app/system/internal/conf" + websocket "kra/app/system/internal/integration/websocket" + "kra/app/system/internal/server/handler" + "kra/app/system/internal/server/httpx" + "kra/app/system/internal/server/middleware" + "kra/app/system/internal/server/router" "kra/app/system/internal/service" - "kra/app/system/internal/transport/handler" - "kra/app/system/internal/transport/httpx" - servermiddleware "kra/app/system/internal/transport/middleware" platformmodule "kra/pkg/module" "github.com/gin-gonic/gin" @@ -22,16 +24,16 @@ import ( ) func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string) *gin.Engine { - return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, platformmodule.NewRuntime(NewRoutes(handlers))) + return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, platformmodule.NewRuntime(router.NewRoutes(handlers)), nil) } -func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessControlService, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime) *gin.Engine { +func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessControlService, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime, ws *websocket.Server) *gin.Engine { gin.SetMode(gin.ReleaseMode) engine := gin.New() if err := engine.SetTrustedProxies(nil); err != nil && logger != nil { logger.Error("disable trusted proxies failed", "error", err) } - engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(logger), servermiddleware.AccessLog(runtime, logger, version), servermiddleware.CORS(runtime), servermiddleware.ErrorAudit(logger), servermiddleware.SecurityRateLimit(security)) + engine.Use(middleware.RequestMeta(), middleware.Recovery(logger), middleware.AccessLog(runtime, logger, version), middleware.CORS(runtime), middleware.ErrorAudit(logger), middleware.SecurityRateLimit(security)) prefix := "" config := runtime.Admin() @@ -46,14 +48,32 @@ func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessContro // password and Casbin/data-scope middleware. Keeping the recorder after the // equivalent Kra guards avoids persisting rejected unauthenticated or // unauthorized requests as successful business operations. - private.Use(servermiddleware.Auth(auth), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access), servermiddleware.OperationAudit(runtime, audit)) + private.Use(middleware.Auth(auth), middleware.MustChangePassword(), middleware.AccessControl(runtime, access), middleware.OperationAudit(runtime, audit)) if routes != nil { routes.RegisterRoutes(public, private, engine) } + if ws != nil && ws.Enabled() { + path := ws.Path() + engine.GET(path, func(c *gin.Context) { + if !ws.Enabled() || c.Request.URL.Path != ws.Path() { + c.Status(http.StatusNotFound) + return + } + if err := ws.HandleRequest(c.Writer, c.Request); err != nil && logger != nil { + logger.Warn("websocket request failed", "mod", "websocket", "error", err) + } + }) + } registerSwagger(engine, prefix, version, logger) registerLocalStorage(engine, runtime) engine.NoRoute(func(c *gin.Context) { + if ws != nil && ws.Enabled() && c.Request.Method == http.MethodGet && c.Request.URL.Path == ws.Path() { + if err := ws.HandleRequest(c.Writer, c.Request); err != nil && logger != nil { + logger.Warn("websocket request failed", "mod", "websocket", "error", err) + } + return + } if serveLocalStorage(c, runtime) { return } diff --git a/app/system/internal/server/gin_test.go b/app/system/internal/server/gin_test.go index c608131..87aa3c5 100644 --- a/app/system/internal/server/gin_test.go +++ b/app/system/internal/server/gin_test.go @@ -12,7 +12,7 @@ import ( "testing" "kra/app/system/internal/conf" - "kra/app/system/internal/transport/handler" + "kra/app/system/internal/server/handler" "github.com/gin-gonic/gin" ) @@ -56,7 +56,7 @@ func TestGinStartupLogsEveryRegisteredRoute(t *testing.T) { if got, want := strings.Count(text, `"msg":"router registered"`), len(engine.Routes()); got != want { t.Fatalf("registered route log count = %d, want %d", got, want) } - if !strings.Contains(text, `"msg":"router register success"`) || !strings.Contains(text, `"route_count":186`) { + if !strings.Contains(text, `"msg":"router register success"`) || !strings.Contains(text, `"route_count":190`) { t.Fatalf("startup route summary is missing: %s", text) } } @@ -258,6 +258,7 @@ GET /logViewer/files GET /menu/getMenuRoles GET /payment/configs GET /payment/orders +GET /payment/orders/:provider/:tradeNo GET /position/findPosition GET /position/getPositionUsers GET /securityConfig/getSecurityConfig @@ -353,7 +354,10 @@ POST /menu/updateBaseMenu POST /payment/callback/:provider POST /payment/config POST /payment/create +POST /payment/fulfill POST /payment/order +POST /payment/orders/:provider/:tradeNo/fulfill +POST /payment/orders/:provider/:tradeNo/refund POST /payment/query POST /payment/refund POST /position/createPosition diff --git a/app/system/internal/transport/handler/announcement.go b/app/system/internal/server/handler/announcement.go similarity index 63% rename from app/system/internal/transport/handler/announcement.go rename to app/system/internal/server/handler/announcement.go index 8a4c3d2..b7deb52 100644 --- a/app/system/internal/transport/handler/announcement.go +++ b/app/system/internal/server/handler/announcement.go @@ -1,11 +1,8 @@ package handler import ( - "time" - - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" + "time" "github.com/gin-gonic/gin" ) @@ -16,86 +13,86 @@ func NewAnnouncement(service *service.AnnouncementService) *Announcement { return &Announcement{service: service} } -func announcementInput(req dto.AnnouncementRequest) service.AnnouncementInput { +func announcementInput(req service.AnnouncementRequest) service.AnnouncementInput { return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments} } func (h *Announcement) Create(c *gin.Context) { - var req dto.AnnouncementRequest + var req service.AnnouncementRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Create(c.Request.Context(), announcementInput(req)); err != nil { - httpx.Fail(c, "创建失败") + Fail(c, "创建失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Announcement) Delete(c *gin.Context) { if err := h.service.Delete(c.Request.Context(), c.Query("ID")); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Announcement) DeleteByIDs(c *gin.Context) { if err := h.service.DeleteByIDs(c.Request.Context(), c.QueryArray("IDs[]")); err != nil { - httpx.Fail(c, "批量删除失败") + Fail(c, "批量删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Announcement) Update(c *gin.Context) { - var req dto.AnnouncementRequest + var req service.AnnouncementRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Update(c.Request.Context(), announcementInput(req)); err != nil { - httpx.Fail(c, "更新失败") + Fail(c, "更新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Announcement) Find(c *gin.Context) { item, err := h.service.Find(c.Request.Context(), c.Query("ID")) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.OKWithData(c, item) + OKWithData(c, item) } func (h *Announcement) List(c *gin.Context) { - var req dto.AnnouncementSearchRequest + var req service.AnnouncementSearchRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.List(c.Request.Context(), req.Page, req.PageSize, req.StartCreatedAt, req.EndCreatedAt) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Announcement) DataSource(c *gin.Context) { users, err := h.service.UserOptions(c.Request.Context()) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.OKWithData(c, gin.H{"userID": users}) + OKWithData(c, gin.H{"userID": users}) } func (h *Announcement) Public(c *gin.Context) { - httpx.Write(c, httpx.CodeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功") + Write(c, CodeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功") } func parseTime(value string) (*time.Time, error) { diff --git a/app/system/internal/transport/handler/api.go b/app/system/internal/server/handler/api.go similarity index 58% rename from app/system/internal/transport/handler/api.go rename to app/system/internal/server/handler/api.go index be846f0..01620bf 100644 --- a/app/system/internal/transport/handler/api.go +++ b/app/system/internal/server/handler/api.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -13,234 +11,234 @@ type API struct{ service *service.APIService } func NewAPI(service *service.APIService) *API { return &API{service: service} } func (h *API) List(c *gin.Context) { - var req dto.APIListRequest + var req service.APIListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Page == 0 { - httpx.Fail(c, "Page值不能为空") + Fail(c, "Page值不能为空") return } if req.PageSize == 0 { - httpx.Fail(c, "PageSize值不能为空") + Fail(c, "PageSize值不能为空") return } items, total, err := h.service.ListAPI(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *API) All(c *gin.Context) { items, err := h.service.AllAPI(c.Request.Context(), true) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"apis": items}, "获取成功") + Write(c, CodeSuccess, gin.H{"apis": items}, "获取成功") } func (h *API) Create(c *gin.Context) { - var req dto.APIRequest + var req service.APIRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if message := validateAPIRequest(&req); message != "" { - httpx.Fail(c, message) + Fail(c, message) return } created, err := h.service.CreateAPIRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "创建失败") + Fail(c, "创建失败") return } - httpx.Write(c, httpx.CodeSuccess, created, "创建成功") + Write(c, CodeSuccess, created, "创建成功") } func (h *API) Update(c *gin.Context) { - var req dto.APIRequest + var req service.APIRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if message := validateAPIRequest(&req); message != "" { - httpx.Fail(c, message) + Fail(c, message) return } if err := h.service.UpdateAPIRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "修改失败") + Fail(c, "修改失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") + Write(c, CodeSuccess, gin.H{}, "修改成功") } func (h *API) Delete(c *gin.Context) { - var req dto.DeleteAPIRequest + var req service.DeleteAPIRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } if err := h.service.DeleteAPI(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *API) DeleteByIDs(c *gin.Context) { - var req dto.DeleteAPIsRequest + var req service.DeleteAPIsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteAPIs(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *API) Find(c *gin.Context) { - var req dto.GetAPIRequest + var req service.GetAPIRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } item, err := h.service.FindAPIResponse(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"api": item}, "获取成功") + Write(c, CodeSuccess, gin.H{"api": item}, "获取成功") } func (h *API) Groups(c *gin.Context) { groups, groupAPIMap, err := h.service.Groups(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.OKWithData(c, gin.H{"groups": groups, "apiGroupMap": groupAPIMap}) + OKWithData(c, gin.H{"groups": groups, "apiGroupMap": groupAPIMap}) } func (h *API) Roles(c *gin.Context) { path, method := c.Query("path"), c.Query("method") if path == "" || method == "" { - httpx.Fail(c, "API路径和请求方法不能为空") + Fail(c, "API路径和请求方法不能为空") return } ids, err := h.service.APIRoleIDs(c.Request.Context(), path, method) if err != nil { - httpx.Fail(c, "获取失败"+err.Error()) + Fail(c, "获取失败"+err.Error()) return } if ids == nil { ids = []uint{} } - httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") + Write(c, CodeSuccess, ids, "获取成功") } func (h *API) SetRoles(c *gin.Context) { - var req dto.SetAPIRolesRequest + var req service.SetAPIRolesRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Path == "" || req.Method == "" { - httpx.Fail(c, "API路径和请求方法不能为空") + Fail(c, "API路径和请求方法不能为空") return } if err := h.service.SetAPIRoles(c.Request.Context(), req.Path, req.Method, req.AuthorityIDs); err != nil { - httpx.Fail(c, "设置失败"+err.Error()) + Fail(c, "设置失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *API) Sync(engine *gin.Engine) gin.HandlerFunc { return func(c *gin.Context) { routes := engine.Routes() - values := make([]dto.APIRequest, 0, len(routes)) + values := make([]service.APIRequest, 0, len(routes)) for _, route := range routes { - values = append(values, dto.APIRequest{Path: route.Path, Method: route.Method}) + values = append(values, service.APIRequest{Path: route.Path, Method: route.Method}) } result, err := h.service.SyncAPIResponses(c.Request.Context(), values) if err != nil { - httpx.Fail(c, "同步失败") + Fail(c, "同步失败") return } - httpx.OKWithData(c, result) + OKWithData(c, result) } } func (h *API) Ignore(c *gin.Context) { - var req dto.IgnoreAPIRequest + var req service.IgnoreAPIRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil { - httpx.Fail(c, "忽略失败") + Fail(c, "忽略失败") return } - httpx.OK(c) + OK(c) } func (h *API) ApplySync(c *gin.Context) { - var req dto.ApplyAPISyncRequest + var req service.ApplyAPISyncRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.ApplyAPISyncRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "忽略失败") + Fail(c, "忽略失败") return } - httpx.OK(c) + OK(c) } func (h *API) FreshCasbin(c *gin.Context) { if err := h.service.FreshCasbin(c.Request.Context()); err != nil { - httpx.Fail(c, "刷新失败") + Fail(c, "刷新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "刷新成功") + Write(c, CodeSuccess, gin.H{}, "刷新成功") } func (h *API) SetPolicyPaths(c *gin.Context) { - var req dto.SetPolicyPathsRequest + var req service.SetPolicyPathsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } if err := h.service.SetPolicyPathsRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + Fail(c, "更新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *API) PolicyPaths(c *gin.Context) { - var req dto.GetPolicyPathsRequest + var req service.GetPolicyPathsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } paths, err := h.service.PolicyPathResponses(c.Request.Context(), req.AuthorityID) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"paths": paths}, "获取成功") + Write(c, CodeSuccess, gin.H{"paths": paths}, "获取成功") } -func validateAPIRequest(req *dto.APIRequest) string { +func validateAPIRequest(req *service.APIRequest) string { switch { case req.Path == "": return "Path值不能为空" diff --git a/app/system/internal/transport/handler/api_token.go b/app/system/internal/server/handler/api_token.go similarity index 59% rename from app/system/internal/transport/handler/api_token.go rename to app/system/internal/server/handler/api_token.go index 3986249..1c3c7ca 100644 --- a/app/system/internal/transport/handler/api_token.go +++ b/app/system/internal/server/handler/api_token.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -17,42 +15,42 @@ func NewAPIToken(service *service.TokenService) *APIToken { } func (h *APIToken) Create(c *gin.Context) { - var req dto.CreateAPITokenRequest + var req service.CreateAPITokenRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } token, err := h.service.CreateAPIToken(c.Request.Context(), req.UserID, req.AuthorityID, req.Days, req.Remark) if err != nil { - httpx.Fail(c, "签发失败: "+err.Error()) + Fail(c, "签发失败: "+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"token": token}, "签发成功") + Write(c, CodeSuccess, gin.H{"token": token}, "签发成功") } func (h *APIToken) List(c *gin.Context) { - var req dto.APITokenListRequest + var req service.APITokenListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.APITokens(c.Request.Context(), req.Page, req.PageSize, req.UserID, req.Status) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *APIToken) Delete(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DisableAPIToken(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "作废失败") + Fail(c, "作废失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "作废成功") + Write(c, CodeSuccess, gin.H{}, "作废成功") } diff --git a/app/system/internal/transport/handler/audit.go b/app/system/internal/server/handler/audit.go similarity index 66% rename from app/system/internal/transport/handler/audit.go rename to app/system/internal/server/handler/audit.go index ada9141..4e90c54 100644 --- a/app/system/internal/transport/handler/audit.go +++ b/app/system/internal/server/handler/audit.go @@ -7,9 +7,7 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -26,135 +24,135 @@ func NewAudit(service *service.AuditService, recorder *service.AuditRecorder, lo } func (h *Audit) Operations(c *gin.Context) { - var req dto.OperationRecordSearchRequest + var req service.OperationRecordSearchRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.OperationsFilter(c.Request.Context(), req.Page, req.PageSize, req.Path, req.Method, req.Status) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Audit) Operation(c *gin.Context) { - var req dto.AuditIDQuery + var req service.AuditIDQuery if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } item, err := h.service.Operation(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysOperationRecord": item}, "查询成功") + Write(c, CodeSuccess, gin.H{"reSysOperationRecord": item}, "查询成功") } func (h *Audit) DeleteOperation(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteOperations(c.Request.Context(), []int{int(req.ID)}); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteOperations(c *gin.Context) { - var req dto.AuditIDsRequest + var req service.AuditIDsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteOperations(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "批量删除失败") + Fail(c, "批量删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) Logins(c *gin.Context) { - var req dto.LoginLogSearchRequest + var req service.LoginLogSearchRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.LoginsFilter(c.Request.Context(), req.Page, req.PageSize, req.Username, req.Status) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Audit) Login(c *gin.Context) { - var req dto.AuditIDQuery + var req service.AuditIDQuery if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } item, err := h.service.Login(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.Write(c, httpx.CodeSuccess, item, "查询成功") + Write(c, CodeSuccess, item, "查询成功") } func (h *Audit) DeleteLogin(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteLogins(c.Request.Context(), []int{int(req.ID)}); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteLogins(c *gin.Context) { - var req dto.AuditIDsRequest + var req service.AuditIDsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteLogins(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "批量删除失败") + Fail(c, "批量删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) DataAccess(c *gin.Context) { - var req dto.DataAccessListRequest + var req service.DataAccessListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.DataAccessRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Audit) DeleteDataAccess(c *gin.Context) { - var req dto.IDsRequest + var req service.IDsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) LogDates(c *gin.Context) { @@ -162,7 +160,7 @@ func (h *Audit) LogDates(c *gin.Context) { // Month uses `binding:"required"`; an omitted or empty query is a // generic parameter error, not the service's format-specific error. if month == "" { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } data, err := h.logs.LogDates(c.Request.Context(), month) @@ -170,12 +168,12 @@ func (h *Audit) LogDates(c *gin.Context) { failLogViewer(c, err, h.logger) return } - httpx.Write(c, httpx.CodeSuccess, data, "获取成功") + Write(c, CodeSuccess, data, "获取成功") } func (h *Audit) LogFiles(c *gin.Context) { date := c.Query("date") if date == "" { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } data, err := h.logs.LogFiles(c.Request.Context(), date) @@ -183,19 +181,19 @@ func (h *Audit) LogFiles(c *gin.Context) { failLogViewer(c, err, h.logger) return } - httpx.Write(c, httpx.CodeSuccess, data, "获取成功") + Write(c, CodeSuccess, data, "获取成功") } func (h *Audit) LogContent(c *gin.Context) { date, path := c.Query("date"), c.Query("path") if date == "" || path == "" { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } var cursor *int64 if raw := c.Query("cursor"); raw != "" { value, err := strconv.ParseInt(raw, 10, 64) if err != nil { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } cursor = &value @@ -207,7 +205,7 @@ func (h *Audit) LogContent(c *gin.Context) { failLogViewer(c, err, h.logger) return } - httpx.Write(c, httpx.CodeSuccess, data, "获取成功") + Write(c, CodeSuccess, data, "获取成功") } func failLogViewer(c *gin.Context, err error, logger *slog.Logger) { @@ -229,76 +227,76 @@ func failLogViewer(c *gin.Context, err error, logger *slog.Logger) { case errors.Is(err, biz.ErrLogRootUnavailable): message = "日志目录不可读取" } - httpx.Fail(c, message) + Fail(c, message) } func (h *Audit) DeleteError(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) if err := h.service.DeleteErrors(c.Request.Context(), []uint{uint(id)}); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Audit) DeleteErrors(c *gin.Context) { if err := h.service.DeleteErrors(c.Request.Context(), IDsFromQuery(c)); err != nil { - httpx.Fail(c, "批量删除失败:"+err.Error()) + Fail(c, "批量删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Audit) UpdateError(c *gin.Context) { - var req dto.ErrorRecordMutationRequest + var req service.ErrorRecordMutationRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.UpdateErrorRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败:"+err.Error()) + Fail(c, "更新失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Audit) Error(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) item, err := h.service.Error(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "查询失败:"+err.Error()) + Fail(c, "查询失败:"+err.Error()) return } - httpx.OKWithData(c, item) + OKWithData(c, item) } func (h *Audit) Errors(c *gin.Context) { p, size, err := page(c) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } createdAtRange := make([]time.Time, 0, 2) for _, raw := range c.QueryArray("createdAtRange[]") { value, parseErr := time.Parse(time.RFC3339, raw) if parseErr != nil { - httpx.Fail(c, parseErr.Error()) + Fail(c, parseErr.Error()) return } createdAtRange = append(createdAtRange, value) } items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("info"), createdAtRange) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") } func (h *Audit) CreateError(c *gin.Context) { - var req dto.ErrorRecordMutationRequest + var req service.ErrorRecordMutationRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.recorder.CreateErrorMutationRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + Fail(c, "创建失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } diff --git a/app/system/internal/transport/handler/authority.go b/app/system/internal/server/handler/authority.go similarity index 56% rename from app/system/internal/transport/handler/authority.go rename to app/system/internal/server/handler/authority.go index d7eb69c..2e76684 100644 --- a/app/system/internal/transport/handler/authority.go +++ b/app/system/internal/server/handler/authority.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -15,155 +13,155 @@ func NewAuthority(service *service.AuthorityService) *Authority { return &Author func (h *Authority) List(c *gin.Context) { items, err := h.service.Authorities(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取失败"+err.Error()) + Fail(c, "获取失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + Write(c, CodeSuccess, items, "获取成功") } func (h *Authority) Create(c *gin.Context) { - var req dto.AuthorityRequest + var req service.AuthorityRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } if req.AuthorityName == "" { - httpx.Fail(c, "AuthorityName值不能为空") + Fail(c, "AuthorityName值不能为空") return } value, err := h.service.CreateAuthorityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "创建失败"+err.Error()) + Fail(c, "创建失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "创建成功") + Write(c, CodeSuccess, gin.H{"authority": value}, "创建成功") } func (h *Authority) Copy(c *gin.Context) { - var req dto.CopyAuthorityRequest + var req service.CopyAuthorityRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.OldAuthorityID == 0 { - httpx.Fail(c, "OldAuthorityId值不能为空") + Fail(c, "OldAuthorityId值不能为空") return } if req.Authority.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } if req.Authority.AuthorityName == "" { - httpx.Fail(c, "AuthorityName值不能为空") + Fail(c, "AuthorityName值不能为空") return } value, err := h.service.CopyAuthorityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "拷贝失败"+err.Error()) + Fail(c, "拷贝失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "拷贝成功") + Write(c, CodeSuccess, gin.H{"authority": value}, "拷贝成功") } func (h *Authority) Update(c *gin.Context) { - var req dto.AuthorityRequest + var req service.AuthorityRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } if req.AuthorityName == "" { - httpx.Fail(c, "AuthorityName值不能为空") + Fail(c, "AuthorityName值不能为空") return } value, err := h.service.UpdateAuthorityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "更新失败"+err.Error()) + Fail(c, "更新失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "更新成功") + Write(c, CodeSuccess, gin.H{"authority": value}, "更新成功") } func (h *Authority) Delete(c *gin.Context) { - var req dto.DeleteAuthorityRequest + var req service.DeleteAuthorityRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } if err := h.service.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil { - httpx.Fail(c, "删除失败"+err.Error()) + Fail(c, "删除失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Authority) SetUsers(c *gin.Context) { - var req dto.SetRoleUsersRequest + var req service.SetRoleUsersRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "角色ID不能为空") + Fail(c, "角色ID不能为空") return } if err := h.service.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil { - httpx.Fail(c, "设置失败"+err.Error()) + Fail(c, "设置失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *Authority) Users(c *gin.Context) { - var req dto.SetRoleUsersRequest + var req service.SetRoleUsersRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } ids, err := h.service.AuthorityUserIDs(c.Request.Context(), req.AuthorityID) if err != nil { - httpx.Fail(c, "获取失败"+err.Error()) + Fail(c, "获取失败"+err.Error()) return } if ids == nil { ids = []uint{} } - httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") + Write(c, CodeSuccess, ids, "获取成功") } func (h *Authority) SetDataScope(c *gin.Context) { - var req dto.SetDataScopeRequest + var req service.SetDataScopeRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "角色ID不能为空") + Fail(c, "角色ID不能为空") return } if err := h.service.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil { - httpx.Fail(c, "设置失败"+err.Error()) + Fail(c, "设置失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *Authority) DataScopeDepartments(c *gin.Context) { - var req dto.SetDataScopeRequest + var req service.SetDataScopeRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } ids, err := h.service.DataScopeDepartmentIDs(c.Request.Context(), req.AuthorityID) if err != nil { - httpx.Fail(c, "获取失败"+err.Error()) + Fail(c, "获取失败"+err.Error()) return } if ids == nil { ids = []uint{} } - httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") + Write(c, CodeSuccess, ids, "获取成功") } diff --git a/app/system/internal/transport/handler/dictionary.go b/app/system/internal/server/handler/dictionary.go similarity index 61% rename from app/system/internal/transport/handler/dictionary.go rename to app/system/internal/server/handler/dictionary.go index bd9090a..6ee2aa6 100644 --- a/app/system/internal/transport/handler/dictionary.go +++ b/app/system/internal/server/handler/dictionary.go @@ -1,11 +1,8 @@ package handler import ( - "strconv" - - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" + "strconv" "github.com/gin-gonic/gin" ) @@ -17,152 +14,152 @@ func NewDictionary(service *service.DictionaryService) *Dictionary { } func (h *Dictionary) Create(c *gin.Context) { - var req dto.DictionaryRequest + var req service.DictionaryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } created, err := h.service.CreateDictionaryRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "创建失败") + Fail(c, "创建失败") return } - httpx.Write(c, httpx.CodeSuccess, created, "创建成功") + Write(c, CodeSuccess, created, "创建成功") } func (h *Dictionary) Update(c *gin.Context) { - var req dto.DictionaryRequest + var req service.DictionaryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.UpdateDictionaryRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + Fail(c, "更新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Dictionary) Delete(c *gin.Context) { - var req dto.DictionaryRequest + var req service.DictionaryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteDictionary(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Dictionary) Find(c *gin.Context) { - var req dto.DictionaryRequest + var req service.DictionaryRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } item, err := h.service.Dictionary(c.Request.Context(), req.ID, req.Type, req.Status, true) if err != nil { - httpx.Fail(c, "字典未创建或未开启") + Fail(c, "字典未创建或未开启") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"resysDictionary": item}, "查询成功") + Write(c, CodeSuccess, gin.H{"resysDictionary": item}, "查询成功") } func (h *Dictionary) List(details bool) gin.HandlerFunc { return func(c *gin.Context) { items, _, err := h.service.Dictionaries(c.Request.Context(), 0, 0, c.Query("name"), "", details) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + Write(c, CodeSuccess, items, "获取成功") } } func (h *Dictionary) Export(c *gin.Context) { - var req dto.DictionaryRequest + var req service.DictionaryRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "字典ID不能为空") + Fail(c, "字典ID不能为空") return } item, err := h.service.ExportDictionary(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "导出失败") + Fail(c, "导出失败") return } - httpx.Write(c, httpx.CodeSuccess, item, "导出成功") + Write(c, CodeSuccess, item, "导出成功") } func (h *Dictionary) Import(c *gin.Context) { - var req dto.ImportDictionaryRequest + var req service.ImportDictionaryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.ImportDictionaryJSON(c.Request.Context(), req.JSON); err != nil { - httpx.Fail(c, "导入失败: "+err.Error()) + Fail(c, "导入失败: "+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功") + Write(c, CodeSuccess, gin.H{}, "导入成功") } func (h *Dictionary) CreateDetail(c *gin.Context) { - var req dto.DictionaryDetailRequest + var req service.DictionaryDetailRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.CreateDictionaryDetailRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败") + Fail(c, "创建失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Dictionary) UpdateDetail(c *gin.Context) { - var req dto.DictionaryDetailRequest + var req service.DictionaryDetailRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.UpdateDictionaryDetailRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + Fail(c, "更新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Dictionary) DeleteDetail(c *gin.Context) { - var req dto.DictionaryDetailRequest + var req service.DictionaryDetailRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.DeleteDictionaryDetail(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Dictionary) FindDetail(c *gin.Context) { - var req dto.DictionaryDetailRequest + var req service.DictionaryDetailRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } item, err := h.service.DictionaryDetail(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysDictionaryDetail": item}, "查询成功") + Write(c, CodeSuccess, gin.H{"reSysDictionaryDetail": item}, "查询成功") } func (h *Dictionary) Details(c *gin.Context) { p, size, err := page(c) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } var dictionaryID uint64 @@ -170,15 +167,15 @@ func (h *Dictionary) Details(c *gin.Context) { var parseErr error dictionaryID, parseErr = strconv.ParseUint(raw, 10, 0) if parseErr != nil { - httpx.Fail(c, parseErr.Error()) + Fail(c, parseErr.Error()) return } } - filter := dto.DictionaryDetailListRequest{DictionaryID: uint(dictionaryID), Label: c.Query("label"), Value: c.Query("value")} + filter := service.DictionaryDetailListRequest{DictionaryID: uint(dictionaryID), Label: c.Query("label"), Value: c.Query("value")} if raw, exists := c.GetQuery("status"); exists { value, parseErr := strconv.ParseBool(raw) if parseErr != nil { - httpx.Fail(c, parseErr.Error()) + Fail(c, parseErr.Error()) return } filter.Status = &value @@ -186,7 +183,7 @@ func (h *Dictionary) Details(c *gin.Context) { if raw, exists := c.GetQuery("parentID"); exists { value, parseErr := strconv.ParseUint(raw, 10, 64) if parseErr != nil { - httpx.Fail(c, parseErr.Error()) + Fail(c, parseErr.Error()) return } parentID := uint(value) @@ -195,17 +192,17 @@ func (h *Dictionary) Details(c *gin.Context) { if raw, exists := c.GetQuery("level"); exists { value, parseErr := strconv.Atoi(raw) if parseErr != nil { - httpx.Fail(c, parseErr.Error()) + Fail(c, parseErr.Error()) return } filter.Level = &value } items, total, err := h.service.DictionaryDetailsRequest(c.Request.Context(), p, size, filter) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") } func (h *Dictionary) Tree(byType bool) gin.HandlerFunc { return func(c *gin.Context) { @@ -215,52 +212,52 @@ func (h *Dictionary) Tree(byType bool) gin.HandlerFunc { if byType { typ = c.Query("type") if typ == "" { - httpx.Fail(c, "字典类型不能为空") + Fail(c, "字典类型不能为空") return } } else if rawID == "" { - httpx.Fail(c, "字典ID不能为空") + Fail(c, "字典ID不能为空") return } else if parseErr != nil { - httpx.Fail(c, "字典ID格式错误") + Fail(c, "字典ID格式错误") return } items, err := h.service.DictionaryTree(c.Request.Context(), uint(id), typ) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items}, "获取成功") + Write(c, CodeSuccess, gin.H{"list": items}, "获取成功") } } func (h *Dictionary) DetailsByParent(c *gin.Context) { - var req dto.DictionaryDetailsByParentRequest + var req service.DictionaryDetailsByParentRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, err := h.service.DictionaryDetailsByParent(c.Request.Context(), req.DictionaryID, req.ParentID, req.IncludeChildren) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items}, "获取成功") + Write(c, CodeSuccess, gin.H{"list": items}, "获取成功") } func (h *Dictionary) Path(c *gin.Context) { rawID := c.Query("id") if rawID == "" { - httpx.Fail(c, "字典详情ID不能为空") + Fail(c, "字典详情ID不能为空") return } id, parseErr := strconv.ParseUint(rawID, 10, 32) if parseErr != nil { - httpx.Fail(c, "字典详情ID格式错误") + Fail(c, "字典详情ID格式错误") return } path, err := h.service.DictionaryPath(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"path": path}, "获取成功") + Write(c, CodeSuccess, gin.H{"path": path}, "获取成功") } diff --git a/app/system/internal/transport/handler/email.go b/app/system/internal/server/handler/email.go similarity index 63% rename from app/system/internal/transport/handler/email.go rename to app/system/internal/server/handler/email.go index c5b867a..b88c3f9 100644 --- a/app/system/internal/transport/handler/email.go +++ b/app/system/internal/server/handler/email.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -14,21 +12,21 @@ func NewEmail(service *service.EmailService) *Email { return &Email{service: ser func (h *Email) Test(c *gin.Context) { if err := h.service.Test(c.Request.Context()); err != nil { - httpx.Fail(c, "发送失败") + Fail(c, "发送失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功") + Write(c, CodeSuccess, gin.H{}, "发送成功") } func (h *Email) Send(c *gin.Context) { - var req dto.SendEmailRequest + var req service.SendEmailRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil { - httpx.Fail(c, "发送失败") + Fail(c, "发送失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功") + Write(c, CodeSuccess, gin.H{}, "发送成功") } diff --git a/app/system/internal/transport/handler/export.go b/app/system/internal/server/handler/export.go similarity index 71% rename from app/system/internal/transport/handler/export.go rename to app/system/internal/server/handler/export.go index 080772b..4230fdb 100644 --- a/app/system/internal/transport/handler/export.go +++ b/app/system/internal/server/handler/export.go @@ -2,14 +2,11 @@ package handler import ( "errors" + "kra/app/system/internal/service" "net/http" "net/url" "strings" - "kra/app/system/internal/dto" - "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" - "github.com/gin-gonic/gin" "github.com/google/uuid" ) @@ -45,179 +42,179 @@ func exportIssueParams(values url.Values, blank bool) (map[string]string, error) } func (h *Export) Create(c *gin.Context) { - var req dto.ExportTemplateRequest + var req service.ExportTemplateRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Name == "" { - httpx.Fail(c, "Name值不能为空") + Fail(c, "Name值不能为空") return } if err := h.service.CreateRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败") + Fail(c, "创建失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Export) Update(c *gin.Context) { - var req dto.ExportTemplateRequest + var req service.ExportTemplateRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Name == "" { - httpx.Fail(c, "Name值不能为空") + Fail(c, "Name值不能为空") return } if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + Fail(c, "更新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Export) Delete(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } // Deleting an empty model is rejected by GORM's global-update guard in the // reference endpoint. Preserve the same externally visible failure. if req.ID == 0 { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } if err := h.service.Delete(c.Request.Context(), []uint{req.ID}); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Export) DeleteMany(c *gin.Context) { - var req dto.IDsRequest + var req service.IDsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Delete(c.Request.Context(), req.IDs); err != nil { - httpx.Fail(c, "批量删除失败") + Fail(c, "批量删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Export) Find(c *gin.Context) { - var req dto.ExportTemplateRequest + var req service.ExportTemplateRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } item, err := h.service.Template(c.Request.Context(), req.ID, "") if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.OKWithData(c, gin.H{"resysExportTemplate": item}) + OKWithData(c, gin.H{"resysExportTemplate": item}) } func (h *Export) List(c *gin.Context) { p, size, err := page(c) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } start, startErr := parseTime(c.Query("startCreatedAt")) end, endErr := parseTime(c.Query("endCreatedAt")) if startErr != nil || endErr != nil { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } items, total, err := h.service.TemplatesFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("tableName"), c.Query("templateID"), start, end) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功") } func (h *Export) Preview(c *gin.Context) { templateID := c.Query("templateID") if templateID == "" { - httpx.Fail(c, "模板ID不能为空") + Fail(c, "模板ID不能为空") return } params, _ := exportParams(c.Request.URL.Query()) sql, err := h.service.Preview(c.Request.Context(), templateID, params) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.OKWithData(c, gin.H{"sql": sql}) + OKWithData(c, gin.H{"sql": sql}) } func (h *Export) Issue(blank bool) gin.HandlerFunc { return func(c *gin.Context) { templateID := c.Query("templateID") if templateID == "" { - httpx.Fail(c, "模板ID不能为空") + Fail(c, "模板ID不能为空") return } params, err := exportIssueParams(c.Request.URL.Query(), blank) if err != nil { - httpx.Fail(c, "解析 params 参数失败") + Fail(c, "解析 params 参数失败") return } token, err := h.service.IssueToken(c.Request.Context(), templateID, params, blank) if err != nil { - httpx.Fail(c, "导出令牌创建失败") + Fail(c, "导出令牌创建失败") return } path := "/sysExportTemplate/exportExcelByToken?token=" + token if blank { path = "/sysExportTemplate/exportTemplateByToken?token=" + token } - httpx.OKWithData(c, path) + OKWithData(c, path) } } func (h *Export) Import(c *gin.Context) { templateID := c.Query("templateID") if templateID == "" { - httpx.Fail(c, "模板ID不能为空") + Fail(c, "模板ID不能为空") return } file, err := c.FormFile("file") if err != nil { - httpx.Fail(c, "文件获取失败") + Fail(c, "文件获取失败") return } opened, err := file.Open() if err != nil { - httpx.Fail(c, "读取文件失败") + Fail(c, "读取文件失败") return } defer opened.Close() if err = h.service.Import(c.Request.Context(), templateID, opened); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功") + Write(c, CodeSuccess, gin.H{}, "导入成功") } func (h *Export) Download(expectBlank bool) gin.HandlerFunc { return func(c *gin.Context) { token := c.Query("token") if token == "" { - httpx.Fail(c, "导出token不能为空") + Fail(c, "导出token不能为空") return } value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank) if err != nil { switch { case errors.Is(err, service.ErrExportTokenMalformed): - httpx.Fail(c, "解析导出参数失败") + Fail(c, "解析导出参数失败") case errors.Is(err, service.ErrExportTokenType): - httpx.Fail(c, "token类型错误") + Fail(c, "token类型错误") default: - httpx.Fail(c, "导出token无效或已过期") + Fail(c, "导出token无效或已过期") } return } @@ -229,7 +226,7 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc { data, name, err = h.service.Export(c.Request.Context(), value.TemplateID, value.Params) } if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } if expectBlank { diff --git a/app/system/internal/transport/handler/export_test.go b/app/system/internal/server/handler/export_test.go similarity index 100% rename from app/system/internal/transport/handler/export_test.go rename to app/system/internal/server/handler/export_test.go diff --git a/app/system/internal/server/handler/http.go b/app/system/internal/server/handler/http.go new file mode 100644 index 0000000..28893fd --- /dev/null +++ b/app/system/internal/server/handler/http.go @@ -0,0 +1,25 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "kra/app/system/internal/biz" + "kra/app/system/internal/server/httpx" + "kra/app/system/internal/server/middleware" +) + +const ( + CodeSuccess = httpx.CodeSuccess + CodeError = httpx.CodeError + CodePasswordChangeRequired = httpx.CodePasswordChangeRequired +) + +type Response = httpx.Response +type PageResult = httpx.PageResult + +func Write(c *gin.Context, code int, data any, message string) { httpx.Write(c, code, data, message) } +func OK(c *gin.Context) { httpx.OK(c) } +func OKWithData(c *gin.Context, data any) { httpx.OKWithData(c, data) } +func Fail(c *gin.Context, message string) { httpx.Fail(c, message) } +func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) } +func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) } +func Claims(c *gin.Context) *biz.AuthClaims { return middleware.Claims(c) } diff --git a/app/system/internal/transport/handler/media.go b/app/system/internal/server/handler/media.go similarity index 60% rename from app/system/internal/transport/handler/media.go rename to app/system/internal/server/handler/media.go index c7508f8..c1f18a7 100644 --- a/app/system/internal/transport/handler/media.go +++ b/app/system/internal/server/handler/media.go @@ -2,14 +2,10 @@ package handler import ( "errors" + "kra/app/system/internal/service" "net/http" "strconv" - "kra/app/system/internal/dto" - "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" - servermiddleware "kra/app/system/internal/transport/middleware" - "github.com/gin-gonic/gin" ) @@ -28,14 +24,14 @@ func rejectMediaTooLarge(c *gin.Context, err error, message string) bool { if !errors.As(err, &tooLarge) { return false } - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, httpx.Response{Code: httpx.CodeError, Data: gin.H{}, Msg: message}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: message}) return true } func (h *Media) Upload(c *gin.Context) { - claims := servermiddleware.Claims(c) + claims := Claims(c) if claims == nil { - httpx.Fail(c, "接收文件失败") + Fail(c, "接收文件失败") return } limit := h.limitMultipartBody(c) @@ -44,18 +40,18 @@ func (h *Media) Upload(c *gin.Context) { if rejectMediaTooLarge(c, err, "文件超过大小上限") { return } - httpx.Fail(c, "接收文件失败") + Fail(c, "接收文件失败") return } if header.Size > limit { - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, httpx.Response{Code: httpx.CodeError, Data: gin.H{}, Msg: "文件超过大小上限"}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: "文件超过大小上限"}) return } opened, err := header.Open() if err != nil { // Let the upload service surface this as the generic upload // failure; keep the endpoint message consistent. - httpx.Fail(c, "上传文件失败") + Fail(c, "上传文件失败") return } defer opened.Close() @@ -63,41 +59,41 @@ func (h *Media) Upload(c *gin.Context) { save := c.DefaultQuery("noSave", "0") == "0" item, err := h.service.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save) if err != nil { - httpx.Fail(c, "上传文件失败") + Fail(c, "上传文件失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"file": item}, "上传成功") + Write(c, CodeSuccess, gin.H{"file": item}, "上传成功") } func (h *Media) List(c *gin.Context) { - var req dto.MediaListRequest + var req service.MediaListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.MediaListRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Media) Delete(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Delete(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Media) DeleteMany(c *gin.Context) { - var req dto.MediaBatchDeleteRequest + var req service.MediaBatchDeleteRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } failed := []uint{} @@ -107,121 +103,121 @@ func (h *Media) DeleteMany(c *gin.Context) { } } if len(failed) > 0 { - httpx.Write(c, httpx.CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败") + Write(c, CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Media) Find(c *gin.Context) { id, parseErr := strconv.ParseUint(c.Query("id"), 10, 64) if parseErr != nil { - httpx.Fail(c, "文件ID非法") + Fail(c, "文件ID非法") return } item, err := h.service.Media(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.OKWithData(c, item) + OKWithData(c, item) } func (h *Media) Rename(c *gin.Context) { - var req dto.RenameMediaRequest + var req service.RenameMediaRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Rename(c.Request.Context(), req.ID, req.Name); err != nil { - httpx.Fail(c, "编辑失败") + Fail(c, "编辑失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "编辑成功") + Write(c, CodeSuccess, gin.H{}, "编辑成功") } func (h *Media) ImportURLs(c *gin.Context) { - var req []dto.ImportMediaRequest + var req []service.ImportMediaRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.ImportURLRequests(c.Request.Context(), req); err != nil { - httpx.Fail(c, "导入URL失败") + Fail(c, "导入URL失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入URL成功") + Write(c, CodeSuccess, gin.H{}, "导入URL成功") } func (h *Media) Storage(c *gin.Context) { - var req dto.StorageListRequest + var req service.StorageListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, next, more, err := h.service.Storage(c.Request.Context(), req.Prefix, req.Cursor, req.Limit) if err != nil { - httpx.Fail(c, "列举存储桶文件失败") + Fail(c, "列举存储桶文件失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功") + Write(c, CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功") } func (h *Media) Categories(c *gin.Context) { items, err := h.service.Categories(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取分类列表失败") + Fail(c, "获取分类列表失败") return } - httpx.OKWithData(c, items) + OKWithData(c, items) } func (h *Media) SaveCategory(c *gin.Context) { - var req dto.CategoryRequest + var req service.CategoryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } if err := h.service.SaveCategoryRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建/更新失败:"+err.Error()) + Fail(c, "创建/更新失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建/更新成功") + Write(c, CodeSuccess, gin.H{}, "创建/更新成功") } func (h *Media) DeleteCategory(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if c.ShouldBindJSON(&req) != nil || req.ID == 0 { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } if err := h.service.DeleteCategory(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Media) InitUpload(c *gin.Context) { - claims := servermiddleware.Claims(c) - var req dto.InitUploadRequest + claims := Claims(c) + var req service.InitUploadRequest if claims == nil { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if config := h.service.MediaConfig(); config.MaxFileSize > 0 && req.FileSize > config.MaxFileSize { - httpx.Fail(c, "文件超过大小上限") + Fail(c, "文件超过大小上限") return } result, err := h.service.InitUpload(c.Request.Context(), claims.ID, req.FileName, req.FileHash, req.FileSize, req.ChunkSize, req.ChunkTotal) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, result, "成功") + Write(c, CodeSuccess, result, "成功") } func (h *Media) SaveChunk(c *gin.Context) { - claims := servermiddleware.Claims(c) + claims := Claims(c) if claims == nil { - httpx.Fail(c, "接收分片失败") + Fail(c, "接收分片失败") return } limit := h.limitMultipartBody(c) @@ -230,63 +226,63 @@ func (h *Media) SaveChunk(c *gin.Context) { if rejectMediaTooLarge(c, err, "分片超过大小上限") { return } - httpx.Fail(c, "接收分片失败") + Fail(c, "接收分片失败") return } if header.Size > limit { - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, httpx.Response{Code: httpx.CodeError, Data: gin.H{}, Msg: "分片超过大小上限"}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: "分片超过大小上限"}) return } uploadID, parseErr := strconv.ParseUint(c.PostForm("uploadId"), 10, 64) if parseErr != nil || uploadID == 0 { - httpx.Fail(c, "上传会话 ID 非法") + Fail(c, "上传会话 ID 非法") return } index, parseErr := strconv.Atoi(c.PostForm("chunkIndex")) if parseErr != nil || index < 0 { - httpx.Fail(c, "分片序号非法") + Fail(c, "分片序号非法") return } opened, err := header.Open() if err != nil { - httpx.Fail(c, "分片读取失败") + Fail(c, "分片读取失败") return } defer opened.Close() if err = h.service.SaveChunk(c.Request.Context(), claims.ID, uint(uploadID), index, c.PostForm("chunkHash"), opened); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "ok") + Write(c, CodeSuccess, gin.H{}, "ok") } func (h *Media) CompleteUpload(c *gin.Context) { - claims := servermiddleware.Claims(c) - var req dto.CompleteUploadRequest + claims := Claims(c) + var req service.CompleteUploadRequest if claims == nil { - httpx.Fail(c, "参数错误") + Fail(c, "参数错误") return } if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } item, err := h.service.CompleteUpload(c.Request.Context(), claims.ID, req.UploadID) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"media": item}, "成功") + Write(c, CodeSuccess, gin.H{"media": item}, "成功") } func (h *Media) CancelUpload(c *gin.Context) { - claims := servermiddleware.Claims(c) + claims := Claims(c) id, _ := strconv.ParseUint(c.Param("uploadId"), 10, 64) if claims == nil { - httpx.Fail(c, "未登录") + Fail(c, "未登录") return } if err := h.service.CancelUpload(c.Request.Context(), claims.ID, uint(id)); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "已取消") + Write(c, CodeSuccess, gin.H{}, "已取消") } diff --git a/app/system/internal/transport/handler/menu.go b/app/system/internal/server/handler/menu.go similarity index 53% rename from app/system/internal/transport/handler/menu.go rename to app/system/internal/server/handler/menu.go index f3f288e..7415e5d 100644 --- a/app/system/internal/transport/handler/menu.go +++ b/app/system/internal/server/handler/menu.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -15,170 +13,170 @@ func NewMenu(service *service.MenuService) *Menu { return &Menu{service: service func (h *Menu) List(c *gin.Context) { items, err := h.service.Tree(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + Write(c, CodeSuccess, items, "获取成功") } func (h *Menu) Tree(c *gin.Context) { items, err := h.service.Tree(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": items}, "获取成功") + Write(c, CodeSuccess, gin.H{"menus": items}, "获取成功") } func (h *Menu) Create(c *gin.Context) { - var req dto.MenuRequest + var req service.MenuRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Path == "" { - httpx.Fail(c, "Path值不能为空") + Fail(c, "Path值不能为空") return } if req.Name == "" { - httpx.Fail(c, "Name值不能为空") + Fail(c, "Name值不能为空") return } if req.Component == "" { - httpx.Fail(c, "Component值不能为空") + Fail(c, "Component值不能为空") return } if req.Sort < 0 { - httpx.Fail(c, "Sort长度或值不在合法范围,ge=0") + Fail(c, "Sort长度或值不在合法范围,ge=0") return } if req.Meta.Title == "" { - httpx.Fail(c, "Title值不能为空") + Fail(c, "Title值不能为空") return } if err := h.service.Create(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "添加失败:"+err.Error()) + Fail(c, "添加失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "添加成功") + Write(c, CodeSuccess, gin.H{}, "添加成功") } func (h *Menu) Update(c *gin.Context) { - var req dto.MenuRequest + var req service.MenuRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Path == "" { - httpx.Fail(c, "Path值不能为空") + Fail(c, "Path值不能为空") return } if req.Name == "" { - httpx.Fail(c, "Name值不能为空") + Fail(c, "Name值不能为空") return } if req.Component == "" { - httpx.Fail(c, "Component值不能为空") + Fail(c, "Component值不能为空") return } if req.Sort < 0 { - httpx.Fail(c, "Sort长度或值不在合法范围,ge=0") + Fail(c, "Sort长度或值不在合法范围,ge=0") return } if req.Meta.Title == "" { - httpx.Fail(c, "Title值不能为空") + Fail(c, "Title值不能为空") return } if err := h.service.Update(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败") + Fail(c, "更新失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Menu) Delete(c *gin.Context) { - var req dto.DeleteMenuRequest + var req service.DeleteMenuRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } if err := h.service.Delete(c.Request.Context(), uint(req.ID)); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Menu) Find(c *gin.Context) { - var req dto.GetMenuRequest + var req service.GetMenuRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } item, err := h.service.Find(c.Request.Context(), uint(req.ID)) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"menu": item}, "获取成功") + Write(c, CodeSuccess, gin.H{"menu": item}, "获取成功") } func (h *Menu) SetAuthorityMenus(c *gin.Context) { - var req dto.SetAuthorityMenusRequest + var req service.SetAuthorityMenusRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } if err := h.service.SetAuthorityMenus(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "添加失败") + Fail(c, "添加失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "添加成功") + Write(c, CodeSuccess, gin.H{}, "添加成功") } func (h *Menu) AuthorityMenus(c *gin.Context) { - var req dto.GetAuthorityMenusRequest + var req service.GetAuthorityMenusRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } menus, err := h.service.AuthorityMenus(c.Request.Context(), req.AuthorityID) if err != nil { - httpx.Write(c, httpx.CodeError, gin.H{"menus": menus}, "获取失败") + Write(c, CodeError, gin.H{"menus": menus}, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功") + Write(c, CodeSuccess, gin.H{"menus": menus}, "获取成功") } func (h *Menu) RoleIDs(c *gin.Context) { - var req dto.SetMenuRolesRequest + var req service.SetMenuRolesRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.MenuID == 0 { - httpx.Fail(c, "菜单ID不能为空") + Fail(c, "菜单ID不能为空") return } ids, err := h.service.RoleIDs(c.Request.Context(), req.MenuID) if err != nil { - httpx.Fail(c, "获取失败"+err.Error()) + Fail(c, "获取失败"+err.Error()) return } if ids == nil { @@ -186,28 +184,28 @@ func (h *Menu) RoleIDs(c *gin.Context) { } defaultIDs, err := h.service.DefaultRouterRoleIDs(c.Request.Context(), req.MenuID) if err != nil { - httpx.Fail(c, "获取失败"+err.Error()) + Fail(c, "获取失败"+err.Error()) return } if defaultIDs == nil { defaultIDs = []uint{} } - httpx.Write(c, httpx.CodeSuccess, gin.H{"authorityIds": ids, "defaultRouterAuthorityIds": defaultIDs}, "获取成功") + Write(c, CodeSuccess, gin.H{"authorityIds": ids, "defaultRouterAuthorityIds": defaultIDs}, "获取成功") } func (h *Menu) SetRoles(c *gin.Context) { - var req dto.SetMenuRolesRequest + var req service.SetMenuRolesRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.MenuID == 0 { - httpx.Fail(c, "菜单ID不能为空") + Fail(c, "菜单ID不能为空") return } if err := h.service.SetRoles(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败"+err.Error()) + Fail(c, "设置失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } diff --git a/app/system/internal/transport/handler/navigation.go b/app/system/internal/server/handler/navigation.go similarity index 60% rename from app/system/internal/transport/handler/navigation.go rename to app/system/internal/server/handler/navigation.go index c4540fe..b1947be 100644 --- a/app/system/internal/transport/handler/navigation.go +++ b/app/system/internal/server/handler/navigation.go @@ -2,8 +2,6 @@ package handler import ( "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" - "kra/app/system/internal/transport/middleware" "github.com/gin-gonic/gin" ) @@ -12,15 +10,15 @@ type Navigation struct{ service *service.UserService } func NewNavigation(service *service.UserService) *Navigation { return &Navigation{service: service} } func (h *Navigation) Menu(c *gin.Context) { - claims := middleware.Claims(c) + claims := Claims(c) if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } menus, err := h.service.Menus(c.Request.Context(), claims.AuthorityID) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功") + Write(c, CodeSuccess, gin.H{"menus": menus}, "获取成功") } diff --git a/app/system/internal/transport/handler/organization.go b/app/system/internal/server/handler/organization.go similarity index 56% rename from app/system/internal/transport/handler/organization.go rename to app/system/internal/server/handler/organization.go index ce65178..0ac5308 100644 --- a/app/system/internal/transport/handler/organization.go +++ b/app/system/internal/server/handler/organization.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -18,236 +16,236 @@ func NewOrganization(departments *service.DepartmentService, positions *service. } func (h *Organization) ListDepartments(c *gin.Context) { - var req dto.DepartmentListRequest + var req service.DepartmentListRequest _ = c.ShouldBindJSON(&req) items, err := h.departments.Departments(c.Request.Context(), req.Name) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, items, "获取成功") + Write(c, CodeSuccess, items, "获取成功") } func (h *Organization) CreateDepartment(c *gin.Context) { - var req dto.DepartmentRequest + var req service.DepartmentRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Name == "" { - httpx.Fail(c, "部门名称不能为空") + Fail(c, "部门名称不能为空") return } if err := h.departments.Create(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + Fail(c, "创建失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Organization) UpdateDepartment(c *gin.Context) { - var req dto.DepartmentRequest + var req service.DepartmentRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "部门ID不能为空") + Fail(c, "部门ID不能为空") return } if err := h.departments.Update(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败:"+err.Error()) + Fail(c, "更新失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Organization) DeleteDepartment(c *gin.Context) { - var req dto.DeleteDepartmentRequest + var req service.DeleteDepartmentRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.departments.Delete(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Organization) FindDepartment(c *gin.Context) { - var req dto.DeleteDepartmentRequest + var req service.DeleteDepartmentRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } item, err := h.departments.Department(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, item, "获取成功") + Write(c, CodeSuccess, item, "获取成功") } func (h *Organization) DepartmentUsers(c *gin.Context) { - var req dto.SetDepartmentUsersRequest + var req service.SetDepartmentUsersRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } ids, err := h.departments.UserIDs(c.Request.Context(), req.DepartmentID) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } if ids == nil { ids = []uint{} } - httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") + Write(c, CodeSuccess, ids, "获取成功") } func (h *Organization) SetDepartmentUsers(c *gin.Context) { - var req dto.SetDepartmentUsersRequest + var req service.SetDepartmentUsersRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.DepartmentID == 0 { - httpx.Fail(c, "部门ID不能为空") + Fail(c, "部门ID不能为空") return } if err := h.departments.SetUsers(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败:"+err.Error()) + Fail(c, "设置失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *Organization) SetUserDepartments(c *gin.Context) { - var req dto.SetUserDepartmentsRequest + var req service.SetUserDepartmentsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "用户ID不能为空") + Fail(c, "用户ID不能为空") return } if err := h.departments.SetUserDepartments(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败:"+err.Error()) + Fail(c, "设置失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *Organization) ListPositions(c *gin.Context) { - var req dto.PositionListRequest + var req service.PositionListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.positions.Positions(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Organization) CreatePosition(c *gin.Context) { - var req dto.PositionRequest + var req service.PositionRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Name == "" { - httpx.Fail(c, "岗位名称不能为空") + Fail(c, "岗位名称不能为空") return } if err := h.positions.Create(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + Fail(c, "创建失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Organization) UpdatePosition(c *gin.Context) { - var req dto.PositionRequest + var req service.PositionRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "岗位ID不能为空") + Fail(c, "岗位ID不能为空") return } if err := h.positions.Update(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败:"+err.Error()) + Fail(c, "更新失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Organization) DeletePosition(c *gin.Context) { - var req dto.DeletePositionRequest + var req service.DeletePositionRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.positions.Delete(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Organization) FindPosition(c *gin.Context) { - var req dto.DeletePositionRequest + var req service.DeletePositionRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } item, err := h.positions.Position(c.Request.Context(), req.ID) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, item, "获取成功") + Write(c, CodeSuccess, item, "获取成功") } func (h *Organization) PositionUsers(c *gin.Context) { - var req dto.SetPositionUsersRequest + var req service.SetPositionUsersRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } ids, err := h.positions.UserIDs(c.Request.Context(), req.PositionID) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } if ids == nil { ids = []uint{} } - httpx.Write(c, httpx.CodeSuccess, ids, "获取成功") + Write(c, CodeSuccess, ids, "获取成功") } func (h *Organization) SetPositionUsers(c *gin.Context) { - var req dto.SetPositionUsersRequest + var req service.SetPositionUsersRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.PositionID == 0 { - httpx.Fail(c, "岗位ID不能为空") + Fail(c, "岗位ID不能为空") return } if err := h.positions.SetUsers(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败:"+err.Error()) + Fail(c, "设置失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *Organization) SetUserPositions(c *gin.Context) { - var req dto.SetUserPositionsRequest + var req service.SetUserPositionsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "用户ID不能为空") + Fail(c, "用户ID不能为空") return } if err := h.positions.SetUserPositions(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败:"+err.Error()) + Fail(c, "设置失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } diff --git a/app/system/internal/transport/handler/parameter.go b/app/system/internal/server/handler/parameter.go similarity index 59% rename from app/system/internal/transport/handler/parameter.go rename to app/system/internal/server/handler/parameter.go index e31e581..134d77c 100644 --- a/app/system/internal/transport/handler/parameter.go +++ b/app/system/internal/server/handler/parameter.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -17,76 +15,76 @@ func NewParameter(service *service.ParameterService) *Parameter { } func (h *Parameter) Create(c *gin.Context) { - var req dto.SystemParameterRequest + var req service.SystemParameterRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.CreateParameterRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "创建失败:"+err.Error()) + Fail(c, "创建失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Parameter) Update(c *gin.Context) { - var req dto.SystemParameterRequest + var req service.SystemParameterRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.UpdateParameterRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败:"+err.Error()) + Fail(c, "更新失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Parameter) Delete(c *gin.Context) { if err := h.service.DeleteParameters(c.Request.Context(), []string{c.Query("ID")}); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Parameter) DeleteMany(c *gin.Context) { ids := c.QueryArray("IDs[]") if err := h.service.DeleteParameters(c.Request.Context(), ids); err != nil { - httpx.Fail(c, "批量删除失败:"+err.Error()) + Fail(c, "批量删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Parameter) Find(c *gin.Context) { item, err := h.service.ParameterByID(c.Request.Context(), c.Query("ID")) if err != nil { - httpx.Fail(c, "查询失败:"+err.Error()) + Fail(c, "查询失败:"+err.Error()) return } - httpx.OKWithData(c, item) + OKWithData(c, item) } func (h *Parameter) Get(c *gin.Context) { item, err := h.service.ParameterByKey(c.Request.Context(), c.Query("key")) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, item, "获取成功") + Write(c, CodeSuccess, item, "获取成功") } func (h *Parameter) List(c *gin.Context) { - var req dto.SystemParameterSearchRequest + var req service.SystemParameterSearchRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.ParametersFilter(c.Request.Context(), req.Page, req.PageSize, req.Name, req.Key, req.StartCreatedAt, req.EndCreatedAt) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } diff --git a/app/system/internal/transport/handler/payment.go b/app/system/internal/server/handler/payment.go similarity index 52% rename from app/system/internal/transport/handler/payment.go rename to app/system/internal/server/handler/payment.go index 23f13c5..4359b27 100644 --- a/app/system/internal/transport/handler/payment.go +++ b/app/system/internal/server/handler/payment.go @@ -2,11 +2,8 @@ package handler import ( "io" - "net/http" - - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" + "net/http" "github.com/gin-gonic/gin" ) @@ -17,89 +14,141 @@ func NewPayment(service *service.PaymentService) *Payment { return &Payment{serv func (h *Payment) Configs(c *gin.Context) { v, err := h.service.Configs(c.Request.Context()) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.OKWithData(c, v) + OKWithData(c, v) } func (h *Payment) SaveConfig(c *gin.Context) { - var req dto.PaymentConfigRequest + var req service.PaymentConfigRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.SaveConfig(c.Request.Context(), &req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.OK(c) + OK(c) } func (h *Payment) Order(c *gin.Context) { - var req dto.PaymentQueryRequest + var req service.PaymentQueryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } order, err := h.service.Order(c.Request.Context(), req.Provider, req.TradeNo) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.OKWithData(c, order) + OKWithData(c, order) +} + +func (h *Payment) OrderByPath(c *gin.Context) { + order, err := h.service.Order(c.Request.Context(), c.Param("provider"), c.Param("tradeNo")) + if err != nil { + Fail(c, err.Error()) + return + } + OKWithData(c, order) +} + +func (h *Payment) RefundByPath(c *gin.Context) { + var req struct { + Amount int64 `json:"amount"` + } + if err := c.ShouldBindJSON(&req); err != nil { + Fail(c, err.Error()) + return + } + result, err := h.service.Refund(c.Request.Context(), &service.PaymentRefundRequest{ + Provider: c.Param("provider"), TradeNo: c.Param("tradeNo"), Amount: req.Amount, + }) + if err != nil { + Fail(c, err.Error()) + return + } + OKWithData(c, result) +} + +func (h *Payment) FulfillByPath(c *gin.Context) { + result, err := h.service.Fulfill(c.Request.Context(), &service.PaymentFulfillRequest{ + Provider: c.Param("provider"), TradeNo: c.Param("tradeNo"), + }) + if err != nil { + Fail(c, err.Error()) + return + } + OKWithData(c, result) } func (h *Payment) Orders(c *gin.Context) { - var req dto.PaymentOrderListRequest + var req service.PaymentOrderListRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, page, pageSize, err := h.service.Orders(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功") } func (h *Payment) Create(c *gin.Context) { - var req dto.PaymentRequest + var req service.PaymentRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } req.ClientIP = c.ClientIP() result, err := h.service.Create(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.OKWithData(c, result) + OKWithData(c, result) } func (h *Payment) Query(c *gin.Context) { - var req dto.PaymentQueryRequest + var req service.PaymentQueryRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } result, err := h.service.Query(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.OKWithData(c, result) + OKWithData(c, result) } func (h *Payment) Refund(c *gin.Context) { - var req dto.PaymentRefundRequest + var req service.PaymentRefundRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } result, err := h.service.Refund(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.OKWithData(c, result) + OKWithData(c, result) +} + +func (h *Payment) Fulfill(c *gin.Context) { + var req service.PaymentFulfillRequest + if err := c.ShouldBindJSON(&req); err != nil { + Fail(c, err.Error()) + return + } + result, err := h.service.Fulfill(c.Request.Context(), &req) + if err != nil { + Fail(c, err.Error()) + return + } + OKWithData(c, result) } func (h *Payment) Callback(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8<<20) @@ -120,12 +169,12 @@ func (h *Payment) Callback(c *gin.Context) { query[k] = v[0] } } - ack, err := h.service.Callback(c.Request.Context(), &dto.PaymentCallbackRequest{Provider: c.Param("provider"), Headers: headers, Body: raw, Query: query}) + ack, err := h.service.Callback(c.Request.Context(), &service.PaymentCallbackRequest{Provider: c.Param("provider"), Headers: headers, Body: raw, Query: query}) writePaymentCallbackAck(c, ack) _ = err } -func writePaymentCallbackAck(c *gin.Context, ack dto.PaymentCallbackAck) { +func writePaymentCallbackAck(c *gin.Context, ack service.PaymentCallbackAck) { if ack.StatusCode == 0 { ack.StatusCode = http.StatusInternalServerError } diff --git a/app/system/internal/transport/handler/payment_test.go b/app/system/internal/server/handler/payment_test.go similarity index 87% rename from app/system/internal/transport/handler/payment_test.go rename to app/system/internal/server/handler/payment_test.go index 6bfae64..1a54029 100644 --- a/app/system/internal/transport/handler/payment_test.go +++ b/app/system/internal/server/handler/payment_test.go @@ -5,14 +5,14 @@ import ( "testing" "github.com/gin-gonic/gin" - "kra/app/system/internal/dto" + "kra/app/system/internal/service" ) func TestWritePaymentCallbackAckDoesNotExposeError(t *testing.T) { gin.SetMode(gin.TestMode) recorder := httptest.NewRecorder() context, _ := gin.CreateTestContext(recorder) - writePaymentCallbackAck(context, dto.PaymentCallbackAck{ + writePaymentCallbackAck(context, service.PaymentCallbackAck{ StatusCode: 500, ContentType: "text/plain; charset=utf-8", Body: []byte("failure"), diff --git a/app/system/internal/transport/handler/permission.go b/app/system/internal/server/handler/permission.go similarity index 62% rename from app/system/internal/transport/handler/permission.go rename to app/system/internal/server/handler/permission.go index e530109..2cc5ec6 100644 --- a/app/system/internal/transport/handler/permission.go +++ b/app/system/internal/server/handler/permission.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -15,41 +13,41 @@ func NewPermission(service *service.PermissionService) *Permission { } func (h *Permission) Buttons(c *gin.Context) { - var req dto.GetAuthorityButtonsRequest + var req service.GetAuthorityButtonsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } selected, err := h.service.SelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID) if err != nil { - httpx.Fail(c, "查询失败") + Fail(c, "查询失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"selected": selected}, "查询成功") + Write(c, CodeSuccess, gin.H{"selected": selected}, "查询成功") } func (h *Permission) SetButtons(c *gin.Context) { - var req dto.SetAuthorityButtonsRequest + var req service.SetAuthorityButtonsRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.SetSelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID, req.Selected); err != nil { - httpx.Fail(c, "分配失败") + Fail(c, "分配失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "分配成功") + Write(c, CodeSuccess, gin.H{}, "分配成功") } func (h *Permission) CanRemoveButton(c *gin.Context) { allowed, err := h.service.CanRemoveButton(c.Request.Context(), c.Query("id")) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if !allowed { - httpx.Fail(c, "此按钮正在被使用无法删除") + Fail(c, "此按钮正在被使用无法删除") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } diff --git a/app/system/internal/server/handler/provider.go b/app/system/internal/server/handler/provider.go new file mode 100644 index 0000000..2454b88 --- /dev/null +++ b/app/system/internal/server/handler/provider.go @@ -0,0 +1,11 @@ +package handler + +import "github.com/google/wire" + +// ProviderSet wires the system HTTP handlers. +var ProviderSet = wire.NewSet( + NewAuthority, NewMenu, NewAPI, NewPermission, NewOrganization, + NewAnnouncement, NewEmail, NewPayment, NewTask, NewMedia, NewAudit, + NewExport, NewVersion, NewDictionary, NewParameter, NewAPIToken, + NewSystemConfig, NewPublic, NewUser, NewNavigation, NewSession, NewSet, +) diff --git a/app/system/internal/transport/handler/public.go b/app/system/internal/server/handler/public.go similarity index 70% rename from app/system/internal/transport/handler/public.go rename to app/system/internal/server/handler/public.go index 7e58911..9200a5a 100644 --- a/app/system/internal/transport/handler/public.go +++ b/app/system/internal/server/handler/public.go @@ -8,9 +8,7 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" "github.com/mojocn/base64Captcha" @@ -36,7 +34,7 @@ func (h *Public) Captcha(c *gin.Context) { keyLong, width, height := h.captchaConfig() security, err := h.settings.CurrentSecurity(c.Request.Context()) if err != nil || security == nil { - httpx.Fail(c, "安全服务暂不可用") + Fail(c, "安全服务暂不可用") return } openCaptcha := true @@ -45,7 +43,7 @@ func (h *Public) Captcha(c *gin.Context) { ttl := time.Duration(security.CaptchaTimeout) * time.Second failures, counterErr := h.settings.EnsureLoginIPCounter(c.Request.Context(), c.ClientIP(), ttl) if counterErr != nil { - httpx.Fail(c, "安全服务暂不可用") + Fail(c, "安全服务暂不可用") return } openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen @@ -53,24 +51,24 @@ func (h *Public) Captcha(c *gin.Context) { driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80) id, picture, _, err := base64Captcha.NewCaptcha(driver, h.store).Generate() if err != nil { - httpx.Fail(c, "验证码获取失败") + Fail(c, "验证码获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"captchaId": id, "picPath": picture, "captchaLength": keyLong, "openCaptcha": openCaptcha}, "验证码获取成功") + Write(c, CodeSuccess, gin.H{"captchaId": id, "picPath": picture, "captchaLength": keyLong, "openCaptcha": openCaptcha}, "验证码获取成功") } func (h *Public) Login(c *gin.Context) { - var req dto.LoginRequest + var req service.LoginRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Username == "" { - httpx.Fail(c, "Username值不能为空") + Fail(c, "Username值不能为空") return } if req.Password == "" { - httpx.Fail(c, "Password值不能为空") + Fail(c, "Password值不能为空") return } result, err := h.auth.Login(c.Request.Context(), &req, c.ClientIP(), c.Request.UserAgent()) @@ -83,66 +81,66 @@ func (h *Public) Login(c *gin.Context) { if locked != nil { minutes = locked.Minutes } - httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试") + Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试") case errors.Is(err, biz.ErrCaptchaInvalid): - httpx.Fail(c, "验证码错误") + Fail(c, "验证码错误") case errors.Is(err, biz.ErrUserDisabled): - httpx.Fail(c, "用户被禁止登录") + Fail(c, "用户被禁止登录") case errors.Is(err, biz.ErrTokenIssue): - httpx.Fail(c, "获取token失败") + Fail(c, "获取token失败") case errors.Is(err, biz.ErrLoginState): - httpx.Fail(c, "设置登录状态失败") + Fail(c, "设置登录状态失败") case errors.Is(err, biz.ErrTokenRevoke): - httpx.Fail(c, "jwt作废失败") + Fail(c, "jwt作废失败") default: - httpx.Fail(c, "用户名不存在或者密码错误") + Fail(c, "用户名不存在或者密码错误") } return } maxAge := int(time.UnixMilli(result.ExpiresAt).Unix() - time.Now().Unix()) - httpx.SetTokenCookie(c, result.Token, maxAge) - httpx.Write(c, httpx.CodeSuccess, result, "登录成功") + SetTokenCookie(c, result.Token, maxAge) + Write(c, CodeSuccess, result, "登录成功") } func (h *Public) CheckDatabase(c *gin.Context) { initialized, err := h.system.IsInitialized(c.Request.Context()) if err != nil { - httpx.Fail(c, "数据库状态检查失败") + Fail(c, "数据库状态检查失败") return } message := "数据库无需初始化" if !initialized { message = "前往初始化数据库" } - httpx.Write(c, httpx.CodeSuccess, gin.H{"needInit": !initialized}, message) + Write(c, CodeSuccess, gin.H{"needInit": !initialized}, message) } func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc { return func(c *gin.Context) { initialized, err := h.system.IsInitialized(c.Request.Context()) if err != nil { - httpx.Fail(c, "数据库状态检查失败") + Fail(c, "数据库状态检查失败") return } if initialized { - httpx.Fail(c, "已存在数据库配置") + Fail(c, "已存在数据库配置") return } - var input dto.DatabaseInitRequest + var input service.DatabaseInitRequest if c.ShouldBindJSON(&input) != nil { - httpx.Fail(c, "参数校验不通过") + Fail(c, "参数校验不通过") return } routes := engine.Routes() - values := make([]dto.Route, 0, len(routes)) + values := make([]service.Route, 0, len(routes)) for _, route := range routes { - values = append(values, dto.Route{Path: route.Path, Method: route.Method}) + values = append(values, service.Route{Path: route.Path, Method: route.Method}) } if err := h.system.InitializeRoutes(c.Request.Context(), &input, values); err != nil { - httpx.Fail(c, "自动创建数据库失败,请查看后台日志,检查后在进行初始化") + Fail(c, "自动创建数据库失败,请查看后台日志,检查后在进行初始化") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "自动创建数据库成功") + Write(c, CodeSuccess, gin.H{}, "自动创建数据库成功") } } diff --git a/app/system/internal/transport/handler/query.go b/app/system/internal/server/handler/query.go similarity index 100% rename from app/system/internal/transport/handler/query.go rename to app/system/internal/server/handler/query.go diff --git a/app/system/internal/transport/handler/session.go b/app/system/internal/server/handler/session.go similarity index 72% rename from app/system/internal/transport/handler/session.go rename to app/system/internal/server/handler/session.go index d3f5b80..77adffa 100644 --- a/app/system/internal/transport/handler/session.go +++ b/app/system/internal/server/handler/session.go @@ -2,7 +2,6 @@ package handler import ( "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -16,9 +15,9 @@ func (h *Session) Logout(c *gin.Context) { token, _ = c.Cookie("x-token") } if err := h.tokens.BlacklistToken(c.Request.Context(), token); err != nil { - httpx.Fail(c, "jwt作废失败") + Fail(c, "jwt作废失败") return } - httpx.SetTokenCookie(c, "", -1) - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "jwt作废成功") + SetTokenCookie(c, "", -1) + Write(c, CodeSuccess, gin.H{}, "jwt作废成功") } diff --git a/app/system/internal/transport/handler/set.go b/app/system/internal/server/handler/set.go similarity index 100% rename from app/system/internal/transport/handler/set.go rename to app/system/internal/server/handler/set.go diff --git a/app/system/internal/transport/handler/system_config.go b/app/system/internal/server/handler/system_config.go similarity index 61% rename from app/system/internal/transport/handler/system_config.go rename to app/system/internal/server/handler/system_config.go index 08de2b1..b4085db 100644 --- a/app/system/internal/transport/handler/system_config.go +++ b/app/system/internal/server/handler/system_config.go @@ -1,9 +1,7 @@ package handler import ( - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -20,61 +18,61 @@ func NewSystemConfig(system *service.SystemConfigService, settings *service.Secu func (h *SystemConfig) GetSecurity(c *gin.Context) { value, err := h.settings.Security(c.Request.Context()) if err != nil { - httpx.Fail(c, "获取安全配置失败") + Fail(c, "获取安全配置失败") return } - httpx.Write(c, httpx.CodeSuccess, value, "获取成功") + Write(c, CodeSuccess, value, "获取成功") } func (h *SystemConfig) SetSecurity(c *gin.Context) { - var req dto.SecurityConfigRequest + var req service.SecurityConfigRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } result, err := h.settings.SaveSecurityRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "设置安全配置失败") + Fail(c, "设置安全配置失败") return } - httpx.Write(c, httpx.CodeSuccess, result, "设置成功") + Write(c, CodeSuccess, result, "设置成功") } func (h *SystemConfig) Get(c *gin.Context) { value, err := h.system.SystemConfig() if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, value, "获取成功") + Write(c, CodeSuccess, value, "获取成功") } func (h *SystemConfig) Set(c *gin.Context) { - var req dto.SetSystemConfigRequest + var req service.SetSystemConfigRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.system.SaveSystemConfig(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "设置失败") + Fail(c, "设置失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *SystemConfig) Reload(c *gin.Context) { if err := h.system.ReloadConfig(c.Request.Context()); err != nil { - httpx.Fail(c, "重载系统失败:"+err.Error()) + Fail(c, "重载系统失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重载系统成功") + Write(c, CodeSuccess, gin.H{}, "重载系统成功") } func (h *SystemConfig) ServerInfo(c *gin.Context) { server, err := h.system.ServerInfo() if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"server": server}, "获取成功") + Write(c, CodeSuccess, gin.H{"server": server}, "获取成功") } diff --git a/app/system/internal/transport/handler/task.go b/app/system/internal/server/handler/task.go similarity index 63% rename from app/system/internal/transport/handler/task.go rename to app/system/internal/server/handler/task.go index f3b981a..bb88120 100644 --- a/app/system/internal/transport/handler/task.go +++ b/app/system/internal/server/handler/task.go @@ -2,14 +2,10 @@ package handler import ( "fmt" + "kra/app/system/internal/service" "net/http" "time" - "kra/app/system/internal/dto" - "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" - "kra/app/system/internal/transport/middleware" - "github.com/gin-gonic/gin" ) @@ -22,107 +18,107 @@ func NewTask(service *service.TaskService) *Task { } func (h *Task) Create(c *gin.Context) { - var req dto.TaskRequest + var req service.TaskRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } _, err := h.service.CreateRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "创建失败: "+err.Error()) + Fail(c, "创建失败: "+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功") + Write(c, CodeSuccess, gin.H{}, "创建成功") } func (h *Task) Update(c *gin.Context) { - var req dto.TaskRequest + var req service.TaskRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "更新失败: "+err.Error()) + Fail(c, "更新失败: "+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功") + Write(c, CodeSuccess, gin.H{}, "更新成功") } func (h *Task) Delete(c *gin.Context) { - var req dto.DeleteTaskRequest + var req service.DeleteTaskRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Delete(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Task) Toggle(c *gin.Context) { - var req dto.ToggleTaskRequest + var req service.ToggleTaskRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil { - httpx.Fail(c, "操作失败: "+err.Error()) + Fail(c, "操作失败: "+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "操作成功") + Write(c, CodeSuccess, gin.H{}, "操作成功") } func (h *Task) Trigger(c *gin.Context) { - var req dto.DeleteTaskRequest + var req service.DeleteTaskRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Trigger(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "触发失败: "+err.Error()) + Fail(c, "触发失败: "+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "已触发, 执行结果见执行日志") + Write(c, CodeSuccess, gin.H{}, "已触发, 执行结果见执行日志") } func (h *Task) List(c *gin.Context) { - var req dto.TaskListRequest + var req service.TaskListRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.ListRequest(c.Request.Context(), req.Page, req.PageSize, req.Name, req.ExecutorType, req.Enabled) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Task) Logs(c *gin.Context) { - var req dto.TaskLogListRequest + var req service.TaskLogListRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } items, total, err := h.service.Logs(c.Request.Context(), req.Page, req.PageSize, req.TaskID, req.Status) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Task) Methods(c *gin.Context) { - httpx.Write(c, httpx.CodeSuccess, gin.H{"methods": h.service.RegisteredMethods()}, "获取成功") + Write(c, CodeSuccess, gin.H{"methods": h.service.RegisteredMethods()}, "获取成功") } func (h *Task) AlertStream(c *gin.Context) { - claims := middleware.Claims(c) + claims := Claims(c) if claims == nil || claims.ID == 0 { - httpx.Fail(c, "未获取到用户身份") + Fail(c, "未获取到用户身份") return } c.Header("Content-Type", "text/event-stream; charset=utf-8") @@ -131,7 +127,7 @@ func (h *Task) AlertStream(c *gin.Context) { c.Header("X-Accel-Buffering", "no") flusher, ok := c.Writer.(http.Flusher) if !ok { - httpx.Fail(c, "SSE 不受支持") + Fail(c, "SSE 不受支持") return } // Remove the normal server deadline for long-lived SSE connections. diff --git a/app/system/internal/transport/handler/user.go b/app/system/internal/server/handler/user.go similarity index 54% rename from app/system/internal/transport/handler/user.go rename to app/system/internal/server/handler/user.go index 2062dfb..0ef7bfb 100644 --- a/app/system/internal/transport/handler/user.go +++ b/app/system/internal/server/handler/user.go @@ -1,14 +1,10 @@ package handler import ( + "kra/app/system/internal/service" "strconv" "time" - "kra/app/system/internal/dto" - "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" - "kra/app/system/internal/transport/middleware" - "github.com/gin-gonic/gin" ) @@ -22,236 +18,236 @@ func NewUser(user *service.UserService, auth *service.AuthService) *User { } func (h *User) List(c *gin.Context) { - var req dto.UserListRequest + var req service.UserListRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Page == 0 { - httpx.Fail(c, "Page值不能为空") + Fail(c, "Page值不能为空") return } if req.PageSize == 0 { - httpx.Fail(c, "PageSize值不能为空") + Fail(c, "PageSize值不能为空") return } items, total, err := h.service.ListUsersRequest(c.Request.Context(), &req) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *User) Create(c *gin.Context) { - var req dto.UserRequest + var req service.UserRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Username == "" { - httpx.Fail(c, "Username值不能为空") + Fail(c, "Username值不能为空") return } if req.Password == "" { - httpx.Fail(c, "Password值不能为空") + Fail(c, "Password值不能为空") return } if req.NickName == "" { - httpx.Fail(c, "NickName值不能为空") + Fail(c, "NickName值不能为空") return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } result, err := h.service.CreateUserRequest(c.Request.Context(), &req) if err != nil { if service.IsPasswordPolicyError(err) { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if result == nil { - result = &dto.UserResponse{} + result = &service.UserResponse{} } - httpx.Write(c, httpx.CodeError, gin.H{"user": result}, "注册失败") + Write(c, CodeError, gin.H{"user": result}, "注册失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"user": result}, "注册成功") + Write(c, CodeSuccess, gin.H{"user": result}, "注册成功") } func (h *User) Update(c *gin.Context) { - var req dto.UserRequest + var req service.UserRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } if err := h.service.UpdateUserRequest(c.Request.Context(), &req); err != nil { - httpx.Fail(c, "修改失败") + Fail(c, "修改失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *User) UpdateSelf(c *gin.Context) { - claims := middleware.Claims(c) + claims := Claims(c) if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } - var req dto.SelfUserRequest + var req service.SelfUserRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.UpdateSelfUserRequest(c.Request.Context(), claims.ID, &req); err != nil { - httpx.Fail(c, "修改失败") + Fail(c, "修改失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *User) Delete(c *gin.Context) { - var req dto.IDRequest + var req service.IDRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.ID == 0 { - httpx.Fail(c, "ID值不能为空") + Fail(c, "ID值不能为空") return } - claims := middleware.Claims(c) + claims := Claims(c) if claims != nil && claims.ID == req.ID { - httpx.Fail(c, "删除失败, 无法删除自己。") + Fail(c, "删除失败, 无法删除自己。") return } if err := h.service.DeleteUser(c.Request.Context(), req.ID); err != nil { - httpx.Fail(c, "删除失败") + Fail(c, "删除失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *User) ResetPassword(c *gin.Context) { - var req dto.ResetPasswordRequest + var req service.ResetPasswordRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil { if service.IsPasswordPolicyError(err) { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Fail(c, "重置失败"+err.Error()) + Fail(c, "重置失败"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重置成功") + Write(c, CodeSuccess, gin.H{}, "重置成功") } func (h *User) ChangePassword(c *gin.Context) { - claims := middleware.Claims(c) - var req dto.ChangePasswordRequest + claims := Claims(c) + var req service.ChangePasswordRequest if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.Password == "" { - httpx.Fail(c, "Password值不能为空") + Fail(c, "Password值不能为空") return } if req.NewPassword == "" { - httpx.Fail(c, "NewPassword值不能为空") + Fail(c, "NewPassword值不能为空") return } if err := h.service.ChangePassword(c.Request.Context(), claims.ID, req.Password, req.NewPassword); err != nil { if service.IsPasswordPolicyError(err) { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } - httpx.Fail(c, "修改失败,原密码与当前账户不符") + Fail(c, "修改失败,原密码与当前账户不符") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") + Write(c, CodeSuccess, gin.H{}, "修改成功") } func (h *User) SetSelfSetting(c *gin.Context) { - claims := middleware.Claims(c) + claims := Claims(c) var setting map[string]any if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } if err := c.ShouldBindJSON(&setting); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.SetUserSetting(c.Request.Context(), claims.ID, setting); err != nil { - httpx.Fail(c, "设置失败") + Fail(c, "设置失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功") + Write(c, CodeSuccess, gin.H{}, "设置成功") } func (h *User) SetAuthorities(c *gin.Context) { - var req dto.UserAuthoritiesRequest + var req service.UserAuthoritiesRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.SetUserAuthorities(c.Request.Context(), req.ID, req.AuthorityIDs); err != nil { - httpx.Fail(c, "修改失败") + Fail(c, "修改失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") + Write(c, CodeSuccess, gin.H{}, "修改成功") } func (h *User) SwitchAuthority(c *gin.Context) { - claims := middleware.Claims(c) - var req dto.SwitchAuthorityRequest + claims := Claims(c) + var req service.SwitchAuthorityRequest if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if req.AuthorityID == 0 { - httpx.Fail(c, "AuthorityId值不能为空") + Fail(c, "AuthorityId值不能为空") return } login, err := h.auth.SwitchAuthority(c.Request.Context(), claims, req.AuthorityID) if err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } c.Header("new-token", login.Token) c.Header("new-expires-at", strconv.FormatInt(login.ExpiresAt/1000, 10)) maxAge := int(time.UnixMilli(login.ExpiresAt).Unix() - time.Now().Unix()) - httpx.SetTokenCookie(c, login.Token, maxAge) - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功") + SetTokenCookie(c, login.Token, maxAge) + Write(c, CodeSuccess, gin.H{}, "修改成功") } func (h *User) Get(c *gin.Context) { - claims := middleware.Claims(c) + claims := Claims(c) if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } result, err := h.service.UserByUUID(c.Request.Context(), claims.UUID) if err != nil { - httpx.Fail(c, "获取失败") + Fail(c, "获取失败") return } - httpx.Write(c, httpx.CodeSuccess, gin.H{"userInfo": result}, "获取成功") + Write(c, CodeSuccess, gin.H{"userInfo": result}, "获取成功") } diff --git a/app/system/internal/transport/handler/version.go b/app/system/internal/server/handler/version.go similarity index 67% rename from app/system/internal/transport/handler/version.go rename to app/system/internal/server/handler/version.go index 5e8ec4a..a42201a 100644 --- a/app/system/internal/transport/handler/version.go +++ b/app/system/internal/server/handler/version.go @@ -8,9 +8,7 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -30,10 +28,10 @@ func versionStage(err error) (biz.VersionStage, bool) { func (h *Version) Delete(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) if err := h.service.DeleteVersions(c.Request.Context(), []uint{uint(id)}); err != nil { - httpx.Fail(c, "删除失败:"+err.Error()) + Fail(c, "删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功") + Write(c, CodeSuccess, gin.H{}, "删除成功") } func (h *Version) DeleteMany(c *gin.Context) { raw := c.QueryArray("IDs[]") @@ -43,24 +41,24 @@ func (h *Version) DeleteMany(c *gin.Context) { ids = append(ids, uint(id)) } if err := h.service.DeleteVersions(c.Request.Context(), ids); err != nil { - httpx.Fail(c, "批量删除失败:"+err.Error()) + Fail(c, "批量删除失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功") + Write(c, CodeSuccess, gin.H{}, "批量删除成功") } func (h *Version) Find(c *gin.Context) { id, _ := strconv.ParseUint(c.Query("ID"), 10, 64) item, err := h.service.Version(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "查询失败:"+err.Error()) + Fail(c, "查询失败:"+err.Error()) return } - httpx.OKWithData(c, item) + OKWithData(c, item) } func (h *Version) List(c *gin.Context) { - var req dto.VersionSearchRequest + var req service.VersionSearchRequest if err := c.ShouldBindQuery(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } name, code := "", "" @@ -76,47 +74,47 @@ func (h *Version) List(c *gin.Context) { } items, total, err := h.service.Versions(c.Request.Context(), req.Page, req.PageSize, name, code, createdAtRange) if err != nil { - httpx.Fail(c, "获取失败:"+err.Error()) + Fail(c, "获取失败:"+err.Error()) return } - httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") + Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功") } func (h *Version) Export(c *gin.Context) { - var req dto.ExportVersionRequest + var req service.ExportVersionRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, err.Error()) + Fail(c, err.Error()) return } if err := h.service.Export(c.Request.Context(), req.VersionName, req.VersionCode, req.Description, req.MenuIDs, req.APIIDs, req.DictIDs); err != nil { switch stage, _ := versionStage(err); stage { case biz.VersionStageMenus: - httpx.Fail(c, "获取菜单数据失败:"+err.Error()) + Fail(c, "获取菜单数据失败:"+err.Error()) case biz.VersionStageAPIs: - httpx.Fail(c, "获取API数据失败:"+err.Error()) + Fail(c, "获取API数据失败:"+err.Error()) case biz.VersionStageDictionaries: - httpx.Fail(c, "获取字典数据失败:"+err.Error()) + Fail(c, "获取字典数据失败:"+err.Error()) case biz.VersionStageJSON: - httpx.Fail(c, "JSON序列化失败:"+err.Error()) + Fail(c, "JSON序列化失败:"+err.Error()) case biz.VersionStageSave: - httpx.Fail(c, "保存版本记录失败:"+err.Error()) + Fail(c, "保存版本记录失败:"+err.Error()) default: - httpx.Fail(c, "创建发版失败:"+err.Error()) + Fail(c, "创建发版失败:"+err.Error()) } return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建发版成功") + Write(c, CodeSuccess, gin.H{}, "创建发版成功") } func (h *Version) Download(c *gin.Context) { rawID := c.Query("ID") if rawID == "" { - httpx.Fail(c, "版本ID不能为空") + Fail(c, "版本ID不能为空") return } id, _ := strconv.ParseUint(rawID, 10, 64) raw, code, err := h.service.VersionData(c.Request.Context(), uint(id)) if err != nil { - httpx.Fail(c, "获取版本记录失败:"+err.Error()) + Fail(c, "获取版本记录失败:"+err.Error()) return } name := fmt.Sprintf("version_%s_%s.json", code, time.Now().Format("20060102150405")) @@ -126,27 +124,27 @@ func (h *Version) Download(c *gin.Context) { c.Data(http.StatusOK, "application/json", []byte(raw)) } func (h *Version) Import(c *gin.Context) { - var req dto.ImportVersionRequest + var req service.ImportVersionRequest if err := c.ShouldBindJSON(&req); err != nil { - httpx.Fail(c, "解析JSON数据失败:"+err.Error()) + Fail(c, "解析JSON数据失败:"+err.Error()) return } if err := h.service.ImportRequest(c.Request.Context(), &req); err != nil { if errors.Is(err, biz.ErrInvalidVersion) { - httpx.Fail(c, "版本信息格式错误") + Fail(c, "版本信息格式错误") return } switch stage, _ := versionStage(err); stage { case biz.VersionStageMenus: - httpx.Fail(c, "导入菜单失败: "+err.Error()) + Fail(c, "导入菜单失败: "+err.Error()) case biz.VersionStageAPIs: - httpx.Fail(c, "导入API失败: "+err.Error()) + Fail(c, "导入API失败: "+err.Error()) case biz.VersionStageDictionaries: - httpx.Fail(c, "导入字典失败: "+err.Error()) + Fail(c, "导入字典失败: "+err.Error()) default: - httpx.Fail(c, "导入失败:"+err.Error()) + Fail(c, "导入失败:"+err.Error()) } return } - httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功") + Write(c, CodeSuccess, gin.H{}, "导入成功") } diff --git a/app/system/internal/transport/httpx/cookie.go b/app/system/internal/server/httpx/cookie.go similarity index 100% rename from app/system/internal/transport/httpx/cookie.go rename to app/system/internal/server/httpx/cookie.go diff --git a/app/system/internal/transport/httpx/response.go b/app/system/internal/server/httpx/response.go similarity index 100% rename from app/system/internal/transport/httpx/response.go rename to app/system/internal/server/httpx/response.go diff --git a/app/system/internal/transport/middleware/access.go b/app/system/internal/server/middleware/access.go similarity index 86% rename from app/system/internal/transport/middleware/access.go rename to app/system/internal/server/middleware/access.go index 3e93725..bd1b281 100644 --- a/app/system/internal/transport/middleware/access.go +++ b/app/system/internal/server/middleware/access.go @@ -6,7 +6,6 @@ import ( "kra/app/system/internal/biz" "kra/app/system/internal/conf" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -20,7 +19,7 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu return func(c *gin.Context) { claims := Claims(c) if claims == nil { - httpx.NoAuth(c, "未登录或非法访问") + NoAuth(c, "未登录或非法访问") return } path := c.Request.URL.Path @@ -30,13 +29,13 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu } allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, policyPath, c.Request.Method) if err != nil || !allowed { - httpx.Write(c, httpx.CodeError, gin.H{}, "权限不足") + Write(c, CodeError, gin.H{}, "权限不足") c.Abort() return } requestContext, err := access.ContextWithDataScope(c.Request.Context(), claims.AuthorityID, claims.ID) if err != nil { - httpx.Write(c, httpx.CodeError, gin.H{}, "数据权限解析失败") + Write(c, CodeError, gin.H{}, "数据权限解析失败") c.Abort() return } diff --git a/app/system/internal/transport/middleware/access_log.go b/app/system/internal/server/middleware/access_log.go similarity index 95% rename from app/system/internal/transport/middleware/access_log.go rename to app/system/internal/server/middleware/access_log.go index e886e66..17338e0 100644 --- a/app/system/internal/transport/middleware/access_log.go +++ b/app/system/internal/server/middleware/access_log.go @@ -11,7 +11,6 @@ import ( "kra/app/system/internal/biz" "kra/app/system/internal/conf" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" ) @@ -41,7 +40,7 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H c.Header("X-Kra-Version", version) requestReadFailed := c.Request.ContentLength > bodyLimit if requestReadFailed { - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": httpx.CodeError, "msg": "请求体超过大小上限"}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": CodeError, "msg": "请求体超过大小上限"}) } else if c.Request.Body != nil && !mediaUpload { limited := http.MaxBytesReader(c.Writer, c.Request.Body, bodyLimit) var err error @@ -50,9 +49,9 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H requestReadFailed = true var tooLarge *http.MaxBytesError if errors.As(err, &tooLarge) { - c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": httpx.CodeError, "msg": "请求体超过大小上限"}) + c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": CodeError, "msg": "请求体超过大小上限"}) } else { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": httpx.CodeError, "msg": "请求体读取失败"}) + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": CodeError, "msg": "请求体读取失败"}) } } else { c.Request.Body = io.NopCloser(bytes.NewReader(requestBody)) diff --git a/app/system/internal/transport/middleware/access_log_test.go b/app/system/internal/server/middleware/access_log_test.go similarity index 100% rename from app/system/internal/transport/middleware/access_log_test.go rename to app/system/internal/server/middleware/access_log_test.go diff --git a/app/system/internal/transport/middleware/access_test.go b/app/system/internal/server/middleware/access_test.go similarity index 100% rename from app/system/internal/transport/middleware/access_test.go rename to app/system/internal/server/middleware/access_test.go diff --git a/app/system/internal/transport/middleware/audit.go b/app/system/internal/server/middleware/audit.go similarity index 93% rename from app/system/internal/transport/middleware/audit.go rename to app/system/internal/server/middleware/audit.go index 2ef9767..aa88b75 100644 --- a/app/system/internal/transport/middleware/audit.go +++ b/app/system/internal/server/middleware/audit.go @@ -13,7 +13,6 @@ import ( "time" "kra/app/system/internal/conf" - "kra/app/system/internal/dto" "kra/app/system/internal/service" "github.com/gin-gonic/gin" @@ -21,7 +20,7 @@ import ( const ctxOperationAuditPersistFailedKey = "operation_audit_persist_failed" -func OperationAudit(runtime *conf.Runtime, service *service.AuditRecorder) gin.HandlerFunc { +func OperationAudit(runtime *conf.Runtime, recorder *service.AuditRecorder) gin.HandlerFunc { return func(c *gin.Context) { path := c.Request.URL.Path if !recordsOperation(c.Request.Method, path) { @@ -81,7 +80,7 @@ func OperationAudit(runtime *conf.Runtime, service *service.AuditRecorder) gin.H responseBody = "[超出记录长度]" } errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String() - if err := service.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes), Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil { + if err := recorder.RecordOperationRequest(c.Request.Context(), &service.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes), Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil { // Preserve the business response, but expose audit persistence failures // to the global access/error logging pipeline. c.Set(ctxOperationAuditPersistFailedKey, true) diff --git a/app/system/internal/transport/middleware/auth.go b/app/system/internal/server/middleware/auth.go similarity index 81% rename from app/system/internal/transport/middleware/auth.go rename to app/system/internal/server/middleware/auth.go index e4bcc3b..269941c 100644 --- a/app/system/internal/transport/middleware/auth.go +++ b/app/system/internal/server/middleware/auth.go @@ -8,7 +8,6 @@ import ( "kra/app/system/internal/biz" "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" "github.com/gin-gonic/gin" "golang.org/x/sync/singleflight" @@ -25,7 +24,7 @@ func Auth(auth *service.AuthService) gin.HandlerFunc { token, _ = c.Cookie("x-token") } if token == "" { - httpx.NoAuth(c, "未登录或非法访问,请登录") + NoAuth(c, "未登录或非法访问,请登录") return } value, err, _ := refreshTokens.Do(token, func() (any, error) { @@ -45,15 +44,15 @@ func Auth(auth *service.AuthService) gin.HandlerFunc { case errors.Is(err, biz.ErrTokenDisabled): message = "您的帐户异地登陆或令牌失效" } - httpx.SetTokenCookie(c, "", -1) - httpx.NoAuth(c, message) + SetTokenCookie(c, "", -1) + NoAuth(c, message) return } authentication := value.(*biz.TokenAuthentication) if authentication.Refreshed != nil { c.Header("new-token", authentication.Refreshed.Value) c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10)) - httpx.SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds())) + SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds())) } c.Set(claimsKey, authentication.Claims) c.Next() @@ -78,6 +77,6 @@ func MustChangePassword() gin.HandlerFunc { c.Next() return } - c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"}) + c.AbortWithStatusJSON(http.StatusConflict, Response{Code: CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"}) } } diff --git a/app/system/internal/transport/middleware/capture.go b/app/system/internal/server/middleware/capture.go similarity index 100% rename from app/system/internal/transport/middleware/capture.go rename to app/system/internal/server/middleware/capture.go diff --git a/app/system/internal/transport/middleware/cors.go b/app/system/internal/server/middleware/cors.go similarity index 100% rename from app/system/internal/transport/middleware/cors.go rename to app/system/internal/server/middleware/cors.go diff --git a/app/system/internal/transport/middleware/cors_test.go b/app/system/internal/server/middleware/cors_test.go similarity index 100% rename from app/system/internal/transport/middleware/cors_test.go rename to app/system/internal/server/middleware/cors_test.go diff --git a/app/system/internal/transport/middleware/error_audit.go b/app/system/internal/server/middleware/error_audit.go similarity index 94% rename from app/system/internal/transport/middleware/error_audit.go rename to app/system/internal/server/middleware/error_audit.go index d3a0996..b299063 100644 --- a/app/system/internal/transport/middleware/error_audit.go +++ b/app/system/internal/server/middleware/error_audit.go @@ -6,8 +6,6 @@ import ( "log/slog" "strings" - "kra/app/system/internal/transport/httpx" - "github.com/gin-gonic/gin" ) @@ -26,7 +24,7 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc { if auditPersistFailed != true && (strings.Contains(c.Request.URL.Path, "/sysError/") || strings.Contains(c.Request.URL.Path, "/logViewer/") || c.Writer.Status() >= 300 && c.Writer.Status() < 500) { return } - var response httpx.Response + var response Response var body []byte if value, ok := c.Get(ctxRespBufferKey); ok { if buffer, valid := value.(*bytes.Buffer); valid { @@ -36,7 +34,7 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc { if json.Unmarshal(body, &response) != nil && privateErrors == "" { return } - if response.Code == httpx.CodeSuccess && privateErrors == "" { + if response.Code == CodeSuccess && privateErrors == "" { return } if privateErrors == "" && expectedClientFailure(response.Msg) { diff --git a/app/system/internal/transport/middleware/error_audit_test.go b/app/system/internal/server/middleware/error_audit_test.go similarity index 100% rename from app/system/internal/transport/middleware/error_audit_test.go rename to app/system/internal/server/middleware/error_audit_test.go diff --git a/app/system/internal/server/middleware/http.go b/app/system/internal/server/middleware/http.go new file mode 100644 index 0000000..49e9589 --- /dev/null +++ b/app/system/internal/server/middleware/http.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "kra/app/system/internal/server/httpx" +) + +const ( + CodeSuccess = httpx.CodeSuccess + CodeError = httpx.CodeError + CodePasswordChangeRequired = httpx.CodePasswordChangeRequired +) + +type Response = httpx.Response + +func Write(c *gin.Context, code int, data any, message string) { httpx.Write(c, code, data, message) } +func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) } +func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) } diff --git a/app/system/internal/transport/middleware/rate_limit.go b/app/system/internal/server/middleware/rate_limit.go similarity index 80% rename from app/system/internal/transport/middleware/rate_limit.go rename to app/system/internal/server/middleware/rate_limit.go index be9908b..7991d1b 100644 --- a/app/system/internal/transport/middleware/rate_limit.go +++ b/app/system/internal/server/middleware/rate_limit.go @@ -1,13 +1,11 @@ package middleware import ( + "kra/app/system/internal/service" "net/http" "strings" "time" - "kra/app/system/internal/service" - "kra/app/system/internal/transport/httpx" - "github.com/gin-gonic/gin" ) @@ -20,7 +18,7 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc { } config, err := settings.CurrentSecurity(c.Request.Context()) if err != nil || config == nil { - c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": httpx.CodeError, "msg": "安全服务暂不可用"}) + c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"}) return } if !config.LimitEnable { @@ -34,11 +32,11 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc { key := "KRA_SecLimit" + c.ClientIP() + c.FullPath() count, cacheErr := settings.IncrementRateLimit(c.Request.Context(), key, time.Duration(window)*time.Second) if cacheErr != nil { - c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": httpx.CodeError, "msg": "安全服务暂不可用"}) + c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"}) return } if int(count) > config.LimitCount { - c.JSON(200, gin.H{"code": httpx.CodeError, "msg": "请求太过频繁,请稍后再试"}) + c.JSON(200, gin.H{"code": CodeError, "msg": "请求太过频繁,请稍后再试"}) c.Abort() return } diff --git a/app/system/internal/transport/middleware/rate_limit_test.go b/app/system/internal/server/middleware/rate_limit_test.go similarity index 100% rename from app/system/internal/transport/middleware/rate_limit_test.go rename to app/system/internal/server/middleware/rate_limit_test.go diff --git a/app/system/internal/transport/middleware/recovery.go b/app/system/internal/server/middleware/recovery.go similarity index 100% rename from app/system/internal/transport/middleware/recovery.go rename to app/system/internal/server/middleware/recovery.go diff --git a/app/system/internal/transport/middleware/request.go b/app/system/internal/server/middleware/request.go similarity index 100% rename from app/system/internal/transport/middleware/request.go rename to app/system/internal/server/middleware/request.go diff --git a/app/system/internal/server/providers_system.go b/app/system/internal/server/providers_system.go index f9e01aa..dd3c7d4 100644 --- a/app/system/internal/server/providers_system.go +++ b/app/system/internal/server/providers_system.go @@ -1,10 +1,8 @@ package server import ( - "kra/app/system/internal/transport/handler" - "github.com/google/wire" ) // ProviderSet is server providers. -var ProviderSet = wire.NewSet(NewGinEngineWithRuntime, NewGinServer, NewRoutes, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewPayment, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, handler.NewSet) +var ProviderSet = wire.NewSet(NewGinEngineWithRuntime, NewGinServer) diff --git a/app/system/internal/server/router/aliases.go b/app/system/internal/server/router/aliases.go new file mode 100644 index 0000000..5d6cdda --- /dev/null +++ b/app/system/internal/server/router/aliases.go @@ -0,0 +1,26 @@ +package router + +import "kra/app/system/internal/server/handler" + +type Set = handler.Set +type Announcement = handler.Announcement +type API = handler.API +type APIToken = handler.APIToken +type Audit = handler.Audit +type Authority = handler.Authority +type Dictionary = handler.Dictionary +type Email = handler.Email +type Export = handler.Export +type Media = handler.Media +type Menu = handler.Menu +type Navigation = handler.Navigation +type Organization = handler.Organization +type Parameter = handler.Parameter +type Payment = handler.Payment +type Permission = handler.Permission +type Public = handler.Public +type Session = handler.Session +type SystemConfig = handler.SystemConfig +type Task = handler.Task +type User = handler.User +type Version = handler.Version diff --git a/app/system/internal/transport/router/announcement.go b/app/system/internal/server/router/announcement.go similarity index 78% rename from app/system/internal/transport/router/announcement.go rename to app/system/internal/server/router/announcement.go index 97e2031..aeb3369 100644 --- a/app/system/internal/transport/router/announcement.go +++ b/app/system/internal/server/router/announcement.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterAnnouncement(private, public *gin.RouterGroup, h *handler.Announcement) { +func RegisterAnnouncement(private, public *gin.RouterGroup, h *Announcement) { privateInfo := private.Group("/info") privateInfo.POST("/createInfo", h.Create) privateInfo.DELETE("/deleteInfo", h.Delete) diff --git a/app/system/internal/transport/router/api.go b/app/system/internal/server/router/api.go similarity index 92% rename from app/system/internal/transport/router/api.go rename to app/system/internal/server/router/api.go index 3e12986..58c3a86 100644 --- a/app/system/internal/transport/router/api.go +++ b/app/system/internal/server/router/api.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *handler.API) { +func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *API) { api := group.Group("/api") api.POST("/getApiList", h.List) api.POST("/getAllApis", h.All) diff --git a/app/system/internal/transport/router/api_token.go b/app/system/internal/server/router/api_token.go similarity index 66% rename from app/system/internal/transport/router/api_token.go rename to app/system/internal/server/router/api_token.go index d52b08e..8e2296c 100644 --- a/app/system/internal/transport/router/api_token.go +++ b/app/system/internal/server/router/api_token.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterAPIToken(group *gin.RouterGroup, handler *handler.APIToken) { +func RegisterAPIToken(group *gin.RouterGroup, handler *APIToken) { router := group.Group("/sysApiToken") router.POST("/createApiToken", handler.Create) router.POST("/getApiTokenList", handler.List) diff --git a/app/system/internal/transport/router/audit.go b/app/system/internal/server/router/audit.go similarity index 91% rename from app/system/internal/transport/router/audit.go rename to app/system/internal/server/router/audit.go index 33d114d..c36a3c0 100644 --- a/app/system/internal/transport/router/audit.go +++ b/app/system/internal/server/router/audit.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterAudit(group, public *gin.RouterGroup, h *handler.Audit) { +func RegisterAudit(group, public *gin.RouterGroup, h *Audit) { operations := group.Group("/sysOperationRecord") operations.GET("/getSysOperationRecordList", h.Operations) operations.GET("/findSysOperationRecord", h.Operation) diff --git a/app/system/internal/transport/router/authority.go b/app/system/internal/server/router/authority.go similarity index 80% rename from app/system/internal/transport/router/authority.go rename to app/system/internal/server/router/authority.go index a59aadf..abbfd66 100644 --- a/app/system/internal/transport/router/authority.go +++ b/app/system/internal/server/router/authority.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterAuthority(group *gin.RouterGroup, h *handler.Authority) { +func RegisterAuthority(group *gin.RouterGroup, h *Authority) { router := group.Group("/authority") router.POST("/getAuthorityList", h.List) router.POST("/createAuthority", h.Create) diff --git a/app/system/internal/transport/router/dictionary.go b/app/system/internal/server/router/dictionary.go similarity index 90% rename from app/system/internal/transport/router/dictionary.go rename to app/system/internal/server/router/dictionary.go index 9f6a2a3..0f6fe84 100644 --- a/app/system/internal/transport/router/dictionary.go +++ b/app/system/internal/server/router/dictionary.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterDictionary(group *gin.RouterGroup, h *handler.Dictionary) { +func RegisterDictionary(group *gin.RouterGroup, h *Dictionary) { dictionaries := group.Group("/sysDictionary") dictionaries.POST("/createSysDictionary", h.Create) dictionaries.PUT("/updateSysDictionary", h.Update) diff --git a/app/system/internal/transport/router/email.go b/app/system/internal/server/router/email.go similarity index 59% rename from app/system/internal/transport/router/email.go rename to app/system/internal/server/router/email.go index b59d12f..24f916b 100644 --- a/app/system/internal/transport/router/email.go +++ b/app/system/internal/server/router/email.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterEmail(group *gin.RouterGroup, h *handler.Email) { +func RegisterEmail(group *gin.RouterGroup, h *Email) { email := group.Group("/email") email.POST("/emailTest", h.Test) email.POST("/sendEmail", h.Send) diff --git a/app/system/internal/transport/router/export.go b/app/system/internal/server/router/export.go similarity index 86% rename from app/system/internal/transport/router/export.go rename to app/system/internal/server/router/export.go index b278933..4930e2c 100644 --- a/app/system/internal/transport/router/export.go +++ b/app/system/internal/server/router/export.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterExport(group, public *gin.RouterGroup, h *handler.Export) { +func RegisterExport(group, public *gin.RouterGroup, h *Export) { router := group.Group("/sysExportTemplate") router.POST("/createSysExportTemplate", h.Create) router.PUT("/updateSysExportTemplate", h.Update) diff --git a/app/system/internal/transport/router/media.go b/app/system/internal/server/router/media.go similarity index 88% rename from app/system/internal/transport/router/media.go rename to app/system/internal/server/router/media.go index f5dceea..e2f9692 100644 --- a/app/system/internal/transport/router/media.go +++ b/app/system/internal/server/router/media.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterMedia(group *gin.RouterGroup, h *handler.Media) { +func RegisterMedia(group *gin.RouterGroup, h *Media) { files := group.Group("/fileUploadAndDownload") files.POST("/upload", h.Upload) files.POST("/getFileList", h.List) diff --git a/app/system/internal/transport/router/menu.go b/app/system/internal/server/router/menu.go similarity index 82% rename from app/system/internal/transport/router/menu.go rename to app/system/internal/server/router/menu.go index 3e6fb4c..087695e 100644 --- a/app/system/internal/transport/router/menu.go +++ b/app/system/internal/server/router/menu.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterMenu(group *gin.RouterGroup, h *handler.Menu) { +func RegisterMenu(group *gin.RouterGroup, h *Menu) { router := group.Group("/menu") router.POST("/getMenuList", h.List) router.POST("/getBaseMenuTree", h.Tree) diff --git a/app/system/internal/transport/router/organization.go b/app/system/internal/server/router/organization.go similarity index 89% rename from app/system/internal/transport/router/organization.go rename to app/system/internal/server/router/organization.go index 1d847fc..bec04aa 100644 --- a/app/system/internal/transport/router/organization.go +++ b/app/system/internal/server/router/organization.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterOrganization(group *gin.RouterGroup, h *handler.Organization) { +func RegisterOrganization(group *gin.RouterGroup, h *Organization) { departments := group.Group("/department") departments.POST("/getDepartmentList", h.ListDepartments) departments.POST("/createDepartment", h.CreateDepartment) diff --git a/app/system/internal/transport/router/parameter.go b/app/system/internal/server/router/parameter.go similarity index 77% rename from app/system/internal/transport/router/parameter.go rename to app/system/internal/server/router/parameter.go index d3dc1c7..c06a9d9 100644 --- a/app/system/internal/transport/router/parameter.go +++ b/app/system/internal/server/router/parameter.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterParameter(group *gin.RouterGroup, handler *handler.Parameter) { +func RegisterParameter(group *gin.RouterGroup, handler *Parameter) { router := group.Group("/sysParams") router.POST("/createSysParams", handler.Create) router.PUT("/updateSysParams", handler.Update) diff --git a/app/system/internal/transport/router/payment.go b/app/system/internal/server/router/payment.go similarity index 56% rename from app/system/internal/transport/router/payment.go rename to app/system/internal/server/router/payment.go index 2ff60a1..e186451 100644 --- a/app/system/internal/transport/router/payment.go +++ b/app/system/internal/server/router/payment.go @@ -2,17 +2,20 @@ package router import ( "github.com/gin-gonic/gin" - "kra/app/system/internal/transport/handler" ) -func RegisterPayment(group, public *gin.RouterGroup, h *handler.Payment) { +func RegisterPayment(group, public *gin.RouterGroup, h *Payment) { payment := group.Group("/payment") payment.GET("/configs", h.Configs) payment.POST("/config", h.SaveConfig) payment.GET("/orders", h.Orders) payment.POST("/order", h.Order) + payment.GET("/orders/:provider/:tradeNo", h.OrderByPath) payment.POST("/create", h.Create) payment.POST("/query", h.Query) payment.POST("/refund", h.Refund) + payment.POST("/orders/:provider/:tradeNo/refund", h.RefundByPath) + payment.POST("/orders/:provider/:tradeNo/fulfill", h.FulfillByPath) + payment.POST("/fulfill", h.Fulfill) public.POST("/payment/callback/:provider", h.Callback) } diff --git a/app/system/internal/transport/router/permission.go b/app/system/internal/server/router/permission.go similarity index 68% rename from app/system/internal/transport/router/permission.go rename to app/system/internal/server/router/permission.go index 05ebe39..2de4bfc 100644 --- a/app/system/internal/transport/router/permission.go +++ b/app/system/internal/server/router/permission.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterPermission(group *gin.RouterGroup, h *handler.Permission) { +func RegisterPermission(group *gin.RouterGroup, h *Permission) { buttons := group.Group("/authorityBtn") buttons.POST("/getAuthorityBtn", h.Buttons) buttons.POST("/setAuthorityBtn", h.SetButtons) diff --git a/app/system/internal/server/router/provider.go b/app/system/internal/server/router/provider.go new file mode 100644 index 0000000..3625eaa --- /dev/null +++ b/app/system/internal/server/router/provider.go @@ -0,0 +1,6 @@ +package router + +import "github.com/google/wire" + +// ProviderSet wires the system route registrar. +var ProviderSet = wire.NewSet(NewRoutes) diff --git a/app/system/internal/transport/router/public.go b/app/system/internal/server/router/public.go similarity index 83% rename from app/system/internal/transport/router/public.go rename to app/system/internal/server/router/public.go index 4df566f..de6d36d 100644 --- a/app/system/internal/transport/router/public.go +++ b/app/system/internal/server/router/public.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, handler *handler.Public) { +func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, handler *Public) { base := group.Group("/base") base.POST("/captcha", handler.Captcha) base.POST("/login", handler.Login) diff --git a/app/system/internal/server/router/routes.go b/app/system/internal/server/router/routes.go new file mode 100644 index 0000000..41b5063 --- /dev/null +++ b/app/system/internal/server/router/routes.go @@ -0,0 +1,42 @@ +package router + +import ( + platformmodule "kra/pkg/module" + + "github.com/gin-gonic/gin" +) + +type Routes struct { + handlers *Set +} + +func NewRoutes(handlers *Set) *Routes { return &Routes{handlers: handlers} } + +var _ platformmodule.RouteRegistrar = (*Routes)(nil) + +func (r *Routes) RegisterRoutes(public, private *gin.RouterGroup, engine *gin.Engine) { + if r == nil || r.handlers == nil { + return + } + RegisterPublic(public, engine, r.handlers.Public) + RegisterUser(private, r.handlers.User) + RegisterNavigation(private, r.handlers.Navigation) + RegisterSession(private, r.handlers.Session) + RegisterAuthority(private, r.handlers.Authority) + RegisterMenu(private, r.handlers.Menu) + RegisterAPI(private, public, engine, r.handlers.API) + RegisterPermission(private, r.handlers.Permission) + RegisterOrganization(private, r.handlers.Organization) + RegisterDictionary(private, r.handlers.Dictionary) + RegisterParameter(private, r.handlers.Parameter) + RegisterAPIToken(private, r.handlers.APIToken) + RegisterSystemConfig(private, r.handlers.SystemConfig) + RegisterVersion(private, r.handlers.Version) + RegisterExport(private, public, r.handlers.Export) + RegisterAudit(private, public, r.handlers.Audit) + RegisterTask(private, r.handlers.Task) + RegisterMedia(private, r.handlers.Media) + RegisterAnnouncement(private, public, r.handlers.Announcement) + RegisterEmail(private, r.handlers.Email) + RegisterPayment(private, public, r.handlers.Payment) +} diff --git a/app/system/internal/transport/router/system_config.go b/app/system/internal/server/router/system_config.go similarity index 77% rename from app/system/internal/transport/router/system_config.go rename to app/system/internal/server/router/system_config.go index 721e562..2237426 100644 --- a/app/system/internal/transport/router/system_config.go +++ b/app/system/internal/server/router/system_config.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterSystemConfig(group *gin.RouterGroup, handler *handler.SystemConfig) { +func RegisterSystemConfig(group *gin.RouterGroup, handler *SystemConfig) { security := group.Group("/securityConfig") security.GET("/getSecurityConfig", handler.GetSecurity) security.POST("/setSecurityConfig", handler.SetSecurity) diff --git a/app/system/internal/transport/router/task.go b/app/system/internal/server/router/task.go similarity index 82% rename from app/system/internal/transport/router/task.go rename to app/system/internal/server/router/task.go index 490d78a..0427a49 100644 --- a/app/system/internal/transport/router/task.go +++ b/app/system/internal/server/router/task.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterTask(group *gin.RouterGroup, h *handler.Task) { +func RegisterTask(group *gin.RouterGroup, h *Task) { router := group.Group("/timedTask") router.POST("/createTimedTask", h.Create) router.PUT("/updateTimedTask", h.Update) diff --git a/app/system/internal/transport/router/user.go b/app/system/internal/server/router/user.go similarity index 73% rename from app/system/internal/transport/router/user.go rename to app/system/internal/server/router/user.go index 6ca5f50..bd063e3 100644 --- a/app/system/internal/transport/router/user.go +++ b/app/system/internal/server/router/user.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterUser(group *gin.RouterGroup, handler *handler.User) { +func RegisterUser(group *gin.RouterGroup, handler *User) { user := group.Group("/user") user.POST("/getUserList", handler.List) user.POST("/admin_register", handler.Create) @@ -21,10 +19,10 @@ func RegisterUser(group *gin.RouterGroup, handler *handler.User) { user.GET("/getUserInfo", handler.Get) } -func RegisterNavigation(group *gin.RouterGroup, handler *handler.Navigation) { +func RegisterNavigation(group *gin.RouterGroup, handler *Navigation) { group.Group("/menu").POST("/getMenu", handler.Menu) } -func RegisterSession(group *gin.RouterGroup, handler *handler.Session) { +func RegisterSession(group *gin.RouterGroup, handler *Session) { group.Group("/jwt").POST("/jsonInBlacklist", handler.Logout) } diff --git a/app/system/internal/transport/router/version.go b/app/system/internal/server/router/version.go similarity index 78% rename from app/system/internal/transport/router/version.go rename to app/system/internal/server/router/version.go index cb54795..0e83228 100644 --- a/app/system/internal/transport/router/version.go +++ b/app/system/internal/server/router/version.go @@ -1,12 +1,10 @@ package router import ( - "kra/app/system/internal/transport/handler" - "github.com/gin-gonic/gin" ) -func RegisterVersion(group *gin.RouterGroup, h *handler.Version) { +func RegisterVersion(group *gin.RouterGroup, h *Version) { router := group.Group("/sysVersion") router.DELETE("/deleteSysVersion", h.Delete) router.DELETE("/deleteSysVersionByIds", h.DeleteMany) diff --git a/app/system/internal/server/routes.go b/app/system/internal/server/routes.go deleted file mode 100644 index 7eff677..0000000 --- a/app/system/internal/server/routes.go +++ /dev/null @@ -1,44 +0,0 @@ -package server - -import ( - "kra/app/system/internal/transport/handler" - serverrouter "kra/app/system/internal/transport/router" - platformmodule "kra/pkg/module" - - "github.com/gin-gonic/gin" -) - -type Routes struct { - handlers *handler.Set -} - -func NewRoutes(handlers *handler.Set) *Routes { return &Routes{handlers: handlers} } - -var _ platformmodule.RouteRegistrar = (*Routes)(nil) - -func (r *Routes) RegisterRoutes(public, private *gin.RouterGroup, engine *gin.Engine) { - if r == nil || r.handlers == nil { - return - } - serverrouter.RegisterPublic(public, engine, r.handlers.Public) - serverrouter.RegisterUser(private, r.handlers.User) - serverrouter.RegisterNavigation(private, r.handlers.Navigation) - serverrouter.RegisterSession(private, r.handlers.Session) - serverrouter.RegisterAuthority(private, r.handlers.Authority) - serverrouter.RegisterMenu(private, r.handlers.Menu) - serverrouter.RegisterAPI(private, public, engine, r.handlers.API) - serverrouter.RegisterPermission(private, r.handlers.Permission) - serverrouter.RegisterOrganization(private, r.handlers.Organization) - serverrouter.RegisterDictionary(private, r.handlers.Dictionary) - serverrouter.RegisterParameter(private, r.handlers.Parameter) - serverrouter.RegisterAPIToken(private, r.handlers.APIToken) - serverrouter.RegisterSystemConfig(private, r.handlers.SystemConfig) - serverrouter.RegisterVersion(private, r.handlers.Version) - serverrouter.RegisterExport(private, public, r.handlers.Export) - serverrouter.RegisterAudit(private, public, r.handlers.Audit) - serverrouter.RegisterTask(private, r.handlers.Task) - serverrouter.RegisterMedia(private, r.handlers.Media) - serverrouter.RegisterAnnouncement(private, public, r.handlers.Announcement) - serverrouter.RegisterEmail(private, r.handlers.Email) - serverrouter.RegisterPayment(private, public, r.handlers.Payment) -} diff --git a/app/system/internal/server/swagger.go b/app/system/internal/server/swagger.go index d4e48d0..1accd2e 100644 --- a/app/system/internal/server/swagger.go +++ b/app/system/internal/server/swagger.go @@ -8,7 +8,7 @@ import ( "strings" "sync" - "kra/app/system/internal/routeinfo" + "kra/app/system/internal/service" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" @@ -86,7 +86,7 @@ func buildSwaggerDocument(routes []gin.RouteInfo, prefix, version string) string apiPath = "/" } documentPath := swaggerPathParameter.ReplaceAllString(apiPath, `{$1}`) - group, description := routeinfo.Metadata(route.Method, apiPath) + group, description := service.RouteMetadata(route.Method, apiPath) if description == "" { description = route.Method + " " + apiPath } diff --git a/app/system/internal/service/announcement.go b/app/system/internal/service/announcement.go index 2231a87..73411cc 100644 --- a/app/system/internal/service/announcement.go +++ b/app/system/internal/service/announcement.go @@ -6,7 +6,6 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type AnnouncementInput struct { @@ -23,8 +22,8 @@ func NewAnnouncementService(uc *biz.AnnouncementUsecase) *AnnouncementService { return &AnnouncementService{uc: uc} } -func announcementDTO(item *biz.Announcement) *dto.AnnouncementResponse { - return &dto.AnnouncementResponse{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, DeletedAt: nil, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} +func announcementDTO(item *biz.Announcement) *AnnouncementResponse { + return &AnnouncementResponse{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, DeletedAt: nil, Title: item.Title, Content: item.Content, UserID: item.UserID, Attachments: json.RawMessage(item.Attachments)} } func announcementDO(in AnnouncementInput) *biz.Announcement { @@ -47,7 +46,7 @@ func (s *AnnouncementService) DeleteByIDs(ctx context.Context, ids []string) err return s.uc.DeleteByIDs(ctx, ids) } -func (s *AnnouncementService) Find(ctx context.Context, id string) (*dto.AnnouncementResponse, error) { +func (s *AnnouncementService) Find(ctx context.Context, id string) (*AnnouncementResponse, error) { item, err := s.uc.Find(ctx, id) if err != nil { return nil, err @@ -55,26 +54,26 @@ func (s *AnnouncementService) Find(ctx context.Context, id string) (*dto.Announc return announcementDTO(item), nil } -func (s *AnnouncementService) List(ctx context.Context, page, pageSize int, start, end *time.Time) ([]*dto.AnnouncementResponse, int64, error) { +func (s *AnnouncementService) List(ctx context.Context, page, pageSize int, start, end *time.Time) ([]*AnnouncementResponse, int64, error) { items, total, err := s.uc.List(ctx, biz.AnnouncementFilter{Page: page, PageSize: pageSize, StartCreatedAt: start, EndCreatedAt: end}) if err != nil { return nil, 0, err } - result := make([]*dto.AnnouncementResponse, 0, len(items)) + result := make([]*AnnouncementResponse, 0, len(items)) for _, item := range items { result = append(result, announcementDTO(item)) } return result, total, nil } -func (s *AnnouncementService) UserOptions(ctx context.Context) ([]*dto.SelectOptionResponse, error) { +func (s *AnnouncementService) UserOptions(ctx context.Context) ([]*SelectOptionResponse, error) { items, err := s.uc.UserOptions(ctx) if err != nil { return nil, err } - result := make([]*dto.SelectOptionResponse, 0, len(items)) + result := make([]*SelectOptionResponse, 0, len(items)) for _, item := range items { - result = append(result, &dto.SelectOptionResponse{Label: item.Label, Value: item.Value}) + result = append(result, &SelectOptionResponse{Label: item.Label, Value: item.Value}) } return result, nil } diff --git a/app/system/internal/service/api.go b/app/system/internal/service/api.go index e354434..eee8c5e 100644 --- a/app/system/internal/service/api.go +++ b/app/system/internal/service/api.go @@ -5,7 +5,6 @@ import ( "strings" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type APIService struct { @@ -24,23 +23,23 @@ func (s *APIService) NormalizeRoutePath(path string) string { return NormalizeRoutePath(path, s.settings.RouterPrefix()) } -func (s *APIService) apiDomain(value *dto.APIRequest) *biz.API { +func (s *APIService) apiDomain(value *APIRequest) *biz.API { return &biz.API{ID: value.ID, Path: s.NormalizeRoutePath(value.Path), Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} } -func apiResponse(value *biz.API) *dto.APIResponse { - return &dto.APIResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} +func apiResponse(value *biz.API) *APIResponse { + return &APIResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Path: value.Path, Description: value.Description, APIGroup: value.APIGroup, Method: value.Method} } -func apiResponses(items []*biz.API) []*dto.APIResponse { - out := make([]*dto.APIResponse, 0, len(items)) +func apiResponses(items []*biz.API) []*APIResponse { + out := make([]*APIResponse, 0, len(items)) for _, item := range items { out = append(out, apiResponse(item)) } return out } -func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*dto.APIResponse, int64, error) { +func (s *APIService) ListAPI(ctx context.Context, req *APIListRequest) ([]*APIResponse, int64, error) { items, total, err := s.uc.ListAPIs(ctx, req.Page, req.PageSize, &biz.API{Path: req.Path, Description: req.Description, APIGroup: req.APIGroup, Method: req.Method, OrderKey: req.OrderKey, Desc: req.Desc}) if err != nil { return nil, 0, err @@ -48,7 +47,7 @@ func (s *APIService) ListAPI(ctx context.Context, req *dto.APIListRequest) ([]*d return apiResponses(items), total, nil } -func (s *APIService) AllAPI(ctx context.Context, strict bool) ([]*dto.APIResponse, error) { +func (s *APIService) AllAPI(ctx context.Context, strict bool) ([]*APIResponse, error) { items, _, err := s.uc.ListAPIs(ctx, 0, 0, &biz.API{StrictAll: strict}) if err != nil { return nil, err @@ -80,24 +79,24 @@ func (s *APIService) Groups(ctx context.Context) ([]string, map[string]string, e return groups, groupAPIMap, nil } -func (s *APIService) CreateAPIRequest(ctx context.Context, req *dto.APIRequest) (*dto.APIResponse, error) { +func (s *APIService) CreateAPIRequest(ctx context.Context, req *APIRequest) (*APIResponse, error) { value := s.apiDomain(req) if err := s.uc.CreateAPI(ctx, value); err != nil { return nil, err } return apiResponse(value), nil } -func (s *APIService) UpdateAPIRequest(ctx context.Context, req *dto.APIRequest) error { +func (s *APIService) UpdateAPIRequest(ctx context.Context, req *APIRequest) error { return s.uc.UpdateAPI(ctx, s.apiDomain(req)) } -func (s *APIService) FindAPIResponse(ctx context.Context, id uint) (*dto.APIResponse, error) { +func (s *APIService) FindAPIResponse(ctx context.Context, id uint) (*APIResponse, error) { value, err := s.uc.FindAPI(ctx, id) if err != nil { return nil, err } return apiResponse(value), nil } -func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPISyncRequest) error { +func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *ApplyAPISyncRequest) error { added := make([]*biz.API, 0, len(req.NewAPIs)) deleted := make([]*biz.API, 0, len(req.DeleteAPIs)) for i := range req.NewAPIs { @@ -108,28 +107,28 @@ func (s *APIService) ApplyAPISyncRequest(ctx context.Context, req *dto.ApplyAPIS } return s.uc.ApplyAPISync(ctx, added, deleted) } -func (s *APIService) SetPolicyPathsRequest(ctx context.Context, req *dto.SetPolicyPathsRequest) error { +func (s *APIService) SetPolicyPathsRequest(ctx context.Context, req *SetPolicyPathsRequest) error { paths := make([]*biz.API, 0, len(req.Infos)) for _, value := range req.Infos { paths = append(paths, &biz.API{Path: value.Path, Method: value.Method}) } return s.uc.SetPolicyPaths(ctx, req.AuthorityID, paths) } -func (s *APIService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]dto.PolicyPathRequest, error) { +func (s *APIService) PolicyPathResponses(ctx context.Context, authorityID uint) ([]PolicyPathRequest, error) { items, err := s.uc.PolicyPaths(ctx, authorityID) if err != nil { return nil, err } - var out []dto.PolicyPathRequest + var out []PolicyPathRequest if len(items) > 0 { - out = make([]dto.PolicyPathRequest, 0, len(items)) + out = make([]PolicyPathRequest, 0, len(items)) } for _, item := range items { - out = append(out, dto.PolicyPathRequest{Path: item.Path, Method: item.Method}) + out = append(out, PolicyPathRequest{Path: item.Path, Method: item.Method}) } return out, nil } -func (s *APIService) SyncAPIResponses(ctx context.Context, routes []dto.APIRequest) (*dto.APISyncResponse, error) { +func (s *APIService) SyncAPIResponses(ctx context.Context, routes []APIRequest) (*APISyncResponse, error) { items := make([]*biz.API, 0, len(routes)) for i := range routes { routes[i].Path = s.NormalizeRoutePath(routes[i].Path) @@ -156,12 +155,12 @@ func (s *APIService) SetAPIRoles(ctx context.Context, path, method string, ids [ func (s *APIService) FreshCasbin(ctx context.Context) error { return s.uc.FreshCasbin(ctx) } -func (s *APIService) SyncAPIs(ctx context.Context, routes []*biz.API) (*dto.APISyncResponse, error) { +func (s *APIService) SyncAPIs(ctx context.Context, routes []*biz.API) (*APISyncResponse, error) { diff, err := s.uc.SyncAPIs(ctx, routes) if err != nil { return nil, err } - return &dto.APISyncResponse{NewAPIs: apiResponses(diff.Added), DeleteAPIs: apiResponses(diff.Deleted), IgnoreAPIs: apiResponses(diff.Ignored)}, nil + return &APISyncResponse{NewAPIs: apiResponses(diff.Added), DeleteAPIs: apiResponses(diff.Deleted), IgnoreAPIs: apiResponses(diff.Ignored)}, nil } func (s *APIService) SetAPIIgnored(ctx context.Context, path, method string, ignored bool) error { return s.uc.SetAPIIgnored(ctx, s.NormalizeRoutePath(path), method, ignored) diff --git a/app/system/internal/service/api_token.go b/app/system/internal/service/api_token.go index a18ad77..75a2e1a 100644 --- a/app/system/internal/service/api_token.go +++ b/app/system/internal/service/api_token.go @@ -5,7 +5,6 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type TokenService struct { @@ -17,12 +16,12 @@ func NewTokenService(uc *biz.TokenUsecase, issuer biz.TokenIssuer) *TokenService return &TokenService{uc: uc, issuer: issuer} } -func tokenDTO(v *biz.APIToken) *dto.APITokenResponse { - var user *dto.UserResponse +func tokenDTO(v *biz.APIToken) *APITokenResponse { + var user *UserResponse if v.User != nil { user = convertUser(v.User) } - return &dto.APITokenResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark, User: user} + return &APITokenResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, UserID: v.UserID, AuthorityID: v.AuthorityID, Token: v.Token, Status: v.Status, ExpiresAt: v.ExpiresAt, Remark: v.Remark, User: user} } func (s *TokenService) CreateAPIToken(ctx context.Context, userID, authorityID uint, days int, remark string) (string, error) { @@ -40,12 +39,12 @@ func (s *TokenService) CreateAPIToken(ctx context.Context, userID, authorityID u } return issued.Value, nil } -func (s *TokenService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*dto.APITokenResponse, int64, error) { +func (s *TokenService) APITokens(ctx context.Context, page, size int, userID uint, status *bool) ([]*APITokenResponse, int64, error) { items, total, err := s.uc.ListAPITokens(ctx, page, size, userID, status) if err != nil { return nil, 0, err } - out := make([]*dto.APITokenResponse, 0, len(items)) + out := make([]*APITokenResponse, 0, len(items)) for _, v := range items { out = append(out, tokenDTO(v)) } diff --git a/app/system/internal/service/audit.go b/app/system/internal/service/audit.go index 33f43d4..c362c3e 100644 --- a/app/system/internal/service/audit.go +++ b/app/system/internal/service/audit.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type AuditService struct{ uc *biz.AuditUsecase } @@ -18,31 +17,31 @@ func NewAuditRecorder(uc *biz.AuditRecorderUsecase) *AuditRecorder { return &Aud func (s *AuditRecorder) RecordOperation(ctx context.Context, v *biz.OperationRecord) error { return s.uc.RecordOperation(ctx, v) } -func (s *AuditRecorder) RecordOperationRequest(ctx context.Context, value *dto.OperationRecordRequest) error { +func (s *AuditRecorder) RecordOperationRequest(ctx context.Context, value *OperationRecordRequest) error { return s.RecordOperation(ctx, &biz.OperationRecord{IP: value.IP, Method: value.Method, Path: value.Path, Status: value.Status, LatencyMS: value.LatencyMS, Agent: value.Agent, ErrorMessage: value.ErrorMessage, Body: value.Body, Response: value.Response, UserID: value.UserID, RequestID: value.RequestID, TraceID: value.TraceID, DeviceID: value.DeviceID}) } -func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]*dto.OperationRecordResponse, int64, error) { +func (s *AuditService) OperationsFilter(ctx context.Context, page, size int, path, method string, status int) ([]*OperationRecordResponse, int64, error) { return s.Operations(ctx, page, size, &biz.OperationRecord{Path: path, Method: method, Status: status}) } -func opDTO(v *biz.OperationRecord) *dto.OperationRecordResponse { +func opDTO(v *biz.OperationRecord) *OperationRecordResponse { var user any = map[string]any{} if v.User != nil { user = convertUser(v.User) } - return &dto.OperationRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID, User: user} + return &OperationRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, IP: v.IP, Method: v.Method, Path: v.Path, Status: v.Status, LatencyMS: v.LatencyMS, Agent: v.Agent, ErrorMessage: v.ErrorMessage, Body: v.Body, Response: v.Response, UserID: v.UserID, RequestID: v.RequestID, TraceID: v.TraceID, DeviceID: v.DeviceID, User: user} } -func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*dto.OperationRecordResponse, int64, error) { +func (s *AuditService) Operations(ctx context.Context, page, size int, q *biz.OperationRecord) ([]*OperationRecordResponse, int64, error) { items, total, err := s.uc.ListOperations(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.OperationRecordResponse, 0, len(items)) + out := make([]*OperationRecordResponse, 0, len(items)) for _, v := range items { out = append(out, opDTO(v)) } return out, total, nil } -func (s *AuditService) Operation(ctx context.Context, id uint) (*dto.OperationRecordResponse, error) { +func (s *AuditService) Operation(ctx context.Context, id uint) (*OperationRecordResponse, error) { v, err := s.uc.FindOperation(ctx, id) if err != nil { return nil, err @@ -56,31 +55,31 @@ func (s *AuditService) DeleteOperations(ctx context.Context, ids []int) error { func (s *AuditRecorder) RecordLogin(ctx context.Context, v *biz.LoginLog) error { return s.uc.RecordLogin(ctx, v) } -func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *dto.LoginLogRequest) error { +func (s *AuditRecorder) RecordLoginRequest(ctx context.Context, value *LoginLogRequest) error { return s.RecordLogin(ctx, &biz.LoginLog{Username: value.Username, IP: value.IP, Status: value.Status, ErrorMessage: value.ErrorMessage, Agent: value.Agent, UserID: value.UserID}) } -func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]*dto.LoginLogResponse, int64, error) { +func (s *AuditService) LoginsFilter(ctx context.Context, page, size int, username string, status bool) ([]*LoginLogResponse, int64, error) { return s.Logins(ctx, page, size, &biz.LoginLog{Username: username, Status: status, FilterByStatus: status}) } -func loginDTO(v *biz.LoginLog) *dto.LoginLogResponse { +func loginDTO(v *biz.LoginLog) *LoginLogResponse { var user any = map[string]any{} if v.User != nil { user = convertUser(v.User) } - return &dto.LoginLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID, User: user} + return &LoginLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Username: v.Username, IP: v.IP, Status: v.Status, ErrorMessage: v.ErrorMessage, Agent: v.Agent, UserID: v.UserID, User: user} } -func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*dto.LoginLogResponse, int64, error) { +func (s *AuditService) Logins(ctx context.Context, page, size int, q *biz.LoginLog) ([]*LoginLogResponse, int64, error) { items, total, err := s.uc.ListLogins(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.LoginLogResponse, 0, len(items)) + out := make([]*LoginLogResponse, 0, len(items)) for _, v := range items { out = append(out, loginDTO(v)) } return out, total, nil } -func (s *AuditService) Login(ctx context.Context, id uint) (*dto.LoginLogResponse, error) { +func (s *AuditService) Login(ctx context.Context, id uint) (*LoginLogResponse, error) { v, err := s.uc.FindLogin(ctx, id) if err != nil { return nil, err @@ -94,21 +93,21 @@ func (s *AuditService) DeleteLogins(ctx context.Context, ids []int) error { func (s *AuditRecorder) RecordDataAccess(ctx context.Context, v *biz.DataAccessLog) error { return s.uc.RecordDataAccess(ctx, v) } -func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *dto.DataAccessRecordRequest) error { +func (s *AuditRecorder) RecordDataAccessRequest(ctx context.Context, value *DataAccessRecordRequest) error { return s.RecordDataAccess(ctx, &biz.DataAccessLog{EventType: value.EventType, Operation: value.Operation, UserID: value.UserID, AuthorityID: value.AuthorityID, RequestID: value.RequestID, Method: value.Method, Path: value.Path, Detail: value.Detail}) } -func (s *AuditService) DataAccessRequest(ctx context.Context, req *dto.DataAccessListRequest) ([]*dto.DataAccessLogResponse, int64, error) { +func (s *AuditService) DataAccessRequest(ctx context.Context, req *DataAccessListRequest) ([]*DataAccessLogResponse, int64, error) { return s.DataAccess(ctx, req.Page, req.PageSize, &biz.DataAccessLog{EventType: req.EventType, TargetTable: req.TargetTable}) } -func dataAccessDTO(v *biz.DataAccessLog) *dto.DataAccessLogResponse { - return &dto.DataAccessLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail} +func dataAccessDTO(v *biz.DataAccessLog) *DataAccessLogResponse { + return &DataAccessLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, EventType: v.EventType, TargetTable: v.TargetTable, Operation: v.Operation, UserID: v.UserID, AuthorityID: v.AuthorityID, Scope: v.Scope, RequestID: v.RequestID, Method: v.Method, Path: v.Path, Detail: v.Detail} } -func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*dto.DataAccessLogResponse, int64, error) { +func (s *AuditService) DataAccess(ctx context.Context, page, size int, q *biz.DataAccessLog) ([]*DataAccessLogResponse, int64, error) { items, total, err := s.uc.ListDataAccess(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.DataAccessLogResponse, 0, len(items)) + out := make([]*DataAccessLogResponse, 0, len(items)) for _, v := range items { out = append(out, dataAccessDTO(v)) } diff --git a/app/system/internal/service/audit_error.go b/app/system/internal/service/audit_error.go index e470983..330ebee 100644 --- a/app/system/internal/service/audit_error.go +++ b/app/system/internal/service/audit_error.go @@ -5,16 +5,15 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) -func errorDTO(v *biz.ErrorRecord) *dto.ErrorRecordResponse { - return &dto.ErrorRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status} +func errorDTO(v *biz.ErrorRecord) *ErrorRecordResponse { + return &ErrorRecordResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Form: v.Form, Info: v.Info, Level: v.Level, RequestID: v.RequestID, TraceID: v.TraceID, Solution: v.Solution, Status: v.Status} } func (s *AuditRecorder) CreateError(ctx context.Context, v *biz.ErrorRecord) error { return s.uc.CreateError(ctx, v) } -func recordedErrorDomain(value *dto.ErrorRecordRequest) *biz.ErrorRecord { +func recordedErrorDomain(value *ErrorRecordRequest) *biz.ErrorRecord { form, info := value.Form, value.Info result := &biz.ErrorRecord{Form: &form, Info: &info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Status: value.Status} if value.Solution != "" { @@ -23,19 +22,19 @@ func recordedErrorDomain(value *dto.ErrorRecordRequest) *biz.ErrorRecord { } return result } -func mutatedErrorDomain(value *dto.ErrorRecordMutationRequest) *biz.ErrorRecord { +func mutatedErrorDomain(value *ErrorRecordMutationRequest) *biz.ErrorRecord { return &biz.ErrorRecord{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Form: value.Form, Info: value.Info, Level: value.Level, RequestID: value.RequestID, TraceID: value.TraceID, Solution: value.Solution, Status: value.Status} } -func (s *AuditRecorder) CreateErrorRequest(ctx context.Context, req *dto.ErrorRecordRequest) error { +func (s *AuditRecorder) CreateErrorRequest(ctx context.Context, req *ErrorRecordRequest) error { return s.CreateError(ctx, recordedErrorDomain(req)) } -func (s *AuditRecorder) CreateErrorMutationRequest(ctx context.Context, req *dto.ErrorRecordMutationRequest) error { +func (s *AuditRecorder) CreateErrorMutationRequest(ctx context.Context, req *ErrorRecordMutationRequest) error { return s.CreateError(ctx, mutatedErrorDomain(req)) } -func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *dto.ErrorRecordMutationRequest) error { +func (s *AuditService) UpdateErrorRequest(ctx context.Context, req *ErrorRecordMutationRequest) error { return s.UpdateError(ctx, mutatedErrorDomain(req)) } -func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, info string, createdAtRange []time.Time) ([]*dto.ErrorRecordResponse, int64, error) { +func (s *AuditService) ErrorsFilter(ctx context.Context, page, size int, form, info string, createdAtRange []time.Time) ([]*ErrorRecordResponse, int64, error) { query := &biz.ErrorRecord{CreatedAtRange: createdAtRange} if form != "" { query.Form = &form @@ -51,19 +50,19 @@ func (s *AuditService) UpdateError(ctx context.Context, v *biz.ErrorRecord) erro func (s *AuditService) DeleteErrors(ctx context.Context, ids []uint) error { return s.uc.DeleteErrors(ctx, ids) } -func (s *AuditService) Error(ctx context.Context, id uint) (*dto.ErrorRecordResponse, error) { +func (s *AuditService) Error(ctx context.Context, id uint) (*ErrorRecordResponse, error) { v, err := s.uc.FindError(ctx, id) if err != nil { return nil, err } return errorDTO(v), nil } -func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*dto.ErrorRecordResponse, int64, error) { +func (s *AuditService) Errors(ctx context.Context, page, size int, q *biz.ErrorRecord) ([]*ErrorRecordResponse, int64, error) { items, total, err := s.uc.ListErrors(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.ErrorRecordResponse, 0, len(items)) + out := make([]*ErrorRecordResponse, 0, len(items)) for _, v := range items { out = append(out, errorDTO(v)) } diff --git a/app/system/internal/service/audit_log_file.go b/app/system/internal/service/audit_log_file.go index 5f3f7b2..7d42c87 100644 --- a/app/system/internal/service/audit_log_file.go +++ b/app/system/internal/service/audit_log_file.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type LogViewerService struct{ uc *biz.LogViewerUsecase } @@ -13,32 +12,32 @@ func NewLogViewerService(uc *biz.LogViewerUsecase) *LogViewerService { return &LogViewerService{uc: uc} } -func (s *LogViewerService) LogDates(ctx context.Context, month string) (*dto.LogDatesResponse, error) { +func (s *LogViewerService) LogDates(ctx context.Context, month string) (*LogDatesResponse, error) { items, err := s.uc.LogDates(ctx, month) if err != nil { return nil, err } - out := make([]*dto.LogDateResponse, 0, len(items)) + out := make([]*LogDateResponse, 0, len(items)) for _, v := range items { - out = append(out, &dto.LogDateResponse{Date: v.Date, FileCount: v.FileCount}) + out = append(out, &LogDateResponse{Date: v.Date, FileCount: v.FileCount}) } - return &dto.LogDatesResponse{Month: month, Dates: out}, nil + return &LogDatesResponse{Month: month, Dates: out}, nil } -func (s *LogViewerService) LogFiles(ctx context.Context, date string) (*dto.LogFilesResponse, error) { +func (s *LogViewerService) LogFiles(ctx context.Context, date string) (*LogFilesResponse, error) { items, err := s.uc.LogFiles(ctx, date) if err != nil { return nil, err } - out := make([]*dto.LogFileResponse, 0, len(items)) + out := make([]*LogFileResponse, 0, len(items)) for _, v := range items { - out = append(out, &dto.LogFileResponse{Path: v.Path, Name: v.Name, Size: v.Size, ModifiedAt: v.ModifiedAt}) + out = append(out, &LogFileResponse{Path: v.Path, Name: v.Name, Size: v.Size, ModifiedAt: v.ModifiedAt}) } - return &dto.LogFilesResponse{Date: date, Files: out}, nil + return &LogFilesResponse{Date: date, Files: out}, nil } -func (s *LogViewerService) LogContent(ctx context.Context, date, path string, cursor *int64) (*dto.LogContentResponse, error) { +func (s *LogViewerService) LogContent(ctx context.Context, date, path string, cursor *int64) (*LogContentResponse, error) { v, err := s.uc.LogContent(ctx, date, path, cursor) if err != nil { return nil, err } - return &dto.LogContentResponse{Date: v.Date, Path: v.Path, Content: v.Content, LineCount: v.LineCount, NextCursor: v.NextCursor, HasMore: v.HasMore, LimitedByBytes: v.LimitedByBytes, Size: v.Size, ModifiedAt: v.ModifiedAt}, nil + return &LogContentResponse{Date: v.Date, Path: v.Path, Content: v.Content, LineCount: v.LineCount, NextCursor: v.NextCursor, HasMore: v.HasMore, LimitedByBytes: v.LimitedByBytes, Size: v.Size, ModifiedAt: v.ModifiedAt}, nil } diff --git a/app/system/internal/service/authentication.go b/app/system/internal/service/authentication.go index 4836f1b..3d8165d 100644 --- a/app/system/internal/service/authentication.go +++ b/app/system/internal/service/authentication.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type AuthService struct { @@ -15,11 +14,11 @@ func NewAuthService(uc *biz.AuthenticationUsecase) *AuthService { return &AuthService{uc: uc} } -func loginResult(value *biz.AuthenticationResult) *dto.LoginResponse { - return &dto.LoginResponse{User: convertUser(value.User), Token: value.Token, ExpiresAt: value.ExpiresAt.UnixMilli(), NeedChangePassword: value.NeedChangePassword} +func loginResult(value *biz.AuthenticationResult) *LoginResponse { + return &LoginResponse{User: convertUser(value.User), Token: value.Token, ExpiresAt: value.ExpiresAt.UnixMilli(), NeedChangePassword: value.NeedChangePassword} } -func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest, ip, agent string) (*dto.LoginResponse, error) { +func (s *AuthService) Login(ctx context.Context, req *LoginRequest, ip, agent string) (*LoginResponse, error) { value, err := s.uc.Login(ctx, &biz.LoginAttempt{Username: req.Username, Password: req.Password, CaptchaID: req.CaptchaID, Captcha: req.Captcha, IP: ip, Agent: agent}) if err != nil { return nil, err @@ -27,7 +26,7 @@ func (s *AuthService) Login(ctx context.Context, req *dto.LoginRequest, ip, agen return loginResult(value), nil } -func (s *AuthService) SwitchAuthority(ctx context.Context, claims *biz.AuthClaims, authorityID uint) (*dto.LoginResponse, error) { +func (s *AuthService) SwitchAuthority(ctx context.Context, claims *biz.AuthClaims, authorityID uint) (*LoginResponse, error) { value, err := s.uc.SwitchAuthority(ctx, claims, authorityID) if err != nil { return nil, err diff --git a/app/system/internal/service/authority.go b/app/system/internal/service/authority.go index b6da6f3..9b91243 100644 --- a/app/system/internal/service/authority.go +++ b/app/system/internal/service/authority.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type AuthorityService struct{ uc *biz.AuthorityUsecase } @@ -13,9 +12,9 @@ func NewAuthorityService(uc *biz.AuthorityUsecase) *AuthorityService { return &AuthorityService{uc: uc} } -func authorityDTO(value *biz.Authority) *dto.AuthorityResponse { +func authorityDTO(value *biz.Authority) *AuthorityResponse { out := convertAuthority(*value) - children := make([]*dto.AuthorityResponse, 0, len(value.Children)) + children := make([]*AuthorityResponse, 0, len(value.Children)) for _, child := range value.Children { children = append(children, authorityDTO(child)) } @@ -23,31 +22,31 @@ func authorityDTO(value *biz.Authority) *dto.AuthorityResponse { return out } -func authorityDomain(req *dto.AuthorityRequest) *biz.Authority { +func authorityDomain(req *AuthorityRequest) *biz.Authority { return &biz.Authority{AuthorityID: req.AuthorityID, AuthorityName: req.AuthorityName, ParentID: req.ParentID, DataScope: req.DataScope, DefaultRouter: req.DefaultRouter} } -func authorityResponse(value *biz.Authority) *dto.AuthorityResponse { - var menus []*dto.MenuResponse +func authorityResponse(value *biz.Authority) *AuthorityResponse { + var menus []*MenuResponse if value.Menus != nil { menus = menuResponses(value.Menus) } - return &dto.AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: value.DeletedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} + return &AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: value.DeletedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} } -func (s *AuthorityService) Authorities(ctx context.Context) ([]*dto.AuthorityResponse, error) { +func (s *AuthorityService) Authorities(ctx context.Context) ([]*AuthorityResponse, error) { items, err := s.uc.AuthorityTree(ctx) if err != nil { return nil, err } - roots := make([]*dto.AuthorityResponse, 0, len(items)) + roots := make([]*AuthorityResponse, 0, len(items)) for _, value := range items { roots = append(roots, authorityDTO(value)) } return roots, nil } -func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { +func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *AuthorityRequest) (*AuthorityResponse, error) { value := authorityDomain(req) if err := s.uc.CreateAuthority(ctx, value); err != nil { return nil, err @@ -55,17 +54,17 @@ func (s *AuthorityService) CreateAuthorityRequest(ctx context.Context, req *dto. return authorityResponse(value), nil } -func (s *AuthorityService) CopyAuthorityRequest(ctx context.Context, req *dto.CopyAuthorityRequest) (*dto.AuthorityResponse, error) { +func (s *AuthorityService) CopyAuthorityRequest(ctx context.Context, req *CopyAuthorityRequest) (*AuthorityResponse, error) { value := authorityDomain(&req.Authority) if err := s.uc.CopyAuthority(ctx, req.OldAuthorityID, value); err != nil { return nil, err } result := authorityResponse(value) - result.Children = []*dto.AuthorityResponse{} + result.Children = []*AuthorityResponse{} return result, nil } -func (s *AuthorityService) UpdateAuthorityRequest(ctx context.Context, req *dto.AuthorityRequest) (*dto.AuthorityResponse, error) { +func (s *AuthorityService) UpdateAuthorityRequest(ctx context.Context, req *AuthorityRequest) (*AuthorityResponse, error) { value := authorityDomain(req) if err := s.uc.UpdateAuthority(ctx, value); err != nil { return nil, err diff --git a/app/system/internal/service/department.go b/app/system/internal/service/department.go index 6190bee..1d61253 100644 --- a/app/system/internal/service/department.go +++ b/app/system/internal/service/department.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type DepartmentService struct{ uc *biz.DepartmentUsecase } @@ -13,10 +12,10 @@ func NewDepartmentService(uc *biz.DepartmentUsecase) *DepartmentService { return &DepartmentService{uc: uc} } -func departmentResponse(value *biz.Department) *dto.DepartmentResponse { - var children []*dto.DepartmentResponse +func departmentResponse(value *biz.Department) *DepartmentResponse { + var children []*DepartmentResponse if value.Children != nil { - children = make([]*dto.DepartmentResponse, 0, len(value.Children)) + children = make([]*DepartmentResponse, 0, len(value.Children)) for _, child := range value.Children { children = append(children, departmentResponse(child)) } @@ -25,30 +24,30 @@ func departmentResponse(value *biz.Department) *dto.DepartmentResponse { if value.Leader != nil { leader = convertUser(value.Leader) } - return &dto.DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath} + return &DepartmentResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, ParentID: value.ParentID, Ancestors: value.Ancestors, Sort: value.Sort, LeaderID: value.LeaderID, Leader: leader, Status: value.Status, Children: children, NamePath: value.NamePath} } -func departmentDomain(value *dto.DepartmentRequest) *biz.Department { +func departmentDomain(value *DepartmentRequest) *biz.Department { return &biz.Department{ID: value.ID, Name: value.Name, ParentID: value.ParentID, Sort: value.Sort, LeaderID: value.LeaderID, Status: value.Status} } -func (s *DepartmentService) Departments(ctx context.Context, name string) ([]*dto.DepartmentResponse, error) { +func (s *DepartmentService) Departments(ctx context.Context, name string) ([]*DepartmentResponse, error) { items, err := s.uc.Departments(ctx, name) if err != nil { return nil, err } - out := make([]*dto.DepartmentResponse, 0, len(items)) + out := make([]*DepartmentResponse, 0, len(items)) for _, item := range items { out = append(out, departmentResponse(item)) } return out, nil } -func (s *DepartmentService) Create(ctx context.Context, req *dto.DepartmentRequest) error { +func (s *DepartmentService) Create(ctx context.Context, req *DepartmentRequest) error { return s.uc.CreateDepartment(ctx, departmentDomain(req)) } -func (s *DepartmentService) Update(ctx context.Context, req *dto.DepartmentRequest) error { +func (s *DepartmentService) Update(ctx context.Context, req *DepartmentRequest) error { return s.uc.UpdateDepartment(ctx, departmentDomain(req)) } @@ -56,7 +55,7 @@ func (s *DepartmentService) Delete(ctx context.Context, id uint) error { return s.uc.DeleteDepartment(ctx, id) } -func (s *DepartmentService) Department(ctx context.Context, id uint) (*dto.DepartmentResponse, error) { +func (s *DepartmentService) Department(ctx context.Context, id uint) (*DepartmentResponse, error) { value, err := s.uc.Department(ctx, id) if err != nil { return nil, err @@ -68,10 +67,10 @@ func (s *DepartmentService) UserIDs(ctx context.Context, id uint) ([]uint, error return s.uc.DepartmentUserIDs(ctx, id) } -func (s *DepartmentService) SetUsers(ctx context.Context, req *dto.SetDepartmentUsersRequest) error { +func (s *DepartmentService) SetUsers(ctx context.Context, req *SetDepartmentUsersRequest) error { return s.uc.SetDepartmentUsers(ctx, req.DepartmentID, req.UserIDs) } -func (s *DepartmentService) SetUserDepartments(ctx context.Context, req *dto.SetUserDepartmentsRequest) error { +func (s *DepartmentService) SetUserDepartments(ctx context.Context, req *SetUserDepartmentsRequest) error { return s.uc.SetUserDepartments(ctx, req.ID, req.DepartmentIDs, req.Primary) } diff --git a/app/system/internal/service/dictionary.go b/app/system/internal/service/dictionary.go index f90970b..7f7179f 100644 --- a/app/system/internal/service/dictionary.go +++ b/app/system/internal/service/dictionary.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type DictionaryService struct{ uc *biz.DictionaryUsecase } @@ -13,67 +12,67 @@ func NewDictionaryService(uc *biz.DictionaryUsecase) *DictionaryService { return &DictionaryService{uc: uc} } -func dictionaryDomain(value *dto.DictionaryRequest) *biz.Dictionary { +func dictionaryDomain(value *DictionaryRequest) *biz.Dictionary { return &biz.Dictionary{ID: value.ID, Name: value.Name, Type: value.Type, Status: value.Status, Desc: value.Description, ParentID: value.ParentID} } -func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) (*dto.DictionaryResponse, error) { +func (s *DictionaryService) CreateDictionaryRequest(ctx context.Context, req *DictionaryRequest) (*DictionaryResponse, error) { value := dictionaryDomain(req) if err := s.CreateDictionary(ctx, value); err != nil { return nil, err } return dictionaryDTO(value), nil } -func (s *DictionaryService) UpdateDictionaryRequest(ctx context.Context, req *dto.DictionaryRequest) error { +func (s *DictionaryService) UpdateDictionaryRequest(ctx context.Context, req *DictionaryRequest) error { return s.UpdateDictionary(ctx, dictionaryDomain(req)) } -func dictionaryDTO(v *biz.Dictionary) *dto.DictionaryResponse { - var children []*dto.DictionaryResponse +func dictionaryDTO(v *biz.Dictionary) *DictionaryResponse { + var children []*DictionaryResponse if v.Children != nil { - children = make([]*dto.DictionaryResponse, 0, len(v.Children)) + children = make([]*DictionaryResponse, 0, len(v.Children)) for _, x := range v.Children { children = append(children, dictionaryDTO(x)) } } - var details []*dto.DictionaryDetailResponse + var details []*DictionaryDetailResponse if v.Details != nil { - details = make([]*dto.DictionaryDetailResponse, 0, len(v.Details)) + details = make([]*DictionaryDetailResponse, 0, len(v.Details)) for _, x := range v.Details { details = append(details, detailDTO(x)) } } - return &dto.DictionaryResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID, Children: children, SysDictionaryDetails: details} + return &DictionaryResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, ParentID: v.ParentID, Children: children, SysDictionaryDetails: details} } -func (s *DictionaryService) Dictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*dto.DictionaryResponse, int64, error) { +func (s *DictionaryService) Dictionaries(ctx context.Context, page, size int, name, typ string, details bool) ([]*DictionaryResponse, int64, error) { items, total, err := s.uc.ListDictionaries(ctx, page, size, name, typ, details) if err != nil { return nil, 0, err } - out := make([]*dto.DictionaryResponse, 0, len(items)) + out := make([]*DictionaryResponse, 0, len(items)) for _, v := range items { out = append(out, dictionaryDTO(v)) } return out, total, nil } -func (s *DictionaryService) Dictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*dto.DictionaryResponse, error) { +func (s *DictionaryService) Dictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*DictionaryResponse, error) { v, err := s.uc.FindDictionary(ctx, id, typ, status, details) if err != nil { return nil, err } return dictionaryDTO(v), nil } -func (s *DictionaryService) ExportDictionary(ctx context.Context, id uint) (*dto.DictionaryExportResponse, error) { +func (s *DictionaryService) ExportDictionary(ctx context.Context, id uint) (*DictionaryExportResponse, error) { v, err := s.uc.ExportDictionary(ctx, id) if err != nil { return nil, err } - var details []*dto.DictionaryDetailExportResponse + var details []*DictionaryDetailExportResponse for _, detail := range v.Details { - details = append(details, &dto.DictionaryDetailExportResponse{ + details = append(details, &DictionaryDetailExportResponse{ Label: detail.Label, Value: detail.Value, Extend: detail.Extend, Status: detail.Status, Sort: detail.Sort, Level: detail.Level, Path: detail.Path, }) } - return &dto.DictionaryExportResponse{ + return &DictionaryExportResponse{ Name: v.Name, Type: v.Type, Status: v.Status, Desc: v.Desc, SysDictionaryDetails: details, }, nil @@ -88,38 +87,38 @@ func (s *DictionaryService) DeleteDictionary(ctx context.Context, id uint) error return s.uc.DeleteDictionary(ctx, id) } -func detailDomain(value *dto.DictionaryDetailRequest) *biz.DictionaryDetail { +func detailDomain(value *DictionaryDetailRequest) *biz.DictionaryDetail { return &biz.DictionaryDetail{ID: value.ID, Label: value.Label, Value: value.Value, Extend: value.Extend, Status: value.Status, Sort: value.Sort, DictionaryID: value.DictionaryID, ParentID: value.ParentID, Level: value.Level, Path: value.Path} } -func (s *DictionaryService) CreateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error { +func (s *DictionaryService) CreateDictionaryDetailRequest(ctx context.Context, req *DictionaryDetailRequest) error { return s.CreateDictionaryDetail(ctx, detailDomain(req)) } -func (s *DictionaryService) UpdateDictionaryDetailRequest(ctx context.Context, req *dto.DictionaryDetailRequest) error { +func (s *DictionaryService) UpdateDictionaryDetailRequest(ctx context.Context, req *DictionaryDetailRequest) error { return s.UpdateDictionaryDetail(ctx, detailDomain(req)) } -func detailDTO(v *biz.DictionaryDetail) *dto.DictionaryDetailResponse { - var children []*dto.DictionaryDetailResponse +func detailDTO(v *biz.DictionaryDetail) *DictionaryDetailResponse { + var children []*DictionaryDetailResponse if v.Children != nil { - children = make([]*dto.DictionaryDetailResponse, 0, len(v.Children)) + children = make([]*DictionaryDetailResponse, 0, len(v.Children)) for _, x := range v.Children { children = append(children, detailDTO(x)) } } disabled := v.Status != nil && !*v.Status - return &dto.DictionaryDetailResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID, Level: v.Level, Path: v.Path, Disabled: disabled, Children: children} + return &DictionaryDetailResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Label: v.Label, Value: v.Value, Extend: v.Extend, Status: v.Status, Sort: v.Sort, DictionaryID: v.DictionaryID, ParentID: v.ParentID, Level: v.Level, Path: v.Path, Disabled: disabled, Children: children} } -func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*dto.DictionaryDetailResponse, int64, error) { +func (s *DictionaryService) DictionaryDetails(ctx context.Context, page, size int, filter biz.DictionaryDetailFilter) ([]*DictionaryDetailResponse, int64, error) { items, total, err := s.uc.ListDictionaryDetails(ctx, page, size, filter) if err != nil { return nil, 0, err } - out := make([]*dto.DictionaryDetailResponse, 0, len(items)) + out := make([]*DictionaryDetailResponse, 0, len(items)) for _, v := range items { out = append(out, detailDTO(v)) } return out, total, nil } -func (s *DictionaryService) DictionaryDetailsRequest(ctx context.Context, page, size int, request dto.DictionaryDetailListRequest) ([]*dto.DictionaryDetailResponse, int64, error) { +func (s *DictionaryService) DictionaryDetailsRequest(ctx context.Context, page, size int, request DictionaryDetailListRequest) ([]*DictionaryDetailResponse, int64, error) { return s.DictionaryDetails(ctx, page, size, biz.DictionaryDetailFilter{ DictionaryID: request.DictionaryID, Label: request.Label, @@ -129,21 +128,21 @@ func (s *DictionaryService) DictionaryDetailsRequest(ctx context.Context, page, Level: request.Level, }) } -func (s *DictionaryService) DictionaryDetail(ctx context.Context, id uint) (*dto.DictionaryDetailResponse, error) { +func (s *DictionaryService) DictionaryDetail(ctx context.Context, id uint) (*DictionaryDetailResponse, error) { v, err := s.uc.FindDictionaryDetail(ctx, id) if err != nil { return nil, err } return detailDTO(v), nil } -func (s *DictionaryService) DictionaryPath(ctx context.Context, id uint) ([]*dto.DictionaryDetailResponse, error) { - path := make([]*dto.DictionaryDetailResponse, 0) +func (s *DictionaryService) DictionaryPath(ctx context.Context, id uint) ([]*DictionaryDetailResponse, error) { + path := make([]*DictionaryDetailResponse, 0) for id != 0 { value, err := s.uc.FindDictionaryDetail(ctx, id) if err != nil { return nil, err } - path = append([]*dto.DictionaryDetailResponse{detailDTO(value)}, path...) + path = append([]*DictionaryDetailResponse{detailDTO(value)}, path...) if value.ParentID == nil { break } @@ -151,12 +150,12 @@ func (s *DictionaryService) DictionaryPath(ctx context.Context, id uint) ([]*dto } return path, nil } -func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ string) ([]*dto.DictionaryDetailResponse, error) { +func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ string) ([]*DictionaryDetailResponse, error) { items, err := s.uc.DictionaryDetailTree(ctx, id, typ) if err != nil { return nil, err } - out := make([]*dto.DictionaryDetailResponse, 0, len(items)) + out := make([]*DictionaryDetailResponse, 0, len(items)) for _, v := range items { out = append(out, detailDTO(v)) } diff --git a/app/system/internal/service/dictionary_import.go b/app/system/internal/service/dictionary_import.go index e87917f..1ff815c 100644 --- a/app/system/internal/service/dictionary_import.go +++ b/app/system/internal/service/dictionary_import.go @@ -6,16 +6,15 @@ import ( "errors" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) func (s *DictionaryService) ImportDictionaryJSON(ctx context.Context, raw string) error { var payload struct { - Name string `json:"name"` - Type string `json:"type"` - Status *bool `json:"status"` - Description string `json:"desc"` - Details []dto.DictionaryDetailRequest `json:"sysDictionaryDetails"` + Name string `json:"name"` + Type string `json:"type"` + Status *bool `json:"status"` + Description string `json:"desc"` + Details []DictionaryDetailRequest `json:"sysDictionaryDetails"` } if err := json.Unmarshal([]byte(raw), &payload); err != nil { return errors.New("JSON 格式错误: " + err.Error()) @@ -26,19 +25,19 @@ func (s *DictionaryService) ImportDictionaryJSON(ctx context.Context, raw string if payload.Type == "" { return errors.New("字典类型不能为空") } - dictionary := dictionaryDomain(&dto.DictionaryRequest{Name: payload.Name, Type: payload.Type, Status: payload.Status, Description: payload.Description}) + dictionary := dictionaryDomain(&DictionaryRequest{Name: payload.Name, Type: payload.Type, Status: payload.Status, Description: payload.Description}) details := make([]*biz.DictionaryDetail, 0, len(payload.Details)) for i := range payload.Details { details = append(details, detailDomain(&payload.Details[i])) } return s.uc.ImportDictionary(ctx, dictionary, details) } -func (s *DictionaryService) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*dto.DictionaryDetailResponse, error) { +func (s *DictionaryService) DictionaryDetailsByParent(ctx context.Context, dictionaryID uint, parentID *uint, includeChildren bool) ([]*DictionaryDetailResponse, error) { items, err := s.uc.DictionaryDetailsByParent(ctx, dictionaryID, parentID, includeChildren) if err != nil { return nil, err } - out := make([]*dto.DictionaryDetailResponse, 0, len(items)) + out := make([]*DictionaryDetailResponse, 0, len(items)) for _, item := range items { out = append(out, detailDTO(item)) } diff --git a/app/system/internal/dto/announcement.go b/app/system/internal/service/dto/announcement.go similarity index 100% rename from app/system/internal/dto/announcement.go rename to app/system/internal/service/dto/announcement.go diff --git a/app/system/internal/dto/api.go b/app/system/internal/service/dto/api.go similarity index 100% rename from app/system/internal/dto/api.go rename to app/system/internal/service/dto/api.go diff --git a/app/system/internal/dto/audit.go b/app/system/internal/service/dto/audit.go similarity index 100% rename from app/system/internal/dto/audit.go rename to app/system/internal/service/dto/audit.go diff --git a/app/system/internal/dto/audit_test.go b/app/system/internal/service/dto/audit_test.go similarity index 100% rename from app/system/internal/dto/audit_test.go rename to app/system/internal/service/dto/audit_test.go diff --git a/app/system/internal/dto/authentication.go b/app/system/internal/service/dto/authentication.go similarity index 100% rename from app/system/internal/dto/authentication.go rename to app/system/internal/service/dto/authentication.go diff --git a/app/system/internal/dto/authority.go b/app/system/internal/service/dto/authority.go similarity index 100% rename from app/system/internal/dto/authority.go rename to app/system/internal/service/dto/authority.go diff --git a/app/system/internal/dto/email.go b/app/system/internal/service/dto/email.go similarity index 100% rename from app/system/internal/dto/email.go rename to app/system/internal/service/dto/email.go diff --git a/app/system/internal/dto/export.go b/app/system/internal/service/dto/export.go similarity index 100% rename from app/system/internal/dto/export.go rename to app/system/internal/service/dto/export.go diff --git a/app/system/internal/dto/media.go b/app/system/internal/service/dto/media.go similarity index 100% rename from app/system/internal/dto/media.go rename to app/system/internal/service/dto/media.go diff --git a/app/system/internal/dto/menu.go b/app/system/internal/service/dto/menu.go similarity index 100% rename from app/system/internal/dto/menu.go rename to app/system/internal/service/dto/menu.go diff --git a/app/system/internal/dto/organization.go b/app/system/internal/service/dto/organization.go similarity index 100% rename from app/system/internal/dto/organization.go rename to app/system/internal/service/dto/organization.go diff --git a/app/system/internal/dto/payment.go b/app/system/internal/service/dto/payment.go similarity index 98% rename from app/system/internal/dto/payment.go rename to app/system/internal/service/dto/payment.go index cad5a1d..b5e8334 100644 --- a/app/system/internal/dto/payment.go +++ b/app/system/internal/service/dto/payment.go @@ -56,6 +56,11 @@ type PaymentRefundRequest struct { Amount int64 `json:"amount"` } +type PaymentFulfillRequest struct { + Provider string `json:"provider"` + TradeNo string `json:"tradeNo"` +} + // PaymentCallbackRequest is populated by the HTTP transport and converted to // the biz callback object inside service. type PaymentCallbackRequest struct { diff --git a/app/system/internal/dto/permission.go b/app/system/internal/service/dto/permission.go similarity index 100% rename from app/system/internal/dto/permission.go rename to app/system/internal/service/dto/permission.go diff --git a/app/system/internal/dto/settings.go b/app/system/internal/service/dto/settings.go similarity index 100% rename from app/system/internal/dto/settings.go rename to app/system/internal/service/dto/settings.go diff --git a/app/system/internal/dto/system.go b/app/system/internal/service/dto/system.go similarity index 100% rename from app/system/internal/dto/system.go rename to app/system/internal/service/dto/system.go diff --git a/app/system/internal/dto/system_config.go b/app/system/internal/service/dto/system_config.go similarity index 100% rename from app/system/internal/dto/system_config.go rename to app/system/internal/service/dto/system_config.go diff --git a/app/system/internal/dto/system_init.go b/app/system/internal/service/dto/system_init.go similarity index 100% rename from app/system/internal/dto/system_init.go rename to app/system/internal/service/dto/system_init.go diff --git a/app/system/internal/dto/task.go b/app/system/internal/service/dto/task.go similarity index 100% rename from app/system/internal/dto/task.go rename to app/system/internal/service/dto/task.go diff --git a/app/system/internal/dto/version.go b/app/system/internal/service/dto/version.go similarity index 100% rename from app/system/internal/dto/version.go rename to app/system/internal/service/dto/version.go diff --git a/app/system/internal/service/dto_alias.go b/app/system/internal/service/dto_alias.go new file mode 100644 index 0000000..0c16d97 --- /dev/null +++ b/app/system/internal/service/dto_alias.go @@ -0,0 +1,162 @@ +package service + +// DTO aliases keep the service API stable while request/response contracts live in service/dto. +import "kra/app/system/internal/service/dto" + +type AnnouncementRequest = dto.AnnouncementRequest +type AnnouncementResponse = dto.AnnouncementResponse +type AnnouncementSearchRequest = dto.AnnouncementSearchRequest +type APIListRequest = dto.APIListRequest +type APIRequest = dto.APIRequest +type APIResponse = dto.APIResponse +type APISyncResponse = dto.APISyncResponse +type APITokenListRequest = dto.APITokenListRequest +type APITokenResponse = dto.APITokenResponse +type ApplyAPISyncRequest = dto.ApplyAPISyncRequest +type AuditIDQuery = dto.AuditIDQuery +type AuditIDsRequest = dto.AuditIDsRequest +type AuthorityMenuResponse = dto.AuthorityMenuResponse +type AuthorityRequest = dto.AuthorityRequest +type AuthorityResponse = dto.AuthorityResponse +type CategoryRequest = dto.CategoryRequest +type ChangePasswordRequest = dto.ChangePasswordRequest +type CompleteUploadRequest = dto.CompleteUploadRequest +type CopyAuthorityRequest = dto.CopyAuthorityRequest +type CreateAPITokenRequest = dto.CreateAPITokenRequest +type DataAccessListRequest = dto.DataAccessListRequest +type DataAccessLogResponse = dto.DataAccessLogResponse +type DataAccessRecordRequest = dto.DataAccessRecordRequest +type DatabaseInitRequest = dto.DatabaseInitRequest +type DeleteAPIRequest = dto.DeleteAPIRequest +type DeleteAPIsRequest = dto.DeleteAPIsRequest +type DeleteAuthorityRequest = dto.DeleteAuthorityRequest +type DeleteDepartmentRequest = dto.DeleteDepartmentRequest +type DeleteMenuRequest = dto.DeleteMenuRequest +type DeletePositionRequest = dto.DeletePositionRequest +type DeleteTaskRequest = dto.DeleteTaskRequest +type DepartmentListRequest = dto.DepartmentListRequest +type DepartmentRequest = dto.DepartmentRequest +type DepartmentResponse = dto.DepartmentResponse +type DictionaryDetailExportResponse = dto.DictionaryDetailExportResponse +type DictionaryDetailListRequest = dto.DictionaryDetailListRequest +type DictionaryDetailRequest = dto.DictionaryDetailRequest +type DictionaryDetailResponse = dto.DictionaryDetailResponse +type DictionaryDetailsByParentRequest = dto.DictionaryDetailsByParentRequest +type DictionaryExportResponse = dto.DictionaryExportResponse +type DictionaryRequest = dto.DictionaryRequest +type DictionaryResponse = dto.DictionaryResponse +type DynamicMenuResponse = dto.DynamicMenuResponse +type ErrorRecordMutationRequest = dto.ErrorRecordMutationRequest +type ErrorRecordRequest = dto.ErrorRecordRequest +type ErrorRecordResponse = dto.ErrorRecordResponse +type ExportConditionRequest = dto.ExportConditionRequest +type ExportConditionResponse = dto.ExportConditionResponse +type ExportJoinRequest = dto.ExportJoinRequest +type ExportJoinResponse = dto.ExportJoinResponse +type ExportTemplateRequest = dto.ExportTemplateRequest +type ExportTemplateResponse = dto.ExportTemplateResponse +type ExportVersionRequest = dto.ExportVersionRequest +type GetAPIRequest = dto.GetAPIRequest +type GetAuthorityButtonsRequest = dto.GetAuthorityButtonsRequest +type GetAuthorityMenusRequest = dto.GetAuthorityMenusRequest +type GetMenuRequest = dto.GetMenuRequest +type GetPolicyPathsRequest = dto.GetPolicyPathsRequest +type IDRequest = dto.IDRequest +type IDsRequest = dto.IDsRequest +type IgnoreAPIRequest = dto.IgnoreAPIRequest +type ImportDictionaryRequest = dto.ImportDictionaryRequest +type ImportMediaRequest = dto.ImportMediaRequest +type ImportVersionRequest = dto.ImportVersionRequest +type InitUploadRequest = dto.InitUploadRequest +type InitUploadResponse = dto.InitUploadResponse +type LogContentResponse = dto.LogContentResponse +type LogDateResponse = dto.LogDateResponse +type LogDatesResponse = dto.LogDatesResponse +type LogFileResponse = dto.LogFileResponse +type LogFilesResponse = dto.LogFilesResponse +type LoginLogRequest = dto.LoginLogRequest +type LoginLogResponse = dto.LoginLogResponse +type LoginLogSearchRequest = dto.LoginLogSearchRequest +type LoginRequest = dto.LoginRequest +type LoginResponse = dto.LoginResponse +type MediaBatchDeleteRequest = dto.MediaBatchDeleteRequest +type MediaCategoryResponse = dto.MediaCategoryResponse +type MediaListRequest = dto.MediaListRequest +type MediaResponse = dto.MediaResponse +type MenuButtonRequest = dto.MenuButtonRequest +type MenuButtonResponse = dto.MenuButtonResponse +type MenuMetaRequest = dto.MenuMetaRequest +type MenuMetaResponse = dto.MenuMetaResponse +type MenuParameterRequest = dto.MenuParameterRequest +type MenuParameterResponse = dto.MenuParameterResponse +type MenuRequest = dto.MenuRequest +type MenuResponse = dto.MenuResponse +type OperationRecordRequest = dto.OperationRecordRequest +type OperationRecordResponse = dto.OperationRecordResponse +type OperationRecordSearchRequest = dto.OperationRecordSearchRequest +type PaymentCallbackAck = dto.PaymentCallbackAck +type PaymentCallbackRequest = dto.PaymentCallbackRequest +type PaymentConfigRequest = dto.PaymentConfigRequest +type PaymentConfigResponse = dto.PaymentConfigResponse +type PaymentFulfillRequest = dto.PaymentFulfillRequest +type PaymentOrderListRequest = dto.PaymentOrderListRequest +type PaymentOrderResponse = dto.PaymentOrderResponse +type PaymentQueryRequest = dto.PaymentQueryRequest +type PaymentRefundRequest = dto.PaymentRefundRequest +type PaymentRequest = dto.PaymentRequest +type PaymentResultResponse = dto.PaymentResultResponse +type PolicyPathRequest = dto.PolicyPathRequest +type PositionListRequest = dto.PositionListRequest +type PositionRequest = dto.PositionRequest +type PositionResponse = dto.PositionResponse +type RenameMediaRequest = dto.RenameMediaRequest +type ResetPasswordRequest = dto.ResetPasswordRequest +type Route = dto.Route +type SecurityConfigRequest = dto.SecurityConfigRequest +type SecurityConfigResponse = dto.SecurityConfigResponse +type SelectOptionResponse = dto.SelectOptionResponse +type SelfUserRequest = dto.SelfUserRequest +type SendEmailRequest = dto.SendEmailRequest +type ServerCPUResponse = dto.ServerCPUResponse +type ServerDiskResponse = dto.ServerDiskResponse +type ServerInfoResponse = dto.ServerInfoResponse +type ServerOSResponse = dto.ServerOSResponse +type ServerRAMResponse = dto.ServerRAMResponse +type SetAPIRolesRequest = dto.SetAPIRolesRequest +type SetAuthorityButtonsRequest = dto.SetAuthorityButtonsRequest +type SetAuthorityMenusRequest = dto.SetAuthorityMenusRequest +type SetDataScopeRequest = dto.SetDataScopeRequest +type SetDepartmentUsersRequest = dto.SetDepartmentUsersRequest +type SetMenuRolesRequest = dto.SetMenuRolesRequest +type SetPolicyPathsRequest = dto.SetPolicyPathsRequest +type SetPositionUsersRequest = dto.SetPositionUsersRequest +type SetRoleUsersRequest = dto.SetRoleUsersRequest +type SetSystemConfigRequest = dto.SetSystemConfigRequest +type SetUserDepartmentsRequest = dto.SetUserDepartmentsRequest +type SetUserPositionsRequest = dto.SetUserPositionsRequest +type StorageListRequest = dto.StorageListRequest +type StorageObjectResponse = dto.StorageObjectResponse +type SwitchAuthorityRequest = dto.SwitchAuthorityRequest +type SystemParameterRequest = dto.SystemParameterRequest +type SystemParameterResponse = dto.SystemParameterResponse +type SystemParameterSearchRequest = dto.SystemParameterSearchRequest +type TaskListRequest = dto.TaskListRequest +type TaskLogListRequest = dto.TaskLogListRequest +type TaskLogResponse = dto.TaskLogResponse +type TaskMethodResponse = dto.TaskMethodResponse +type TaskRequest = dto.TaskRequest +type TaskResponse = dto.TaskResponse +type ToggleTaskRequest = dto.ToggleTaskRequest +type UserAuthoritiesRequest = dto.UserAuthoritiesRequest +type UserListRequest = dto.UserListRequest +type UserRequest = dto.UserRequest +type UserResponse = dto.UserResponse +type VersionAPIRequest = dto.VersionAPIRequest +type VersionDictionaryDetailRequest = dto.VersionDictionaryDetailRequest +type VersionDictionaryRequest = dto.VersionDictionaryRequest +type VersionMenuButtonRequest = dto.VersionMenuButtonRequest +type VersionMenuMetaRequest = dto.VersionMenuMetaRequest +type VersionMenuParameterRequest = dto.VersionMenuParameterRequest +type VersionMenuRequest = dto.VersionMenuRequest +type VersionResponse = dto.VersionResponse +type VersionSearchRequest = dto.VersionSearchRequest diff --git a/app/system/internal/service/export.go b/app/system/internal/service/export.go index 67a96ba..76cf5dd 100644 --- a/app/system/internal/service/export.go +++ b/app/system/internal/service/export.go @@ -8,7 +8,6 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" "github.com/google/uuid" ) @@ -67,7 +66,7 @@ func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBl _ = s.cache.Delete(ctx, "export:"+token) return &ExportToken{TemplateID: value.TemplateID, Params: value.Params, Blank: value.Blank}, nil } -func exportTemplateDomain(value *dto.ExportTemplateRequest) *biz.ExportTemplate { +func exportTemplateDomain(value *ExportTemplateRequest) *biz.ExportTemplate { out := &biz.ExportTemplate{ID: value.ID, DBName: value.DBName, Name: value.Name, TableName: value.TableName, TemplateID: value.TemplateID, TemplateInfo: value.TemplateInfo, SQL: value.SQL, ImportSQL: value.ImportSQL, Limit: value.Limit, Order: value.Order} for _, item := range value.Conditions { out.Conditions = append(out.Conditions, biz.ExportCondition{ID: item.ID, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, TemplateID: item.TemplateID, From: item.From, Column: item.Column, Operator: item.Operator}) @@ -77,31 +76,31 @@ func exportTemplateDomain(value *dto.ExportTemplateRequest) *biz.ExportTemplate } return out } -func (s *ExportService) CreateRequest(ctx context.Context, req *dto.ExportTemplateRequest) error { +func (s *ExportService) CreateRequest(ctx context.Context, req *ExportTemplateRequest) error { return s.Create(ctx, exportTemplateDomain(req)) } -func (s *ExportService) UpdateRequest(ctx context.Context, req *dto.ExportTemplateRequest) error { +func (s *ExportService) UpdateRequest(ctx context.Context, req *ExportTemplateRequest) error { return s.Update(ctx, exportTemplateDomain(req)) } -func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]*dto.ExportTemplateResponse, int64, error) { +func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, name, tableName, templateID string, start, end *time.Time) ([]*ExportTemplateResponse, int64, error) { return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end}) } -func exportDTO(v *biz.ExportTemplate) *dto.ExportTemplateResponse { - var conditions []dto.ExportConditionResponse +func exportDTO(v *biz.ExportTemplate) *ExportTemplateResponse { + var conditions []ExportConditionResponse if v.Conditions != nil { - conditions = make([]dto.ExportConditionResponse, 0, len(v.Conditions)) + conditions = make([]ExportConditionResponse, 0, len(v.Conditions)) for _, x := range v.Conditions { - conditions = append(conditions, dto.ExportConditionResponse{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, From: x.From, Column: x.Column, Operator: x.Operator}) + conditions = append(conditions, ExportConditionResponse{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, From: x.From, Column: x.Column, Operator: x.Operator}) } } - var joins []dto.ExportJoinResponse + var joins []ExportJoinResponse if v.Joins != nil { - joins = make([]dto.ExportJoinResponse, 0, len(v.Joins)) + joins = make([]ExportJoinResponse, 0, len(v.Joins)) for _, x := range v.Joins { - joins = append(joins, dto.ExportJoinResponse{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, Join: x.Join, Table: x.Table, On: x.On}) + joins = append(joins, ExportJoinResponse{ID: x.ID, CreatedAt: x.CreatedAt, UpdatedAt: x.UpdatedAt, TemplateID: x.TemplateID, Join: x.Join, Table: x.Table, On: x.On}) } } - return &dto.ExportTemplateResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order, Conditions: conditions, Joins: joins} + return &ExportTemplateResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order, Conditions: conditions, Joins: joins} } func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error { return s.uc.CreateExportTemplate(ctx, v) @@ -112,19 +111,19 @@ func (s *ExportService) Update(ctx context.Context, v *biz.ExportTemplate) error func (s *ExportService) Delete(ctx context.Context, ids []uint) error { return s.uc.DeleteExportTemplates(ctx, ids) } -func (s *ExportService) Template(ctx context.Context, id uint, tid string) (*dto.ExportTemplateResponse, error) { +func (s *ExportService) Template(ctx context.Context, id uint, tid string) (*ExportTemplateResponse, error) { v, err := s.uc.FindExportTemplate(ctx, id, tid) if err != nil { return nil, err } return exportDTO(v), nil } -func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*dto.ExportTemplateResponse, int64, error) { +func (s *ExportService) Templates(ctx context.Context, page, size int, q *biz.ExportTemplate) ([]*ExportTemplateResponse, int64, error) { items, total, err := s.uc.ListExportTemplates(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.ExportTemplateResponse, 0, len(items)) + out := make([]*ExportTemplateResponse, 0, len(items)) for _, v := range items { out = append(out, exportDTO(v)) } diff --git a/app/system/internal/service/media.go b/app/system/internal/service/media.go index bd071bf..3b33752 100644 --- a/app/system/internal/service/media.go +++ b/app/system/internal/service/media.go @@ -5,7 +5,6 @@ import ( "io" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type MediaService struct { @@ -19,45 +18,45 @@ func NewMediaService(uc *biz.MediaUsecase, settings biz.RuntimeSettings) *MediaS func (s *MediaService) MediaConfig() biz.MediaSettings { return s.settings.MediaSettings() } -func mediaDTO(v *biz.MediaFile) *dto.MediaResponse { - return &dto.MediaResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, ClassID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} +func mediaDTO(v *biz.MediaFile) *MediaResponse { + return &MediaResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, ClassID: v.CategoryID, URL: v.URL, Tag: v.Tag, Key: v.Key, Size: v.Size, Mime: v.Mime, MD5: v.MD5, UserID: v.UserID} } -func categoryDTO(v *biz.AttachmentCategory) *dto.MediaCategoryResponse { - var children []*dto.MediaCategoryResponse +func categoryDTO(v *biz.AttachmentCategory) *MediaCategoryResponse { + var children []*MediaCategoryResponse if v.Children != nil { - children = make([]*dto.MediaCategoryResponse, 0, len(v.Children)) + children = make([]*MediaCategoryResponse, 0, len(v.Children)) for _, x := range v.Children { children = append(children, categoryDTO(x)) } } - return &dto.MediaCategoryResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, PID: v.ParentID, Children: children} + return &MediaCategoryResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, PID: v.ParentID, Children: children} } -func (s *MediaService) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (*dto.MediaResponse, error) { +func (s *MediaService) Upload(ctx context.Context, userID uint, name, mime string, categoryID int, reader io.Reader, save bool) (*MediaResponse, error) { v, err := s.uc.Upload(ctx, userID, name, mime, categoryID, reader, save) if err != nil { return nil, err } return mediaDTO(v), nil } -func (s *MediaService) Media(ctx context.Context, id uint) (*dto.MediaResponse, error) { +func (s *MediaService) Media(ctx context.Context, id uint) (*MediaResponse, error) { v, err := s.uc.FindMedia(ctx, id) if err != nil { return nil, err } return mediaDTO(v), nil } -func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([]*dto.MediaResponse, int64, error) { +func (s *MediaService) MediaList(ctx context.Context, filter biz.MediaFilter) ([]*MediaResponse, int64, error) { items, total, err := s.uc.ListMedia(ctx, filter) if err != nil { return nil, 0, err } - out := make([]*dto.MediaResponse, 0, len(items)) + out := make([]*MediaResponse, 0, len(items)) for _, v := range items { out = append(out, mediaDTO(v)) } return out, total, nil } -func (s *MediaService) MediaListRequest(ctx context.Context, request *dto.MediaListRequest) ([]*dto.MediaResponse, int64, error) { +func (s *MediaService) MediaListRequest(ctx context.Context, request *MediaListRequest) ([]*MediaResponse, int64, error) { return s.MediaList(ctx, biz.MediaFilter{ Page: request.Page, PageSize: request.PageSize, Keyword: request.Keyword, CategoryID: request.ClassID, Tag: request.Tag, UserID: request.UserID, @@ -72,30 +71,30 @@ func (s *MediaService) Rename(ctx context.Context, id uint, name string) error { func (s *MediaService) ImportURLs(ctx context.Context, items []*biz.MediaFile) error { return s.uc.CreateMediaBatch(ctx, items) } -func (s *MediaService) ImportURLRequests(ctx context.Context, values []dto.ImportMediaRequest) error { +func (s *MediaService) ImportURLRequests(ctx context.Context, values []ImportMediaRequest) error { items := make([]*biz.MediaFile, 0, len(values)) for _, value := range values { items = append(items, &biz.MediaFile{Name: value.Name, CategoryID: value.ClassID, URL: value.URL, Tag: value.Tag, Key: value.Key, Size: value.Size, Mime: value.Mime, MD5: value.MD5, UserID: value.UserID}) } return s.ImportURLs(ctx, items) } -func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit int) ([]*dto.StorageObjectResponse, string, bool, error) { +func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit int) ([]*StorageObjectResponse, string, bool, error) { items, next, more, err := s.uc.ListStorage(ctx, prefix, cursor, limit) if err != nil { return nil, "", false, err } - out := make([]*dto.StorageObjectResponse, 0, len(items)) + out := make([]*StorageObjectResponse, 0, len(items)) for _, v := range items { - out = append(out, &dto.StorageObjectResponse{Key: v.Path, Size: v.Size, LastModified: v.LastModified, ContentType: v.ContentType}) + out = append(out, &StorageObjectResponse{Key: v.Path, Size: v.Size, LastModified: v.LastModified, ContentType: v.ContentType}) } return out, next, more, nil } -func (s *MediaService) Categories(ctx context.Context) ([]*dto.MediaCategoryResponse, error) { +func (s *MediaService) Categories(ctx context.Context) ([]*MediaCategoryResponse, error) { items, err := s.uc.ListCategories(ctx) if err != nil { return nil, err } - out := make([]*dto.MediaCategoryResponse, 0, len(items)) + out := make([]*MediaCategoryResponse, 0, len(items)) for _, v := range items { out = append(out, categoryDTO(v)) } @@ -104,7 +103,7 @@ func (s *MediaService) Categories(ctx context.Context) ([]*dto.MediaCategoryResp func (s *MediaService) SaveCategory(ctx context.Context, v *biz.AttachmentCategory) error { return s.uc.SaveCategory(ctx, v) } -func (s *MediaService) SaveCategoryRequest(ctx context.Context, value *dto.CategoryRequest) error { +func (s *MediaService) SaveCategoryRequest(ctx context.Context, value *CategoryRequest) error { return s.SaveCategory(ctx, &biz.AttachmentCategory{ID: value.ID, Name: value.Name, ParentID: value.PID}) } func (s *MediaService) DeleteCategory(ctx context.Context, id uint) error { diff --git a/app/system/internal/service/media_upload.go b/app/system/internal/service/media_upload.go index 8e9fb2d..41eb870 100644 --- a/app/system/internal/service/media_upload.go +++ b/app/system/internal/service/media_upload.go @@ -3,11 +3,9 @@ package service import ( "context" "io" - - "kra/app/system/internal/dto" ) -func (s *MediaService) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*dto.InitUploadResponse, error) { +func (s *MediaService) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*InitUploadResponse, error) { session, media, chunks, err := s.uc.InitUpload(ctx, userID, name, hash, size, chunkSize, total) if err != nil { return nil, err @@ -15,14 +13,14 @@ func (s *MediaService) InitUpload(ctx context.Context, userID uint, name, hash s if media != nil { // The compatible response leaves UploadedChunks nil on an instant-upload hit, producing // JSON null while still returning uploadId: 0. - return &dto.InitUploadResponse{Instant: true, Media: mediaDTO(media)}, nil + return &InitUploadResponse{Instant: true, Media: mediaDTO(media)}, nil } - return &dto.InitUploadResponse{Instant: false, UploadID: session.ID, UploadedChunks: chunks, Media: nil}, nil + return &InitUploadResponse{Instant: false, UploadID: session.ID, UploadedChunks: chunks, Media: nil}, nil } func (s *MediaService) SaveChunk(ctx context.Context, userID, uploadID uint, index int, hash string, reader io.Reader) error { return s.uc.SaveChunk(ctx, userID, uploadID, index, hash, reader) } -func (s *MediaService) CompleteUpload(ctx context.Context, userID, uploadID uint) (*dto.MediaResponse, error) { +func (s *MediaService) CompleteUpload(ctx context.Context, userID, uploadID uint) (*MediaResponse, error) { v, err := s.uc.CompleteUpload(ctx, userID, uploadID, "") if err != nil { return nil, err diff --git a/app/system/internal/service/menu.go b/app/system/internal/service/menu.go index 9480285..04feaaa 100644 --- a/app/system/internal/service/menu.go +++ b/app/system/internal/service/menu.go @@ -4,14 +4,13 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type MenuService struct{ uc *biz.MenuUsecase } func NewMenuService(uc *biz.MenuUsecase) *MenuService { return &MenuService{uc: uc} } -func (s *MenuService) List(ctx context.Context) ([]*dto.MenuResponse, error) { +func (s *MenuService) List(ctx context.Context) ([]*MenuResponse, error) { items, err := s.uc.List(ctx) if err != nil { return nil, err @@ -19,7 +18,7 @@ func (s *MenuService) List(ctx context.Context) ([]*dto.MenuResponse, error) { return menuResponses(items), nil } -func (s *MenuService) Tree(ctx context.Context) ([]*dto.MenuResponse, error) { +func (s *MenuService) Tree(ctx context.Context) ([]*MenuResponse, error) { items, err := s.uc.Tree(ctx) if err != nil { return nil, err @@ -27,15 +26,15 @@ func (s *MenuService) Tree(ctx context.Context) ([]*dto.MenuResponse, error) { return menuResponses(items), nil } -func (s *MenuService) Create(ctx context.Context, req *dto.MenuRequest) error { +func (s *MenuService) Create(ctx context.Context, req *MenuRequest) error { return s.uc.Create(ctx, menuDomain(req)) } -func (s *MenuService) Update(ctx context.Context, req *dto.MenuRequest) error { +func (s *MenuService) Update(ctx context.Context, req *MenuRequest) error { return s.uc.Update(ctx, menuDomain(req)) } func (s *MenuService) Delete(ctx context.Context, id uint) error { return s.uc.Delete(ctx, id) } -func (s *MenuService) Find(ctx context.Context, id uint) (*dto.MenuResponse, error) { +func (s *MenuService) Find(ctx context.Context, id uint) (*MenuResponse, error) { item, err := s.uc.Find(ctx, id) if err != nil { return nil, err @@ -43,7 +42,7 @@ func (s *MenuService) Find(ctx context.Context, id uint) (*dto.MenuResponse, err return menuResponse(item), nil } -func (s *MenuService) SetAuthorityMenus(ctx context.Context, req *dto.SetAuthorityMenusRequest) error { +func (s *MenuService) SetAuthorityMenus(ctx context.Context, req *SetAuthorityMenusRequest) error { ids := make([]uint, 0, len(req.Menus)) for _, menu := range req.Menus { ids = append(ids, menu.ID) @@ -51,15 +50,15 @@ func (s *MenuService) SetAuthorityMenus(ctx context.Context, req *dto.SetAuthori return s.uc.SetAuthorityMenus(ctx, req.AuthorityID, ids) } -func (s *MenuService) AuthorityMenus(ctx context.Context, authorityID uint) ([]*dto.AuthorityMenuResponse, error) { +func (s *MenuService) AuthorityMenus(ctx context.Context, authorityID uint) ([]*AuthorityMenuResponse, error) { items, err := s.uc.AuthorityMenus(ctx, authorityID) if err != nil { return nil, err } - var out []*dto.AuthorityMenuResponse + var out []*AuthorityMenuResponse for _, item := range items { item.MenuID = item.ID - out = append(out, &dto.AuthorityMenuResponse{DynamicMenuResponse: dynamicMenuResponse(item), AuthorityID: authorityID}) + out = append(out, &AuthorityMenuResponse{DynamicMenuResponse: dynamicMenuResponse(item), AuthorityID: authorityID}) } return out, nil } @@ -70,11 +69,11 @@ func (s *MenuService) RoleIDs(ctx context.Context, menuID uint) ([]uint, error) func (s *MenuService) DefaultRouterRoleIDs(ctx context.Context, menuID uint) ([]uint, error) { return s.uc.DefaultRouterRoleIDs(ctx, menuID) } -func (s *MenuService) SetRoles(ctx context.Context, req *dto.SetMenuRolesRequest) error { +func (s *MenuService) SetRoles(ctx context.Context, req *SetMenuRolesRequest) error { return s.uc.SetRoles(ctx, req.MenuID, req.AuthorityIDs) } -func menuDomain(v *dto.MenuRequest) *biz.Menu { +func menuDomain(v *MenuRequest) *biz.Menu { menu := &biz.Menu{CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, ID: v.ID, ParentID: v.ParentID, Path: v.Path, Name: v.Name, Hidden: v.Hidden, Component: v.Component, Sort: v.Sort, ActiveName: v.Meta.ActiveName, KeepAlive: v.Meta.KeepAlive, DefaultMenu: v.Meta.DefaultMenu, Title: v.Meta.Title, Icon: v.Meta.Icon, CloseTab: v.Meta.CloseTab, TransitionType: v.Meta.TransitionType} menu.Buttons = make([]*biz.MenuButton, 0, len(v.MenuButtons)) for _, button := range v.MenuButtons { @@ -87,44 +86,44 @@ func menuDomain(v *dto.MenuRequest) *biz.Menu { return menu } -func menuResponse(menu *biz.Menu) *dto.MenuResponse { - var children []*dto.MenuResponse +func menuResponse(menu *biz.Menu) *MenuResponse { + var children []*MenuResponse if menu.Children != nil { - children = make([]*dto.MenuResponse, 0, len(menu.Children)) + children = make([]*MenuResponse, 0, len(menu.Children)) for _, child := range menu.Children { children = append(children, menuResponse(child)) } } - var buttons []dto.MenuButtonResponse + var buttons []MenuButtonResponse if menu.Buttons != nil { - buttons = make([]dto.MenuButtonResponse, 0, len(menu.Buttons)) + buttons = make([]MenuButtonResponse, 0, len(menu.Buttons)) for _, button := range menu.Buttons { - buttons = append(buttons, dto.MenuButtonResponse{CreatedAt: button.CreatedAt, UpdatedAt: button.UpdatedAt, ID: button.ID, Name: button.Name, Description: button.Description, SysBaseMenuID: button.MenuID}) + buttons = append(buttons, MenuButtonResponse{CreatedAt: button.CreatedAt, UpdatedAt: button.UpdatedAt, ID: button.ID, Name: button.Name, Description: button.Description, SysBaseMenuID: button.MenuID}) } } - var parameters []dto.MenuParameterResponse + var parameters []MenuParameterResponse if menu.Parameters != nil { - parameters = make([]dto.MenuParameterResponse, 0, len(menu.Parameters)) + parameters = make([]MenuParameterResponse, 0, len(menu.Parameters)) for _, parameter := range menu.Parameters { - parameters = append(parameters, dto.MenuParameterResponse{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + parameters = append(parameters, MenuParameterResponse{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } } - return &dto.MenuResponse{CreatedAt: menu.CreatedAt, UpdatedAt: menu.UpdatedAt, ID: menu.ID, MenuID: menu.MenuID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: dto.MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: buttons} + return &MenuResponse{CreatedAt: menu.CreatedAt, UpdatedAt: menu.UpdatedAt, ID: menu.ID, MenuID: menu.MenuID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: buttons} } -func dynamicMenuResponse(menu *biz.Menu) *dto.DynamicMenuResponse { - var children []*dto.DynamicMenuResponse +func dynamicMenuResponse(menu *biz.Menu) *DynamicMenuResponse { + var children []*DynamicMenuResponse if menu.Children != nil { - children = make([]*dto.DynamicMenuResponse, 0, len(menu.Children)) + children = make([]*DynamicMenuResponse, 0, len(menu.Children)) for _, child := range menu.Children { children = append(children, dynamicMenuResponse(child)) } } - var parameters []dto.MenuParameterResponse + var parameters []MenuParameterResponse if menu.Parameters != nil { - parameters = make([]dto.MenuParameterResponse, 0, len(menu.Parameters)) + parameters = make([]MenuParameterResponse, 0, len(menu.Parameters)) for _, parameter := range menu.Parameters { - parameters = append(parameters, dto.MenuParameterResponse{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) + parameters = append(parameters, MenuParameterResponse{CreatedAt: parameter.CreatedAt, UpdatedAt: parameter.UpdatedAt, ID: parameter.ID, SysBaseMenuID: parameter.MenuID, Type: parameter.Type, Key: parameter.Key, Value: parameter.Value}) } } var buttonAuthorities map[string]uint @@ -137,22 +136,22 @@ func dynamicMenuResponse(menu *biz.Menu) *dto.DynamicMenuResponse { } buttonAuthorities[button.Name] = button.AuthorityID } - return &dto.DynamicMenuResponse{CreatedAt: menu.CreatedAt, UpdatedAt: menu.UpdatedAt, ID: menu.ID, MenuID: menu.MenuID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: dto.MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: nil, Buttons: buttonAuthorities} + return &DynamicMenuResponse{CreatedAt: menu.CreatedAt, UpdatedAt: menu.UpdatedAt, ID: menu.ID, MenuID: menu.MenuID, ParentID: menu.ParentID, Path: menu.Path, Name: menu.Name, Hidden: menu.Hidden, Component: menu.Component, Sort: menu.Sort, Meta: MenuMetaResponse{ActiveName: menu.ActiveName, KeepAlive: menu.KeepAlive, DefaultMenu: menu.DefaultMenu, Title: menu.Title, Icon: menu.Icon, CloseTab: menu.CloseTab, TransitionType: menu.TransitionType}, Children: children, Parameters: parameters, MenuButtons: nil, Buttons: buttonAuthorities} } -func menuResponses(items []*biz.Menu) []*dto.MenuResponse { +func menuResponses(items []*biz.Menu) []*MenuResponse { if items == nil { return nil } - out := make([]*dto.MenuResponse, 0, len(items)) + out := make([]*MenuResponse, 0, len(items)) for _, item := range items { out = append(out, menuResponse(item)) } return out } -func dynamicMenuResponses(items []*biz.Menu) []*dto.DynamicMenuResponse { - out := make([]*dto.DynamicMenuResponse, 0, len(items)) +func dynamicMenuResponses(items []*biz.Menu) []*DynamicMenuResponse { + out := make([]*DynamicMenuResponse, 0, len(items)) for _, item := range items { out = append(out, dynamicMenuResponse(item)) } diff --git a/app/system/internal/service/menu_test.go b/app/system/internal/service/menu_test.go index 7b5c7a7..43e729b 100644 --- a/app/system/internal/service/menu_test.go +++ b/app/system/internal/service/menu_test.go @@ -6,20 +6,19 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) func TestMenuDomainPreservesRelationIdentity(t *testing.T) { createdAt := time.Date(2025, time.March, 4, 5, 6, 7, 0, time.UTC) updatedAt := time.Date(2025, time.April, 5, 6, 7, 8, 0, time.UTC) - menu := menuDomain(&dto.MenuRequest{ + menu := menuDomain(&MenuRequest{ CreatedAt: createdAt, UpdatedAt: updatedAt, ID: 10, - MenuButtons: []dto.MenuButtonRequest{{ + MenuButtons: []MenuButtonRequest{{ CreatedAt: createdAt, UpdatedAt: updatedAt, ID: 31, Name: "edit", Description: "编辑", }}, - Parameters: []dto.MenuParameterRequest{{ + Parameters: []MenuParameterRequest{{ CreatedAt: createdAt, UpdatedAt: updatedAt, ID: 21, Type: "query", Key: "id", Value: "1", }}, }) diff --git a/app/system/internal/service/parameter.go b/app/system/internal/service/parameter.go index b8c7dc3..6a3b2d5 100644 --- a/app/system/internal/service/parameter.go +++ b/app/system/internal/service/parameter.go @@ -5,7 +5,6 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type ParameterService struct{ uc *biz.ParameterUsecase } @@ -14,40 +13,40 @@ func NewParameterService(uc *biz.ParameterUsecase) *ParameterService { return &ParameterService{uc: uc} } -func parameterDomain(value *dto.SystemParameterRequest) *biz.SystemParameter { +func parameterDomain(value *SystemParameterRequest) *biz.SystemParameter { return &biz.SystemParameter{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, Name: value.Name, Key: value.Key, Value: value.Value, Desc: value.Description} } -func (s *ParameterService) CreateParameterRequest(ctx context.Context, req *dto.SystemParameterRequest) error { +func (s *ParameterService) CreateParameterRequest(ctx context.Context, req *SystemParameterRequest) error { return s.CreateParameter(ctx, parameterDomain(req)) } -func (s *ParameterService) UpdateParameterRequest(ctx context.Context, req *dto.SystemParameterRequest) error { +func (s *ParameterService) UpdateParameterRequest(ctx context.Context, req *SystemParameterRequest) error { return s.UpdateParameter(ctx, parameterDomain(req)) } -func (s *ParameterService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]*dto.SystemParameterResponse, int64, error) { +func (s *ParameterService) ParametersFilter(ctx context.Context, page, size int, name, key string, start, end *time.Time) ([]*SystemParameterResponse, int64, error) { return s.Parameters(ctx, page, size, &biz.SystemParameter{Name: name, Key: key, StartCreatedAt: start, EndCreatedAt: end}) } -func parameterDTO(v *biz.SystemParameter) *dto.SystemParameterResponse { - return &dto.SystemParameterResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc} +func parameterDTO(v *biz.SystemParameter) *SystemParameterResponse { + return &SystemParameterResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Key: v.Key, Value: v.Value, Desc: v.Desc} } -func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*dto.SystemParameterResponse, int64, error) { +func (s *ParameterService) Parameters(ctx context.Context, page, size int, q *biz.SystemParameter) ([]*SystemParameterResponse, int64, error) { items, total, err := s.uc.ListParameters(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.SystemParameterResponse, 0, len(items)) + out := make([]*SystemParameterResponse, 0, len(items)) for _, v := range items { out = append(out, parameterDTO(v)) } return out, total, nil } -func (s *ParameterService) ParameterByID(ctx context.Context, id string) (*dto.SystemParameterResponse, error) { +func (s *ParameterService) ParameterByID(ctx context.Context, id string) (*SystemParameterResponse, error) { v, err := s.uc.FindParameterByID(ctx, id) if err != nil { return nil, err } return parameterDTO(v), nil } -func (s *ParameterService) ParameterByKey(ctx context.Context, key string) (*dto.SystemParameterResponse, error) { +func (s *ParameterService) ParameterByKey(ctx context.Context, key string) (*SystemParameterResponse, error) { v, err := s.uc.FindParameterByKey(ctx, key) if err != nil { return nil, err diff --git a/app/system/internal/service/payment.go b/app/system/internal/service/payment.go index 112ec35..856d58b 100644 --- a/app/system/internal/service/payment.go +++ b/app/system/internal/service/payment.go @@ -5,38 +5,37 @@ import ( "errors" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type PaymentService struct{ uc *biz.PaymentUsecase } func NewPaymentService(uc *biz.PaymentUsecase) *PaymentService { return &PaymentService{uc: uc} } -func (s *PaymentService) Configs(ctx context.Context) ([]*dto.PaymentConfigResponse, error) { +func (s *PaymentService) Configs(ctx context.Context) ([]*PaymentConfigResponse, error) { configs, err := s.uc.Configs(ctx) if err != nil { return nil, err } - result := make([]*dto.PaymentConfigResponse, 0, len(configs)) + result := make([]*PaymentConfigResponse, 0, len(configs)) for _, config := range configs { if config == nil { continue } - result = append(result, &dto.PaymentConfigResponse{Provider: config.Provider, Enabled: config.Enabled, Config: config.Values}) + result = append(result, &PaymentConfigResponse{Provider: config.Provider, Enabled: config.Enabled, Config: config.Values}) } return result, nil } -func (s *PaymentService) SaveConfig(ctx context.Context, req *dto.PaymentConfigRequest) error { +func (s *PaymentService) SaveConfig(ctx context.Context, req *PaymentConfigRequest) error { if req == nil { return errors.New("支付配置请求为空") } return s.uc.SaveConfig(ctx, &biz.PaymentConfig{Provider: req.Provider, Enabled: req.Enabled, Values: req.Config}) } -func (s *PaymentService) Order(ctx context.Context, provider, tradeNo string) (*dto.PaymentOrderResponse, error) { +func (s *PaymentService) Order(ctx context.Context, provider, tradeNo string) (*PaymentOrderResponse, error) { order, err := s.uc.Order(ctx, provider, tradeNo) if err != nil { return nil, err } - return &dto.PaymentOrderResponse{ + return &PaymentOrderResponse{ ID: order.ID, Provider: order.Provider, TradeNo: order.TradeNo, ProviderTradeNo: order.ProviderTradeNo, BusinessType: order.BusinessType, BusinessID: order.BusinessID, Subject: order.Subject, @@ -53,7 +52,7 @@ func (s *PaymentService) Order(ctx context.Context, provider, tradeNo string) (* }, nil } -func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRequest) ([]*dto.PaymentOrderResponse, int64, int, int, error) { +func (s *PaymentService) Orders(ctx context.Context, req *PaymentOrderListRequest) ([]*PaymentOrderResponse, int64, int, int, error) { if req == nil { return nil, 0, 1, 10, errors.New("支付订单列表请求为空") } @@ -71,7 +70,7 @@ func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRe if err != nil { return nil, 0, page, pageSize, err } - result := make([]*dto.PaymentOrderResponse, 0, len(items)) + result := make([]*PaymentOrderResponse, 0, len(items)) for _, item := range items { converted, convertErr := paymentOrderResponse(item) if convertErr != nil { @@ -82,11 +81,11 @@ func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRe return result, total, page, pageSize, nil } -func paymentOrderResponse(order *biz.PaymentOrder) (*dto.PaymentOrderResponse, error) { +func paymentOrderResponse(order *biz.PaymentOrder) (*PaymentOrderResponse, error) { if order == nil { return nil, errors.New("支付订单为空") } - return &dto.PaymentOrderResponse{ + return &PaymentOrderResponse{ ID: order.ID, Provider: order.Provider, TradeNo: order.TradeNo, ProviderTradeNo: order.ProviderTradeNo, BusinessType: order.BusinessType, BusinessID: order.BusinessID, Subject: order.Subject, PaymentMode: order.PaymentMode, OriginalAmount: order.OriginalAmount, Amount: order.Amount, PaidAmount: order.PaidAmount, @@ -100,7 +99,7 @@ func paymentOrderResponse(order *biz.PaymentOrder) (*dto.PaymentOrderResponse, e PaidAt: order.PaidAt, FulfilledAt: order.FulfilledAt, RefundedAt: order.RefundedAt, }, nil } -func (s *PaymentService) Create(ctx context.Context, req *dto.PaymentRequest) (*dto.PaymentResultResponse, error) { +func (s *PaymentService) Create(ctx context.Context, req *PaymentRequest) (*PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付请求为空") } @@ -113,21 +112,29 @@ func (s *PaymentService) Create(ctx context.Context, req *dto.PaymentRequest) (* }) return paymentResultResponse(result), err } -func (s *PaymentService) Query(ctx context.Context, req *dto.PaymentQueryRequest) (*dto.PaymentResultResponse, error) { +func (s *PaymentService) Query(ctx context.Context, req *PaymentQueryRequest) (*PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付查询请求为空") } result, err := s.uc.Query(ctx, req.Provider, req.TradeNo) return paymentResultResponse(result), err } -func (s *PaymentService) Refund(ctx context.Context, req *dto.PaymentRefundRequest) (*dto.PaymentResultResponse, error) { +func (s *PaymentService) Refund(ctx context.Context, req *PaymentRefundRequest) (*PaymentResultResponse, error) { if req == nil { return nil, errors.New("支付退款请求为空") } result, err := s.uc.Refund(ctx, req.Provider, req.TradeNo, req.Amount) return paymentResultResponse(result), err } -func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackRequest) (dto.PaymentCallbackAck, error) { + +func (s *PaymentService) Fulfill(ctx context.Context, req *PaymentFulfillRequest) (*PaymentResultResponse, error) { + if req == nil { + return nil, errors.New("支付发货请求为空") + } + result, err := s.uc.Fulfill(ctx, req.Provider, req.TradeNo) + return paymentResultResponse(result), err +} +func (s *PaymentService) Callback(ctx context.Context, req *PaymentCallbackRequest) (PaymentCallbackAck, error) { if req == nil { return paymentCallbackAckResponse(biz.DefaultPaymentCallbackAck("", false)), errors.New("支付回调请求为空") } @@ -142,15 +149,15 @@ func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackR return paymentCallbackAckResponse(ack), nil } -func DefaultPaymentCallbackAck(provider string, success bool) dto.PaymentCallbackAck { +func DefaultPaymentCallbackAck(provider string, success bool) PaymentCallbackAck { return paymentCallbackAckResponse(biz.DefaultPaymentCallbackAck(provider, success)) } -func paymentResultResponse(result *biz.PaymentResult) *dto.PaymentResultResponse { +func paymentResultResponse(result *biz.PaymentResult) *PaymentResultResponse { if result == nil { return nil } - return &dto.PaymentResultResponse{ + return &PaymentResultResponse{ Provider: result.Provider, Status: result.Status, TradeNo: result.TradeNo, ProviderTradeNo: result.ProviderTradeNo, Amount: result.Amount, PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount, @@ -164,6 +171,6 @@ func paymentResultResponse(result *biz.PaymentResult) *dto.PaymentResultResponse } } -func paymentCallbackAckResponse(ack biz.PaymentCallbackAck) dto.PaymentCallbackAck { - return dto.PaymentCallbackAck{StatusCode: ack.StatusCode, ContentType: ack.ContentType, Body: ack.Body} +func paymentCallbackAckResponse(ack biz.PaymentCallbackAck) PaymentCallbackAck { + return PaymentCallbackAck{StatusCode: ack.StatusCode, ContentType: ack.ContentType, Body: ack.Body} } diff --git a/app/system/internal/service/position.go b/app/system/internal/service/position.go index 37ae64e..d7b3bf2 100644 --- a/app/system/internal/service/position.go +++ b/app/system/internal/service/position.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type PositionService struct{ uc *biz.PositionUsecase } @@ -13,31 +12,31 @@ func NewPositionService(uc *biz.PositionUsecase) *PositionService { return &PositionService{uc: uc} } -func positionDomain(value *dto.PositionRequest) *biz.Position { +func positionDomain(value *PositionRequest) *biz.Position { return &biz.Position{ID: value.ID, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} } -func positionResponse(value *biz.Position) *dto.PositionResponse { - return &dto.PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} +func positionResponse(value *biz.Position) *PositionResponse { + return &PositionResponse{ID: value.ID, CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: nil, Name: value.Name, Code: value.Code, Sort: value.Sort, Status: value.Status, Remark: value.Remark} } -func (s *PositionService) Positions(ctx context.Context, req *dto.PositionListRequest) ([]*dto.PositionResponse, int64, error) { +func (s *PositionService) Positions(ctx context.Context, req *PositionListRequest) ([]*PositionResponse, int64, error) { items, total, err := s.uc.Positions(ctx, req.Page, req.PageSize, &biz.PositionListFilter{Name: req.Name, Code: req.Code, Status: req.Status}) if err != nil { return nil, 0, err } - out := make([]*dto.PositionResponse, 0, len(items)) + out := make([]*PositionResponse, 0, len(items)) for _, item := range items { out = append(out, positionResponse(item)) } return out, total, nil } -func (s *PositionService) Create(ctx context.Context, req *dto.PositionRequest) error { +func (s *PositionService) Create(ctx context.Context, req *PositionRequest) error { return s.uc.CreatePosition(ctx, positionDomain(req)) } -func (s *PositionService) Update(ctx context.Context, req *dto.PositionRequest) error { +func (s *PositionService) Update(ctx context.Context, req *PositionRequest) error { return s.uc.UpdatePosition(ctx, positionDomain(req)) } @@ -45,7 +44,7 @@ func (s *PositionService) Delete(ctx context.Context, id uint) error { return s.uc.DeletePosition(ctx, id) } -func (s *PositionService) Position(ctx context.Context, id uint) (*dto.PositionResponse, error) { +func (s *PositionService) Position(ctx context.Context, id uint) (*PositionResponse, error) { value, err := s.uc.Position(ctx, id) if err != nil { return nil, err @@ -57,10 +56,10 @@ func (s *PositionService) UserIDs(ctx context.Context, id uint) ([]uint, error) return s.uc.PositionUserIDs(ctx, id) } -func (s *PositionService) SetUsers(ctx context.Context, req *dto.SetPositionUsersRequest) error { +func (s *PositionService) SetUsers(ctx context.Context, req *SetPositionUsersRequest) error { return s.uc.SetPositionUsers(ctx, req.PositionID, req.UserIDs) } -func (s *PositionService) SetUserPositions(ctx context.Context, req *dto.SetUserPositionsRequest) error { +func (s *PositionService) SetUserPositions(ctx context.Context, req *SetUserPositionsRequest) error { return s.uc.SetUserPositions(ctx, req.ID, req.PositionIDs) } diff --git a/app/system/internal/routeinfo/metadata.go b/app/system/internal/service/route_metadata.go similarity index 99% rename from app/system/internal/routeinfo/metadata.go rename to app/system/internal/service/route_metadata.go index 05e8e21..1d968e0 100644 --- a/app/system/internal/routeinfo/metadata.go +++ b/app/system/internal/service/route_metadata.go @@ -1,4 +1,4 @@ -package routeinfo +package service import "strings" @@ -176,7 +176,7 @@ var apiMetadata = map[string]apiMetadataValue{ } // Metadata returns the administration group and description for a route. -func Metadata(method, path string) (string, string) { +func RouteMetadata(method, path string) (string, string) { if value, ok := apiMetadata[strings.ToUpper(method)+" "+path]; ok { return value.group, value.description } diff --git a/app/system/internal/service/security.go b/app/system/internal/service/security.go index 2883868..2f1b90f 100644 --- a/app/system/internal/service/security.go +++ b/app/system/internal/service/security.go @@ -5,7 +5,6 @@ import ( "errors" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type PasswordPolicyError struct{ Err error } @@ -18,26 +17,26 @@ func IsPasswordPolicyError(err error) bool { return errors.As(err, &policyErr) } -func securityDTO(v *biz.SecurityConfig) *dto.SecurityConfigResponse { - return &dto.SecurityConfigResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} +func securityDTO(v *biz.SecurityConfig) *SecurityConfigResponse { + return &SecurityConfigResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, CaptchaOpen: v.CaptchaOpen, CaptchaTimeout: v.CaptchaTimeout, KeyLong: v.KeyLong, ImgWidth: v.ImgWidth, ImgHeight: v.ImgHeight, PwdMinLength: v.PwdMinLength, PwdRequireUpper: v.PwdRequireUpper, PwdRequireLower: v.PwdRequireLower, PwdRequireDigit: v.PwdRequireDigit, PwdRequireSpecial: v.PwdRequireSpecial, LimitEnable: v.LimitEnable, LimitWindow: v.LimitWindow, LimitCount: v.LimitCount, LockEnable: v.LockEnable, LockThreshold: v.LockThreshold, LockDuration: v.LockDuration, PwdExpireEnable: v.PwdExpireEnable, PwdExpireDays: v.PwdExpireDays, ForceNewUserChangePassword: v.ForceNewUserChangePassword} } func (s *SecurityService) CurrentSecurity(ctx context.Context) (*biz.SecurityConfig, error) { return s.uc.Current(ctx) } -func (s *SecurityService) Security(ctx context.Context) (*dto.SecurityConfigResponse, error) { +func (s *SecurityService) Security(ctx context.Context) (*SecurityConfigResponse, error) { value, err := s.uc.Security(ctx) if err != nil { return nil, err } return securityDTO(value), nil } -func (s *SecurityService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (*dto.SecurityConfigResponse, error) { +func (s *SecurityService) SaveSecurity(ctx context.Context, value *biz.SecurityConfig) (*SecurityConfigResponse, error) { if err := s.uc.UpdateSecurity(ctx, value); err != nil { return nil, err } return securityDTO(value), nil } -func (s *SecurityService) SaveSecurityRequest(ctx context.Context, value *dto.SecurityConfigRequest) (*dto.SecurityConfigResponse, error) { +func (s *SecurityService) SaveSecurityRequest(ctx context.Context, value *SecurityConfigRequest) (*SecurityConfigResponse, error) { return s.SaveSecurity(ctx, &biz.SecurityConfig{ ID: value.ID, CaptchaOpen: value.CaptchaOpen, CaptchaTimeout: value.CaptchaTimeout, KeyLong: value.KeyLong, ImgWidth: value.ImgWidth, ImgHeight: value.ImgHeight, diff --git a/app/system/internal/service/system_config.go b/app/system/internal/service/system_config.go index a57ce13..6caebbe 100644 --- a/app/system/internal/service/system_config.go +++ b/app/system/internal/service/system_config.go @@ -3,8 +3,6 @@ package service import ( "context" "encoding/json" - - "kra/app/system/internal/dto" ) func (s *SystemConfigService) PersistConfig(ctx context.Context) error { @@ -21,6 +19,6 @@ func (s *SystemConfigService) SystemConfig() (json.RawMessage, error) { return s.uc.ConfigurationJSON() } -func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *dto.SetSystemConfigRequest) error { +func (s *SystemConfigService) SaveSystemConfig(ctx context.Context, req *SetSystemConfigRequest) error { return s.uc.SaveConfigurationJSON(ctx, req.Config) } diff --git a/app/system/internal/service/system_info.go b/app/system/internal/service/system_info.go index cb50294..36556bf 100644 --- a/app/system/internal/service/system_info.go +++ b/app/system/internal/service/system_info.go @@ -4,14 +4,12 @@ import ( "runtime" "time" - "kra/app/system/internal/dto" - "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/disk" "github.com/shirou/gopsutil/v4/mem" ) -func (s *SystemConfigService) ServerInfo() (*dto.ServerInfoResponse, error) { +func (s *SystemConfigService) ServerInfo() (*ServerInfoResponse, error) { physicalCores, err := cpu.Counts(false) if err != nil { return nil, err @@ -24,19 +22,19 @@ func (s *SystemConfigService) ServerInfo() (*dto.ServerInfoResponse, error) { if err != nil { return nil, err } - diskInfo := make([]dto.ServerDiskResponse, 0) + diskInfo := make([]ServerDiskResponse, 0) for _, mountPoint := range s.DiskMountPoints() { usage, usageErr := disk.Usage(mountPoint) if usageErr != nil { return nil, usageErr } - diskInfo = append(diskInfo, dto.ServerDiskResponse{MountPoint: mountPoint, UsedMB: usage.Used / 1024 / 1024, UsedGB: usage.Used / 1024 / 1024 / 1024, TotalMB: usage.Total / 1024 / 1024, TotalGB: usage.Total / 1024 / 1024 / 1024, UsedPercent: int(usage.UsedPercent)}) + diskInfo = append(diskInfo, ServerDiskResponse{MountPoint: mountPoint, UsedMB: usage.Used / 1024 / 1024, UsedGB: usage.Used / 1024 / 1024 / 1024, TotalMB: usage.Total / 1024 / 1024, TotalGB: usage.Total / 1024 / 1024 / 1024, UsedPercent: int(usage.UsedPercent)}) } usedMB, totalMB := memory.Used/1024/1024, memory.Total/1024/1024 - return &dto.ServerInfoResponse{ - OS: dto.ServerOSResponse{GOOS: runtime.GOOS, NumCPU: runtime.NumCPU(), Compiler: runtime.Compiler, GoVersion: runtime.Version(), NumGoroutine: runtime.NumGoroutine()}, - CPU: dto.ServerCPUResponse{CPUs: cpuPercent, Cores: physicalCores}, - RAM: dto.ServerRAMResponse{UsedMB: usedMB, TotalMB: totalMB, UsedPercent: int(memory.UsedPercent)}, + return &ServerInfoResponse{ + OS: ServerOSResponse{GOOS: runtime.GOOS, NumCPU: runtime.NumCPU(), Compiler: runtime.Compiler, GoVersion: runtime.Version(), NumGoroutine: runtime.NumGoroutine()}, + CPU: ServerCPUResponse{CPUs: cpuPercent, Cores: physicalCores}, + RAM: ServerRAMResponse{UsedMB: usedMB, TotalMB: totalMB, UsedPercent: int(memory.UsedPercent)}, Disk: diskInfo, }, nil } diff --git a/app/system/internal/service/system_init.go b/app/system/internal/service/system_init.go index 7e405f8..4a7bb80 100644 --- a/app/system/internal/service/system_init.go +++ b/app/system/internal/service/system_init.go @@ -4,11 +4,9 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" - "kra/app/system/internal/routeinfo" ) -func (s *SystemConfigService) Initialize(ctx context.Context, input *dto.DatabaseInitRequest, apis []*biz.API) error { +func (s *SystemConfigService) Initialize(ctx context.Context, input *DatabaseInitRequest, apis []*biz.API) error { driver, config := input.DBType, "" switch driver { case "mysql": @@ -25,11 +23,11 @@ func (s *SystemConfigService) Initialize(ctx context.Context, input *dto.Databas return s.uc.Initialize(ctx, &biz.DatabaseConfig{Driver: driver, Host: input.Host, Port: input.Port, User: input.UserName, Password: input.Password, Name: input.DBName, Path: input.DBPath, Config: config, Template: input.Template, AdminPassword: input.AdminPassword, APIs: apis}) } -func (s *SystemConfigService) InitializeRoutes(ctx context.Context, input *dto.DatabaseInitRequest, routes []dto.Route) error { +func (s *SystemConfigService) InitializeRoutes(ctx context.Context, input *DatabaseInitRequest, routes []Route) error { apis := make([]*biz.API, 0, len(routes)) for _, route := range routes { path := NormalizeRoutePath(route.Path, s.settings.RouterPrefix()) - group, description := routeinfo.Metadata(route.Method, path) + group, description := RouteMetadata(route.Method, path) apis = append(apis, &biz.API{Path: path, Method: route.Method, APIGroup: group, Description: description}) } return s.Initialize(ctx, input, apis) diff --git a/app/system/internal/service/task.go b/app/system/internal/service/task.go index 79c36d8..4dfc515 100644 --- a/app/system/internal/service/task.go +++ b/app/system/internal/service/task.go @@ -6,7 +6,6 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type TaskService struct { @@ -16,20 +15,20 @@ type TaskService struct { func NewTaskService(uc *biz.TaskApplicationUsecase) *TaskService { return &TaskService{uc: uc} } -func taskDomain(v *dto.TaskRequest) *biz.TimedTask { +func taskDomain(v *TaskRequest) *biz.TimedTask { return &biz.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } -func (s *TaskService) CreateRequest(ctx context.Context, req *dto.TaskRequest) (uint, error) { +func (s *TaskService) CreateRequest(ctx context.Context, req *TaskRequest) (uint, error) { value := taskDomain(req) if err := s.Create(ctx, value); err != nil { return 0, err } return value.ID, nil } -func (s *TaskService) UpdateRequest(ctx context.Context, req *dto.TaskRequest) error { +func (s *TaskService) UpdateRequest(ctx context.Context, req *TaskRequest) error { return s.Update(ctx, taskDomain(req)) } -func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]*dto.TaskResponse, int64, error) { +func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]*TaskResponse, int64, error) { return s.Tasks(ctx, page, size, &biz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}) } func (s *TaskService) Create(ctx context.Context, v *biz.TimedTask) error { @@ -44,15 +43,15 @@ func (s *TaskService) Delete(ctx context.Context, id uint) error { func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error { return s.uc.Toggle(ctx, id, enabled) } -func taskDTO(v *biz.TimedTask, next *time.Time) *dto.TaskResponse { - return &dto.TaskResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: json.RawMessage(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: json.RawMessage(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled, NextRunAt: next} +func taskDTO(v *biz.TimedTask, next *time.Time) *TaskResponse { + return &TaskResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: json.RawMessage(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: json.RawMessage(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled, NextRunAt: next} } -func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*dto.TaskResponse, int64, error) { +func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTask) ([]*TaskResponse, int64, error) { items, total, next, err := s.uc.List(ctx, page, size, q) if err != nil { return nil, 0, err } - out := make([]*dto.TaskResponse, 0, len(items)) + out := make([]*TaskResponse, 0, len(items)) for _, v := range items { var ptr *time.Time if value, ok := next[v.ID]; ok { @@ -64,14 +63,14 @@ func (s *TaskService) Tasks(ctx context.Context, page, size int, q *biz.TimedTas return out, total, nil } -func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]*dto.TaskLogResponse, int64, error) { +func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, status string) ([]*TaskLogResponse, int64, error) { items, total, err := s.uc.Logs(ctx, page, size, taskID, status) if err != nil { return nil, 0, err } - out := make([]*dto.TaskLogResponse, 0, len(items)) + out := make([]*TaskLogResponse, 0, len(items)) for _, v := range items { - out = append(out, &dto.TaskLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}) + out = append(out, &TaskLogResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}) } return out, total, nil } @@ -81,11 +80,11 @@ func (s *TaskService) Reload(ctx context.Context) error { return s.uc func (s *TaskService) Subscribe(userID uint) chan []byte { return s.uc.Subscribe(userID) } func (s *TaskService) Unsubscribe(userID uint, events chan []byte) { s.uc.Unsubscribe(userID, events) } -func (s *TaskService) RegisteredMethods() []*dto.TaskMethodResponse { +func (s *TaskService) RegisteredMethods() []*TaskMethodResponse { methods := s.uc.RegisteredMethods() - out := make([]*dto.TaskMethodResponse, 0, len(methods)) + out := make([]*TaskMethodResponse, 0, len(methods)) for _, method := range methods { - out = append(out, &dto.TaskMethodResponse{Name: method.Name, Description: method.Description}) + out = append(out, &TaskMethodResponse{Name: method.Name, Description: method.Description}) } return out } diff --git a/app/system/internal/service/user.go b/app/system/internal/service/user.go index 98a2769..b536681 100644 --- a/app/system/internal/service/user.go +++ b/app/system/internal/service/user.go @@ -4,7 +4,6 @@ import ( "context" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type userInput struct { @@ -24,23 +23,23 @@ func NewUserService(uc *biz.UserUsecase, settings *SecurityService) *UserService return &UserService{uc: uc, settings: settings} } -func userRequestInput(value *dto.UserRequest) userInput { +func userRequestInput(value *UserRequest) userInput { return userInput{ID: value.ID, Username: value.Username, Password: value.Password, NickName: value.NickName, HeaderImg: value.HeaderImg, AuthorityID: value.AuthorityID, AuthorityIDs: value.AuthorityIDs, Enable: value.Enable, Phone: value.Phone, Email: value.Email} } -func (s *UserService) ListUsersRequest(ctx context.Context, value *dto.UserListRequest) ([]*dto.UserResponse, int64, error) { +func (s *UserService) ListUsersRequest(ctx context.Context, value *UserListRequest) ([]*UserResponse, int64, error) { return s.ListUsers(ctx, value.Page, value.PageSize, &biz.UserListFilter{Username: value.Username, NickName: value.NickName, Phone: value.Phone, Email: value.Email, OrderKey: value.OrderKey, Desc: value.Desc}) } -func (s *UserService) CreateUserRequest(ctx context.Context, value *dto.UserRequest) (*dto.UserResponse, error) { +func (s *UserService) CreateUserRequest(ctx context.Context, value *UserRequest) (*UserResponse, error) { return s.createUser(ctx, userRequestInput(value)) } -func (s *UserService) UpdateUserRequest(ctx context.Context, value *dto.UserRequest) error { +func (s *UserService) UpdateUserRequest(ctx context.Context, value *UserRequest) error { return s.updateUser(ctx, userRequestInput(value)) } -func (s *UserService) UpdateSelfUserRequest(ctx context.Context, id uint, value *dto.SelfUserRequest) error { +func (s *UserService) UpdateSelfUserRequest(ctx context.Context, id uint, value *SelfUserRequest) error { return s.updateSelfUser(ctx, userInput{ID: id, NickName: value.NickName, HeaderImg: value.HeaderImg, Phone: value.Phone, Email: value.Email, Enable: value.Enable}) } -func (s *UserService) User(ctx context.Context, id uint) (*dto.UserResponse, error) { +func (s *UserService) User(ctx context.Context, id uint) (*UserResponse, error) { value, err := s.uc.User(ctx, id) if err != nil { return nil, err @@ -48,7 +47,7 @@ func (s *UserService) User(ctx context.Context, id uint) (*dto.UserResponse, err return convertUser(value), nil } -func (s *UserService) UserByUUID(ctx context.Context, uuid string) (*dto.UserResponse, error) { +func (s *UserService) UserByUUID(ctx context.Context, uuid string) (*UserResponse, error) { value, err := s.uc.UserByUUID(ctx, uuid) if err != nil { return nil, err @@ -56,7 +55,7 @@ func (s *UserService) UserByUUID(ctx context.Context, uuid string) (*dto.UserRes return convertUser(value), nil } -func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]*dto.DynamicMenuResponse, error) { +func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]*DynamicMenuResponse, error) { menus, err := s.uc.Menus(ctx, authorityID) if err != nil { return nil, err @@ -64,31 +63,31 @@ func (s *UserService) Menus(ctx context.Context, authorityID uint) ([]*dto.Dynam return dynamicMenuResponses(menus), nil } -func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*dto.UserResponse, int64, error) { +func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, filter *biz.UserListFilter) ([]*UserResponse, int64, error) { users, total, err := s.uc.ListUsers(ctx, page, pageSize, filter) if err != nil { return nil, 0, err } - result := make([]*dto.UserResponse, 0, len(users)) + result := make([]*UserResponse, 0, len(users)) for _, user := range users { result = append(result, convertUser(user)) } return result, total, nil } -func (s *UserService) Authorities(ctx context.Context) ([]*dto.AuthorityResponse, error) { +func (s *UserService) Authorities(ctx context.Context) ([]*AuthorityResponse, error) { values, err := s.uc.Authorities(ctx) if err != nil { return nil, err } - result := make([]*dto.AuthorityResponse, 0, len(values)) + result := make([]*AuthorityResponse, 0, len(values)) for _, value := range values { result = append(result, convertAuthority(*value)) } return result, nil } -func (s *UserService) createUser(ctx context.Context, input userInput) (*dto.UserResponse, error) { +func (s *UserService) createUser(ctx context.Context, input userInput) (*UserResponse, error) { if err := s.settings.ValidatePassword(ctx, input.Password); err != nil { return nil, err } diff --git a/app/system/internal/service/user_conversion.go b/app/system/internal/service/user_conversion.go index ed450e3..0b709a8 100644 --- a/app/system/internal/service/user_conversion.go +++ b/app/system/internal/service/user_conversion.go @@ -2,43 +2,42 @@ package service import ( "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) -func convertAuthority(value biz.Authority) *dto.AuthorityResponse { - var menus []*dto.MenuResponse +func convertAuthority(value biz.Authority) *AuthorityResponse { + var menus []*MenuResponse if value.Menus != nil { menus = menuResponses(value.Menus) } - return &dto.AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: value.DeletedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} + return &AuthorityResponse{CreatedAt: value.CreatedAt, UpdatedAt: value.UpdatedAt, DeletedAt: value.DeletedAt, AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, Children: nil, Menus: menus, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} } -func convertUser(user *biz.User) *dto.UserResponse { - var authorities []*dto.AuthorityResponse +func convertUser(user *biz.User) *UserResponse { + var authorities []*AuthorityResponse if user.Authorities != nil { - authorities = make([]*dto.AuthorityResponse, 0, len(user.Authorities)) + authorities = make([]*AuthorityResponse, 0, len(user.Authorities)) for _, value := range user.Authorities { authorities = append(authorities, convertAuthority(value)) } } - var departments []*dto.DepartmentResponse + var departments []*DepartmentResponse if user.Departments != nil { - departments = make([]*dto.DepartmentResponse, 0, len(user.Departments)) + departments = make([]*DepartmentResponse, 0, len(user.Departments)) for i := range user.Departments { departments = append(departments, departmentResponse(&user.Departments[i])) } } - var positions []*dto.PositionResponse + var positions []*PositionResponse if user.Positions != nil { - positions = make([]*dto.PositionResponse, 0, len(user.Positions)) + positions = make([]*PositionResponse, 0, len(user.Positions)) for i := range user.Positions { positions = append(positions, positionResponse(&user.Positions[i])) } } // The primary department is represented by a zero-value object when it is // absent, preserving the response shape expected by the administration UI. - var department any = dto.DepartmentResponse{} + var department any = DepartmentResponse{} if user.Department != nil { department = departmentResponse(user.Department) } - return &dto.UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, DeletedAt: nil, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting, PasswordUpdatedAt: user.PasswordUpdatedAt} + return &UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, DeletedAt: nil, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting, PasswordUpdatedAt: user.PasswordUpdatedAt} } diff --git a/app/system/internal/service/version.go b/app/system/internal/service/version.go index acdb7a6..6c8804f 100644 --- a/app/system/internal/service/version.go +++ b/app/system/internal/service/version.go @@ -6,7 +6,6 @@ import ( "time" "kra/app/system/internal/biz" - "kra/app/system/internal/dto" ) type VersionService struct{ uc *biz.VersionUsecase } @@ -49,7 +48,7 @@ type versionExportMenu struct { Hidden bool `json:"hidden"` Component string `json:"component"` Sort int `json:"sort"` - Meta dto.VersionMenuMetaRequest `json:"meta"` + Meta VersionMenuMetaRequest `json:"meta"` Authorities []any `json:"authoritys"` Children []*versionExportMenu `json:"children"` Parameters []versionExportMenuParameter `json:"parameters"` @@ -106,7 +105,7 @@ type versionExportDocument struct { func exportMenu(value *biz.Menu) *versionExportMenu { menu := &versionExportMenu{ Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, - Meta: dto.VersionMenuMetaRequest{ActiveName: value.ActiveName, KeepAlive: value.KeepAlive, DefaultMenu: value.DefaultMenu, Title: value.Title, Icon: value.Icon, CloseTab: value.CloseTab, TransitionType: value.TransitionType}, + Meta: VersionMenuMetaRequest{ActiveName: value.ActiveName, KeepAlive: value.KeepAlive, DefaultMenu: value.DefaultMenu, Title: value.Title, Icon: value.Icon, CloseTab: value.CloseTab, TransitionType: value.TransitionType}, } if len(value.Parameters) > 0 { menu.Parameters = make([]versionExportMenuParameter, 0, len(value.Parameters)) @@ -129,8 +128,8 @@ func exportMenu(value *biz.Menu) *versionExportMenu { return menu } -func versionDTO(v *biz.Version) *dto.VersionResponse { - return &dto.VersionResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, VersionName: v.Name, VersionCode: v.Code, Description: v.Description, VersionData: v.Data} +func versionDTO(v *biz.Version) *VersionResponse { + return &VersionResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, VersionName: v.Name, VersionCode: v.Code, Description: v.Description, VersionData: v.Data} } func versionValue(value *string) string { @@ -141,18 +140,18 @@ func versionValue(value *string) string { } func versionPointer(value string) *string { return &value } -func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*dto.VersionResponse, int64, error) { +func (s *VersionService) Versions(ctx context.Context, page, size int, name, code string, createdAtRange []*time.Time) ([]*VersionResponse, int64, error) { items, total, err := s.uc.ListVersions(ctx, page, size, name, code, createdAtRange) if err != nil { return nil, 0, err } - out := make([]*dto.VersionResponse, 0, len(items)) + out := make([]*VersionResponse, 0, len(items)) for _, v := range items { out = append(out, versionDTO(v)) } return out, total, nil } -func (s *VersionService) Version(ctx context.Context, id uint) (*dto.VersionResponse, error) { +func (s *VersionService) Version(ctx context.Context, id uint) (*VersionResponse, error) { v, err := s.uc.FindVersion(ctx, id) if err != nil { return nil, err @@ -223,7 +222,7 @@ func (s *VersionService) Export(ctx context.Context, name, code, description str func (s *VersionService) Import(ctx context.Context, bundle *biz.VersionBundle) error { return s.uc.ImportVersionBundle(ctx, bundle) } -func versionMenuDomain(value dto.VersionMenuRequest) *biz.Menu { +func versionMenuDomain(value VersionMenuRequest) *biz.Menu { menu := &biz.Menu{Path: value.Path, Name: value.Name, Hidden: value.Hidden, Component: value.Component, Sort: value.Sort, ActiveName: value.Meta.ActiveName, KeepAlive: value.Meta.KeepAlive, DefaultMenu: value.Meta.DefaultMenu, Title: value.Meta.Title, Icon: value.Meta.Icon, CloseTab: value.Meta.CloseTab, TransitionType: value.Meta.TransitionType} for _, button := range value.MenuButtons { menu.Buttons = append(menu.Buttons, &biz.MenuButton{Name: button.Name, Description: button.Description}) @@ -236,7 +235,7 @@ func versionMenuDomain(value dto.VersionMenuRequest) *biz.Menu { } return menu } -func (s *VersionService) ImportRequest(ctx context.Context, req *dto.ImportVersionRequest) error { +func (s *VersionService) ImportRequest(ctx context.Context, req *ImportVersionRequest) error { if req.Version.Name == "" || req.Version.Code == "" { return biz.ErrInvalidVersion } diff --git a/app/system/internal/utils/configutil/json.go b/app/system/internal/utils/configutil/json.go deleted file mode 100644 index 8f3e980..0000000 --- a/app/system/internal/utils/configutil/json.go +++ /dev/null @@ -1,16 +0,0 @@ -package configutil - -import ( - "encoding/json" - - "kra/pkg/protoutil" - - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" -) - -// MergeProtoJSON applies a JSON object as a partial protobuf update while -// retaining fields omitted by the caller. -func MergeProtoJSON(target proto.Message, patch json.RawMessage, options protojson.UnmarshalOptions) error { - return protoutil.MergeProtoJSON(target, patch, options) -} diff --git a/docs/system-pkg-audit.md b/docs/system-pkg-audit.md index 5e0c91b..dc1dc5d 100644 --- a/docs/system-pkg-audit.md +++ b/docs/system-pkg-audit.md @@ -1,66 +1,70 @@ # `app/system` 到 `pkg` 复用性审查 -审查范围:`app/system` 下全部 Go 文件,以及现有 `pkg` 目录。判断标准是: -公共包只能提供跨模块稳定的机制、协议或纯函数,不能依赖 `app/*/internal`, -也不承载 system 的业务表、用例、provider 生命周期或运行时配置。 +审查原则:公共包只提供跨模块稳定的机制、协议或无状态纯函数;不能依赖 +`app/*/internal`,也不承载 system 的业务表、用例、provider 客户端或运行时 +配置。 -## 本轮已抽取 +## 已抽取到 `pkg` -| 原位置 | 公共位置 | 结论 | -|---|---|---| -| `internal/transport/httpx/response.go` | `pkg/httpx/response.go` | `Response`、`PageResult`、状态码和 Gin 响应助手是跨模块 HTTP 契约。system 原包保留别名/转发,兼容既有调用方。 | -| `internal/utils/configutil/json.go` | `pkg/protoutil/json.go` | protobuf JSON 局部合并、snake_case 到 lowerCamel 归一化与 system 业务无关,已由 system wrapper 转发。 | -| `internal/biz/payment.go` provider 常量 | `pkg/paymentkit/provider.go` | provider 标识、支付模式和支持列表是跨业务支付边界,system `biz` 保留兼容常量。 | -| `internal/biz/payment.go` 回调应答 | `pkg/paymentkit/callback.go` | 回调 ACK、回调错误和默认 provider 应答是跨模块协议,已由 system 类型别名兼容。 | +| 能力 | 公共位置 | 说明 | +| --- | --- | --- | +| HTTP JSON 响应契约 | `pkg/httpx` | `Response`、`PageResult`、状态码和 Gin 响应助手;`server/httpx/response.go` 仅保留 system 适配。 | +| protobuf JSON 局部合并 | `pkg/protoutil` | 与业务无关的字段归一化和局部反序列化;初始化直接使用公共包。 | +| 支付 provider/mode 标识 | `pkg/paymentkit` | provider 常量、支持列表、金额/签名/JSON 等跨模块协议;system `biz` 只保留兼容别名。 | +| 支付回调 ACK | `pkg/paymentkit` | 回调应答、失败包装和默认 provider 应答;具体渠道 SDK 仍留在 system integration。 | +| WebSocket 通用收发 | `pkg/websocket` | Melody 的连接、事件、点对点发送、广播和会话查询封装;system integration 管理配置与生命周期。 | +| 消息队列 | `pkg/mq` | Broker 无关的发布、订阅、JSON 和 QoS 接口;EMQX/Paho 客户端由 system integration 管理。 | +| 模块、任务和迁移协议 | `pkg/module`、`pkg/task`、`pkg/database/migration` | 供不同业务模块注册贡献,不带 system 业务语义。 | -## `app/system` 逐目录结论 +## system 内部保留边界 -| 目录 | 结论 | 代表文件/原因 | -|---|---|---| -| `cmd` | 保留 system | `main.go`、`wire.go` 是组合根和启动生命周期,不能下沉。 | -| `internal/app`、`internal/module` | 保留 system | `catalog.go`、`definition.go` 绑定 system 迁移、菜单、路由和任务贡献。公共协议已在 `pkg/module`。 | -| `internal/biz` | 大部分保留;仅抽公共支付边界 | 用户、权限、菜单、审计、任务、支付订单和系统配置都是领域模型/用例;`payment.go` 中 provider 与 ACK 已兼容转发。 | -| `internal/conf` | 保留 system | 配置 proto 由 system 运行时和生成代码拥有,不能成为跨模块配置模型。 | -| `internal/data` | 保留 system | 数据库连接、迁移、PO、system 表、支付持久化和配置 watcher 都是 system 数据层。通用迁移/分页/GORM 能力已在 `pkg/database`。 | -| `internal/dto` | 保留 system | DTO 与 system API 形状绑定,不应跨模块共享。 | -| `internal/initialize` | 保留 system | 首次安装、兼容迁移、配置持久化和种子编排具有 system 语义。 | -| `internal/integration/cache` | 保留 system | Redis/cache 客户端生命周期和运行时配置属于 integration,不是 stateless utils。 | -| `internal/integration/email` | 保留 system | 邮件 provider 配置和发送生命周期属于 system integration。 | -| `internal/integration/payment` | 保留 system | `alipay.go`、`wechat_v3.go`、`paypal.go` 等是具体 provider SDK 适配器;不能搬到通用 pkg。纯金额、JSON、签名、状态函数已经在 `pkg/paymentkit`。 | -| `internal/integration/storage` | 保留 system | OSS/S3/本地存储客户端、重载和配置绑定属于 integration。 | -| `internal/repository` | 保留 system | 仓储实现、PO、Casbin、system 表和事务边界都不能跨模块复用。 | -| `internal/routeinfo` | 保留 system | 路由元数据包含 system 初始化与菜单语义。 | -| `internal/security/adminauth` | 保留 system | JWT claims、签发和后台认证策略属于 system 安全边界。 | -| `internal/service` | 保留 system | DTO 与 system DO 的转换和服务编排,不可放入公共包。 | -| `internal/transport/handler` | 保留 system | 每个 handler 都依赖 system service/DTO;只有 HTTP 响应协议抽到 `pkg/httpx`。 | -| `internal/transport/httpx` | 兼容层 | `response.go` 已转发到 `pkg/httpx`;`cookie.go` 的 token cookie 名称和认证策略仍是 system 专属。 | -| `internal/transport/middleware` | 保留 system | 审计、认证、数据权限、限流、system 错误记录和请求采集均带业务/配置语义。 | -| `internal/transport/router` | 保留 system | 路由注册指向 system handlers。 | -| `internal/utils/configutil` | 兼容层 | `json.go` 已转发到 `pkg/protoutil`;其余 system 专用工具应留在本目录。 | -| `internal/worker` | 保留 system | 任务执行器依赖 system task repo、runtime、日志和业务方法。跨模块任务注册协议已在 `pkg/task`。 | +- `app`:组合根,绑定 system 的迁移、菜单、路由和任务贡献。 +- `biz`:用户、权限、菜单、审计、任务、支付订单和系统配置等领域模型与用例。 +- `conf`:system 配置 proto、运行时快照和生成代码。 +- `data`:数据库连接、PO、仓储、system 表、支付持久化和配置 watcher。 +- `initialize`:首次安装、配置迁移、种子编排和运行时重载。 +- `integration`:Redis、邮件、存储、支付、WebSocket 和 EMQX 的 provider 生命周期。 +- `security`:JWT claims、签发/解析和后台安全实现。 +- `service`:HTTP DTO(`service/dto`)、DTO 与 DO 转换、应用服务和路由元数据。 +- `server`:Gin 生命周期;handler、middleware、router、HTTP 适配按子包维护。 +- `worker`:任务调度、执行器、SSE 订阅及其并发状态。 -## 现有 `pkg` 复核 +这些代码都带有 system 的 API、配置、数据表或生命周期语义,不应为了减少 +文件数量搬到 `pkg`。 -- `pkg/database/*`:边界正确,继续保留。 -- `pkg/logging/*`:边界正确,继续保留。 -- `pkg/module/*`:跨模块迁移/路由/元数据协议,边界正确。 -- `pkg/task/*`:跨模块任务注册协议,边界正确。 -- `pkg/paymentkit/*`:本轮补齐 provider 与 callback 协议;具体渠道 SDK 仍留在 system。 -- 新增 `pkg/httpx`、`pkg/protoutil`:均不引用 `app/*/internal`。 +## 目录分类约定 -## 暂不抽取的后续候选 +```text +internal/ + app/ # 组合根和模块定义 + biz/ # DO、usecase、repo interface + conf/ # 配置 proto/runtime + data/ # PO、repo、数据库和迁移 + initialize/ # 首次安装和配置编排 + integration/ # 外部 I/O provider + security/ # JWT 和安全实现 + server/ # Gin 生命周期,内部按 handler/middleware/router/httpx 分类 + service/ # 应用服务;DTO 集中在 dto 子包 + worker/ # 任务运行时 +``` -1. `internal/transport/httpx/cookie.go`:只有在多个 app 统一 token cookie 名称、SameSite 和反向代理策略后,才适合抽成公共 HTTP cookie 包。 -2. `internal/transport/middleware/capture.go`:包含 operation-audit 脱敏字段和 system context key,当前不是通用中间件。 -3. `internal/biz/pagination.go`:依赖 AIP filtering/ordering,且目前只服务 system 的仓储接口;若其他模块形成同样 DO 查询协议,再独立设计 `pkg/query`,不要直接搬 biz 类型。 -4. `internal/biz/errors.go` 及认证/安全错误:错误 reason 与 system API 语义绑定,不应公共化。 +技术角色使用子目录表达,子目录内部使用资源名,例如 `handler/payment.go`、 +`router/payment.go`、`dto/payment.go`。只有少量代码且没有独立边界时不建立 +新包;同一角色文件较多时也不应全部堆在父目录。 + +## 暂不抽取的候选 + +1. token cookie 名称、SameSite 和反向代理策略:目前是 system 认证策略。 +2. operation-audit 脱敏和请求采集:包含 system context key 与审计字段。 +3. biz 查询选项和错误:当前绑定 system 的领域接口及 API reason。 + +只有当其他模块出现相同、稳定且不带 system 语义的契约时,才新增公共包;不要 +直接把单个 system 类型搬到 `pkg`。 ## 验证 -已运行: - ```text -go test ./pkg/... ./app/system/... +go test ./... ``` -结果:全部通过。 +当前全仓测试通过,且未发现对已删除旧路径的 Go import。 diff --git a/docs/system-structure-audit.md b/docs/system-structure-audit.md new file mode 100644 index 0000000..94cf7eb --- /dev/null +++ b/docs/system-structure-audit.md @@ -0,0 +1,57 @@ +# `app/system/internal` 目录结构优化结论 + +参考 Go Kratos 的分层方式,顶层保留 `app`、`biz`、`conf`、`data`、 +`initialize`、`integration`、`security`、`server`、`service`、`worker` 十个 +稳定职责。目录不是越少越好:同一技术角色文件较多时,应在所属层下分组,避免 +一个目录堆积几十个文件。 + +## 当前结构 + +```text +internal/ + app/ # 组合根、模块 catalog 和 definition + biz/ # DO、usecase、repo interface + conf/ # 配置 proto/runtime + data/ # PO、repo、数据库和迁移 + initialize/ # 首次安装和配置编排 + integration/ # 外部 I/O provider + security/ # JWT 和安全实现 + server/ + handler/ # Gin handler,按资源命名 + middleware/ # 认证、审计、限流、恢复等中间件 + router/ # 各资源路由及 Routes 聚合 + httpx/ # system HTTP 响应和 cookie 适配 + service/ + dto/ # HTTP 请求、响应和查询 DTO,按模块命名 + worker/ # 任务运行时 +``` + +## 本次调整 + +- DTO 从 `service` 根目录归档到 `service/dto`。 +- handler、middleware、router、HTTP helper 分别归档到 `server` 子包。 +- 子目录内文件直接使用资源名,例如 `handler/payment.go`、 + `router/payment.go`、`dto/payment.go`,不保留重复角色前缀。 +- Wire 直接装配 `handler.ProviderSet` 和 `router.ProviderSet`,server 根目录只 + 负责 Gin/Swagger 生命周期。 +- `security/adminauth/token.go` 合并为 `security/token.go`;单文件子包没有 + 独立边界时不继续拆分。 +- 删除只转发 `pkg/protoutil` 的 `utils/configutil`。 + +## `internal/app` 为什么只有两个文件 + +`app/catalog.go` 和 `app/definition.go` 是有意保留的组合根。`catalog.go` 负责 +组装模块、任务注册与运行时;`definition.go` 声明 system 提供的迁移、管理面 +和默认定时任务。它们依赖多个层,只能位于应用组合边界,不能并入 `biz`、 +`service` 或 `data`。 + +## 其他目录审查 + +- `data` 与 `integration` 已按仓储或 provider 分类,边界和生命周期明确。 +- `biz`、`service` 根目录采用一资源一文件;进一步拆成资源子包会改变 Go 包 + 边界并容易引入循环依赖,本次不做纯视觉拆分。 +- `security` 当前只有一个文件,但安全实现是明确的依赖边界,后续认证机制也会 + 在此扩展,因此保留顶层包。 + +新增目录应至少满足独立依赖方向、状态生命周期或稳定技术角色之一。不要回到 +一文件一目录,也不要为了减少目录数量把大量不同角色重新铺平。 diff --git a/go.mod b/go.mod index 9d5221e..46b694f 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1 github.com/casbin/casbin/v3 v3.10.0 github.com/dzwvip/gorm-oracle v0.1.2 + github.com/eclipse/paho.mqtt.golang v1.5.1 github.com/fsnotify/fsnotify v1.10.1 github.com/gin-gonic/gin v1.10.0 github.com/glebarez/sqlite v1.11.0 @@ -21,9 +22,11 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/google/wire v0.7.0 + github.com/gorilla/websocket v1.5.3 github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible github.com/minio/minio-go/v7 v7.0.92 github.com/mojocn/base64Captcha v1.3.8 + github.com/olahol/melody v1.4.0 github.com/qiniu/go-sdk/v7 v7.25.2 github.com/redis/go-redis/v9 v9.7.0 github.com/robfig/cron/v3 v3.0.1 @@ -114,7 +117,6 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect diff --git a/go.sum b/go.sum index 740089b..0d1d6e0 100644 --- a/go.sum +++ b/go.sum @@ -108,6 +108,8 @@ github.com/dzwvip/gorm-oracle v0.1.2 h1:811aFDY7oDfKWHc0Z0lHdXzzr89EmKBSwc/jLJ8G github.com/dzwvip/gorm-oracle v0.1.2/go.mod h1:TbF7idnO9UgGpJ0qJpDZby1/wGquzP5GYof88ScBITE= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= +github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= @@ -309,6 +311,8 @@ github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI= +github.com/olahol/melody v1.4.0/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= diff --git a/pkg/logging/source.go b/pkg/logging/source.go index f37673b..3cc8959 100644 --- a/pkg/logging/source.go +++ b/pkg/logging/source.go @@ -53,6 +53,12 @@ func skipStackFile(filename string) bool { "/pkg/logging/", "/internal/transport/middleware/", "/internal/transport/router/", + "/internal/server/handler/", + "/internal/server/middleware/", + "/internal/server/router/", + "/internal/server/httpx/", + "/internal/server/middleware_", + "/internal/server/route_", } { if strings.Contains(normalized, marker) { return true diff --git a/pkg/logging/zap_test.go b/pkg/logging/zap_test.go index be393ba..7827c6a 100644 --- a/pkg/logging/zap_test.go +++ b/pkg/logging/zap_test.go @@ -95,6 +95,30 @@ func TestErrorEntryIncludesFinalApplicationSource(t *testing.T) { } } +func TestSkipStackFileRecognizesServerTransportPackages(t *testing.T) { + tests := []struct { + name string + path string + want bool + }{ + {name: "handler", path: `D:\workspace\app\system\internal\server\handler\user.go`, want: true}, + {name: "middleware", path: `D:\workspace\app\system\internal\server\middleware\auth.go`, want: true}, + {name: "router", path: `/workspace/app/system/internal/server/router/user.go`, want: true}, + {name: "http helper", path: `/workspace/app/system/internal/server/httpx/response.go`, want: true}, + {name: "legacy flattened middleware", path: `D:\workspace\app\system\internal\server\middleware_auth.go`, want: true}, + {name: "legacy flattened route", path: `/workspace/app/system/internal/server/route_user.go`, want: true}, + {name: "handler remains application boundary", path: `/workspace/app/system/internal/server/handler_user.go`, want: false}, + {name: "legacy middleware", path: `/workspace/app/system/internal/transport/middleware/auth.go`, want: true}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := skipStackFile(test.path); got != test.want { + t.Fatalf("skipStackFile(%q) = %v, want %v", test.path, got, test.want) + } + }) + } +} + func TestErrorSinkSkipsGORMBridge(t *testing.T) { root := t.TempDir() var entries []ErrorEntry diff --git a/pkg/mq/mq.go b/pkg/mq/mq.go new file mode 100644 index 0000000..f06a0b7 --- /dev/null +++ b/pkg/mq/mq.go @@ -0,0 +1,58 @@ +// Package mq exposes the broker-agnostic messaging seam used by services. +package mq + +import ( + "context" + "encoding/json" + "errors" + "time" +) + +var ErrUnavailable = errors.New("message broker unavailable") + +const ( + AtMostOnce byte = 0 + AtLeastOnce byte = 1 + ExactlyOnce byte = 2 +) + +type Message struct { + Topic string + Payload []byte + QoS byte + Retain bool +} + +func (m Message) DecodeJSON(target any) error { return json.Unmarshal(m.Payload, target) } + +type Handler func(context.Context, Message) + +type Client interface { + Publish(context.Context, string, []byte, byte, bool) error + Subscribe(context.Context, string, byte, Handler) error + Unsubscribe(context.Context, ...string) error + Connected() bool + Close() error +} + +type Config struct { + Enabled bool + Broker string + ClientID string + Username string + Password string + KeepAlive time.Duration + CleanSession bool + ConnectTimeout time.Duration +} + +func PublishJSON(ctx context.Context, client Client, topic string, value any, qos byte, retain bool) error { + if client == nil { + return ErrUnavailable + } + payload, err := json.Marshal(value) + if err != nil { + return err + } + return client.Publish(ctx, topic, payload, qos, retain) +} diff --git a/pkg/mq/mqtt.go b/pkg/mq/mqtt.go new file mode 100644 index 0000000..ccba0c8 --- /dev/null +++ b/pkg/mq/mqtt.go @@ -0,0 +1,126 @@ +package mq + +import ( + "context" + "fmt" + "sync" + "time" + + paho "github.com/eclipse/paho.mqtt.golang" +) + +type MQTT struct { + client paho.Client + mu sync.RWMutex + closed bool +} + +func NewMQTT(cfg Config) (*MQTT, error) { + if !cfg.Enabled { + return &MQTT{}, nil + } + if cfg.Broker == "" { + return nil, fmt.Errorf("mqtt broker is empty") + } + if cfg.KeepAlive <= 0 { + cfg.KeepAlive = 30 * time.Second + } + if cfg.ConnectTimeout <= 0 { + cfg.ConnectTimeout = 10 * time.Second + } + opts := paho.NewClientOptions().AddBroker(cfg.Broker).SetClientID(cfg.ClientID).SetUsername(cfg.Username).SetPassword(cfg.Password) + opts.SetKeepAlive(cfg.KeepAlive).SetCleanSession(cfg.CleanSession).SetConnectTimeout(cfg.ConnectTimeout).SetAutoReconnect(true) + opts.SetResumeSubs(true).SetOrderMatters(false) + c := &MQTT{} + token := paho.NewClient(opts) + connect := token.Connect() + if !connect.WaitTimeout(cfg.ConnectTimeout) { + return nil, fmt.Errorf("connect mqtt: %w", ErrUnavailable) + } + if err := connect.Error(); err != nil { + return nil, err + } + c.client = token + return c, nil +} + +func (c *MQTT) Publish(ctx context.Context, topic string, payload []byte, qos byte, retain bool) error { + if topic == "" { + return fmt.Errorf("mqtt topic is empty") + } + if qos > ExactlyOnce { + return fmt.Errorf("invalid mqtt qos %d", qos) + } + if c == nil || c.client == nil || !c.client.IsConnected() { + return ErrUnavailable + } + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + return waitToken(ctx, c.client.Publish(topic, qos, retain, payload)) +} + +func (c *MQTT) Subscribe(ctx context.Context, topic string, qos byte, handler Handler) error { + if topic == "" { + return fmt.Errorf("mqtt topic is empty") + } + if qos > ExactlyOnce { + return fmt.Errorf("invalid mqtt qos %d", qos) + } + if c == nil || c.client == nil || !c.client.IsConnected() { + return ErrUnavailable + } + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if handler == nil { + return fmt.Errorf("mqtt handler is nil") + } + return waitToken(ctx, c.client.Subscribe(topic, qos, func(_ paho.Client, msg paho.Message) { + handler(context.Background(), Message{Topic: msg.Topic(), Payload: append([]byte(nil), msg.Payload()...), QoS: msg.Qos(), Retain: msg.Retained()}) + })) +} + +func (c *MQTT) Unsubscribe(ctx context.Context, topics ...string) error { + if len(topics) == 0 { + return fmt.Errorf("mqtt topics are empty") + } + if c == nil || c.client == nil || !c.client.IsConnected() { + return ErrUnavailable + } + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + return waitToken(ctx, c.client.Unsubscribe(topics...)) +} + +func waitToken(ctx context.Context, token paho.Token) error { + if token == nil { + return ErrUnavailable + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-token.Done(): + return token.Error() + } +} +func (c *MQTT) Connected() bool { return c != nil && c.client != nil && c.client.IsConnected() } +func (c *MQTT) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + if c.closed { + return nil + } + c.closed = true + if c.client != nil && c.client.IsConnected() { + c.client.Disconnect(250) + } + return nil +} diff --git a/pkg/mq/mqtt_test.go b/pkg/mq/mqtt_test.go new file mode 100644 index 0000000..27be681 --- /dev/null +++ b/pkg/mq/mqtt_test.go @@ -0,0 +1,57 @@ +package mq + +import ( + "context" + "errors" + "testing" +) + +type captureClient struct{ payload []byte } + +func (c *captureClient) Publish(_ context.Context, _ string, payload []byte, _ byte, _ bool) error { + c.payload = append([]byte(nil), payload...) + return nil +} +func (*captureClient) Subscribe(context.Context, string, byte, Handler) error { return nil } +func (*captureClient) Unsubscribe(context.Context, ...string) error { return nil } +func (*captureClient) Connected() bool { return true } +func (*captureClient) Close() error { return nil } + +func TestDisabledMQTTIsSafeAndUnavailable(t *testing.T) { + client, err := NewMQTT(Config{}) + if err != nil { + t.Fatal(err) + } + if client.Connected() { + t.Fatal("disabled client reported connected") + } + if err = client.Publish(context.Background(), "events/test", []byte("test"), AtLeastOnce, false); !errors.Is(err, ErrUnavailable) { + t.Fatalf("publish error = %v", err) + } + if err = client.Close(); err != nil { + t.Fatal(err) + } + if err = client.Close(); err != nil { + t.Fatalf("second close: %v", err) + } +} + +func TestEnabledMQTTRequiresBroker(t *testing.T) { + if _, err := NewMQTT(Config{Enabled: true}); err == nil { + t.Fatal("enabled MQTT without a broker should fail") + } +} + +func TestPublishAndDecodeJSON(t *testing.T) { + client := &captureClient{} + if err := PublishJSON(context.Background(), client, "orders/paid", map[string]int{"id": 7}, AtLeastOnce, false); err != nil { + t.Fatal(err) + } + var value map[string]int + if err := (Message{Payload: client.payload}).DecodeJSON(&value); err != nil { + t.Fatal(err) + } + if value["id"] != 7 { + t.Fatalf("decoded value = %#v", value) + } +} diff --git a/pkg/websocket/melody.go b/pkg/websocket/melody.go new file mode 100644 index 0000000..aafef0c --- /dev/null +++ b/pkg/websocket/melody.go @@ -0,0 +1,236 @@ +// Package websocket provides a small, framework-neutral wrapper around Melody. +package websocket + +import ( + "net/http" + "net/url" + "strings" + "sync" + "time" + + melody "github.com/olahol/melody" +) + +type Config struct { + WriteWait time.Duration + PongWait time.Duration + PingPeriod time.Duration + MaxMessageSize int64 + MessageBufferSize int + ConcurrentMessageHandling bool + AllowOrigins []string +} + +type Session = melody.Session + +// Hub is the shared WebSocket boundary consumed by business modules. The +// application composition root supplies the runtime-configured implementation. +type Hub interface { + Enabled() bool + Broadcast([]byte) error + BroadcastBinary([]byte) error + Sessions() ([]*Session, error) + Len() int + Send(*Session, []byte) error + SendBinary(*Session, []byte) error + OnMessage(func(*Session, []byte)) + OnBinaryMessage(func(*Session, []byte)) + OnConnect(func(*Session)) + OnDisconnect(func(*Session)) +} + +func (c Config) defaults() Config { + if c.WriteWait <= 0 { + c.WriteWait = 10 * time.Second + } + if c.PongWait <= 0 { + c.PongWait = 60 * time.Second + } + if c.PingPeriod <= 0 { + c.PingPeriod = (c.PongWait * 9) / 10 + } + if c.MaxMessageSize <= 0 { + c.MaxMessageSize = 1 << 20 + } + if c.MessageBufferSize <= 0 { + c.MessageBufferSize = 256 + } + return c +} + +type Server struct { + m *melody.Melody + mu sync.RWMutex + closed bool + messageHandlers []func(*melody.Session, []byte) + binaryHandlers []func(*melody.Session, []byte) + connectHandlers []func(*melody.Session) + disconnectHandlers []func(*melody.Session) +} + +func New(config Config) *Server { + c := config.defaults() + m := melody.New() + m.Config.WriteWait = c.WriteWait + m.Config.PongWait = c.PongWait + m.Config.PingPeriod = c.PingPeriod + m.Config.MaxMessageSize = c.MaxMessageSize + m.Config.MessageBufferSize = c.MessageBufferSize + m.Config.ConcurrentMessageHandling = c.ConcurrentMessageHandling + allowed := append([]string(nil), c.AllowOrigins...) + m.Upgrader.CheckOrigin = func(r *http.Request) bool { + if len(allowed) == 0 { + origin := r.Header.Get("Origin") + if origin == "" { + return true + } + parsed, err := url.Parse(origin) + return err == nil && strings.EqualFold(parsed.Host, r.Host) + } + origin := r.Header.Get("Origin") + for _, value := range allowed { + if value == "*" || strings.EqualFold(strings.TrimSpace(value), origin) { + return true + } + } + return false + } + s := &Server{m: m} + m.HandleMessage(func(session *melody.Session, message []byte) { + for _, handler := range s.messageHandlerSnapshot() { + handler(session, message) + } + }) + m.HandleMessageBinary(func(session *melody.Session, message []byte) { + for _, handler := range s.binaryHandlerSnapshot() { + handler(session, message) + } + }) + m.HandleConnect(func(session *melody.Session) { + for _, handler := range s.connectHandlerSnapshot() { + handler(session) + } + }) + m.HandleDisconnect(func(session *melody.Session) { + for _, handler := range s.disconnectHandlerSnapshot() { + handler(session) + } + }) + return s +} + +func (s *Server) Melody() *melody.Melody { + if s == nil { + return nil + } + return s.m +} +func (s *Server) Enabled() bool { + if s == nil || s.m == nil { + return false + } + s.mu.RLock() + defer s.mu.RUnlock() + return !s.closed +} +func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) error { + return s.m.HandleRequest(w, r) +} +func (s *Server) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]any) error { + return s.m.HandleRequestWithKeys(w, r, keys) +} +func (s *Server) Broadcast(message []byte) error { return s.m.Broadcast(message) } +func (s *Server) BroadcastBinary(message []byte) error { return s.m.BroadcastBinary(message) } +func (s *Server) Sessions() ([]*melody.Session, error) { return s.m.Sessions() } +func (s *Server) Len() int { return s.m.Len() } +func (s *Server) OnMessage(handler func(*melody.Session, []byte)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.messageHandlers = append(s.messageHandlers, handler) + s.mu.Unlock() +} +func (s *Server) OnBinaryMessage(handler func(*melody.Session, []byte)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.binaryHandlers = append(s.binaryHandlers, handler) + s.mu.Unlock() +} +func (s *Server) OnConnect(handler func(*melody.Session)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.connectHandlers = append(s.connectHandlers, handler) + s.mu.Unlock() +} +func (s *Server) OnDisconnect(handler func(*melody.Session)) { + if s == nil || handler == nil { + return + } + s.mu.Lock() + s.disconnectHandlers = append(s.disconnectHandlers, handler) + s.mu.Unlock() +} + +func (s *Server) messageHandlerSnapshot() []func(*melody.Session, []byte) { + s.mu.RLock() + defer s.mu.RUnlock() + handlers := make([]func(*melody.Session, []byte), len(s.messageHandlers)) + copy(handlers, s.messageHandlers) + return handlers +} + +func (s *Server) binaryHandlerSnapshot() []func(*melody.Session, []byte) { + s.mu.RLock() + defer s.mu.RUnlock() + handlers := make([]func(*melody.Session, []byte), len(s.binaryHandlers)) + copy(handlers, s.binaryHandlers) + return handlers +} + +func (s *Server) connectHandlerSnapshot() []func(*melody.Session) { + s.mu.RLock() + defer s.mu.RUnlock() + handlers := make([]func(*melody.Session), len(s.connectHandlers)) + copy(handlers, s.connectHandlers) + return handlers +} + +func (s *Server) disconnectHandlerSnapshot() []func(*melody.Session) { + s.mu.RLock() + defer s.mu.RUnlock() + handlers := make([]func(*melody.Session), len(s.disconnectHandlers)) + copy(handlers, s.disconnectHandlers) + return handlers +} +func (s *Server) Send(session *melody.Session, message []byte) error { + if session == nil { + return melody.ErrClosed + } + return session.Write(message) +} + +func (s *Server) SendBinary(session *melody.Session, message []byte) error { + if session == nil { + return melody.ErrClosed + } + return session.WriteBinary(message) +} + +func (s *Server) Close() error { + if s == nil || s.m == nil { + return nil + } + s.mu.Lock() + if s.closed { + s.mu.Unlock() + return nil + } + s.closed = true + s.mu.Unlock() + return s.m.Close() +} diff --git a/pkg/websocket/melody_test.go b/pkg/websocket/melody_test.go new file mode 100644 index 0000000..c9d7e8d --- /dev/null +++ b/pkg/websocket/melody_test.go @@ -0,0 +1,50 @@ +package websocket + +import ( + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/gorilla/websocket" + melody "github.com/olahol/melody" +) + +func TestServerReceivesAndSendsTextMessages(t *testing.T) { + server := New(Config{}) + var firstHandlerCalled atomic.Bool + server.OnMessage(func(_ *melody.Session, _ []byte) { + firstHandlerCalled.Store(true) + }) + server.OnMessage(func(session *melody.Session, message []byte) { + _ = server.Send(session, append([]byte("echo:"), message...)) + }) + httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _ = server.HandleRequest(w, r) + })) + defer httpServer.Close() + defer server.Close() + + url := "ws" + strings.TrimPrefix(httpServer.URL, "http") + conn, _, err := websocket.DefaultDialer.Dial(url, nil) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + if err = conn.WriteMessage(websocket.TextMessage, []byte("hello")); err != nil { + t.Fatal(err) + } + _ = conn.SetReadDeadline(time.Now().Add(time.Second)) + _, message, err := conn.ReadMessage() + if err != nil { + t.Fatal(err) + } + if string(message) != "echo:hello" { + t.Fatalf("message = %q", message) + } + if !firstHandlerCalled.Load() { + t.Fatal("first message handler was overwritten") + } +} diff --git a/web/src/api/payment.js b/web/src/api/payment.js index 77e82ba..f37b3d2 100644 --- a/web/src/api/payment.js +++ b/web/src/api/payment.js @@ -19,3 +19,15 @@ export const getPaymentOrder = (data) => service({ method: 'post', data }) + +export const queryPaymentOrder = (data) => service({ + url: '/payment/query', + method: 'post', + data +}) + +export const refundPaymentOrder = (data) => service({ + url: '/payment/refund', + method: 'post', + data +}) diff --git a/web/src/view/systemTools/payment/config.vue b/web/src/view/systemTools/payment/config.vue index 9d68779..460983f 100644 --- a/web/src/view/systemTools/payment/config.vue +++ b/web/src/view/systemTools/payment/config.vue @@ -1,12 +1,16 @@ @@ -15,16 +19,15 @@ import { ref } from 'vue' import { ElMessage } from 'element-plus' import { getPaymentConfigs, savePaymentConfig } from '@/api/payment' -const configs = ref([]) -const load = async () => { - const res = await getPaymentConfigs() - if (res.code === 0) configs.value = (res.data || []).map((item) => ({ ...item, editor: JSON.stringify(item.config || {}, null, 2) })) -} -const save = async (row) => { - let config - try { config = JSON.parse(row.editor || '{}') } catch { ElMessage.error('配置必须是合法 JSON'); return } - const res = await savePaymentConfig({ provider: row.provider, enabled: row.enabled, config }) - if (res.code === 0) { ElMessage.success('保存成功'); await load() } -} +const configs = ref([]); const loading = ref(false) +const parseConfig = (row, notify = true) => { try { JSON.parse(row.editor || '{}'); row.valid = true; return true } catch { row.valid = false; if (notify) ElMessage.error(`${row.provider} 配置必须是合法 JSON`); return false } } +const load = async () => { loading.value = true; try { const res = await getPaymentConfigs(); if (res.code === 0) configs.value = (res.data || []).map((item) => ({ ...item, editor: JSON.stringify(item.config || {}, null, 2), valid: true })) } finally { loading.value = false } } +const pretty = (row) => { try { row.editor = JSON.stringify(JSON.parse(row.editor || '{}'), null, 2); row.valid = true } catch { parseConfig(row) } } +const save = async (row) => { if (!parseConfig(row)) return; const config = JSON.parse(row.editor || '{}'); const res = await savePaymentConfig({ provider: row.provider, enabled: row.enabled, config }); if (res.code === 0) { ElMessage.success(`${row.provider} 配置保存成功`); await load() } } load() + + diff --git a/web/src/view/systemTools/payment/orders.vue b/web/src/view/systemTools/payment/orders.vue index 1b964ef..eaf70dc 100644 --- a/web/src/view/systemTools/payment/orders.vue +++ b/web/src/view/systemTools/payment/orders.vue @@ -1,55 +1,68 @@ + +