From 373d9627fb4595b50dd27a1dc9065afcf0572e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=9F=E5=B8=85?= <133814250@qq.com> Date: Thu, 20 Jul 2023 18:01:10 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=8F=91=E5=B8=83v2.8.4=E7=89=88=E6=9C=AC?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E8=AF=B7=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=EF=BC=9Ahttps://github.com/bufanyun/hotgo/tree/v2.0/d?= =?UTF-8?q?ocs/guide-zh-CN/addon-version-upgrade.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/guide-zh-CN/README.md | 15 +- .../images/sys-middleware-com-response.png | Bin 10347 -> 33792 bytes docs/guide-zh-CN/start-update-log.md | 14 +- docs/guide-zh-CN/sys-code.md | 61 +- docs/guide-zh-CN/sys-cron.md | 71 ++ docs/guide-zh-CN/sys-middleware.md | 4 +- docs/guide-zh-CN/sys-tcp-server.md | 271 +++++++ docs/guide-zh-CN/sys-test.md | 3 + docs/guide-zh-CN/sys-utility.md | 20 + docs/guide-zh-CN/web-form.md | 30 + server/Makefile | 27 +- .../hgexample/api/admin/config/config.go | 2 + .../addons/hgexample/api/admin/index/index.go | 1 - .../addons/hgexample/api/admin/table/table.go | 8 +- .../addons/hgexample/api/api/index/index.go | 1 - .../addons/hgexample/api/home/index/index.go | 1 - .../hgexample/api/websocket/index/index.go | 1 - .../hgexample/controller/admin/sys/config.go | 21 +- .../hgexample/controller/admin/sys/index.go | 15 +- .../hgexample/controller/admin/sys/table.go | 68 +- .../addons/hgexample/controller/api/index.go | 15 +- .../addons/hgexample/controller/home/index.go | 14 +- .../hgexample/controller/websocket/index.go | 15 +- server/addons/hgexample/crons/crons.go | 5 + server/addons/hgexample/logic/sys/config.go | 15 +- server/addons/hgexample/logic/sys/index.go | 2 +- server/addons/hgexample/logic/sys/table.go | 119 ++-- .../hgexample/model/input/sysin/table.go | 19 + server/addons/hgexample/queues/queues.go | 5 + server/addons/hgexample/service/sys.go | 24 +- server/api/admin/addons/addons.go | 1 + server/api/admin/attachment/attachment.go | 38 +- server/api/admin/blacklist/blacklist.go | 27 +- server/api/admin/cash/cash.go | 6 +- server/api/admin/common/ems.go | 2 +- server/api/admin/common/sms.go | 12 +- server/api/admin/common/upload.go | 8 - server/api/admin/config/config.go | 5 +- server/api/admin/cron/cron_group.go | 1 + server/api/admin/curddemo/curddemo.go | 4 +- server/api/admin/dept/dept.go | 18 +- server/api/admin/dict/dict_data.go | 15 +- server/api/admin/dict/dict_type.go | 5 +- server/api/admin/gencodes/gencodes.go | 16 +- server/api/admin/log/log.go | 25 +- server/api/admin/loginlog/loginlog.go | 1 - server/api/admin/member/member.go | 1 + server/api/admin/menu/menu.go | 1 - server/api/admin/monitor/monitor.go | 100 ++- server/api/admin/notice/notice.go | 3 +- server/api/admin/post/post.go | 41 +- server/api/admin/provinces/provinces.go | 14 +- server/api/admin/role/role.go | 28 +- server/api/admin/servelicense/servelicense.go | 74 ++ server/api/admin/servelog/servelog.go | 1 - server/api/admin/smslog/smslog.go | 27 +- server/api/admin/user/hello.go | 18 - server/api/api/pay/notify.go | 8 +- server/api/servmsg/auth.go | 16 + server/api/servmsg/cron.go | 46 ++ server/api/servmsg/example.go | 28 + server/api/websocket/base/send.go | 2 +- server/go.mod | 22 +- server/go.sum | 51 +- server/internal/cmd/http.go | 1 + server/internal/cmd/tools.go | 59 +- server/internal/consts/context.go | 1 - server/internal/consts/debris.go | 16 +- server/internal/consts/servelicense.go | 14 + server/internal/consts/tcp.go | 24 - server/internal/consts/upload.go | 9 - server/internal/consts/version.go | 2 +- .../internal/controller/admin/admin/cash.go | 23 +- .../controller/admin/admin/credits_log.go | 31 +- .../internal/controller/admin/admin/dept.go | 63 +- .../internal/controller/admin/admin/member.go | 133 +--- .../internal/controller/admin/admin/menu.go | 36 +- .../controller/admin/admin/monitor.go | 181 ++++- .../internal/controller/admin/admin/notice.go | 109 +-- .../internal/controller/admin/admin/order.go | 108 +-- .../internal/controller/admin/admin/post.go | 63 +- .../internal/controller/admin/admin/role.go | 71 +- .../internal/controller/admin/common/ems.go | 11 +- .../internal/controller/admin/common/site.go | 28 +- .../internal/controller/admin/common/sms.go | 32 +- .../controller/admin/common/upload.go | 25 +- .../controller/admin/common/wechat.go | 25 +- .../internal/controller/admin/pay/refund.go | 31 +- .../internal/controller/admin/sys/addons.go | 49 +- .../controller/admin/sys/attachment.go | 65 +- .../controller/admin/sys/blacklist.go | 52 +- .../internal/controller/admin/sys/config.go | 19 +- server/internal/controller/admin/sys/cron.go | 48 +- .../controller/admin/sys/cron_group.go | 52 +- .../controller/admin/sys/curd_demo.go | 90 +-- .../controller/admin/sys/dict_data.go | 41 +- .../controller/admin/sys/dict_type.go | 21 +- .../internal/controller/admin/sys/ems_log.go | 40 +- .../controller/admin/sys/gen_codes.go | 82 +-- server/internal/controller/admin/sys/log.go | 35 +- .../controller/admin/sys/login_log.go | 53 +- .../controller/admin/sys/provinces.go | 75 +- .../controller/admin/sys/serve_license.go | 74 ++ .../controller/admin/sys/serve_log.go | 53 +- .../internal/controller/admin/sys/sms_log.go | 56 +- server/internal/controller/api/pay/notify.go | 6 +- .../websocket/handler/admin/monitor.go | 14 +- .../dao/internal/addon_hgexample_table.go | 2 +- server/internal/dao/internal/admin_cash.go | 2 +- .../dao/internal/admin_credits_log.go | 2 +- server/internal/dao/internal/admin_dept.go | 2 +- server/internal/dao/internal/admin_member.go | 2 +- .../dao/internal/admin_member_post.go | 2 +- .../dao/internal/admin_member_role.go | 2 +- server/internal/dao/internal/admin_menu.go | 2 +- server/internal/dao/internal/admin_notice.go | 2 +- .../dao/internal/admin_notice_read.go | 2 +- server/internal/dao/internal/admin_oauth.go | 2 +- server/internal/dao/internal/admin_order.go | 2 +- server/internal/dao/internal/admin_post.go | 4 +- server/internal/dao/internal/admin_role.go | 2 +- .../dao/internal/admin_role_casbin.go | 2 +- .../internal/dao/internal/admin_role_menu.go | 2 +- server/internal/dao/internal/pay_log.go | 2 +- server/internal/dao/internal/pay_refund.go | 2 +- .../dao/internal/sys_addons_config.go | 2 +- .../internal/dao/internal/sys_attachment.go | 6 +- server/internal/dao/internal/sys_blacklist.go | 2 +- server/internal/dao/internal/sys_config.go | 2 +- server/internal/dao/internal/sys_cron.go | 2 +- .../internal/dao/internal/sys_cron_group.go | 2 +- server/internal/dao/internal/sys_dict_data.go | 2 +- server/internal/dao/internal/sys_dict_type.go | 2 +- server/internal/dao/internal/sys_ems_log.go | 2 +- server/internal/dao/internal/sys_gen_codes.go | 2 +- .../dao/internal/sys_gen_curd_demo.go | 2 +- server/internal/dao/internal/sys_log.go | 2 +- server/internal/dao/internal/sys_login_log.go | 2 +- server/internal/dao/internal/sys_provinces.go | 2 +- .../dao/internal/sys_serve_license.go | 14 +- server/internal/dao/internal/sys_serve_log.go | 2 +- server/internal/dao/internal/sys_sms_log.go | 2 +- server/internal/dao/internal/test_category.go | 2 +- server/internal/global/init.go | 3 + server/internal/library/casbin/enforcer.go | 9 +- server/internal/library/cron/cron.go | 14 +- server/internal/library/hggen/hggen.go | 20 +- .../library/hggen/internal/cmd/cmd.go | 25 +- .../library/hggen/internal/cmd/cmd_build.go | 23 +- .../library/hggen/internal/cmd/cmd_docker.go | 30 +- .../library/hggen/internal/cmd/cmd_env.go | 9 +- .../library/hggen/internal/cmd/cmd_fix.go | 95 ++- .../hggen/internal/cmd/cmd_fix_test.go | 20 + .../library/hggen/internal/cmd/cmd_gen.go | 7 + .../hggen/internal/cmd/cmd_gen_ctrl.go | 15 + .../library/hggen/internal/cmd/cmd_gen_dao.go | 9 +- .../library/hggen/internal/cmd/cmd_gen_pb.go | 82 +-- .../hggen/internal/cmd/cmd_gen_pbentity.go | 414 +---------- .../hggen/internal/cmd/cmd_gen_service.go | 6 + .../library/hggen/internal/cmd/cmd_init.go | 8 +- .../library/hggen/internal/cmd/cmd_install.go | 6 + .../library/hggen/internal/cmd/cmd_pack.go | 6 + .../library/hggen/internal/cmd/cmd_run.go | 9 +- .../library/hggen/internal/cmd/cmd_tpl.go | 6 + .../library/hggen/internal/cmd/cmd_up.go | 160 ++++- .../library/hggen/internal/cmd/cmd_version.go | 8 + .../hggen/internal/cmd/genctrl/genctrl.go | 233 ++++++ .../internal/cmd/genctrl/genctrl_api_item.go | 22 + .../internal/cmd/genctrl/genctrl_calculate.go | 136 ++++ .../cmd/genctrl/genctrl_generate_ctrl.go | 137 ++++ .../genctrl/genctrl_generate_ctrl_clear.go | 57 ++ .../cmd/genctrl/genctrl_generate_interface.go | 111 +++ .../cmd/genctrl/genctrl_generate_sdk.go | 194 +++++ .../hggen/internal/cmd/gendao/gendao.go | 160 +++-- .../hggen/internal/cmd/gendao/gendao_clear.go | 6 + .../hggen/internal/cmd/gendao/gendao_dao.go | 22 +- .../hggen/internal/cmd/gendao/gendao_do.go | 22 +- .../internal/cmd/gendao/gendao_entity.go | 40 +- .../internal/cmd/gendao/gendao_structure.go | 72 +- .../hggen/internal/cmd/genenums/genenums.go | 14 +- .../internal/cmd/genenums/genenums_parser.go | 38 +- .../library/hggen/internal/cmd/genpb/genpb.go | 128 ++++ .../internal/cmd/genpb/genpb_controller.go | 196 +++++ .../hggen/internal/cmd/genpb/genpb_tag.go | 113 +++ .../internal/cmd/genpbentity/genpbentity.go | 419 +++++++++++ .../internal/cmd/genservice/genservice.go | 234 ++++-- .../cmd/genservice/genservice_calculate.go | 80 ++- .../cmd/genservice/genservice_generate.go | 48 +- .../cmd/testdata/fix25_content.go.txt | 30 + .../library/hggen/internal/consts/consts.go | 6 + .../consts/consts_gen_ctrl_template.go | 65 ++ .../consts/consts_gen_ctrl_template_sdk.go | 94 +++ .../consts/consts_gen_dao_template_dao.go | 8 +- .../consts/consts_gen_dao_template_do.go | 8 +- .../consts/consts_gen_dao_template_entity.go | 8 +- .../consts/consts_gen_enums_template.go | 2 +- .../consts/consts_gen_pbentity_template.go | 12 +- .../consts/consts_gen_service_template.go | 8 +- .../consts_gen_service_template_logic.go | 8 +- .../library/hggen/internal/packed/packed.go | 6 + .../hggen/internal/packed/template-mono.go | 2 +- .../hggen/internal/packed/template-single.go | 2 +- .../library/hggen/internal/service/install.go | 19 +- .../hggen/internal/utility/allyes/allyes.go | 6 + .../hggen/internal/utility/mlog/mlog.go | 6 + .../hggen/internal/utility/utils/utils.go | 96 ++- .../utility/utils/utils_http_download.go | 105 +++ .../internal/utility/utils/utils_test.go | 22 + server/internal/library/hggen/views/column.go | 10 +- .../library/hggen/views/column_default.go | 5 + server/internal/library/hggen/views/curd.go | 21 +- .../hggen/views/curd_generate_input.go | 1 - .../hggen/views/curd_generate_web_edit.go | 2 +- .../hggen/views/curd_generate_web_index.go | 1 - server/internal/library/hggen/views/utils.go | 3 - server/internal/library/hgorm/dao.go | 4 + server/internal/library/network/tcp/client.go | 382 ++++------ .../library/network/tcp/client_cron.go | 22 +- .../library/network/tcp/client_handle.go | 71 +- server/internal/library/network/tcp/conn.go | 177 +++++ server/internal/library/network/tcp/consts.go | 32 + .../internal/library/network/tcp/context.go | 35 +- server/internal/library/network/tcp/model.go | 30 - server/internal/library/network/tcp/msg.go | 84 +++ .../library/network/tcp/msg_parser.go | 222 ++++++ .../internal/library/network/tcp/response.go | 27 - server/internal/library/network/tcp/router.go | 116 +-- server/internal/library/network/tcp/rpc.go | 147 ++-- server/internal/library/network/tcp/server.go | 415 +++++------ .../library/network/tcp/server_cron.go | 30 +- .../library/network/tcp/server_handle.go | 166 ----- server/internal/library/network/tcp/sign.go | 49 -- .../library/network/tcp/tcp_example_test.go | 128 ++++ server/internal/library/payment/notifycall.go | 4 +- server/internal/library/sms/aliyun/handle.go | 120 ---- server/internal/library/sms/config.go | 22 + server/internal/library/sms/sms.go | 9 +- server/internal/library/sms/sms_aliyun.go | 60 ++ .../sms/{tencent/handle.go => sms_tencent.go} | 11 +- server/internal/library/storager/mime.go | 93 ++- server/internal/library/storager/model.go | 6 +- server/internal/library/storager/upload.go | 35 +- .../internal/library/storager/upload_cos.go | 2 +- .../internal/library/storager/upload_oss.go | 2 +- .../internal/library/storager/upload_qiniu.go | 2 +- server/internal/logic/admin/cash.go | 16 +- server/internal/logic/admin/credits_log.go | 13 +- server/internal/logic/admin/dept.go | 120 +++- server/internal/logic/admin/member.go | 118 ++- server/internal/logic/admin/menu.go | 28 +- server/internal/logic/admin/notice.go | 39 +- server/internal/logic/admin/order.go | 33 +- server/internal/logic/admin/post.go | 162 ++--- server/internal/logic/admin/role.go | 26 +- server/internal/logic/admin/site.go | 25 +- server/internal/logic/common/upload.go | 20 +- server/internal/logic/common/wechat.go | 4 +- server/internal/logic/logic.go | 2 +- server/internal/logic/middleware/init.go | 5 +- .../internal/logic/middleware/pre_filter.go | 83 +++ server/internal/logic/middleware/response.go | 2 - server/internal/logic/pay/notify.go | 4 +- server/internal/logic/pay/refund.go | 7 +- server/internal/logic/sys/addons.go | 14 +- server/internal/logic/sys/addons_config.go | 4 +- server/internal/logic/sys/attachment.go | 34 +- server/internal/logic/sys/blacklist.go | 59 +- server/internal/logic/sys/config.go | 34 +- server/internal/logic/sys/cron.go | 16 +- server/internal/logic/sys/cron_group.go | 16 +- server/internal/logic/sys/curd_demo.go | 34 +- server/internal/logic/sys/dict_data.go | 8 +- server/internal/logic/sys/dict_type.go | 6 +- server/internal/logic/sys/ems_log.go | 16 +- server/internal/logic/sys/gen_codes.go | 34 +- server/internal/logic/sys/log.go | 15 +- server/internal/logic/sys/login_log.go | 10 +- server/internal/logic/sys/provinces.go | 22 +- server/internal/logic/sys/serve_license.go | 193 +++++ server/internal/logic/sys/serve_log.go | 8 +- server/internal/logic/sys/sms_log.go | 82 +-- server/internal/logic/tcpclient/auth.go | 82 ++- .../internal/logic/tcpclient/auth_handle.go | 56 +- server/internal/logic/tcpclient/client.go | 5 + server/internal/logic/tcpclient/cron.go | 74 +- .../internal/logic/tcpclient/cron_handle.go | 96 +-- .../logic/tcpclient/cron_interceptor.go | 18 + .../internal/logic/tcpserver/auth_handle.go | 74 +- .../internal/logic/tcpserver/cron_handle.go | 88 +-- .../logic/tcpserver/example_handle.go | 49 ++ server/internal/logic/tcpserver/server.go | 46 +- .../internal/logic/tcpserver/server_handle.go | 167 +++++ .../logic/tcpserver/server_interceptor.go | 75 ++ .../model/do/addon_hgexample_table.go | 2 +- server/internal/model/do/admin_cash.go | 2 +- server/internal/model/do/admin_credits_log.go | 2 +- server/internal/model/do/admin_dept.go | 2 +- server/internal/model/do/admin_member.go | 2 +- server/internal/model/do/admin_member_post.go | 2 +- server/internal/model/do/admin_member_role.go | 2 +- server/internal/model/do/admin_menu.go | 2 +- server/internal/model/do/admin_notice.go | 2 +- server/internal/model/do/admin_notice_read.go | 2 +- server/internal/model/do/admin_oauth.go | 2 +- server/internal/model/do/admin_order.go | 2 +- server/internal/model/do/admin_post.go | 4 +- server/internal/model/do/admin_role.go | 2 +- server/internal/model/do/admin_role_casbin.go | 2 +- server/internal/model/do/admin_role_menu.go | 2 +- server/internal/model/do/pay_log.go | 2 +- server/internal/model/do/pay_refund.go | 2 +- server/internal/model/do/sys_addons_config.go | 2 +- server/internal/model/do/sys_attachment.go | 4 +- server/internal/model/do/sys_blacklist.go | 2 +- server/internal/model/do/sys_config.go | 2 +- server/internal/model/do/sys_cron.go | 2 +- server/internal/model/do/sys_cron_group.go | 2 +- server/internal/model/do/sys_dict_data.go | 2 +- server/internal/model/do/sys_dict_type.go | 2 +- server/internal/model/do/sys_ems_log.go | 2 +- server/internal/model/do/sys_gen_codes.go | 2 +- server/internal/model/do/sys_gen_curd_demo.go | 2 +- server/internal/model/do/sys_log.go | 2 +- server/internal/model/do/sys_login_log.go | 2 +- server/internal/model/do/sys_provinces.go | 2 +- server/internal/model/do/sys_serve_license.go | 12 +- server/internal/model/do/sys_serve_log.go | 2 +- server/internal/model/do/sys_sms_log.go | 2 +- server/internal/model/do/test_category.go | 2 +- .../model/entity/addon_hgexample_table.go | 2 +- server/internal/model/entity/admin_cash.go | 2 +- .../model/entity/admin_credits_log.go | 2 +- server/internal/model/entity/admin_dept.go | 2 +- server/internal/model/entity/admin_member.go | 2 +- .../model/entity/admin_member_post.go | 2 +- .../model/entity/admin_member_role.go | 2 +- server/internal/model/entity/admin_menu.go | 2 +- server/internal/model/entity/admin_notice.go | 2 +- .../model/entity/admin_notice_read.go | 2 +- server/internal/model/entity/admin_oauth.go | 2 +- server/internal/model/entity/admin_order.go | 2 +- server/internal/model/entity/admin_post.go | 4 +- server/internal/model/entity/admin_role.go | 2 +- .../model/entity/admin_role_casbin.go | 2 +- .../internal/model/entity/admin_role_menu.go | 2 +- server/internal/model/entity/pay_log.go | 2 +- server/internal/model/entity/pay_refund.go | 2 +- .../model/entity/sys_addons_config.go | 2 +- .../internal/model/entity/sys_attachment.go | 4 +- server/internal/model/entity/sys_blacklist.go | 2 +- server/internal/model/entity/sys_config.go | 2 +- server/internal/model/entity/sys_cron.go | 2 +- .../internal/model/entity/sys_cron_group.go | 2 +- server/internal/model/entity/sys_dict_data.go | 2 +- server/internal/model/entity/sys_dict_type.go | 2 +- server/internal/model/entity/sys_ems_log.go | 2 +- server/internal/model/entity/sys_gen_codes.go | 2 +- .../model/entity/sys_gen_curd_demo.go | 2 +- server/internal/model/entity/sys_log.go | 2 +- server/internal/model/entity/sys_login_log.go | 2 +- server/internal/model/entity/sys_provinces.go | 2 +- .../model/entity/sys_serve_license.go | 12 +- server/internal/model/entity/sys_serve_log.go | 2 +- server/internal/model/entity/sys_sms_log.go | 2 +- server/internal/model/entity/test_category.go | 2 +- server/internal/model/input/adminin/cash.go | 1 - .../model/input/adminin/credits_log.go | 10 +- server/internal/model/input/adminin/dept.go | 17 +- server/internal/model/input/adminin/member.go | 33 +- server/internal/model/input/adminin/menu.go | 43 +- server/internal/model/input/adminin/notice.go | 10 +- server/internal/model/input/adminin/post.go | 77 +- server/internal/model/input/adminin/role.go | 2 +- server/internal/model/input/form/base.go | 112 +-- server/internal/model/input/form/page.go | 60 ++ server/internal/model/input/form/select.go | 25 + server/internal/model/input/msgin/auth.go | 19 - server/internal/model/input/msgin/common.go | 102 --- server/internal/model/input/msgin/cron.go | 49 -- server/internal/model/input/servmsgin/auth.go | 11 + .../internal/model/input/servmsgin/example.go | 9 + server/internal/model/input/sysin/addons.go | 18 +- .../internal/model/input/sysin/attachment.go | 51 +- .../internal/model/input/sysin/blacklist.go | 53 +- server/internal/model/input/sysin/config.go | 78 +- server/internal/model/input/sysin/cron.go | 2 +- .../internal/model/input/sysin/cron_group.go | 4 +- .../internal/model/input/sysin/curd_demo.go | 2 +- .../internal/model/input/sysin/dict_data.go | 16 +- .../internal/model/input/sysin/dict_type.go | 4 +- server/internal/model/input/sysin/ems_log.go | 6 +- .../internal/model/input/sysin/gen_codes.go | 22 +- server/internal/model/input/sysin/log.go | 25 +- .../internal/model/input/sysin/provinces.go | 9 +- .../model/input/sysin/serve_license.go | 190 +++++ server/internal/model/input/sysin/sms_log.go | 25 +- .../internal/model/input/websocketin/send.go | 5 +- server/internal/queues/serve_log.go | 6 +- server/internal/router/admin.go | 1 + server/internal/router/genrouter/curd_demo.go | 2 +- server/internal/service/admin.go | 271 +++---- server/internal/service/common.go | 13 +- server/internal/service/middleware.go | 3 + server/internal/service/pay.go | 14 +- server/internal/service/sys.go | 671 +++++++++++------- server/internal/service/tcpclient.go | 42 +- server/internal/service/tcpserver.go | 18 +- server/internal/websocket/client.go | 4 +- server/internal/websocket/client_manager.go | 2 +- server/internal/websocket/init.go | 16 +- server/manifest/config/config.example.yaml | 41 +- .../controller/admin/sys/config.go.template | 21 +- .../controller/admin/sys/index.go.template | 14 +- .../addon/controller/api/index.go.template | 15 +- .../addon/controller/home/index.go.template | 14 +- .../controller/websocket/index.go.template | 14 +- .../default/addon/crons/crons.go.template | 5 + .../addon/logic/sys/config.go.template | 19 +- .../default/addon/logic/sys/index.go.template | 2 +- .../default/addon/queues/queues.go.template | 5 + .../default/addon/service/sys.go.template | 6 +- .../generate/default/curd/api.go.template | 1 + .../default/curd/controller.go.template | 85 +-- .../generate/default/curd/logic.go.template | 21 +- server/storage/data/hotgo.sql | 653 +++++++++-------- server/utility/convert/match.go | 11 + server/utility/simple/simple.go | 12 +- server/utility/validate/filter.go | 14 +- web/package.json | 2 +- web/src/api/apply/attachment.ts | 15 + web/src/api/monitor/monitor.ts | 27 +- web/src/api/order/index.ts | 2 - web/src/api/serveLicense/index.ts | 60 ++ web/src/api/system/menu.ts | 4 +- web/src/components/CountTo/CountTo.vue | 2 +- web/src/components/FileChooser/index.vue | 361 ++++++++++ .../components/FileChooser/src/Chooser.vue | 428 +++++++++++ .../components/FileChooser/src/Preview.vue | 29 + web/src/components/FileChooser/src/Upload.vue | 113 +++ web/src/components/FileChooser/src/model.ts | 57 ++ web/src/components/SvgIcon/index.vue | 14 +- web/src/components/Upload/src/BasicUpload.vue | 19 +- web/src/components/Upload/uploadImage.vue | 9 +- web/src/directives/debounce.ts | 40 +- web/src/directives/draggable.ts | 62 +- web/src/directives/throttle.ts | 50 +- web/src/hooks/useTime.ts | 4 +- web/src/layout/components/Menu/index.vue | 4 +- web/src/store/modules/user.ts | 4 +- web/src/utils/array.ts | 82 +-- web/src/utils/http/axios/index.ts | 11 +- web/src/utils/is/index.ts | 7 + web/src/views/addons/hgexample/table/edit.vue | 7 +- .../views/addons/hgexample/table/index.vue | 4 +- web/src/views/apply/attachment/columns.ts | 131 ++-- web/src/views/apply/attachment/index.vue | 157 +--- web/src/views/asset/creditsLog/model.ts | 22 +- web/src/views/curdDemo/edit.vue | 2 +- web/src/views/develop/addons/index.vue | 38 +- web/src/views/home/account/BasicSetting.vue | 41 +- web/src/views/home/account/CashSetting.vue | 11 +- web/src/views/home/account/ThirdBind.vue | 14 +- web/src/views/home/message/list.vue | 2 +- web/src/views/log/log/index.vue | 9 +- web/src/views/log/sms-log/index.vue | 53 +- web/src/views/monitor/netconn/columns.ts | 144 ++++ web/src/views/monitor/netconn/index.vue | 215 ++++++ web/src/views/monitor/netconn/modal/edit.vue | 192 +++++ web/src/views/monitor/netconn/modal/index.vue | 347 +++++++++ web/src/views/monitor/netconn/modal/modal.vue | 29 + web/src/views/monitor/netconn/modal/model.ts | 307 ++++++++ web/src/views/monitor/online/index.vue | 63 +- web/src/views/monitor/serve-log/index.vue | 14 +- .../components/chart/FullYearSalesChart.vue | 4 +- .../components/chart/LoadChart.vue | 75 +- web/src/views/monitor/serve-monitor/index.vue | 31 +- web/src/views/org/dept/dept.vue | 35 +- web/src/views/org/post/post.vue | 30 +- web/src/views/org/user/list.vue | 10 +- web/src/views/org/user/model.ts | 6 +- web/src/views/org/user/user.vue | 3 +- .../views/permission/menu/CreateDrawer.vue | 2 +- web/src/views/permission/role/columns.ts | 10 +- web/src/views/permission/role/role.vue | 25 +- web/src/views/system/blacklist/index.vue | 58 +- web/src/views/system/config/BasicSetting.vue | 10 +- web/src/views/system/config/LoginSetting.vue | 24 +- web/src/views/system/config/PaySetting.vue | 2 +- web/src/views/system/config/UploadSetting.vue | 20 +- web/src/views/system/config/WechatSetting.vue | 6 +- web/src/views/system/cron/index.vue | 1 - 492 files changed, 12170 insertions(+), 6982 deletions(-) create mode 100644 docs/guide-zh-CN/sys-cron.md create mode 100644 docs/guide-zh-CN/sys-tcp-server.md create mode 100644 docs/guide-zh-CN/sys-test.md create mode 100644 docs/guide-zh-CN/sys-utility.md create mode 100644 server/api/admin/servelicense/servelicense.go delete mode 100644 server/api/admin/user/hello.go create mode 100644 server/api/servmsg/auth.go create mode 100644 server/api/servmsg/cron.go create mode 100644 server/api/servmsg/example.go create mode 100644 server/internal/consts/servelicense.go delete mode 100644 server/internal/consts/tcp.go create mode 100644 server/internal/controller/admin/sys/serve_license.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_fix_test.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go create mode 100644 server/internal/library/hggen/internal/cmd/genpb/genpb.go create mode 100644 server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go create mode 100644 server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go create mode 100644 server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go create mode 100644 server/internal/library/hggen/internal/cmd/testdata/fix25_content.go.txt create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go create mode 100644 server/internal/library/hggen/internal/utility/utils/utils_http_download.go create mode 100644 server/internal/library/hggen/internal/utility/utils/utils_test.go create mode 100644 server/internal/library/network/tcp/conn.go create mode 100644 server/internal/library/network/tcp/consts.go delete mode 100644 server/internal/library/network/tcp/model.go create mode 100644 server/internal/library/network/tcp/msg.go create mode 100644 server/internal/library/network/tcp/msg_parser.go delete mode 100644 server/internal/library/network/tcp/response.go delete mode 100644 server/internal/library/network/tcp/server_handle.go delete mode 100644 server/internal/library/network/tcp/sign.go create mode 100644 server/internal/library/network/tcp/tcp_example_test.go delete mode 100644 server/internal/library/sms/aliyun/handle.go create mode 100644 server/internal/library/sms/config.go create mode 100644 server/internal/library/sms/sms_aliyun.go rename server/internal/library/sms/{tencent/handle.go => sms_tencent.go} (97%) create mode 100644 server/internal/logic/middleware/pre_filter.go create mode 100644 server/internal/logic/sys/serve_license.go create mode 100644 server/internal/logic/tcpclient/cron_interceptor.go create mode 100644 server/internal/logic/tcpserver/example_handle.go create mode 100644 server/internal/logic/tcpserver/server_handle.go create mode 100644 server/internal/logic/tcpserver/server_interceptor.go create mode 100644 server/internal/model/input/form/page.go create mode 100644 server/internal/model/input/form/select.go delete mode 100644 server/internal/model/input/msgin/auth.go delete mode 100644 server/internal/model/input/msgin/common.go delete mode 100644 server/internal/model/input/msgin/cron.go create mode 100644 server/internal/model/input/servmsgin/auth.go create mode 100644 server/internal/model/input/servmsgin/example.go create mode 100644 server/internal/model/input/sysin/serve_license.go create mode 100644 web/src/api/serveLicense/index.ts create mode 100644 web/src/components/FileChooser/index.vue create mode 100644 web/src/components/FileChooser/src/Chooser.vue create mode 100644 web/src/components/FileChooser/src/Preview.vue create mode 100644 web/src/components/FileChooser/src/Upload.vue create mode 100644 web/src/components/FileChooser/src/model.ts create mode 100644 web/src/views/monitor/netconn/columns.ts create mode 100644 web/src/views/monitor/netconn/index.vue create mode 100644 web/src/views/monitor/netconn/modal/edit.vue create mode 100644 web/src/views/monitor/netconn/modal/index.vue create mode 100644 web/src/views/monitor/netconn/modal/modal.vue create mode 100644 web/src/views/monitor/netconn/modal/model.ts diff --git a/README.md b/README.md index bd68f71..3a755e1 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ 15. 代码生成:支持自动化生成前后端代码。CURD关联表、树表、消息队列、定时任务一键生成等。 16. 插件应用:支持一键生成插件模板,每个插件之间开发隔离,拥有独立多应用入口、独立配置。完美支持多人协同开发、插件插拔不会对原系统产生影响等。 17. 服务监控:监视当前系统CPU、内存、磁盘、网络、堆栈等相关信息。 -18. 附件管理:文件图片上传,支持本地、阿里云oss、腾讯云cos、ucloud对象存储、七牛云对象存储等多种上传驱动,后台一键切换配置。 -19. TCP服务:基于gtcp的应用实例,支持长连接、断线重连、自动维护心跳、签名、服务登录、服务授权等。主要用于C/S服务器和服务进程之间的数据通讯。 +18. 附件管理:文件图片上传,支持本地、阿里云oss、腾讯云cos、ucloud对象存储、七牛云对象存储等多种上传驱动,后台一键切换配置,并集成了文件选择器。 +19. TCP服务:基于gtcp的服务应用,支持长连接、断线重连、服务认证、路由分发、RPC消息、拦截器和数据绑定等。简化和规范了服务器开发流程。 20. 消息队列:同时兼容 kafka、redis、rocketmq、磁盘队列,一键配置切换到场景适用的MQ。 21. 通知公告:采用websocket实时推送在线用户最新通知、公告、私信消息。 22. 地区编码:整合国内通用省市区编码,运用于项目于一身,支持动态省市区选项。 diff --git a/docs/guide-zh-CN/README.md b/docs/guide-zh-CN/README.md index b9291e9..3c2721b 100644 --- a/docs/guide-zh-CN/README.md +++ b/docs/guide-zh-CN/README.md @@ -22,15 +22,16 @@ - [支付网关](sys-payment.md) - [数据库](sys-db.md) - [代码生成](sys-code.md) -- 定时任务 +- [定时任务](sys-cron.md) - [消息队列](sys-queue.md) - [功能扩展库](sys-library.md) -- 工具方法 +- [工具方法](sys-utility.md) - RESTful Api - Websocket服务器 -- TCP服务器 -- 单元测试 +- [TCP服务器](sys-tcp-server.md) +- [单元测试](sys-test.md) + #### 插件模块开发 - [模块介绍及目录](addon-introduce-catalog.md) @@ -38,16 +39,12 @@ - [模块辅助说明](addon-helper.md) -#### 实战开发 -- 服务端 -- web前端 - - ### 前端开发 - [表单组件](web-form.md) - Websocket客户端 - 工具库 - [独立部署](web-deploy.md) + #### 附录 - [网址收录](append-website.md) \ No newline at end of file diff --git a/docs/guide-zh-CN/images/sys-middleware-com-response.png b/docs/guide-zh-CN/images/sys-middleware-com-response.png index 86e86d0a5faabb4db16e1b6a7d230958ba3d7005..26d46a5ee9d94d690de431e476c47e12bc61ef80 100644 GIT binary patch literal 33792 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 diff --git a/docs/guide-zh-CN/start-update-log.md b/docs/guide-zh-CN/start-update-log.md index 67888b0..b865c7c 100644 --- a/docs/guide-zh-CN/start-update-log.md +++ b/docs/guide-zh-CN/start-update-log.md @@ -12,6 +12,19 @@ > 如果升级(覆盖)代码后打开会出现 sql 报错, 请检查更新的数据库格式或自行调整 +### v2.8.4 +updated 2023.07.22 + +- 增加:增加输入预处理中间件 +- 增加:增加文件选择器 +- 增加:增加在线服务监控,服务许可证管理 +- 增加:TCP服务器增加RPC路由消息注册、路由分发、拦截器注册,更方便的进行应用开发 +- 优化:gf版本升级到v2.5.0 +- 优化:优化CURD代码生成,简化控制器代码逻辑,升级列表数据查询方式 +- 修复:修复角色菜单子权限取消导致父级权限失效问题 +- 修复:修复上传驱动路径错误 +- 修复:修复CURD代码生成时间类型字段默认值获取异常 + ### v2.7.6 updated 2023.06.19 @@ -19,7 +32,6 @@ updated 2023.06.19 - 修复:部门管理查询空指针问题 - 优化:附件md5生成方式调整(与更新前已上传的文件md5不兼容,如果业务有影响请注意调整) - ### v2.7.3 updated 2023.05.14 diff --git a/docs/guide-zh-CN/sys-code.md b/docs/guide-zh-CN/sys-code.md index 5d9e9b6..c233d2a 100644 --- a/docs/guide-zh-CN/sys-code.md +++ b/docs/guide-zh-CN/sys-code.md @@ -5,9 +5,11 @@ - 使用条件 - 生成配置 - 一个生成增删改查列表例子 -- 内置gf-cli - 多数据库生成配置 - 自定义生成模板 +- 内置gf-cli +- 指定gf-cli版本 +- 指定数据库驱动类型 > 在HotGo中可以通过后台开发工具快速的一键生成CRUD,自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。 @@ -263,13 +265,6 @@ INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `conte -### 内置gf-cli - -> 由于gf版本更新较常出现向下不兼容的情况,所以我们为了保证生成代码的依赖稳定性,我们将gf-cli工具内置到了系统中并做了一些在线执行的调整。 - -- 后续我们也将开放在线运行`gf gen dao`、`gf gen service`功能。在做插件开发时也会支持到在线生成插件下的service接口,这将会使得插件开发更加方便 - - ## 多数据库生成配置 #### 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码 @@ -339,3 +334,53 @@ hggen: - 如果你在实际的开发过程中默认模板需要调整的地方较多时,HotGo允许你新建新的模板分组来满足你的需求。新的模板可根据现有模板基础拷贝一份出来做改造,默认模板目录:[server/resource/generate/default](../../server/resource/generate/default) + + +### 内置gf-cli + +> 为了确保生成代码的依赖稳定性,在面对`gf`版本更新可能导致向下不兼容情况时,HotGo将`gf-cli`工具内置到系统中并进行在线执行调整,从而提供更可靠和一致的生成代码功能。 + +- 后续我们也将开放在线运行`gf gen ...`功能。在做插件开发时也会支持到在线生成插件下的service接口,这将会使得插件开发更加方便 + + + +### 指定gf-cli版本 + +> HotGo多数情况下会和最新版本的gf-cli保持同步,如果更新不及时或你不想使用最新版本的gf-cli来生成代码,可以找到自己想要的版本进行替换即可。 + +- 下面大致做一些替换步骤说明: + +1. 打开https://github.com/gogf/gf,找到你想要使用的版本`clone`下来 +2. 将`clone`代码中`gf/cmd/gf/internal/`目录覆盖到`server/internal/library/hggen/internal` +3. 将覆盖过来的目录文件中引入包名`github.com/gogf/gf/cmd/gf/v2/`批量改为`hotgo/internal/library/hggen/` +4. 运行`go mod tidy` +5. 运行`go run main.go`,如果没有报错,那么恭喜你已经完成了。如果有报错一般都是版本差异带来的影响,需要根据情况自行调整 + + + +### 指定数据库驱动类型 + +> HotGo默认使用mysql驱动,如果你想用其他数据库驱动打开下方文件中注释即可 + +修改文件路径:server/internal/library/hggen/internal/cmd/cmd_gen_dao.go +```go +package cmd + +import ( + //_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2" + //_ "github.com/gogf/gf/contrib/drivers/mssql/v2" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + //_ "github.com/gogf/gf/contrib/drivers/oracle/v2" + //_ "github.com/gogf/gf/contrib/drivers/pgsql/v2" + //_ "github.com/gogf/gf/contrib/drivers/sqlite/v2" + + "hotgo/internal/library/hggen/internal/cmd/gendao" +) + +type ( + cGenDao = gendao.CGenDao +) + +``` + +修改完成后运行`go mod tidy` diff --git a/docs/guide-zh-CN/sys-cron.md b/docs/guide-zh-CN/sys-cron.md new file mode 100644 index 0000000..10835d7 --- /dev/null +++ b/docs/guide-zh-CN/sys-cron.md @@ -0,0 +1,71 @@ +## 定时任务 + +目录 + +- 实现接口 +- 一个例子 +- 更多 + +> 在实际的项目开发中,定时任务几乎成为不可或缺的一部分。HotGo为定时任务提供一个方便的后台操作界面,让您能够轻松地进行在线启停、修改和立即执行等操作。这样的设计可以极大地改善您在使用定时任务过程中的体验,让整个过程更加顺畅、高效。 + + +### 实现接口 +- 为了提供高度的扩展性,定时任务在设计上采用了接口化的思路。只需要实现以下接口,您就可以在任何地方注册和使用定时任务功能,从而实现更大的灵活性和可扩展性。 + +```go +// Cron 定时任务接口 +type Cron interface { + // GetName 获取任务名称 + GetName() string + // Execute 执行一次任务 + Execute(ctx context.Context) +} +``` + + +### 一个例子 + +定时任务的文件结构可以根据具体需要进行调整,以下是一个常见的参考结构: + +- 文件路径:server/internal/crons/test.go + +```go +package crons + +import ( + "context" + "hotgo/internal/library/cron" + "time" +) + +func init() { + cron.Register(Test) +} + +// Test 测试任务(无参数) +var Test = &cTest{name: "test"} + +type cTest struct { + name string +} + +func (c *cTest) GetName() string { + return c.name +} + +// Execute 执行任务 +func (c *cTest) Execute(ctx context.Context) { + cron.Logger().Infof(ctx, "cron test Execute:%v", time.Now()) +} + + +``` + +继续在后台系统设置-定时任务-添加任务,填写的任务名称需要和上面的名称保持一致,再进行简单的策略配置以后,一个后台可控的定时任务就添加好了! + + +### 更多 + +定时任务源码路径:server/internal/library/cron/cron.go + +更多文档请参考:https://goframe.org/pages/viewpage.action?pageId=1114187 \ No newline at end of file diff --git a/docs/guide-zh-CN/sys-middleware.md b/docs/guide-zh-CN/sys-middleware.md index 01deb94..53e49b7 100644 --- a/docs/guide-zh-CN/sys-middleware.md +++ b/docs/guide-zh-CN/sys-middleware.md @@ -33,6 +33,9 @@ func main() { // 演示系統操作限制,当开启演示模式时,所有POST请求将被拒绝 service.Middleware().DemoLimit() + + // 请求输入预处理,api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可隐式预处理 + service.Middleware().PreFilter() // HTTP响应预处理,在业务处理完成后,对响应结果进行格式化和错误过滤,将处理后的数据发送给请求方 service.Middleware().ResponseHandler() @@ -143,7 +146,6 @@ func main() { 2. 在`server/internal/logic/middleware/response.go`中根据请求的独有特征进行单独的处理,兼容后续http处理。 - #### 重写响应错误提示 - 在实际开发中,我们可能想要隐藏一些敏感错误,返回给客户端友好的错误提示,但开发者同时又想需要看到真实的敏感错误。对此hotgo已经进行了过滤处理,下面是一个简单的例子: diff --git a/docs/guide-zh-CN/sys-tcp-server.md b/docs/guide-zh-CN/sys-tcp-server.md new file mode 100644 index 0000000..8ef612f --- /dev/null +++ b/docs/guide-zh-CN/sys-tcp-server.md @@ -0,0 +1,271 @@ +## TCP服务器 + +目录 + +- 配置文件 +- 一个基本的消息收发例子 +- 注册路由 +- 拦截器 +- 服务认证 +- 更多 + +> HotGo基于GF框架的TCP服务器组件,提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能,如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等,大大简化和规范了服务器开发流程。 + +### 配置文件 +- 配置文件:server/manifest/config/config.yaml + +```yaml +tcp: + # 服务器 + server: + address: ":8099" + # 客户端 + client: + # 定时任务 + cron: + group: "cron" # 分组名称 + name: "cron1" # 客户端名称 + address: "127.0.0.1:8099" # 服务器地址 + appId: "1002" # 应用名称 + secretKey: "hotgo" # 密钥 + # 系统授权 + auth: + group: "auth" # 分组名称 + name: "auth1" # 客户端名称 + address: "127.0.0.1:8099" # 服务器地址 + appId: "mengshuai" # 应用名称 + secretKey: "123456" # 密钥 + +``` +- 可以看到,除了服务器配置外,还有两个客户端配置`cron` 和`auth` +- `cron`是HotGo内置的定时任务服务,和http服务通过RPC通讯以实现和后台交互,使其可以独立、集群部署。 +- `auth`可以为第三方平台提供授权服务。如果你需要他,可以将它部署在第三方程序中,在重要的位置进行授权验证。 + +### 一个基本的消息收发测试用例 + +- 文件路径:server/internal/library/network/tcp/tcp_example_test.go + +```go +package tcp_test + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" + "hotgo/internal/library/network/tcp" + "testing" + "time" +) + +var T *testing.T // 声明一个全局的 *testing.T 变量 + +type TestMsgReq struct { + Name string `json:"name"` +} + +type TestMsgRes struct { + tcp.ServerRes +} + +type TestRPCMsgReq struct { + Name string `json:"name"` +} + +type TestRPCMsgRes struct { + tcp.ServerRes +} + +func onTestMsg(ctx context.Context, req *TestMsgReq) { + fmt.Printf("服务器收到消息 ==> onTestMsg:%+v\n", req) + conn := tcp.ConnFromCtx(ctx) + gtest.C(T, func(t *gtest.T) { + t.AssertNE(conn, nil) + }) + + res := new(TestMsgRes) + res.Message = fmt.Sprintf("你的名字:%v", req.Name) + conn.Send(ctx, res) +} + +func onResponseTestMsg(ctx context.Context, req *TestMsgRes) { + fmt.Printf("客户端收到响应消息 ==> TestMsgRes:%+v\n", req) + err := req.GetError() + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) +} + +func onTestRPCMsg(ctx context.Context, req *TestRPCMsgReq) (res *TestRPCMsgRes, err error) { + fmt.Printf("服务器收到消息 ==> onTestRPCMsg:%+v\n", req) + res = new(TestRPCMsgRes) + res.Message = fmt.Sprintf("你的名字:%v", req.Name) + return +} + +func startTCPServer() { + serv := tcp.NewServer(&tcp.ServerConfig{ + Name: "hotgo", + Addr: ":8002", + }) + + // 注册路由 + serv.RegisterRouter( + onTestMsg, + ) + + // 注册RPC路由 + serv.RegisterRPCRouter( + onTestRPCMsg, + ) + + // 服务监听 + err := serv.Listen() + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) +} + +// 一个基本的消息收发 +func TestSendMsg(t *testing.T) { + T = t + go startTCPServer() + + ctx := gctx.New() + client := tcp.NewClient(&tcp.ClientConfig{ + Addr: "127.0.0.1:8002", + }) + + // 注册路由 + client.RegisterRouter( + onResponseTestMsg, + ) + + go func() { + err := client.Start() + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) + }() + + // 确保服务都启动完成 + time.Sleep(time.Second * 1) + + // 拿到客户端的连接 + conn := client.Conn() + gtest.C(T, func(t *gtest.T) { + t.AssertNE(conn, nil) + }) + + // 向服务器发送tcp消息,不会阻塞程序执行 + err := conn.Send(ctx, &TestMsgReq{Name: "Tom"}) + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) + + // 向服务器发送rpc消息,会等待服务器响应结果,直到拿到结果或响应超时才会继续 + var res TestRPCMsgRes + if err = conn.RequestScan(ctx, &TestRPCMsgReq{Name: "Tony"}, &res); err != nil { + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) + } + + fmt.Printf("客户端收到RPC消息响应 ==> TestRPCMsgRes:%+v\n", res) + time.Sleep(time.Second * 1) +} + +``` + + +### 注册路由 + +- 从上面的例子可以看到,不管是普通TCP消息和RPC消息的请求/响应结构体都采用类似GF框架的规范路由的结构,请求`XxxRes`/响应`XxxRes`的格式,是不是很亲切? + + +### 拦截器 + +- 不管是服务端还是客户端,在初始化时都可以注册多个拦截器来满足更多场景的服务开发,下面是一个使用例子: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "hotgo/internal/library/network/tcp" +) + +func main() { + serv = tcp.NewServer(&tcp.ServerConfig{ + Name: "hotgo", + Addr: ":8002", + }) + + // 注册拦截器 + // 执行顺序是从前到后,即Interceptor -> Interceptor2 -> Interceptor3。如果中间有任意一个抛出错误,则会中断后续处理 + serv.RegisterInterceptor(Interceptor, Interceptor2, Interceptor3) + + // 服务监听 + if err := serv.Listen(); err != nil { + if !serv.IsClose() { + g.Log().Warningf(ctx, "TCPServer Listen err:%v", err) + } + } +} + +func Interceptor(ctx context.Context, msg *tcp.Message) (err error) { + // 可以在拦截器中通过上下文拿到连接 + conn := tcp.ConnFromCtx(ctx) + + // 拿到原始请求消息 + g.Dump(msg) + + // 如果想要中断后续处理只需返回一个错误即可,但注意两种情况 + // tcp消息:如果你还想对该消息进行回复应在拦截器中进行处理,例如:conn.Send(ctx, 回复消息内容) + // rpc消息:返回一个错误后系统会将错误自动回复到rpc响应中,无需单独处理 + return +} + +func Interceptor2(ctx context.Context, msg *tcp.Message) (err error) { + // ... + return +} + +func Interceptor3(ctx context.Context, msg *tcp.Message) (err error) { + // ... + return +} + +``` + + +### 服务认证 + +- 一般情况下,建议客户端连接到服务器时都通过`授权许可证`的方式进行登录认证,当初始化客户端配置认证数据时,连接成功后会自动进行登录认证。 + +```go + // 创建客户端配置 + clientConfig := &tcp.ClientConfig{ + Addr: "127.0.0.1:8002", + AutoReconnect: true, + // 认证数据 + // 认证数据可以在后台-系统监控-在线服务-许可证列表中添加,同一个授权支持多个服务使用,但多个服务不能使用相同的名称进行连接 + Auth: &tcp.AuthMeta{ + Name: "服务名称", + Group: "服务分组", + AppId: "APPID", + SecretKey: "SecretKey", + }, + } + + // 初始化客户端 + client = tcp.NewClient(clientConfig) +``` + + +### 更多 + +TCP服务器源码路径:server/internal/library/network/tcp + +更多文档请参考:https://goframe.org/pages/viewpage.action?pageId=1114625 diff --git a/docs/guide-zh-CN/sys-test.md b/docs/guide-zh-CN/sys-test.md new file mode 100644 index 0000000..0e19710 --- /dev/null +++ b/docs/guide-zh-CN/sys-test.md @@ -0,0 +1,3 @@ +## 单元测试 + +请参考:https://goframe.org/pages/viewpage.action?pageId=1114153 \ No newline at end of file diff --git a/docs/guide-zh-CN/sys-utility.md b/docs/guide-zh-CN/sys-utility.md new file mode 100644 index 0000000..ee5ef2e --- /dev/null +++ b/docs/guide-zh-CN/sys-utility.md @@ -0,0 +1,20 @@ +## 工具方法 + +HotGo还提供一些系统中常用的工具库方法,在这里简单说明: + +``` +/server +├── utility +│ ├── charset # 字符串处理 +│ ├── convert # 数据类型转换 +│ ├── encrypt # 数据加密/解密 +│ ├── excel # 电子表格导出/导入 +│ ├── file # 文件/目录处理 +│ ├── format # 数据格式化 +│ ├── simple # 一些简捷函数 +│ ├── tree # 树形结构 +│ ├── url # URL处理 +│ ├── useragent # 请求头代理处理 +└── └── validate # 数据验证 +``` + diff --git a/docs/guide-zh-CN/web-form.md b/docs/guide-zh-CN/web-form.md index 243ba1d..d23b812 100644 --- a/docs/guide-zh-CN/web-form.md +++ b/docs/guide-zh-CN/web-form.md @@ -20,6 +20,7 @@ - 多图上传 UploadImage - 单文件上传 UploadFile - 多文件上传 UploadFile +- 文件选择器 FileChooser - 开关 Switch - 评分 Rate - 省市区选择器 CitySelector @@ -765,6 +766,35 @@ const value = ref(null); ``` +### 文件选择器 FileChooser +- 基础用法 +```vue + + + +``` + +- 指定fileType,支持多种选择器类型,默认情况是全部都可以选择 +```ts +type FileType = 'image' | 'doc' | 'audio' | 'video' | 'zip' | 'other' | 'default'; +``` + +- 图片选择器 +```vue + +``` + +- 多选支持,指定`maxNumber`多选数量 +```vue + +``` + ### 开关 Switch ```vue diff --git a/web/src/views/home/account/BasicSetting.vue b/web/src/views/home/account/BasicSetting.vue index f30722b..adad530 100644 --- a/web/src/views/home/account/BasicSetting.vue +++ b/web/src/views/home/account/BasicSetting.vue @@ -73,7 +73,7 @@ style="margin-top: 15px" > - + @@ -122,13 +122,13 @@ + + diff --git a/web/src/views/monitor/netconn/modal/edit.vue b/web/src/views/monitor/netconn/modal/edit.vue new file mode 100644 index 0000000..6b33182 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/edit.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/index.vue b/web/src/views/monitor/netconn/modal/index.vue new file mode 100644 index 0000000..1ee83b1 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/index.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/modal.vue b/web/src/views/monitor/netconn/modal/modal.vue new file mode 100644 index 0000000..cbdd692 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/modal.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/model.ts b/web/src/views/monitor/netconn/modal/model.ts new file mode 100644 index 0000000..1b9dadf --- /dev/null +++ b/web/src/views/monitor/netconn/modal/model.ts @@ -0,0 +1,307 @@ +import { h, ref } from 'vue'; +import { NTag } from 'naive-ui'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { Dicts } from '@/api/dict/dict'; +import { isNullObject } from '@/utils/is'; +import { defRangeShortcuts, formatBefore } from '@/utils/dateUtil'; +import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo'; +import { NetOption } from '@/api/monitor/monitor'; + +export interface State { + id: number; + group: string; + name: string; + appid: string; + secretKey: string; + remoteAddr: string; + onlineLimit: number; + loginTimes: number; + lastLoginAt: string; + lastActiveAt: string; + routes: any; + allowedIps: string; + endAt: string; + remark: string; + status: number; + createdAt: string; + updatedAt: string; +} + +export const defaultState = { + id: 0, + group: '', + name: '', + appid: '', + secretKey: '', + remoteAddr: '', + onlineLimit: 1, + loginTimes: 0, + lastLoginAt: '', + lastActiveAt: '', + routes: null, + allowedIps: '', + endAt: '', + remark: '', + status: 1, + createdAt: '', + updatedAt: '', +}; + +export function newState(state: State | null): State { + if (state !== null) { + return cloneDeep(state); + } + return cloneDeep(defaultState); +} + +export const options = ref({ + sys_normal_disable: [], + group: [], + routes: [], +}); + +export const rules = { + group: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入分组', + }, + name: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入许可名称', + }, + appid: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入应用ID', + }, + endAt: { + required: true, + trigger: ['blur', 'input', 'focus'], + type: 'string', + message: '请输入授权结束时间', + }, +}; + +export const schemas = ref([ + { + field: 'id', + component: 'NInput', + label: '许可ID', + componentProps: { + placeholder: '请输入许可ID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'group', + component: 'NSelect', + label: '授权分组', + defaultValue: null, + componentProps: { + placeholder: '请选择授权分组', + options: options.value.group, + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'name', + component: 'NInput', + label: '许可名称', + componentProps: { + placeholder: '请输入许可名称', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'appid', + component: 'NInput', + label: 'APPID', + componentProps: { + placeholder: '请输入APPID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择状态', + options: [], + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'endAt', + component: 'NDatePicker', + label: '过期时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +export const columns = [ + { + title: '许可ID', + key: 'id', + width: 100, + }, + { + title: '授权分组', + key: 'group', + width: 100, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => getOptionLabel(options.value.group, row.group), + } + ); + }, + }, + { + title: '授权许可', + key: 'name', + render(row) { + return h('p', { id: 'app' }, [ + h('div', { + innerHTML: '

名称:' + row.name + '

', + }), + h('div', { + innerHTML: '

APPID:' + row.appid + '

', + }), + ]); + }, + width: 180, + }, + { + title: '在线', + key: 'online', + render(row) { + return row.online + ' / ' + row.onlineLimit; + }, + width: 100, + }, + { + title: '授权有效期', + key: 'endAt', + width: 150, + }, + { + title: '状态', + key: 'status', + width: 100, + render(row) { + if (isNullObject(row.status)) { + return ``; + } + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: getOptionTag(options.value.sys_normal_disable, row.status), + bordered: false, + }, + { + default: () => getOptionLabel(options.value.sys_normal_disable, row.status), + } + ); + }, + }, + { + title: '最后连接', + key: 'remoteAddr', + width: 150, + }, + { + title: '最近登录 / 心跳', + key: 'name', + render(row) { + if (row.lastLoginAt === null) { + return '从未登录'; + } + return ( + formatBefore(new Date(row.lastLoginAt)) + ' / ' + formatBefore(new Date(row.lastActiveAt)) + ); + }, + width: 180, + }, + { + title: '累计登录', + key: 'loginTimes', + width: 100, + }, + { + title: '创建时间', + key: 'createdAt', + width: 150, + }, +]; + +async function loadOptions() { + options.value = await Dicts({ + types: ['sys_normal_disable'], + }); + + const netOption = await NetOption(); + options.value.group = netOption.licenseGroup; + options.value.routes = netOption.routes; + + for (const item of schemas.value) { + switch (item.field) { + case 'status': + item.componentProps.options = options.value.sys_normal_disable; + break; + case 'group': + item.componentProps.options = options.value.group; + break; + } + } +} + +await loadOptions(); diff --git a/web/src/views/monitor/online/index.vue b/web/src/views/monitor/online/index.vue index 8224362..d581eb7 100644 --- a/web/src/views/monitor/online/index.vue +++ b/web/src/views/monitor/online/index.vue @@ -1,20 +1,25 @@ diff --git a/web/src/views/org/user/model.ts b/web/src/views/org/user/model.ts index aaf80c1..b595b73 100644 --- a/web/src/views/org/user/model.ts +++ b/web/src/views/org/user/model.ts @@ -178,7 +178,7 @@ export const options = ref({ post: [], }); -async function loadOptions() { +export async function loadOptions() { const dept = await getDeptOption(); if (dept.list !== undefined) { options.value.dept = dept.list; @@ -187,6 +187,7 @@ async function loadOptions() { const role = await getRoleOption(); if (role.list !== undefined) { options.value.role = role.list; + options.value.roleTabs = [{ id: -1, name: '全部' }]; treeDataToCompressed(role.list); } @@ -207,8 +208,5 @@ function treeDataToCompressed(source) { ? treeDataToCompressed(source[i].children) : ''; // 子级递归 } - return options.value.roleTabs; } - -await loadOptions(); diff --git a/web/src/views/org/user/user.vue b/web/src/views/org/user/user.vue index c82054e..705a35f 100644 --- a/web/src/views/org/user/user.vue +++ b/web/src/views/org/user/user.vue @@ -39,7 +39,8 @@ } }); - function handleBeforeLeave(tabName: string) { + function handleBeforeLeave(tabName: string): boolean | Promise { defaultTab.value = tabName; + return true; } diff --git a/web/src/views/permission/menu/CreateDrawer.vue b/web/src/views/permission/menu/CreateDrawer.vue index 8f7612c..656b6e9 100644 --- a/web/src/views/permission/menu/CreateDrawer.vue +++ b/web/src/views/permission/menu/CreateDrawer.vue @@ -296,7 +296,7 @@ }, }, emits: ['loadData'], - setup(props, context) { + setup(_props, context) { const message = useMessage(); const formRef: any = ref(null); const state = reactive({ diff --git a/web/src/views/permission/role/columns.ts b/web/src/views/permission/role/columns.ts index 2ed0b91..198db0c 100644 --- a/web/src/views/permission/role/columns.ts +++ b/web/src/views/permission/role/columns.ts @@ -40,7 +40,7 @@ export const columns = [ { title: '角色编码', key: 'key', - // width: 150, + width: 150, }, // { // title: '上级角色', @@ -60,22 +60,22 @@ export const columns = [ } ); }, - // width: 80, + width: 80, }, { title: '排序', key: 'sort', - // width: 100, + width: 100, }, { title: '备注', key: 'remark', - // width: 300, + width: 180, }, { title: '状态', key: 'status', - // width: 80, + width: 80, render(row) { return h( NTag, diff --git a/web/src/views/permission/role/role.vue b/web/src/views/permission/role/role.vue index 8b81402..e816e82 100644 --- a/web/src/views/permission/role/role.vue +++ b/web/src/views/permission/role/role.vue @@ -31,8 +31,8 @@