From 24f356c87c1fb41c5e001c04ebb7ab8e4da35c1c Mon Sep 17 00:00:00 2001 From: Code Migration Date: Sun, 18 Oct 2026 13:25:17 +0800 Subject: [PATCH] chore: seed migration objects batch 12 --- server/internal/crons/test2.go | 45 + server/internal/dao/.gitkeep | 0 server/internal/dao/addon_hgexample_table.go | 22 + .../dao/addon_hgexample_tenant_order.go | 22 + server/internal/dao/admin_cash.go | 22 + server/internal/dao/admin_credits_log.go | 22 + server/internal/dao/admin_dept.go | 22 + server/internal/dao/admin_member.go | 22 + server/internal/dao/admin_member_post.go | 22 + server/internal/dao/admin_member_role.go | 22 + server/internal/dao/admin_menu.go | 22 + server/internal/dao/admin_notice.go | 22 + server/internal/dao/admin_notice_read.go | 22 + server/internal/dao/admin_oauth.go | 22 + server/internal/dao/admin_order.go | 22 + server/internal/dao/admin_post.go | 22 + server/internal/dao/admin_role.go | 22 + server/internal/dao/admin_role_casbin.go | 22 + server/internal/dao/admin_role_menu.go | 22 + .../dao/internal/addon_hgexample_table.go | 151 +++ .../internal/addon_hgexample_tenant_order.go | 99 ++ server/internal/dao/internal/admin_cash.go | 97 ++ .../dao/internal/admin_credits_log.go | 107 ++ server/internal/dao/internal/admin_dept.go | 105 ++ server/internal/dao/internal/admin_member.go | 133 +++ .../dao/internal/admin_member_post.go | 81 ++ .../dao/internal/admin_member_role.go | 81 ++ server/internal/dao/internal/admin_menu.go | 129 +++ server/internal/dao/internal/admin_notice.go | 105 ++ .../dao/internal/admin_notice_read.go | 89 ++ server/internal/dao/internal/admin_oauth.go | 107 ++ server/internal/dao/internal/admin_order.go | 101 ++ server/internal/dao/internal/admin_post.go | 93 ++ server/internal/dao/internal/admin_role.go | 103 ++ .../dao/internal/admin_role_casbin.go | 93 ++ .../internal/dao/internal/admin_role_menu.go | 81 ++ server/internal/dao/internal/pay_log.go | 137 +++ server/internal/dao/internal/pay_refund.go | 103 ++ .../dao/internal/sys_addons_config.go | 105 ++ .../dao/internal/sys_addons_install.go | 89 ++ .../internal/dao/internal/sys_attachment.go | 111 ++ server/internal/dao/internal/sys_blacklist.go | 89 ++ server/internal/dao/internal/sys_config.go | 103 ++ server/internal/dao/internal/sys_cron.go | 103 ++ .../internal/dao/internal/sys_cron_group.go | 95 ++ server/internal/dao/internal/sys_dict_data.go | 101 ++ server/internal/dao/internal/sys_dict_type.go | 95 ++ server/internal/dao/internal/sys_ems_log.go | 97 ++ server/internal/dao/internal/sys_gen_codes.go | 105 ++ .../dao/internal/sys_gen_curd_demo.go | 111 ++ .../dao/internal/sys_gen_tree_demo.go | 105 ++ server/internal/dao/internal/sys_log.go | 123 ++ server/internal/dao/internal/sys_login_log.go | 105 ++ server/internal/dao/internal/sys_provinces.go | 101 ++ .../dao/internal/sys_serve_license.go | 111 ++ server/internal/dao/internal/sys_serve_log.go | 97 ++ server/internal/dao/internal/sys_sms_log.go | 95 ++ server/internal/dao/internal/test_category.go | 97 ++ server/internal/dao/pay_log.go | 22 + server/internal/dao/pay_refund.go | 22 + server/internal/dao/sys_addons_config.go | 22 + server/internal/dao/sys_addons_install.go | 22 + server/internal/dao/sys_attachment.go | 22 + server/internal/dao/sys_blacklist.go | 22 + server/internal/dao/sys_config.go | 22 + server/internal/dao/sys_cron.go | 22 + server/internal/dao/sys_cron_group.go | 22 + server/internal/dao/sys_dict_data.go | 22 + server/internal/dao/sys_dict_type.go | 22 + server/internal/dao/sys_ems_log.go | 22 + server/internal/dao/sys_gen_codes.go | 22 + server/internal/dao/sys_gen_curd_demo.go | 22 + server/internal/dao/sys_gen_tree_demo.go | 22 + server/internal/dao/sys_log.go | 22 + server/internal/dao/sys_login_log.go | 22 + server/internal/dao/sys_provinces.go | 22 + server/internal/dao/sys_serve_license.go | 22 + server/internal/dao/sys_serve_log.go | 22 + server/internal/dao/sys_sms_log.go | 22 + server/internal/dao/test_category.go | 22 + server/internal/global/cluster.go | 51 + server/internal/global/global.go | 8 + server/internal/global/httproutes.go | 125 ++ server/internal/global/init.go | 168 +++ server/internal/library/addons/addons.go | 61 + server/internal/library/addons/build.go | 138 +++ .../internal/library/addons/build_layout.go | 232 ++++ server/internal/library/addons/install.go | 120 ++ server/internal/library/addons/module.go | 186 +++ server/internal/library/cache/cache.go | 58 + server/internal/library/cache/file/file.go | 322 ++++++ server/internal/library/captcha/captcha.go | 77 ++ server/internal/library/casbin/adapter.go | 333 ++++++ server/internal/library/casbin/casbin_test.go | 67 ++ server/internal/library/casbin/enforcer.go | 164 +++ server/internal/library/contexts/context.go | 195 ++++ server/internal/library/contexts/detached.go | 35 + server/internal/library/cron/cron.go | 313 +++++ server/internal/library/cron/cron_gen.go | 88 ++ server/internal/library/debris/debris.go | 9 + server/internal/library/dict/dict.go | 81 ++ server/internal/library/dict/dict_option.go | 181 +++ .../library/dict/dict_register_enums.go | 70 ++ .../library/dict/dict_register_func.go | 129 +++ server/internal/library/ems/ems.go | 50 + server/internal/library/hggen/hggen.go | 290 +++++ server/internal/library/hggen/init.go | 130 +++ .../library/hggen/internal/cmd/cmd.go | 79 ++ .../library/hggen/internal/cmd/cmd_build.go | 399 +++++++ .../library/hggen/internal/cmd/cmd_doc.go | 184 +++ .../library/hggen/internal/cmd/cmd_docker.go | 179 +++ .../library/hggen/internal/cmd/cmd_env.go | 85 ++ .../library/hggen/internal/cmd/cmd_fix.go | 152 +++ .../library/hggen/internal/cmd/cmd_gen.go | 42 + .../hggen/internal/cmd/cmd_gen_ctrl.go | 15 + .../library/hggen/internal/cmd/cmd_gen_dao.go | 25 + .../hggen/internal/cmd/cmd_gen_enums.go | 15 + .../library/hggen/internal/cmd/cmd_gen_pb.go | 13 + .../hggen/internal/cmd/cmd_gen_pbentity.go | 13 + .../hggen/internal/cmd/cmd_gen_service.go | 15 + .../library/hggen/internal/cmd/cmd_init.go | 182 +++ .../library/hggen/internal/cmd/cmd_install.go | 34 + .../library/hggen/internal/cmd/cmd_pack.go | 104 ++ .../library/hggen/internal/cmd/cmd_run.go | 283 +++++ .../library/hggen/internal/cmd/cmd_tpl.go | 174 +++ .../library/hggen/internal/cmd/cmd_up.go | 279 +++++ .../library/hggen/internal/cmd/cmd_version.go | 162 +++ .../hggen/internal/cmd/genctrl/genctrl.go | 238 ++++ .../internal/cmd/genctrl/genctrl_api_item.go | 37 + .../internal/cmd/genctrl/genctrl_ast_parse.go | 95 ++ .../cmd/genctrl/genctrl_ast_parse_clear.go | 43 + .../internal/cmd/genctrl/genctrl_calculate.go | 137 +++ .../cmd/genctrl/genctrl_generate_ctrl.go | 231 ++++ .../genctrl/genctrl_generate_ctrl_clear.go | 57 + .../cmd/genctrl/genctrl_generate_interface.go | 118 ++ .../cmd/genctrl/genctrl_generate_sdk.go | 199 ++++ .../hggen/internal/cmd/gendao/gendao.go | 413 +++++++ .../hggen/internal/cmd/gendao/gendao_clear.go | 48 + .../hggen/internal/cmd/gendao/gendao_dao.go | 260 +++++ .../hggen/internal/cmd/gendao/gendao_do.go | 100 ++ .../internal/cmd/gendao/gendao_entity.go | 85 ++ .../internal/cmd/gendao/gendao_gen_item.go | 53 + .../internal/cmd/gendao/gendao_structure.go | 229 ++++ .../hggen/internal/cmd/gendao/gendao_table.go | 147 +++ .../hggen/internal/cmd/gendao/gendao_tag.go | 155 +++ .../hggen/internal/cmd/genenums/genenums.go | 85 ++ .../internal/cmd/genenums/genenums_parser.go | 149 +++ .../library/hggen/internal/cmd/genpb/genpb.go | 132 +++ .../internal/cmd/genpb/genpb_controller.go | 197 ++++ .../hggen/internal/cmd/genpb/genpb_tag.go | 123 ++ .../internal/cmd/genpbentity/genpbentity.go | 591 ++++++++++ .../internal/cmd/genservice/genservice.go | 391 +++++++ .../cmd/genservice/genservice_ast_parse.go | 319 ++++++ .../cmd/genservice/genservice_ast_utils.go | 48 + .../cmd/genservice/genservice_calculate.go | 228 ++++ .../cmd/genservice/genservice_generate.go | 87 ++ .../genservice_generate_template.go | 105 ++ .../library/hggen/internal/consts/consts.go | 13 + .../consts/consts_gen_ctrl_template.go | 87 ++ .../consts/consts_gen_ctrl_template_sdk.go | 89 ++ .../consts/consts_gen_dao_template_dao.go | 139 +++ .../consts/consts_gen_dao_template_do.go | 20 + .../consts/consts_gen_dao_template_entity.go | 20 + .../consts/consts_gen_dao_template_table.go | 35 + .../consts/consts_gen_enums_template.go | 23 + .../consts/consts_gen_pbentity_template.go | 23 + .../consts/consts_gen_service_template.go | 41 + .../consts_gen_service_template_logic.go | 19 + .../library/hggen/internal/service/install.go | 246 ++++ .../hggen/internal/utility/allyes/allyes.go | 28 + .../hggen/internal/utility/mlog/mlog.go | 76 ++ .../hggen/internal/utility/utils/utils.go | 135 +++ .../utility/utils/utils_http_download.go | 55 + server/internal/library/hggen/views/column.go | 305 +++++ .../library/hggen/views/column_default.go | 326 ++++++ .../library/hggen/views/column_map.go | 343 ++++++ server/internal/library/hggen/views/curd.go | 1009 +++++++++++++++++ .../hggen/views/curd_generate_input.go | 354 ++++++ .../hggen/views/curd_generate_logic.go | 318 ++++++ .../hggen/views/curd_generate_web_edit.go | 232 ++++ .../hggen/views/curd_generate_web_index.go | 167 +++ .../hggen/views/curd_generate_web_model.go | 493 ++++++++ .../hggen/views/curd_generate_web_view.go | 90 ++ .../library/hggen/views/gohtml/consts.go | 7 + .../library/hggen/views/gohtml/doc.go | 2 + .../library/hggen/views/gohtml/element.go | 7 + .../library/hggen/views/gohtml/formatter.go | 36 + .../hggen/views/gohtml/html_document.go | 54 + .../library/hggen/views/gohtml/parser.go | 128 +++ .../library/hggen/views/gohtml/tag_element.go | 145 +++ .../hggen/views/gohtml/text_element.go | 43 + .../library/hggen/views/gohtml/utils.go | 101 ++ .../library/hggen/views/gohtml/writer.go | 33 + server/internal/library/hggen/views/utils.go | 543 +++++++++ server/internal/library/hgorm/dao.go | 232 ++++ server/internal/library/hgorm/dao_tenant.go | 96 ++ server/internal/library/hgorm/dao_tree.go | 146 +++ .../library/hgorm/handler/filter_auth.go | 160 +++ .../library/hgorm/handler/force_cache.go | 15 + .../internal/library/hgorm/handler/handler.go | 42 + .../internal/library/hgorm/handler/sorter.go | 200 ++++ .../library/hgorm/handler/sorter_test.go | 96 ++ .../internal/library/hgorm/handler/tenant.go | 49 + server/internal/library/hgorm/hook/hook.go | 8 + server/internal/library/hgorm/hook/member.go | 146 +++ .../internal/library/hgorm/hook/provinces.go | 63 + server/internal/library/hgorm/hook/tenant.go | 288 +++++ .../library/hgorm/hook/tenant_test.go | 67 ++ server/internal/library/hgrds/lock/consts.go | 59 + server/internal/library/hgrds/lock/lock.go | 183 +++ .../internal/library/hgrds/lock/lock_test.go | 152 +++ .../internal/library/hgrds/pubsub/publish.go | 16 + .../library/hgrds/pubsub/subscribe.go | 82 ++ server/internal/library/location/cache.go | 45 + server/internal/library/location/geocode.go | 95 ++ server/internal/library/location/location.go | 232 ++++ .../library/location/location_test.go | 72 ++ server/internal/library/location/region.go | 113 ++ server/internal/library/network/tcp/client.go | 303 +++++ .../library/network/tcp/client_cron.go | 50 + .../library/network/tcp/client_handle.go | 79 ++ server/internal/library/network/tcp/conn.go | 178 +++ server/internal/library/network/tcp/consts.go | 32 + .../internal/library/network/tcp/context.go | 50 + server/internal/library/network/tcp/msg.go | 82 ++ .../library/network/tcp/msg_parser.go | 220 ++++ server/internal/library/network/tcp/router.go | 94 ++ server/internal/library/network/tcp/rpc.go | 101 ++ server/internal/library/network/tcp/server.go | 294 +++++ .../library/network/tcp/server_cron.go | 68 ++ .../library/network/tcp/tcp_example_test.go | 133 +++ .../internal/library/payment/alipay/handle.go | 200 ++++ .../internal/library/payment/alipay/model.go | 66 ++ server/internal/library/payment/config.go | 18 + server/internal/library/payment/notifycall.go | 55 + server/internal/library/payment/payment.go | 125 ++ .../internal/library/payment/qqpay/handle.go | 137 +++ .../internal/library/payment/qqpay/model.go | 28 + .../internal/library/payment/wxpay/handle.go | 314 +++++ .../internal/library/payment/wxpay/model.go | 6 + server/internal/library/queue/consumer.go | 75 ++ server/internal/library/queue/disk.go | 148 +++ server/internal/library/queue/disk/disk.go | 123 ++ server/internal/library/queue/disk/reader.go | 181 +++ server/internal/library/queue/disk/writer.go | 107 ++ server/internal/library/queue/kafkamq.go | 258 +++++ server/internal/library/queue/logger.go | 35 + server/internal/library/queue/producer.go | 36 + server/internal/library/queue/queue.go | 219 ++++ server/internal/library/queue/redismq.go | 277 +++++ server/internal/library/queue/rocketmq.go | 311 +++++ server/internal/library/response/response.go | 115 ++ server/internal/library/sms/config.go | 28 + server/internal/library/sms/sms.go | 39 + server/internal/library/sms/sms_aliyun.go | 65 ++ server/internal/library/sms/sms_tencent.go | 129 +++ server/internal/library/storager/config.go | 28 + server/internal/library/storager/mime.go | 232 ++++ server/internal/library/storager/model.go | 69 ++ server/internal/library/storager/upload.go | 392 +++++++ .../internal/library/storager/upload_cos.go | 59 + .../internal/library/storager/upload_local.go | 156 +++ .../library/storager/upload_local_test.go | 61 + .../internal/library/storager/upload_minio.go | 76 ++ .../internal/library/storager/upload_oss.go | 59 + .../internal/library/storager/upload_qiniu.go | 69 ++ .../library/storager/upload_ucloud.go | 62 + server/internal/library/token/token.go | 305 +++++ server/internal/library/validate/yaml.go | 50 + server/internal/library/wechat/cache.go | 69 ++ server/internal/library/wechat/config.go | 64 ++ server/internal/library/wechat/wechat.go | 41 + server/internal/logic/.gitkeep | 0 server/internal/logic/admin/cash.go | 285 +++++ server/internal/logic/admin/credits_log.go | 266 +++++ server/internal/logic/admin/dept.go | 349 ++++++ server/internal/logic/admin/member.go | 914 +++++++++++++++ server/internal/logic/admin/member_post.go | 44 + server/internal/logic/admin/menu.go | 312 +++++ server/internal/logic/admin/monitor.go | 97 ++ server/internal/logic/admin/notice.go | 534 +++++++++ server/internal/logic/admin/order.go | 407 +++++++ 282 files changed, 34372 insertions(+) create mode 100644 server/internal/crons/test2.go create mode 100644 server/internal/dao/.gitkeep create mode 100755 server/internal/dao/addon_hgexample_table.go create mode 100755 server/internal/dao/addon_hgexample_tenant_order.go create mode 100755 server/internal/dao/admin_cash.go create mode 100755 server/internal/dao/admin_credits_log.go create mode 100755 server/internal/dao/admin_dept.go create mode 100755 server/internal/dao/admin_member.go create mode 100755 server/internal/dao/admin_member_post.go create mode 100755 server/internal/dao/admin_member_role.go create mode 100755 server/internal/dao/admin_menu.go create mode 100755 server/internal/dao/admin_notice.go create mode 100755 server/internal/dao/admin_notice_read.go create mode 100755 server/internal/dao/admin_oauth.go create mode 100755 server/internal/dao/admin_order.go create mode 100755 server/internal/dao/admin_post.go create mode 100755 server/internal/dao/admin_role.go create mode 100755 server/internal/dao/admin_role_casbin.go create mode 100755 server/internal/dao/admin_role_menu.go create mode 100755 server/internal/dao/internal/addon_hgexample_table.go create mode 100755 server/internal/dao/internal/addon_hgexample_tenant_order.go create mode 100755 server/internal/dao/internal/admin_cash.go create mode 100755 server/internal/dao/internal/admin_credits_log.go create mode 100755 server/internal/dao/internal/admin_dept.go create mode 100755 server/internal/dao/internal/admin_member.go create mode 100755 server/internal/dao/internal/admin_member_post.go create mode 100755 server/internal/dao/internal/admin_member_role.go create mode 100755 server/internal/dao/internal/admin_menu.go create mode 100755 server/internal/dao/internal/admin_notice.go create mode 100755 server/internal/dao/internal/admin_notice_read.go create mode 100755 server/internal/dao/internal/admin_oauth.go create mode 100755 server/internal/dao/internal/admin_order.go create mode 100755 server/internal/dao/internal/admin_post.go create mode 100755 server/internal/dao/internal/admin_role.go create mode 100755 server/internal/dao/internal/admin_role_casbin.go create mode 100755 server/internal/dao/internal/admin_role_menu.go create mode 100755 server/internal/dao/internal/pay_log.go create mode 100755 server/internal/dao/internal/pay_refund.go create mode 100755 server/internal/dao/internal/sys_addons_config.go create mode 100644 server/internal/dao/internal/sys_addons_install.go create mode 100755 server/internal/dao/internal/sys_attachment.go create mode 100755 server/internal/dao/internal/sys_blacklist.go create mode 100755 server/internal/dao/internal/sys_config.go create mode 100755 server/internal/dao/internal/sys_cron.go create mode 100755 server/internal/dao/internal/sys_cron_group.go create mode 100755 server/internal/dao/internal/sys_dict_data.go create mode 100755 server/internal/dao/internal/sys_dict_type.go create mode 100755 server/internal/dao/internal/sys_ems_log.go create mode 100755 server/internal/dao/internal/sys_gen_codes.go create mode 100755 server/internal/dao/internal/sys_gen_curd_demo.go create mode 100755 server/internal/dao/internal/sys_gen_tree_demo.go create mode 100755 server/internal/dao/internal/sys_log.go create mode 100755 server/internal/dao/internal/sys_login_log.go create mode 100755 server/internal/dao/internal/sys_provinces.go create mode 100755 server/internal/dao/internal/sys_serve_license.go create mode 100755 server/internal/dao/internal/sys_serve_log.go create mode 100755 server/internal/dao/internal/sys_sms_log.go create mode 100755 server/internal/dao/internal/test_category.go create mode 100755 server/internal/dao/pay_log.go create mode 100755 server/internal/dao/pay_refund.go create mode 100755 server/internal/dao/sys_addons_config.go create mode 100644 server/internal/dao/sys_addons_install.go create mode 100755 server/internal/dao/sys_attachment.go create mode 100755 server/internal/dao/sys_blacklist.go create mode 100755 server/internal/dao/sys_config.go create mode 100755 server/internal/dao/sys_cron.go create mode 100755 server/internal/dao/sys_cron_group.go create mode 100755 server/internal/dao/sys_dict_data.go create mode 100755 server/internal/dao/sys_dict_type.go create mode 100755 server/internal/dao/sys_ems_log.go create mode 100755 server/internal/dao/sys_gen_codes.go create mode 100755 server/internal/dao/sys_gen_curd_demo.go create mode 100755 server/internal/dao/sys_gen_tree_demo.go create mode 100755 server/internal/dao/sys_log.go create mode 100755 server/internal/dao/sys_login_log.go create mode 100755 server/internal/dao/sys_provinces.go create mode 100755 server/internal/dao/sys_serve_license.go create mode 100755 server/internal/dao/sys_serve_log.go create mode 100755 server/internal/dao/sys_sms_log.go create mode 100755 server/internal/dao/test_category.go create mode 100644 server/internal/global/cluster.go create mode 100644 server/internal/global/global.go create mode 100644 server/internal/global/httproutes.go create mode 100644 server/internal/global/init.go create mode 100644 server/internal/library/addons/addons.go create mode 100644 server/internal/library/addons/build.go create mode 100644 server/internal/library/addons/build_layout.go create mode 100644 server/internal/library/addons/install.go create mode 100644 server/internal/library/addons/module.go create mode 100644 server/internal/library/cache/cache.go create mode 100644 server/internal/library/cache/file/file.go create mode 100644 server/internal/library/captcha/captcha.go create mode 100644 server/internal/library/casbin/adapter.go create mode 100644 server/internal/library/casbin/casbin_test.go create mode 100644 server/internal/library/casbin/enforcer.go create mode 100644 server/internal/library/contexts/context.go create mode 100644 server/internal/library/contexts/detached.go create mode 100644 server/internal/library/cron/cron.go create mode 100644 server/internal/library/cron/cron_gen.go create mode 100644 server/internal/library/debris/debris.go create mode 100644 server/internal/library/dict/dict.go create mode 100644 server/internal/library/dict/dict_option.go create mode 100644 server/internal/library/dict/dict_register_enums.go create mode 100644 server/internal/library/dict/dict_register_func.go create mode 100644 server/internal/library/ems/ems.go create mode 100644 server/internal/library/hggen/hggen.go create mode 100644 server/internal/library/hggen/init.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_build.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_doc.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_docker.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_env.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_fix.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_dao.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_enums.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_pb.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_gen_service.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_init.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_install.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_pack.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_run.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_tpl.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_up.go create mode 100644 server/internal/library/hggen/internal/cmd/cmd_version.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_ast_parse.go create mode 100644 server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.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/gendao/gendao.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_do.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_table.go create mode 100644 server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go create mode 100644 server/internal/library/hggen/internal/cmd/genenums/genenums.go create mode 100644 server/internal/library/hggen/internal/cmd/genenums/genenums_parser.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/genservice/genservice.go create mode 100644 server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go create mode 100644 server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go create mode 100644 server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go create mode 100644 server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go create mode 100644 server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go create mode 100644 server/internal/library/hggen/internal/consts/consts.go 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/consts/consts_gen_dao_template_dao.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_enums_template.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_service_template.go create mode 100644 server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go create mode 100644 server/internal/library/hggen/internal/service/install.go create mode 100644 server/internal/library/hggen/internal/utility/allyes/allyes.go create mode 100644 server/internal/library/hggen/internal/utility/mlog/mlog.go create mode 100644 server/internal/library/hggen/internal/utility/utils/utils.go create mode 100644 server/internal/library/hggen/internal/utility/utils/utils_http_download.go create mode 100644 server/internal/library/hggen/views/column.go create mode 100644 server/internal/library/hggen/views/column_default.go create mode 100644 server/internal/library/hggen/views/column_map.go create mode 100644 server/internal/library/hggen/views/curd.go create mode 100644 server/internal/library/hggen/views/curd_generate_input.go create mode 100644 server/internal/library/hggen/views/curd_generate_logic.go create mode 100644 server/internal/library/hggen/views/curd_generate_web_edit.go create mode 100644 server/internal/library/hggen/views/curd_generate_web_index.go create mode 100644 server/internal/library/hggen/views/curd_generate_web_model.go create mode 100644 server/internal/library/hggen/views/curd_generate_web_view.go create mode 100644 server/internal/library/hggen/views/gohtml/consts.go create mode 100644 server/internal/library/hggen/views/gohtml/doc.go create mode 100644 server/internal/library/hggen/views/gohtml/element.go create mode 100644 server/internal/library/hggen/views/gohtml/formatter.go create mode 100644 server/internal/library/hggen/views/gohtml/html_document.go create mode 100644 server/internal/library/hggen/views/gohtml/parser.go create mode 100644 server/internal/library/hggen/views/gohtml/tag_element.go create mode 100644 server/internal/library/hggen/views/gohtml/text_element.go create mode 100644 server/internal/library/hggen/views/gohtml/utils.go create mode 100644 server/internal/library/hggen/views/gohtml/writer.go create mode 100644 server/internal/library/hggen/views/utils.go create mode 100644 server/internal/library/hgorm/dao.go create mode 100644 server/internal/library/hgorm/dao_tenant.go create mode 100644 server/internal/library/hgorm/dao_tree.go create mode 100644 server/internal/library/hgorm/handler/filter_auth.go create mode 100644 server/internal/library/hgorm/handler/force_cache.go create mode 100644 server/internal/library/hgorm/handler/handler.go create mode 100644 server/internal/library/hgorm/handler/sorter.go create mode 100644 server/internal/library/hgorm/handler/sorter_test.go create mode 100644 server/internal/library/hgorm/handler/tenant.go create mode 100644 server/internal/library/hgorm/hook/hook.go create mode 100644 server/internal/library/hgorm/hook/member.go create mode 100644 server/internal/library/hgorm/hook/provinces.go create mode 100644 server/internal/library/hgorm/hook/tenant.go create mode 100644 server/internal/library/hgorm/hook/tenant_test.go create mode 100644 server/internal/library/hgrds/lock/consts.go create mode 100644 server/internal/library/hgrds/lock/lock.go create mode 100644 server/internal/library/hgrds/lock/lock_test.go create mode 100644 server/internal/library/hgrds/pubsub/publish.go create mode 100644 server/internal/library/hgrds/pubsub/subscribe.go create mode 100644 server/internal/library/location/cache.go create mode 100644 server/internal/library/location/geocode.go create mode 100644 server/internal/library/location/location.go create mode 100644 server/internal/library/location/location_test.go create mode 100644 server/internal/library/location/region.go create mode 100644 server/internal/library/network/tcp/client.go create mode 100644 server/internal/library/network/tcp/client_cron.go create mode 100644 server/internal/library/network/tcp/client_handle.go create mode 100644 server/internal/library/network/tcp/conn.go create mode 100644 server/internal/library/network/tcp/consts.go create mode 100644 server/internal/library/network/tcp/context.go create mode 100644 server/internal/library/network/tcp/msg.go create mode 100644 server/internal/library/network/tcp/msg_parser.go create mode 100644 server/internal/library/network/tcp/router.go create mode 100644 server/internal/library/network/tcp/rpc.go create mode 100644 server/internal/library/network/tcp/server.go create mode 100644 server/internal/library/network/tcp/server_cron.go create mode 100644 server/internal/library/network/tcp/tcp_example_test.go create mode 100644 server/internal/library/payment/alipay/handle.go create mode 100644 server/internal/library/payment/alipay/model.go create mode 100644 server/internal/library/payment/config.go create mode 100644 server/internal/library/payment/notifycall.go create mode 100644 server/internal/library/payment/payment.go create mode 100644 server/internal/library/payment/qqpay/handle.go create mode 100644 server/internal/library/payment/qqpay/model.go create mode 100644 server/internal/library/payment/wxpay/handle.go create mode 100644 server/internal/library/payment/wxpay/model.go create mode 100644 server/internal/library/queue/consumer.go create mode 100644 server/internal/library/queue/disk.go create mode 100644 server/internal/library/queue/disk/disk.go create mode 100644 server/internal/library/queue/disk/reader.go create mode 100644 server/internal/library/queue/disk/writer.go create mode 100644 server/internal/library/queue/kafkamq.go create mode 100644 server/internal/library/queue/logger.go create mode 100644 server/internal/library/queue/producer.go create mode 100644 server/internal/library/queue/queue.go create mode 100644 server/internal/library/queue/redismq.go create mode 100644 server/internal/library/queue/rocketmq.go create mode 100644 server/internal/library/response/response.go create mode 100644 server/internal/library/sms/config.go create mode 100644 server/internal/library/sms/sms.go create mode 100644 server/internal/library/sms/sms_aliyun.go create mode 100644 server/internal/library/sms/sms_tencent.go create mode 100644 server/internal/library/storager/config.go create mode 100644 server/internal/library/storager/mime.go create mode 100644 server/internal/library/storager/model.go create mode 100644 server/internal/library/storager/upload.go create mode 100644 server/internal/library/storager/upload_cos.go create mode 100644 server/internal/library/storager/upload_local.go create mode 100644 server/internal/library/storager/upload_local_test.go create mode 100644 server/internal/library/storager/upload_minio.go create mode 100644 server/internal/library/storager/upload_oss.go create mode 100644 server/internal/library/storager/upload_qiniu.go create mode 100644 server/internal/library/storager/upload_ucloud.go create mode 100644 server/internal/library/token/token.go create mode 100644 server/internal/library/validate/yaml.go create mode 100644 server/internal/library/wechat/cache.go create mode 100644 server/internal/library/wechat/config.go create mode 100644 server/internal/library/wechat/wechat.go create mode 100644 server/internal/logic/.gitkeep create mode 100644 server/internal/logic/admin/cash.go create mode 100644 server/internal/logic/admin/credits_log.go create mode 100644 server/internal/logic/admin/dept.go create mode 100644 server/internal/logic/admin/member.go create mode 100644 server/internal/logic/admin/member_post.go create mode 100644 server/internal/logic/admin/menu.go create mode 100644 server/internal/logic/admin/monitor.go create mode 100644 server/internal/logic/admin/notice.go create mode 100644 server/internal/logic/admin/order.go diff --git a/server/internal/crons/test2.go b/server/internal/crons/test2.go new file mode 100644 index 0000000..c312e13 --- /dev/null +++ b/server/internal/crons/test2.go @@ -0,0 +1,45 @@ +// Package crons +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package crons + +import ( + "context" + "github.com/gogf/gf/v2/errors/gerror" + "hotgo/internal/library/cron" + "time" +) + +func init() { + cron.Register(Test2) +} + +// Test2 测试2任务(带参数) +var Test2 = &cTest2{name: "test2"} + +type cTest2 struct { + name string +} + +func (c *cTest2) GetName() string { + return c.name +} + +// Execute 执行任务 +func (c *cTest2) Execute(ctx context.Context, parser *cron.Parser) (err error) { + if len(parser.Args) != 3 { + err = gerror.New("传入参数不正确!") + return + } + + var ( + name = parser.Args[0] + age = parser.Args[1] + msg = parser.Args[2] + ) + + parser.Logger.Infof(ctx, "cron test2 Execute:%v, name:%v, age:%v, msg:%v", time.Now(), name, age, msg) + return +} diff --git a/server/internal/dao/.gitkeep b/server/internal/dao/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server/internal/dao/addon_hgexample_table.go b/server/internal/dao/addon_hgexample_table.go new file mode 100755 index 0000000..1191340 --- /dev/null +++ b/server/internal/dao/addon_hgexample_table.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// addonHgexampleTableDao is the data access object for the table hg_addon_hgexample_table. +// You can define custom methods on it to extend its functionality as needed. +type addonHgexampleTableDao struct { + *internal.AddonHgexampleTableDao +} + +var ( + // AddonHgexampleTable is a globally accessible object for table hg_addon_hgexample_table operations. + AddonHgexampleTable = addonHgexampleTableDao{internal.NewAddonHgexampleTableDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/addon_hgexample_tenant_order.go b/server/internal/dao/addon_hgexample_tenant_order.go new file mode 100755 index 0000000..d369ac6 --- /dev/null +++ b/server/internal/dao/addon_hgexample_tenant_order.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// addonHgexampleTenantOrderDao is the data access object for the table hg_addon_hgexample_tenant_order. +// You can define custom methods on it to extend its functionality as needed. +type addonHgexampleTenantOrderDao struct { + *internal.AddonHgexampleTenantOrderDao +} + +var ( + // AddonHgexampleTenantOrder is a globally accessible object for table hg_addon_hgexample_tenant_order operations. + AddonHgexampleTenantOrder = addonHgexampleTenantOrderDao{internal.NewAddonHgexampleTenantOrderDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_cash.go b/server/internal/dao/admin_cash.go new file mode 100755 index 0000000..6bda8cf --- /dev/null +++ b/server/internal/dao/admin_cash.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminCashDao is the data access object for the table hg_admin_cash. +// You can define custom methods on it to extend its functionality as needed. +type adminCashDao struct { + *internal.AdminCashDao +} + +var ( + // AdminCash is a globally accessible object for table hg_admin_cash operations. + AdminCash = adminCashDao{internal.NewAdminCashDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_credits_log.go b/server/internal/dao/admin_credits_log.go new file mode 100755 index 0000000..b415991 --- /dev/null +++ b/server/internal/dao/admin_credits_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminCreditsLogDao is the data access object for the table hg_admin_credits_log. +// You can define custom methods on it to extend its functionality as needed. +type adminCreditsLogDao struct { + *internal.AdminCreditsLogDao +} + +var ( + // AdminCreditsLog is a globally accessible object for table hg_admin_credits_log operations. + AdminCreditsLog = adminCreditsLogDao{internal.NewAdminCreditsLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_dept.go b/server/internal/dao/admin_dept.go new file mode 100755 index 0000000..4eeced7 --- /dev/null +++ b/server/internal/dao/admin_dept.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminDeptDao is the data access object for the table hg_admin_dept. +// You can define custom methods on it to extend its functionality as needed. +type adminDeptDao struct { + *internal.AdminDeptDao +} + +var ( + // AdminDept is a globally accessible object for table hg_admin_dept operations. + AdminDept = adminDeptDao{internal.NewAdminDeptDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_member.go b/server/internal/dao/admin_member.go new file mode 100755 index 0000000..112f3f0 --- /dev/null +++ b/server/internal/dao/admin_member.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminMemberDao is the data access object for the table hg_admin_member. +// You can define custom methods on it to extend its functionality as needed. +type adminMemberDao struct { + *internal.AdminMemberDao +} + +var ( + // AdminMember is a globally accessible object for table hg_admin_member operations. + AdminMember = adminMemberDao{internal.NewAdminMemberDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_member_post.go b/server/internal/dao/admin_member_post.go new file mode 100755 index 0000000..1c4ab68 --- /dev/null +++ b/server/internal/dao/admin_member_post.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminMemberPostDao is the data access object for the table hg_admin_member_post. +// You can define custom methods on it to extend its functionality as needed. +type adminMemberPostDao struct { + *internal.AdminMemberPostDao +} + +var ( + // AdminMemberPost is a globally accessible object for table hg_admin_member_post operations. + AdminMemberPost = adminMemberPostDao{internal.NewAdminMemberPostDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_member_role.go b/server/internal/dao/admin_member_role.go new file mode 100755 index 0000000..7b48a86 --- /dev/null +++ b/server/internal/dao/admin_member_role.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminMemberRoleDao is the data access object for the table hg_admin_member_role. +// You can define custom methods on it to extend its functionality as needed. +type adminMemberRoleDao struct { + *internal.AdminMemberRoleDao +} + +var ( + // AdminMemberRole is a globally accessible object for table hg_admin_member_role operations. + AdminMemberRole = adminMemberRoleDao{internal.NewAdminMemberRoleDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_menu.go b/server/internal/dao/admin_menu.go new file mode 100755 index 0000000..8bac378 --- /dev/null +++ b/server/internal/dao/admin_menu.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminMenuDao is the data access object for the table hg_admin_menu. +// You can define custom methods on it to extend its functionality as needed. +type adminMenuDao struct { + *internal.AdminMenuDao +} + +var ( + // AdminMenu is a globally accessible object for table hg_admin_menu operations. + AdminMenu = adminMenuDao{internal.NewAdminMenuDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_notice.go b/server/internal/dao/admin_notice.go new file mode 100755 index 0000000..457c3e7 --- /dev/null +++ b/server/internal/dao/admin_notice.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminNoticeDao is the data access object for the table hg_admin_notice. +// You can define custom methods on it to extend its functionality as needed. +type adminNoticeDao struct { + *internal.AdminNoticeDao +} + +var ( + // AdminNotice is a globally accessible object for table hg_admin_notice operations. + AdminNotice = adminNoticeDao{internal.NewAdminNoticeDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_notice_read.go b/server/internal/dao/admin_notice_read.go new file mode 100755 index 0000000..1a8f484 --- /dev/null +++ b/server/internal/dao/admin_notice_read.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminNoticeReadDao is the data access object for the table hg_admin_notice_read. +// You can define custom methods on it to extend its functionality as needed. +type adminNoticeReadDao struct { + *internal.AdminNoticeReadDao +} + +var ( + // AdminNoticeRead is a globally accessible object for table hg_admin_notice_read operations. + AdminNoticeRead = adminNoticeReadDao{internal.NewAdminNoticeReadDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_oauth.go b/server/internal/dao/admin_oauth.go new file mode 100755 index 0000000..eed3b14 --- /dev/null +++ b/server/internal/dao/admin_oauth.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminOauthDao is the data access object for the table hg_admin_oauth. +// You can define custom methods on it to extend its functionality as needed. +type adminOauthDao struct { + *internal.AdminOauthDao +} + +var ( + // AdminOauth is a globally accessible object for table hg_admin_oauth operations. + AdminOauth = adminOauthDao{internal.NewAdminOauthDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_order.go b/server/internal/dao/admin_order.go new file mode 100755 index 0000000..8fd4a80 --- /dev/null +++ b/server/internal/dao/admin_order.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminOrderDao is the data access object for the table hg_admin_order. +// You can define custom methods on it to extend its functionality as needed. +type adminOrderDao struct { + *internal.AdminOrderDao +} + +var ( + // AdminOrder is a globally accessible object for table hg_admin_order operations. + AdminOrder = adminOrderDao{internal.NewAdminOrderDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_post.go b/server/internal/dao/admin_post.go new file mode 100755 index 0000000..ecf69cd --- /dev/null +++ b/server/internal/dao/admin_post.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminPostDao is the data access object for the table hg_admin_post. +// You can define custom methods on it to extend its functionality as needed. +type adminPostDao struct { + *internal.AdminPostDao +} + +var ( + // AdminPost is a globally accessible object for table hg_admin_post operations. + AdminPost = adminPostDao{internal.NewAdminPostDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_role.go b/server/internal/dao/admin_role.go new file mode 100755 index 0000000..8315e43 --- /dev/null +++ b/server/internal/dao/admin_role.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminRoleDao is the data access object for the table hg_admin_role. +// You can define custom methods on it to extend its functionality as needed. +type adminRoleDao struct { + *internal.AdminRoleDao +} + +var ( + // AdminRole is a globally accessible object for table hg_admin_role operations. + AdminRole = adminRoleDao{internal.NewAdminRoleDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_role_casbin.go b/server/internal/dao/admin_role_casbin.go new file mode 100755 index 0000000..3557470 --- /dev/null +++ b/server/internal/dao/admin_role_casbin.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminRoleCasbinDao is the data access object for the table hg_admin_role_casbin. +// You can define custom methods on it to extend its functionality as needed. +type adminRoleCasbinDao struct { + *internal.AdminRoleCasbinDao +} + +var ( + // AdminRoleCasbin is a globally accessible object for table hg_admin_role_casbin operations. + AdminRoleCasbin = adminRoleCasbinDao{internal.NewAdminRoleCasbinDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/admin_role_menu.go b/server/internal/dao/admin_role_menu.go new file mode 100755 index 0000000..e180651 --- /dev/null +++ b/server/internal/dao/admin_role_menu.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// adminRoleMenuDao is the data access object for the table hg_admin_role_menu. +// You can define custom methods on it to extend its functionality as needed. +type adminRoleMenuDao struct { + *internal.AdminRoleMenuDao +} + +var ( + // AdminRoleMenu is a globally accessible object for table hg_admin_role_menu operations. + AdminRoleMenu = adminRoleMenuDao{internal.NewAdminRoleMenuDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/internal/addon_hgexample_table.go b/server/internal/dao/internal/addon_hgexample_table.go new file mode 100755 index 0000000..97a8551 --- /dev/null +++ b/server/internal/dao/internal/addon_hgexample_table.go @@ -0,0 +1,151 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AddonHgexampleTableDao is the data access object for the table hg_addon_hgexample_table. +type AddonHgexampleTableDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AddonHgexampleTableColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AddonHgexampleTableColumns defines and stores column names for the table hg_addon_hgexample_table. +type AddonHgexampleTableColumns struct { + Id string // ID + Pid string // 上级ID + Level string // 树等级 + Tree string // 关系树 + CategoryId string // 分类ID + Flag string // 标签 + Title string // 标题 + Description string // 描述 + Content string // 内容 + Image string // 单图 + Images string // 多图 + Attachfile string // 附件 + Attachfiles string // 多附件 + Map string // 动态键值对 + Star string // 推荐星 + Price string // 价格 + Views string // 浏览次数 + ActivityAt string // 活动时间 + StartAt string // 开启时间 + EndAt string // 结束时间 + Switch string // 开关 + Sort string // 排序 + Avatar string // 头像 + Sex string // 性别 + Qq string // qq + Email string // 邮箱 + Mobile string // 手机号码 + Hobby string // 爱好 + Channel string // 渠道 + CityId string // 所在城市 + Remark string // 备注 + Status string // 状态 + CreatedBy string // 创建者 + UpdatedBy string // 更新者 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 + DeletedAt string // 删除时间 +} + +// addonHgexampleTableColumns holds the columns for the table hg_addon_hgexample_table. +var addonHgexampleTableColumns = AddonHgexampleTableColumns{ + Id: "id", + Pid: "pid", + Level: "level", + Tree: "tree", + CategoryId: "category_id", + Flag: "flag", + Title: "title", + Description: "description", + Content: "content", + Image: "image", + Images: "images", + Attachfile: "attachfile", + Attachfiles: "attachfiles", + Map: "map", + Star: "star", + Price: "price", + Views: "views", + ActivityAt: "activity_at", + StartAt: "start_at", + EndAt: "end_at", + Switch: "switch", + Sort: "sort", + Avatar: "avatar", + Sex: "sex", + Qq: "qq", + Email: "email", + Mobile: "mobile", + Hobby: "hobby", + Channel: "channel", + CityId: "city_id", + Remark: "remark", + Status: "status", + CreatedBy: "created_by", + UpdatedBy: "updated_by", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewAddonHgexampleTableDao creates and returns a new DAO object for table data access. +func NewAddonHgexampleTableDao(handlers ...gdb.ModelHandler) *AddonHgexampleTableDao { + return &AddonHgexampleTableDao{ + group: "default", + table: "hg_addon_hgexample_table", + columns: addonHgexampleTableColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AddonHgexampleTableDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AddonHgexampleTableDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AddonHgexampleTableDao) Columns() AddonHgexampleTableColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AddonHgexampleTableDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AddonHgexampleTableDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AddonHgexampleTableDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/addon_hgexample_tenant_order.go b/server/internal/dao/internal/addon_hgexample_tenant_order.go new file mode 100755 index 0000000..bf41fd8 --- /dev/null +++ b/server/internal/dao/internal/addon_hgexample_tenant_order.go @@ -0,0 +1,99 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AddonHgexampleTenantOrderDao is the data access object for the table hg_addon_hgexample_tenant_order. +type AddonHgexampleTenantOrderDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AddonHgexampleTenantOrderColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AddonHgexampleTenantOrderColumns defines and stores column names for the table hg_addon_hgexample_tenant_order. +type AddonHgexampleTenantOrderColumns struct { + Id string // 主键 + TenantId string // 租户ID + MerchantId string // 商户ID + UserId string // 用户ID + ProductName string // 购买产品 + OrderSn string // 订单号 + Money string // 充值金额 + Remark string // 备注 + Status string // 订单状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// addonHgexampleTenantOrderColumns holds the columns for the table hg_addon_hgexample_tenant_order. +var addonHgexampleTenantOrderColumns = AddonHgexampleTenantOrderColumns{ + Id: "id", + TenantId: "tenant_id", + MerchantId: "merchant_id", + UserId: "user_id", + ProductName: "product_name", + OrderSn: "order_sn", + Money: "money", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAddonHgexampleTenantOrderDao creates and returns a new DAO object for table data access. +func NewAddonHgexampleTenantOrderDao(handlers ...gdb.ModelHandler) *AddonHgexampleTenantOrderDao { + return &AddonHgexampleTenantOrderDao{ + group: "default", + table: "hg_addon_hgexample_tenant_order", + columns: addonHgexampleTenantOrderColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AddonHgexampleTenantOrderDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AddonHgexampleTenantOrderDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AddonHgexampleTenantOrderDao) Columns() AddonHgexampleTenantOrderColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AddonHgexampleTenantOrderDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AddonHgexampleTenantOrderDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AddonHgexampleTenantOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_cash.go b/server/internal/dao/internal/admin_cash.go new file mode 100755 index 0000000..a9bdb1b --- /dev/null +++ b/server/internal/dao/internal/admin_cash.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminCashDao is the data access object for the table hg_admin_cash. +type AdminCashDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminCashColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminCashColumns defines and stores column names for the table hg_admin_cash. +type AdminCashColumns struct { + Id string // ID + MemberId string // 管理员ID + Money string // 提现金额 + Fee string // 手续费 + LastMoney string // 最终到账金额 + Ip string // 申请人IP + Status string // 状态码 + Msg string // 处理结果 + HandleAt string // 处理时间 + CreatedAt string // 申请时间 +} + +// adminCashColumns holds the columns for the table hg_admin_cash. +var adminCashColumns = AdminCashColumns{ + Id: "id", + MemberId: "member_id", + Money: "money", + Fee: "fee", + LastMoney: "last_money", + Ip: "ip", + Status: "status", + Msg: "msg", + HandleAt: "handle_at", + CreatedAt: "created_at", +} + +// NewAdminCashDao creates and returns a new DAO object for table data access. +func NewAdminCashDao(handlers ...gdb.ModelHandler) *AdminCashDao { + return &AdminCashDao{ + group: "default", + table: "hg_admin_cash", + columns: adminCashColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminCashDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminCashDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminCashDao) Columns() AdminCashColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminCashDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminCashDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminCashDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_credits_log.go b/server/internal/dao/internal/admin_credits_log.go new file mode 100755 index 0000000..0b3602e --- /dev/null +++ b/server/internal/dao/internal/admin_credits_log.go @@ -0,0 +1,107 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminCreditsLogDao is the data access object for the table hg_admin_credits_log. +type AdminCreditsLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminCreditsLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminCreditsLogColumns defines and stores column names for the table hg_admin_credits_log. +type AdminCreditsLogColumns struct { + Id string // 变动ID + MemberId string // 管理员ID + AppId string // 应用id + AddonsName string // 插件名称 + CreditType string // 变动类型 + CreditGroup string // 变动组别 + BeforeNum string // 变动前 + Num string // 变动数据 + AfterNum string // 变动后 + Remark string // 备注 + Ip string // 操作人IP + MapId string // 关联ID + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// adminCreditsLogColumns holds the columns for the table hg_admin_credits_log. +var adminCreditsLogColumns = AdminCreditsLogColumns{ + Id: "id", + MemberId: "member_id", + AppId: "app_id", + AddonsName: "addons_name", + CreditType: "credit_type", + CreditGroup: "credit_group", + BeforeNum: "before_num", + Num: "num", + AfterNum: "after_num", + Remark: "remark", + Ip: "ip", + MapId: "map_id", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminCreditsLogDao creates and returns a new DAO object for table data access. +func NewAdminCreditsLogDao(handlers ...gdb.ModelHandler) *AdminCreditsLogDao { + return &AdminCreditsLogDao{ + group: "default", + table: "hg_admin_credits_log", + columns: adminCreditsLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminCreditsLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminCreditsLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminCreditsLogDao) Columns() AdminCreditsLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminCreditsLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminCreditsLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminCreditsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_dept.go b/server/internal/dao/internal/admin_dept.go new file mode 100755 index 0000000..becb639 --- /dev/null +++ b/server/internal/dao/internal/admin_dept.go @@ -0,0 +1,105 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminDeptDao is the data access object for the table hg_admin_dept. +type AdminDeptDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminDeptColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminDeptColumns defines and stores column names for the table hg_admin_dept. +type AdminDeptColumns struct { + Id string // 部门ID + Pid string // 父部门ID + Name string // 部门名称 + Code string // 部门编码 + Type string // 部门类型 + Leader string // 负责人 + Phone string // 联系电话 + Email string // 邮箱 + Level string // 关系树等级 + Tree string // 关系树 + Sort string // 排序 + Status string // 部门状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// adminDeptColumns holds the columns for the table hg_admin_dept. +var adminDeptColumns = AdminDeptColumns{ + Id: "id", + Pid: "pid", + Name: "name", + Code: "code", + Type: "type", + Leader: "leader", + Phone: "phone", + Email: "email", + Level: "level", + Tree: "tree", + Sort: "sort", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminDeptDao creates and returns a new DAO object for table data access. +func NewAdminDeptDao(handlers ...gdb.ModelHandler) *AdminDeptDao { + return &AdminDeptDao{ + group: "default", + table: "hg_admin_dept", + columns: adminDeptColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminDeptDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminDeptDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminDeptDao) Columns() AdminDeptColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminDeptDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminDeptDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminDeptDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_member.go b/server/internal/dao/internal/admin_member.go new file mode 100755 index 0000000..93ebaa7 --- /dev/null +++ b/server/internal/dao/internal/admin_member.go @@ -0,0 +1,133 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminMemberDao is the data access object for the table hg_admin_member. +type AdminMemberDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminMemberColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminMemberColumns defines and stores column names for the table hg_admin_member. +type AdminMemberColumns struct { + Id string // 管理员ID + DeptId string // 部门ID + RoleId string // 角色ID + RealName string // 真实姓名 + Username string // 帐号 + PasswordHash string // 密码 + Salt string // 密码盐 + PasswordResetToken string // 密码重置令牌 + Integral string // 积分 + Balance string // 余额 + Avatar string // 头像 + Sex string // 性别 + Qq string // qq + Email string // 邮箱 + Mobile string // 手机号码 + Birthday string // 生日 + CityId string // 城市编码 + Address string // 联系地址 + Pid string // 上级管理员ID + Level string // 关系树等级 + Tree string // 关系树 + InviteCode string // 邀请码 + Cash string // 提现配置 + LastActiveAt string // 最后活跃时间 + Remark string // 备注 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// adminMemberColumns holds the columns for the table hg_admin_member. +var adminMemberColumns = AdminMemberColumns{ + Id: "id", + DeptId: "dept_id", + RoleId: "role_id", + RealName: "real_name", + Username: "username", + PasswordHash: "password_hash", + Salt: "salt", + PasswordResetToken: "password_reset_token", + Integral: "integral", + Balance: "balance", + Avatar: "avatar", + Sex: "sex", + Qq: "qq", + Email: "email", + Mobile: "mobile", + Birthday: "birthday", + CityId: "city_id", + Address: "address", + Pid: "pid", + Level: "level", + Tree: "tree", + InviteCode: "invite_code", + Cash: "cash", + LastActiveAt: "last_active_at", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminMemberDao creates and returns a new DAO object for table data access. +func NewAdminMemberDao(handlers ...gdb.ModelHandler) *AdminMemberDao { + return &AdminMemberDao{ + group: "default", + table: "hg_admin_member", + columns: adminMemberColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminMemberDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminMemberDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminMemberDao) Columns() AdminMemberColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminMemberDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminMemberDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminMemberDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_member_post.go b/server/internal/dao/internal/admin_member_post.go new file mode 100755 index 0000000..8a30c9e --- /dev/null +++ b/server/internal/dao/internal/admin_member_post.go @@ -0,0 +1,81 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminMemberPostDao is the data access object for the table hg_admin_member_post. +type AdminMemberPostDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminMemberPostColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminMemberPostColumns defines and stores column names for the table hg_admin_member_post. +type AdminMemberPostColumns struct { + MemberId string // 管理员ID + PostId string // 岗位ID +} + +// adminMemberPostColumns holds the columns for the table hg_admin_member_post. +var adminMemberPostColumns = AdminMemberPostColumns{ + MemberId: "member_id", + PostId: "post_id", +} + +// NewAdminMemberPostDao creates and returns a new DAO object for table data access. +func NewAdminMemberPostDao(handlers ...gdb.ModelHandler) *AdminMemberPostDao { + return &AdminMemberPostDao{ + group: "default", + table: "hg_admin_member_post", + columns: adminMemberPostColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminMemberPostDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminMemberPostDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminMemberPostDao) Columns() AdminMemberPostColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminMemberPostDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminMemberPostDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminMemberPostDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_member_role.go b/server/internal/dao/internal/admin_member_role.go new file mode 100755 index 0000000..094d663 --- /dev/null +++ b/server/internal/dao/internal/admin_member_role.go @@ -0,0 +1,81 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminMemberRoleDao is the data access object for the table hg_admin_member_role. +type AdminMemberRoleDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminMemberRoleColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminMemberRoleColumns defines and stores column names for the table hg_admin_member_role. +type AdminMemberRoleColumns struct { + MemberId string // 管理员ID + RoleId string // 角色ID +} + +// adminMemberRoleColumns holds the columns for the table hg_admin_member_role. +var adminMemberRoleColumns = AdminMemberRoleColumns{ + MemberId: "member_id", + RoleId: "role_id", +} + +// NewAdminMemberRoleDao creates and returns a new DAO object for table data access. +func NewAdminMemberRoleDao(handlers ...gdb.ModelHandler) *AdminMemberRoleDao { + return &AdminMemberRoleDao{ + group: "default", + table: "hg_admin_member_role", + columns: adminMemberRoleColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminMemberRoleDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminMemberRoleDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminMemberRoleDao) Columns() AdminMemberRoleColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminMemberRoleDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminMemberRoleDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminMemberRoleDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_menu.go b/server/internal/dao/internal/admin_menu.go new file mode 100755 index 0000000..3312718 --- /dev/null +++ b/server/internal/dao/internal/admin_menu.go @@ -0,0 +1,129 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminMenuDao is the data access object for the table hg_admin_menu. +type AdminMenuDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminMenuColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminMenuColumns defines and stores column names for the table hg_admin_menu. +type AdminMenuColumns struct { + Id string // 菜单ID + Pid string // 父菜单ID + Level string // 关系树等级 + Tree string // 关系树 + Title string // 菜单名称 + Name string // 名称编码 + Path string // 路由地址 + Icon string // 菜单图标 + Type string // 菜单类型(1目录 2菜单 3按钮) + Redirect string // 重定向地址 + Permissions string // 菜单包含权限集合 + PermissionName string // 权限名称 + Component string // 组件路径 + AlwaysShow string // 取消自动计算根路由模式 + ActiveMenu string // 高亮菜单编码 + IsRoot string // 是否跟路由 + IsFrame string // 是否内嵌 + FrameSrc string // 内联外部地址 + KeepAlive string // 缓存该路由 + Hidden string // 是否隐藏 + Affix string // 是否固定 + Sort string // 排序 + Remark string // 备注 + Status string // 菜单状态 + UpdatedAt string // 更新时间 + CreatedAt string // 创建时间 +} + +// adminMenuColumns holds the columns for the table hg_admin_menu. +var adminMenuColumns = AdminMenuColumns{ + Id: "id", + Pid: "pid", + Level: "level", + Tree: "tree", + Title: "title", + Name: "name", + Path: "path", + Icon: "icon", + Type: "type", + Redirect: "redirect", + Permissions: "permissions", + PermissionName: "permission_name", + Component: "component", + AlwaysShow: "always_show", + ActiveMenu: "active_menu", + IsRoot: "is_root", + IsFrame: "is_frame", + FrameSrc: "frame_src", + KeepAlive: "keep_alive", + Hidden: "hidden", + Affix: "affix", + Sort: "sort", + Remark: "remark", + Status: "status", + UpdatedAt: "updated_at", + CreatedAt: "created_at", +} + +// NewAdminMenuDao creates and returns a new DAO object for table data access. +func NewAdminMenuDao(handlers ...gdb.ModelHandler) *AdminMenuDao { + return &AdminMenuDao{ + group: "default", + table: "hg_admin_menu", + columns: adminMenuColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminMenuDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminMenuDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminMenuDao) Columns() AdminMenuColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminMenuDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminMenuDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminMenuDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_notice.go b/server/internal/dao/internal/admin_notice.go new file mode 100755 index 0000000..ecb0cb9 --- /dev/null +++ b/server/internal/dao/internal/admin_notice.go @@ -0,0 +1,105 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminNoticeDao is the data access object for the table hg_admin_notice. +type AdminNoticeDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminNoticeColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminNoticeColumns defines and stores column names for the table hg_admin_notice. +type AdminNoticeColumns struct { + Id string // 公告ID + Title string // 公告标题 + Type string // 公告类型 + Tag string // 标签 + Content string // 公告内容 + Receiver string // 接收者 + Remark string // 备注 + Sort string // 排序 + Status string // 公告状态 + CreatedBy string // 发送人 + UpdatedBy string // 修改人 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 + DeletedAt string // 删除时间 +} + +// adminNoticeColumns holds the columns for the table hg_admin_notice. +var adminNoticeColumns = AdminNoticeColumns{ + Id: "id", + Title: "title", + Type: "type", + Tag: "tag", + Content: "content", + Receiver: "receiver", + Remark: "remark", + Sort: "sort", + Status: "status", + CreatedBy: "created_by", + UpdatedBy: "updated_by", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewAdminNoticeDao creates and returns a new DAO object for table data access. +func NewAdminNoticeDao(handlers ...gdb.ModelHandler) *AdminNoticeDao { + return &AdminNoticeDao{ + group: "default", + table: "hg_admin_notice", + columns: adminNoticeColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminNoticeDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminNoticeDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminNoticeDao) Columns() AdminNoticeColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminNoticeDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminNoticeDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminNoticeDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_notice_read.go b/server/internal/dao/internal/admin_notice_read.go new file mode 100755 index 0000000..2ee0f7b --- /dev/null +++ b/server/internal/dao/internal/admin_notice_read.go @@ -0,0 +1,89 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminNoticeReadDao is the data access object for the table hg_admin_notice_read. +type AdminNoticeReadDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminNoticeReadColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminNoticeReadColumns defines and stores column names for the table hg_admin_notice_read. +type AdminNoticeReadColumns struct { + Id string // 记录ID + NoticeId string // 公告ID + MemberId string // 会员ID + Clicks string // 已读次数 + UpdatedAt string // 更新时间 + CreatedAt string // 阅读时间 +} + +// adminNoticeReadColumns holds the columns for the table hg_admin_notice_read. +var adminNoticeReadColumns = AdminNoticeReadColumns{ + Id: "id", + NoticeId: "notice_id", + MemberId: "member_id", + Clicks: "clicks", + UpdatedAt: "updated_at", + CreatedAt: "created_at", +} + +// NewAdminNoticeReadDao creates and returns a new DAO object for table data access. +func NewAdminNoticeReadDao(handlers ...gdb.ModelHandler) *AdminNoticeReadDao { + return &AdminNoticeReadDao{ + group: "default", + table: "hg_admin_notice_read", + columns: adminNoticeReadColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminNoticeReadDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminNoticeReadDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminNoticeReadDao) Columns() AdminNoticeReadColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminNoticeReadDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminNoticeReadDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminNoticeReadDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_oauth.go b/server/internal/dao/internal/admin_oauth.go new file mode 100755 index 0000000..f812ac6 --- /dev/null +++ b/server/internal/dao/internal/admin_oauth.go @@ -0,0 +1,107 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminOauthDao is the data access object for the table hg_admin_oauth. +type AdminOauthDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminOauthColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminOauthColumns defines and stores column names for the table hg_admin_oauth. +type AdminOauthColumns struct { + Id string // 主键 + MemberId string // 用户ID + Unionid string // 唯一ID + OauthClient string // 授权组别 + OauthOpenid string // 授权开放ID + Sex string // 性别 + Nickname string // 昵称 + HeadPortrait string // 头像 + Birthday string // 生日 + Country string // 国家 + Province string // 省 + City string // 市 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// adminOauthColumns holds the columns for the table hg_admin_oauth. +var adminOauthColumns = AdminOauthColumns{ + Id: "id", + MemberId: "member_id", + Unionid: "unionid", + OauthClient: "oauth_client", + OauthOpenid: "oauth_openid", + Sex: "sex", + Nickname: "nickname", + HeadPortrait: "head_portrait", + Birthday: "birthday", + Country: "country", + Province: "province", + City: "city", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminOauthDao creates and returns a new DAO object for table data access. +func NewAdminOauthDao(handlers ...gdb.ModelHandler) *AdminOauthDao { + return &AdminOauthDao{ + group: "default", + table: "hg_admin_oauth", + columns: adminOauthColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminOauthDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminOauthDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminOauthDao) Columns() AdminOauthColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminOauthDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminOauthDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminOauthDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_order.go b/server/internal/dao/internal/admin_order.go new file mode 100755 index 0000000..8b3df6f --- /dev/null +++ b/server/internal/dao/internal/admin_order.go @@ -0,0 +1,101 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminOrderDao is the data access object for the table hg_admin_order. +type AdminOrderDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminOrderColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminOrderColumns defines and stores column names for the table hg_admin_order. +type AdminOrderColumns struct { + Id string // 主键 + MemberId string // 管理员id + OrderType string // 订单类型 + ProductId string // 产品id + OrderSn string // 关联订单号 + Money string // 充值金额 + Remark string // 备注 + RefundReason string // 退款原因 + RejectRefundReason string // 拒绝退款原因 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// adminOrderColumns holds the columns for the table hg_admin_order. +var adminOrderColumns = AdminOrderColumns{ + Id: "id", + MemberId: "member_id", + OrderType: "order_type", + ProductId: "product_id", + OrderSn: "order_sn", + Money: "money", + Remark: "remark", + RefundReason: "refund_reason", + RejectRefundReason: "reject_refund_reason", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminOrderDao creates and returns a new DAO object for table data access. +func NewAdminOrderDao(handlers ...gdb.ModelHandler) *AdminOrderDao { + return &AdminOrderDao{ + group: "default", + table: "hg_admin_order", + columns: adminOrderColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminOrderDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminOrderDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminOrderDao) Columns() AdminOrderColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminOrderDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminOrderDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminOrderDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_post.go b/server/internal/dao/internal/admin_post.go new file mode 100755 index 0000000..986103d --- /dev/null +++ b/server/internal/dao/internal/admin_post.go @@ -0,0 +1,93 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminPostDao is the data access object for the table hg_admin_post. +type AdminPostDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminPostColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminPostColumns defines and stores column names for the table hg_admin_post. +type AdminPostColumns struct { + Id string // 岗位ID + Code string // 岗位编码 + Name string // 岗位名称 + Remark string // 备注 + Sort string // 排序 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// adminPostColumns holds the columns for the table hg_admin_post. +var adminPostColumns = AdminPostColumns{ + Id: "id", + Code: "code", + Name: "name", + Remark: "remark", + Sort: "sort", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminPostDao creates and returns a new DAO object for table data access. +func NewAdminPostDao(handlers ...gdb.ModelHandler) *AdminPostDao { + return &AdminPostDao{ + group: "default", + table: "hg_admin_post", + columns: adminPostColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminPostDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminPostDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminPostDao) Columns() AdminPostColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminPostDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminPostDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminPostDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_role.go b/server/internal/dao/internal/admin_role.go new file mode 100755 index 0000000..6d8a324 --- /dev/null +++ b/server/internal/dao/internal/admin_role.go @@ -0,0 +1,103 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminRoleDao is the data access object for the table hg_admin_role. +type AdminRoleDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminRoleColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminRoleColumns defines and stores column names for the table hg_admin_role. +type AdminRoleColumns struct { + Id string // 角色ID + Name string // 角色名称 + Key string // 角色权限字符串 + DataScope string // 数据范围 + CustomDept string // 自定义部门权限 + Pid string // 上级角色ID + Level string // 关系树等级 + Tree string // 关系树 + Remark string // 备注 + Sort string // 排序 + Status string // 角色状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// adminRoleColumns holds the columns for the table hg_admin_role. +var adminRoleColumns = AdminRoleColumns{ + Id: "id", + Name: "name", + Key: "key", + DataScope: "data_scope", + CustomDept: "custom_dept", + Pid: "pid", + Level: "level", + Tree: "tree", + Remark: "remark", + Sort: "sort", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewAdminRoleDao creates and returns a new DAO object for table data access. +func NewAdminRoleDao(handlers ...gdb.ModelHandler) *AdminRoleDao { + return &AdminRoleDao{ + group: "default", + table: "hg_admin_role", + columns: adminRoleColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminRoleDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminRoleDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminRoleDao) Columns() AdminRoleColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminRoleDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminRoleDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminRoleDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_role_casbin.go b/server/internal/dao/internal/admin_role_casbin.go new file mode 100755 index 0000000..e00b886 --- /dev/null +++ b/server/internal/dao/internal/admin_role_casbin.go @@ -0,0 +1,93 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminRoleCasbinDao is the data access object for the table hg_admin_role_casbin. +type AdminRoleCasbinDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminRoleCasbinColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminRoleCasbinColumns defines and stores column names for the table hg_admin_role_casbin. +type AdminRoleCasbinColumns struct { + Id string // + PType string // + V0 string // + V1 string // + V2 string // + V3 string // + V4 string // + V5 string // +} + +// adminRoleCasbinColumns holds the columns for the table hg_admin_role_casbin. +var adminRoleCasbinColumns = AdminRoleCasbinColumns{ + Id: "id", + PType: "p_type", + V0: "v0", + V1: "v1", + V2: "v2", + V3: "v3", + V4: "v4", + V5: "v5", +} + +// NewAdminRoleCasbinDao creates and returns a new DAO object for table data access. +func NewAdminRoleCasbinDao(handlers ...gdb.ModelHandler) *AdminRoleCasbinDao { + return &AdminRoleCasbinDao{ + group: "default", + table: "hg_admin_role_casbin", + columns: adminRoleCasbinColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminRoleCasbinDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminRoleCasbinDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminRoleCasbinDao) Columns() AdminRoleCasbinColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminRoleCasbinDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminRoleCasbinDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminRoleCasbinDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/admin_role_menu.go b/server/internal/dao/internal/admin_role_menu.go new file mode 100755 index 0000000..9f9e4ff --- /dev/null +++ b/server/internal/dao/internal/admin_role_menu.go @@ -0,0 +1,81 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// AdminRoleMenuDao is the data access object for the table hg_admin_role_menu. +type AdminRoleMenuDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns AdminRoleMenuColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// AdminRoleMenuColumns defines and stores column names for the table hg_admin_role_menu. +type AdminRoleMenuColumns struct { + RoleId string // 角色ID + MenuId string // 菜单ID +} + +// adminRoleMenuColumns holds the columns for the table hg_admin_role_menu. +var adminRoleMenuColumns = AdminRoleMenuColumns{ + RoleId: "role_id", + MenuId: "menu_id", +} + +// NewAdminRoleMenuDao creates and returns a new DAO object for table data access. +func NewAdminRoleMenuDao(handlers ...gdb.ModelHandler) *AdminRoleMenuDao { + return &AdminRoleMenuDao{ + group: "default", + table: "hg_admin_role_menu", + columns: adminRoleMenuColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *AdminRoleMenuDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *AdminRoleMenuDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *AdminRoleMenuDao) Columns() AdminRoleMenuColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *AdminRoleMenuDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *AdminRoleMenuDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *AdminRoleMenuDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/pay_log.go b/server/internal/dao/internal/pay_log.go new file mode 100755 index 0000000..0ef4f61 --- /dev/null +++ b/server/internal/dao/internal/pay_log.go @@ -0,0 +1,137 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// PayLogDao is the data access object for the table hg_pay_log. +type PayLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns PayLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// PayLogColumns defines and stores column names for the table hg_pay_log. +type PayLogColumns struct { + Id string // 主键 + MemberId string // 会员ID + AppId string // 应用ID + AddonsName string // 插件名称 + OrderSn string // 关联订单号 + OrderGroup string // 组别[默认统一支付类型] + Openid string // openid + MchId string // 商户支付账户 + Subject string // 订单标题 + Detail string // 支付商品详情 + AuthCode string // 刷卡码 + OutTradeNo string // 商户订单号 + TransactionId string // 交易号 + PayType string // 支付类型 + PayAmount string // 支付金额 + ActualAmount string // 实付金额 + PayStatus string // 支付状态 + PayAt string // 支付时间 + TradeType string // 交易类型 + RefundSn string // 退款单号 + IsRefund string // 是否退款 + Custom string // 自定义参数 + CreateIp string // 创建者IP + PayIp string // 支付者IP + NotifyUrl string // 支付通知回调地址 + ReturnUrl string // 买家付款成功跳转地址 + TraceIds string // 链路ID集合 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// payLogColumns holds the columns for the table hg_pay_log. +var payLogColumns = PayLogColumns{ + Id: "id", + MemberId: "member_id", + AppId: "app_id", + AddonsName: "addons_name", + OrderSn: "order_sn", + OrderGroup: "order_group", + Openid: "openid", + MchId: "mch_id", + Subject: "subject", + Detail: "detail", + AuthCode: "auth_code", + OutTradeNo: "out_trade_no", + TransactionId: "transaction_id", + PayType: "pay_type", + PayAmount: "pay_amount", + ActualAmount: "actual_amount", + PayStatus: "pay_status", + PayAt: "pay_at", + TradeType: "trade_type", + RefundSn: "refund_sn", + IsRefund: "is_refund", + Custom: "custom", + CreateIp: "create_ip", + PayIp: "pay_ip", + NotifyUrl: "notify_url", + ReturnUrl: "return_url", + TraceIds: "trace_ids", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewPayLogDao creates and returns a new DAO object for table data access. +func NewPayLogDao(handlers ...gdb.ModelHandler) *PayLogDao { + return &PayLogDao{ + group: "default", + table: "hg_pay_log", + columns: payLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *PayLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *PayLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *PayLogDao) Columns() PayLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *PayLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *PayLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *PayLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/pay_refund.go b/server/internal/dao/internal/pay_refund.go new file mode 100755 index 0000000..8b7684f --- /dev/null +++ b/server/internal/dao/internal/pay_refund.go @@ -0,0 +1,103 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// PayRefundDao is the data access object for the table hg_pay_refund. +type PayRefundDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns PayRefundColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// PayRefundColumns defines and stores column names for the table hg_pay_refund. +type PayRefundColumns struct { + Id string // 主键ID + MemberId string // 会员ID + AppId string // 应用ID + OrderSn string // 业务订单号 + RefundTradeNo string // 退款交易号 + RefundMoney string // 退款金额 + RefundWay string // 退款方式 + Ip string // 申请者IP + Reason string // 申请退款原因 + Remark string // 退款备注 + Status string // 退款状态 + CreatedAt string // 申请时间 + UpdatedAt string // 更新时间 +} + +// payRefundColumns holds the columns for the table hg_pay_refund. +var payRefundColumns = PayRefundColumns{ + Id: "id", + MemberId: "member_id", + AppId: "app_id", + OrderSn: "order_sn", + RefundTradeNo: "refund_trade_no", + RefundMoney: "refund_money", + RefundWay: "refund_way", + Ip: "ip", + Reason: "reason", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewPayRefundDao creates and returns a new DAO object for table data access. +func NewPayRefundDao(handlers ...gdb.ModelHandler) *PayRefundDao { + return &PayRefundDao{ + group: "default", + table: "hg_pay_refund", + columns: payRefundColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *PayRefundDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *PayRefundDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *PayRefundDao) Columns() PayRefundColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *PayRefundDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *PayRefundDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *PayRefundDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_addons_config.go b/server/internal/dao/internal/sys_addons_config.go new file mode 100755 index 0000000..fa73736 --- /dev/null +++ b/server/internal/dao/internal/sys_addons_config.go @@ -0,0 +1,105 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysAddonsConfigDao is the data access object for the table hg_sys_addons_config. +type SysAddonsConfigDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysAddonsConfigColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysAddonsConfigColumns defines and stores column names for the table hg_sys_addons_config. +type SysAddonsConfigColumns struct { + Id string // 配置ID + AddonName string // 插件名称 + Group string // 分组 + Name string // 参数名称 + Type string // 键值类型:string,int,uint,bool,TIMESTAMP,date + Key string // 参数键名 + Value string // 参数键值 + DefaultValue string // 默认值 + Sort string // 排序 + Tip string // 变量描述 + IsDefault string // 是否为系统默认 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysAddonsConfigColumns holds the columns for the table hg_sys_addons_config. +var sysAddonsConfigColumns = SysAddonsConfigColumns{ + Id: "id", + AddonName: "addon_name", + Group: "group", + Name: "name", + Type: "type", + Key: "key", + Value: "value", + DefaultValue: "default_value", + Sort: "sort", + Tip: "tip", + IsDefault: "is_default", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysAddonsConfigDao creates and returns a new DAO object for table data access. +func NewSysAddonsConfigDao(handlers ...gdb.ModelHandler) *SysAddonsConfigDao { + return &SysAddonsConfigDao{ + group: "default", + table: "hg_sys_addons_config", + columns: sysAddonsConfigColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysAddonsConfigDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysAddonsConfigDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysAddonsConfigDao) Columns() SysAddonsConfigColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysAddonsConfigDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysAddonsConfigDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysAddonsConfigDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_addons_install.go b/server/internal/dao/internal/sys_addons_install.go new file mode 100644 index 0000000..9611545 --- /dev/null +++ b/server/internal/dao/internal/sys_addons_install.go @@ -0,0 +1,89 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysAddonsInstallDao is the data access object for the table hg_sys_addons_install. +type SysAddonsInstallDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysAddonsInstallColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysAddonsInstallColumns defines and stores column names for the table hg_sys_addons_install. +type SysAddonsInstallColumns struct { + Id string // 主键 + Name string // 插件名称 + Version string // 版本号 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysAddonsInstallColumns holds the columns for the table hg_sys_addons_install. +var sysAddonsInstallColumns = SysAddonsInstallColumns{ + Id: "id", + Name: "name", + Version: "version", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysAddonsInstallDao creates and returns a new DAO object for table data access. +func NewSysAddonsInstallDao(handlers ...gdb.ModelHandler) *SysAddonsInstallDao { + return &SysAddonsInstallDao{ + group: "default", + table: "hg_sys_addons_install", + columns: sysAddonsInstallColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysAddonsInstallDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysAddonsInstallDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysAddonsInstallDao) Columns() SysAddonsInstallColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysAddonsInstallDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysAddonsInstallDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysAddonsInstallDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_attachment.go b/server/internal/dao/internal/sys_attachment.go new file mode 100755 index 0000000..dcd6558 --- /dev/null +++ b/server/internal/dao/internal/sys_attachment.go @@ -0,0 +1,111 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysAttachmentDao is the data access object for the table hg_sys_attachment. +type SysAttachmentDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysAttachmentColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysAttachmentColumns defines and stores column names for the table hg_sys_attachment. +type SysAttachmentColumns struct { + Id string // 文件ID + AppId string // 应用ID + MemberId string // 管理员ID + CateId string // 上传分类 + Drive string // 上传驱动 + Name string // 文件原始名 + Kind string // 上传类型 + MimeType string // 扩展类型 + NaiveType string // NaiveUI类型 + Path string // 本地路径 + FileUrl string // url + Size string // 文件大小 + Ext string // 扩展名 + Md5 string // md5校验码 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// sysAttachmentColumns holds the columns for the table hg_sys_attachment. +var sysAttachmentColumns = SysAttachmentColumns{ + Id: "id", + AppId: "app_id", + MemberId: "member_id", + CateId: "cate_id", + Drive: "drive", + Name: "name", + Kind: "kind", + MimeType: "mime_type", + NaiveType: "naive_type", + Path: "path", + FileUrl: "file_url", + Size: "size", + Ext: "ext", + Md5: "md5", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysAttachmentDao creates and returns a new DAO object for table data access. +func NewSysAttachmentDao(handlers ...gdb.ModelHandler) *SysAttachmentDao { + return &SysAttachmentDao{ + group: "default", + table: "hg_sys_attachment", + columns: sysAttachmentColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysAttachmentDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysAttachmentDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysAttachmentDao) Columns() SysAttachmentColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysAttachmentDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysAttachmentDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysAttachmentDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_blacklist.go b/server/internal/dao/internal/sys_blacklist.go new file mode 100755 index 0000000..1838a14 --- /dev/null +++ b/server/internal/dao/internal/sys_blacklist.go @@ -0,0 +1,89 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysBlacklistDao is the data access object for the table hg_sys_blacklist. +type SysBlacklistDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysBlacklistColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysBlacklistColumns defines and stores column names for the table hg_sys_blacklist. +type SysBlacklistColumns struct { + Id string // 黑名单ID + Ip string // IP地址 + Remark string // 备注 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysBlacklistColumns holds the columns for the table hg_sys_blacklist. +var sysBlacklistColumns = SysBlacklistColumns{ + Id: "id", + Ip: "ip", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysBlacklistDao creates and returns a new DAO object for table data access. +func NewSysBlacklistDao(handlers ...gdb.ModelHandler) *SysBlacklistDao { + return &SysBlacklistDao{ + group: "default", + table: "hg_sys_blacklist", + columns: sysBlacklistColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysBlacklistDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysBlacklistDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysBlacklistDao) Columns() SysBlacklistColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysBlacklistDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysBlacklistDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysBlacklistDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_config.go b/server/internal/dao/internal/sys_config.go new file mode 100755 index 0000000..5c46adb --- /dev/null +++ b/server/internal/dao/internal/sys_config.go @@ -0,0 +1,103 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysConfigDao is the data access object for the table hg_sys_config. +type SysConfigDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysConfigColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysConfigColumns defines and stores column names for the table hg_sys_config. +type SysConfigColumns struct { + Id string // 配置ID + Group string // 配置分组 + Name string // 参数名称 + Type string // 键值类型:string,int,uint,bool,TIMESTAMP,date + Key string // 参数键名 + Value string // 参数键值 + DefaultValue string // 默认值 + Sort string // 排序 + Tip string // 变量描述 + IsDefault string // 是否为系统默认 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysConfigColumns holds the columns for the table hg_sys_config. +var sysConfigColumns = SysConfigColumns{ + Id: "id", + Group: "group", + Name: "name", + Type: "type", + Key: "key", + Value: "value", + DefaultValue: "default_value", + Sort: "sort", + Tip: "tip", + IsDefault: "is_default", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysConfigDao creates and returns a new DAO object for table data access. +func NewSysConfigDao(handlers ...gdb.ModelHandler) *SysConfigDao { + return &SysConfigDao{ + group: "default", + table: "hg_sys_config", + columns: sysConfigColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysConfigDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysConfigDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysConfigDao) Columns() SysConfigColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysConfigDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysConfigDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysConfigDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_cron.go b/server/internal/dao/internal/sys_cron.go new file mode 100755 index 0000000..a8a8dac --- /dev/null +++ b/server/internal/dao/internal/sys_cron.go @@ -0,0 +1,103 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysCronDao is the data access object for the table hg_sys_cron. +type SysCronDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysCronColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysCronColumns defines and stores column names for the table hg_sys_cron. +type SysCronColumns struct { + Id string // 任务ID + GroupId string // 分组ID + Title string // 任务标题 + Name string // 任务方法 + Params string // 函数参数 + Pattern string // 表达式 + Policy string // 策略 + Count string // 执行次数 + Sort string // 排序 + Remark string // 备注 + Status string // 任务状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysCronColumns holds the columns for the table hg_sys_cron. +var sysCronColumns = SysCronColumns{ + Id: "id", + GroupId: "group_id", + Title: "title", + Name: "name", + Params: "params", + Pattern: "pattern", + Policy: "policy", + Count: "count", + Sort: "sort", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysCronDao creates and returns a new DAO object for table data access. +func NewSysCronDao(handlers ...gdb.ModelHandler) *SysCronDao { + return &SysCronDao{ + group: "default", + table: "hg_sys_cron", + columns: sysCronColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysCronDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysCronDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysCronDao) Columns() SysCronColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysCronDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysCronDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysCronDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_cron_group.go b/server/internal/dao/internal/sys_cron_group.go new file mode 100755 index 0000000..983a238 --- /dev/null +++ b/server/internal/dao/internal/sys_cron_group.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysCronGroupDao is the data access object for the table hg_sys_cron_group. +type SysCronGroupDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysCronGroupColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysCronGroupColumns defines and stores column names for the table hg_sys_cron_group. +type SysCronGroupColumns struct { + Id string // 任务分组ID + Pid string // 父类任务分组ID + Name string // 分组名称 + IsDefault string // 是否默认 + Sort string // 排序 + Remark string // 备注 + Status string // 分组状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysCronGroupColumns holds the columns for the table hg_sys_cron_group. +var sysCronGroupColumns = SysCronGroupColumns{ + Id: "id", + Pid: "pid", + Name: "name", + IsDefault: "is_default", + Sort: "sort", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysCronGroupDao creates and returns a new DAO object for table data access. +func NewSysCronGroupDao(handlers ...gdb.ModelHandler) *SysCronGroupDao { + return &SysCronGroupDao{ + group: "default", + table: "hg_sys_cron_group", + columns: sysCronGroupColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysCronGroupDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysCronGroupDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysCronGroupDao) Columns() SysCronGroupColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysCronGroupDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysCronGroupDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysCronGroupDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_dict_data.go b/server/internal/dao/internal/sys_dict_data.go new file mode 100755 index 0000000..67caa80 --- /dev/null +++ b/server/internal/dao/internal/sys_dict_data.go @@ -0,0 +1,101 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysDictDataDao is the data access object for the table hg_sys_dict_data. +type SysDictDataDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysDictDataColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysDictDataColumns defines and stores column names for the table hg_sys_dict_data. +type SysDictDataColumns struct { + Id string // 字典数据ID + Label string // 字典标签 + Value string // 字典键值 + ValueType string // 键值数据类型:string,int,uint,bool,TIMESTAMP,date + Type string // 字典类型 + ListClass string // 表格回显样式 + IsDefault string // 是否为系统默认 + Sort string // 字典排序 + Remark string // 备注 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysDictDataColumns holds the columns for the table hg_sys_dict_data. +var sysDictDataColumns = SysDictDataColumns{ + Id: "id", + Label: "label", + Value: "value", + ValueType: "value_type", + Type: "type", + ListClass: "list_class", + IsDefault: "is_default", + Sort: "sort", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysDictDataDao creates and returns a new DAO object for table data access. +func NewSysDictDataDao(handlers ...gdb.ModelHandler) *SysDictDataDao { + return &SysDictDataDao{ + group: "default", + table: "hg_sys_dict_data", + columns: sysDictDataColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysDictDataDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysDictDataDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysDictDataDao) Columns() SysDictDataColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysDictDataDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysDictDataDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysDictDataDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_dict_type.go b/server/internal/dao/internal/sys_dict_type.go new file mode 100755 index 0000000..b0b05f7 --- /dev/null +++ b/server/internal/dao/internal/sys_dict_type.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysDictTypeDao is the data access object for the table hg_sys_dict_type. +type SysDictTypeDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysDictTypeColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysDictTypeColumns defines and stores column names for the table hg_sys_dict_type. +type SysDictTypeColumns struct { + Id string // 字典类型ID + Pid string // 父类字典类型ID + Name string // 字典类型名称 + Type string // 字典类型 + Sort string // 排序 + Remark string // 备注 + Status string // 字典类型状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysDictTypeColumns holds the columns for the table hg_sys_dict_type. +var sysDictTypeColumns = SysDictTypeColumns{ + Id: "id", + Pid: "pid", + Name: "name", + Type: "type", + Sort: "sort", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysDictTypeDao creates and returns a new DAO object for table data access. +func NewSysDictTypeDao(handlers ...gdb.ModelHandler) *SysDictTypeDao { + return &SysDictTypeDao{ + group: "default", + table: "hg_sys_dict_type", + columns: sysDictTypeColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysDictTypeDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysDictTypeDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysDictTypeDao) Columns() SysDictTypeColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysDictTypeDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysDictTypeDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysDictTypeDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_ems_log.go b/server/internal/dao/internal/sys_ems_log.go new file mode 100755 index 0000000..3cac58d --- /dev/null +++ b/server/internal/dao/internal/sys_ems_log.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysEmsLogDao is the data access object for the table hg_sys_ems_log. +type SysEmsLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysEmsLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysEmsLogColumns defines and stores column names for the table hg_sys_ems_log. +type SysEmsLogColumns struct { + Id string // 主键 + Event string // 事件 + Email string // 邮箱地址,多个用;隔开 + Code string // 验证码 + Times string // 验证次数 + Content string // 邮件内容 + Ip string // ip地址 + Status string // 状态(1未验证,2已验证) + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysEmsLogColumns holds the columns for the table hg_sys_ems_log. +var sysEmsLogColumns = SysEmsLogColumns{ + Id: "id", + Event: "event", + Email: "email", + Code: "code", + Times: "times", + Content: "content", + Ip: "ip", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysEmsLogDao creates and returns a new DAO object for table data access. +func NewSysEmsLogDao(handlers ...gdb.ModelHandler) *SysEmsLogDao { + return &SysEmsLogDao{ + group: "default", + table: "hg_sys_ems_log", + columns: sysEmsLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysEmsLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysEmsLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysEmsLogDao) Columns() SysEmsLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysEmsLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysEmsLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysEmsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_gen_codes.go b/server/internal/dao/internal/sys_gen_codes.go new file mode 100755 index 0000000..e909a3f --- /dev/null +++ b/server/internal/dao/internal/sys_gen_codes.go @@ -0,0 +1,105 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysGenCodesDao is the data access object for the table hg_sys_gen_codes. +type SysGenCodesDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysGenCodesColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysGenCodesColumns defines and stores column names for the table hg_sys_gen_codes. +type SysGenCodesColumns struct { + Id string // 生成ID + GenType string // 生成类型 + GenTemplate string // 生成模板 + VarName string // 实体命名 + Options string // 配置选项 + DbName string // 数据库名称 + TableName string // 主表名称 + TableComment string // 主表注释 + DaoName string // 主表dao模型 + MasterColumns string // 主表字段 + AddonName string // 插件名称 + Status string // 生成状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysGenCodesColumns holds the columns for the table hg_sys_gen_codes. +var sysGenCodesColumns = SysGenCodesColumns{ + Id: "id", + GenType: "gen_type", + GenTemplate: "gen_template", + VarName: "var_name", + Options: "options", + DbName: "db_name", + TableName: "table_name", + TableComment: "table_comment", + DaoName: "dao_name", + MasterColumns: "master_columns", + AddonName: "addon_name", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysGenCodesDao creates and returns a new DAO object for table data access. +func NewSysGenCodesDao(handlers ...gdb.ModelHandler) *SysGenCodesDao { + return &SysGenCodesDao{ + group: "default", + table: "hg_sys_gen_codes", + columns: sysGenCodesColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysGenCodesDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysGenCodesDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysGenCodesDao) Columns() SysGenCodesColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysGenCodesDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysGenCodesDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysGenCodesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_gen_curd_demo.go b/server/internal/dao/internal/sys_gen_curd_demo.go new file mode 100755 index 0000000..25bd694 --- /dev/null +++ b/server/internal/dao/internal/sys_gen_curd_demo.go @@ -0,0 +1,111 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysGenCurdDemoDao is the data access object for the table hg_sys_gen_curd_demo. +type SysGenCurdDemoDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysGenCurdDemoColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysGenCurdDemoColumns defines and stores column names for the table hg_sys_gen_curd_demo. +type SysGenCurdDemoColumns struct { + Id string // ID + CategoryId string // 分类ID + Title string // 标题 + Description string // 描述 + Content string // 内容 + Image string // 单图 + Attachfile string // 附件 + CityId string // 所在城市 + Switch string // 显示开关 + Sort string // 排序 + Status string // 状态 + CreatedBy string // 创建者 + UpdatedBy string // 更新者 + DeletedBy string // 删除者 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 + DeletedAt string // 删除时间 +} + +// sysGenCurdDemoColumns holds the columns for the table hg_sys_gen_curd_demo. +var sysGenCurdDemoColumns = SysGenCurdDemoColumns{ + Id: "id", + CategoryId: "category_id", + Title: "title", + Description: "description", + Content: "content", + Image: "image", + Attachfile: "attachfile", + CityId: "city_id", + Switch: "switch", + Sort: "sort", + Status: "status", + CreatedBy: "created_by", + UpdatedBy: "updated_by", + DeletedBy: "deleted_by", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewSysGenCurdDemoDao creates and returns a new DAO object for table data access. +func NewSysGenCurdDemoDao(handlers ...gdb.ModelHandler) *SysGenCurdDemoDao { + return &SysGenCurdDemoDao{ + group: "default", + table: "hg_sys_gen_curd_demo", + columns: sysGenCurdDemoColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysGenCurdDemoDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysGenCurdDemoDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysGenCurdDemoDao) Columns() SysGenCurdDemoColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysGenCurdDemoDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysGenCurdDemoDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysGenCurdDemoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_gen_tree_demo.go b/server/internal/dao/internal/sys_gen_tree_demo.go new file mode 100755 index 0000000..5e3cdc5 --- /dev/null +++ b/server/internal/dao/internal/sys_gen_tree_demo.go @@ -0,0 +1,105 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysGenTreeDemoDao is the data access object for the table hg_sys_gen_tree_demo. +type SysGenTreeDemoDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysGenTreeDemoColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysGenTreeDemoColumns defines and stores column names for the table hg_sys_gen_tree_demo. +type SysGenTreeDemoColumns struct { + Id string // ID + Pid string // 上级ID + Level string // 关系树级别 + Tree string // 关系树 + CategoryId string // 分类ID + Title string // 标题 + Description string // 描述 + Sort string // 排序 + Status string // 状态 + CreatedBy string // 创建者 + UpdatedBy string // 更新者 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 + DeletedAt string // 删除时间 +} + +// sysGenTreeDemoColumns holds the columns for the table hg_sys_gen_tree_demo. +var sysGenTreeDemoColumns = SysGenTreeDemoColumns{ + Id: "id", + Pid: "pid", + Level: "level", + Tree: "tree", + CategoryId: "category_id", + Title: "title", + Description: "description", + Sort: "sort", + Status: "status", + CreatedBy: "created_by", + UpdatedBy: "updated_by", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewSysGenTreeDemoDao creates and returns a new DAO object for table data access. +func NewSysGenTreeDemoDao(handlers ...gdb.ModelHandler) *SysGenTreeDemoDao { + return &SysGenTreeDemoDao{ + group: "default", + table: "hg_sys_gen_tree_demo", + columns: sysGenTreeDemoColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysGenTreeDemoDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysGenTreeDemoDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysGenTreeDemoDao) Columns() SysGenTreeDemoColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysGenTreeDemoDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysGenTreeDemoDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysGenTreeDemoDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_log.go b/server/internal/dao/internal/sys_log.go new file mode 100755 index 0000000..79813b3 --- /dev/null +++ b/server/internal/dao/internal/sys_log.go @@ -0,0 +1,123 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysLogDao is the data access object for the table hg_sys_log. +type SysLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysLogColumns defines and stores column names for the table hg_sys_log. +type SysLogColumns struct { + Id string // 日志ID + ReqId string // 对外ID + AppId string // 应用ID + MerchantId string // 商户ID + MemberId string // 用户ID + Method string // 提交类型 + Module string // 访问模块 + Url string // 提交url + GetData string // get数据 + PostData string // post数据 + HeaderData string // header数据 + Ip string // IP地址 + ProvinceId string // 省编码 + CityId string // 市编码 + ErrorCode string // 报错code + ErrorMsg string // 对外错误提示 + ErrorData string // 报错日志 + UserAgent string // UA信息 + TakeUpTime string // 请求耗时 + Timestamp string // 响应时间 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// sysLogColumns holds the columns for the table hg_sys_log. +var sysLogColumns = SysLogColumns{ + Id: "id", + ReqId: "req_id", + AppId: "app_id", + MerchantId: "merchant_id", + MemberId: "member_id", + Method: "method", + Module: "module", + Url: "url", + GetData: "get_data", + PostData: "post_data", + HeaderData: "header_data", + Ip: "ip", + ProvinceId: "province_id", + CityId: "city_id", + ErrorCode: "error_code", + ErrorMsg: "error_msg", + ErrorData: "error_data", + UserAgent: "user_agent", + TakeUpTime: "take_up_time", + Timestamp: "timestamp", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysLogDao creates and returns a new DAO object for table data access. +func NewSysLogDao(handlers ...gdb.ModelHandler) *SysLogDao { + return &SysLogDao{ + group: "default", + table: "hg_sys_log", + columns: sysLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysLogDao) Columns() SysLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_login_log.go b/server/internal/dao/internal/sys_login_log.go new file mode 100755 index 0000000..0637049 --- /dev/null +++ b/server/internal/dao/internal/sys_login_log.go @@ -0,0 +1,105 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysLoginLogDao is the data access object for the table hg_sys_login_log. +type SysLoginLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysLoginLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysLoginLogColumns defines and stores column names for the table hg_sys_login_log. +type SysLoginLogColumns struct { + Id string // 日志ID + ReqId string // 请求ID + MemberId string // 用户ID + Username string // 用户名 + Response string // 响应数据 + LoginAt string // 登录时间 + LoginIp string // 登录IP + ProvinceId string // 省编码 + CityId string // 市编码 + UserAgent string // UA信息 + ErrMsg string // 错误提示 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// sysLoginLogColumns holds the columns for the table hg_sys_login_log. +var sysLoginLogColumns = SysLoginLogColumns{ + Id: "id", + ReqId: "req_id", + MemberId: "member_id", + Username: "username", + Response: "response", + LoginAt: "login_at", + LoginIp: "login_ip", + ProvinceId: "province_id", + CityId: "city_id", + UserAgent: "user_agent", + ErrMsg: "err_msg", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysLoginLogDao creates and returns a new DAO object for table data access. +func NewSysLoginLogDao(handlers ...gdb.ModelHandler) *SysLoginLogDao { + return &SysLoginLogDao{ + group: "default", + table: "hg_sys_login_log", + columns: sysLoginLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysLoginLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysLoginLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysLoginLogDao) Columns() SysLoginLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysLoginLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysLoginLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysLoginLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_provinces.go b/server/internal/dao/internal/sys_provinces.go new file mode 100755 index 0000000..377de59 --- /dev/null +++ b/server/internal/dao/internal/sys_provinces.go @@ -0,0 +1,101 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysProvincesDao is the data access object for the table hg_sys_provinces. +type SysProvincesDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysProvincesColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysProvincesColumns defines and stores column names for the table hg_sys_provinces. +type SysProvincesColumns struct { + Id string // 省市区ID + Title string // 栏目名称 + Pinyin string // 拼音 + Lng string // 经度 + Lat string // 纬度 + Pid string // 父栏目 + Level string // 关系树等级 + Tree string // 关系 + Sort string // 排序 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysProvincesColumns holds the columns for the table hg_sys_provinces. +var sysProvincesColumns = SysProvincesColumns{ + Id: "id", + Title: "title", + Pinyin: "pinyin", + Lng: "lng", + Lat: "lat", + Pid: "pid", + Level: "level", + Tree: "tree", + Sort: "sort", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysProvincesDao creates and returns a new DAO object for table data access. +func NewSysProvincesDao(handlers ...gdb.ModelHandler) *SysProvincesDao { + return &SysProvincesDao{ + group: "default", + table: "hg_sys_provinces", + columns: sysProvincesColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysProvincesDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysProvincesDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysProvincesDao) Columns() SysProvincesColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysProvincesDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysProvincesDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysProvincesDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_serve_license.go b/server/internal/dao/internal/sys_serve_license.go new file mode 100755 index 0000000..46ef2f5 --- /dev/null +++ b/server/internal/dao/internal/sys_serve_license.go @@ -0,0 +1,111 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysServeLicenseDao is the data access object for the table hg_sys_serve_license. +type SysServeLicenseDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysServeLicenseColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysServeLicenseColumns defines and stores column names for the table hg_sys_serve_license. +type SysServeLicenseColumns struct { + Id string // 许可ID + Group string // 分组 + Name string // 许可名称 + Appid string // 应用ID + SecretKey string // 应用秘钥 + RemoteAddr string // 最后连接地址 + OnlineLimit string // 在线限制 + LoginTimes string // 登录次数 + LastLoginAt string // 最后登录时间 + LastActiveAt string // 最后心跳 + Routes string // 路由表,空使用默认分组路由 + AllowedIps string // IP白名单 + EndAt string // 授权有效期 + Remark string // 备注 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// sysServeLicenseColumns holds the columns for the table hg_sys_serve_license. +var sysServeLicenseColumns = SysServeLicenseColumns{ + Id: "id", + Group: "group", + Name: "name", + Appid: "appid", + SecretKey: "secret_key", + RemoteAddr: "remote_addr", + OnlineLimit: "online_limit", + LoginTimes: "login_times", + LastLoginAt: "last_login_at", + LastActiveAt: "last_active_at", + Routes: "routes", + AllowedIps: "allowed_ips", + EndAt: "end_at", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysServeLicenseDao creates and returns a new DAO object for table data access. +func NewSysServeLicenseDao(handlers ...gdb.ModelHandler) *SysServeLicenseDao { + return &SysServeLicenseDao{ + group: "default", + table: "hg_sys_serve_license", + columns: sysServeLicenseColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysServeLicenseDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysServeLicenseDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysServeLicenseDao) Columns() SysServeLicenseColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysServeLicenseDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysServeLicenseDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysServeLicenseDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_serve_log.go b/server/internal/dao/internal/sys_serve_log.go new file mode 100755 index 0000000..abf52f9 --- /dev/null +++ b/server/internal/dao/internal/sys_serve_log.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysServeLogDao is the data access object for the table hg_sys_serve_log. +type SysServeLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysServeLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysServeLogColumns defines and stores column names for the table hg_sys_serve_log. +type SysServeLogColumns struct { + Id string // 日志ID + TraceId string // 链路ID + LevelFormat string // 日志级别 + Content string // 日志内容 + Stack string // 打印堆栈 + Line string // 调用行 + TriggerNs string // 触发时间(ns) + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 +} + +// sysServeLogColumns holds the columns for the table hg_sys_serve_log. +var sysServeLogColumns = SysServeLogColumns{ + Id: "id", + TraceId: "trace_id", + LevelFormat: "level_format", + Content: "content", + Stack: "stack", + Line: "line", + TriggerNs: "trigger_ns", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysServeLogDao creates and returns a new DAO object for table data access. +func NewSysServeLogDao(handlers ...gdb.ModelHandler) *SysServeLogDao { + return &SysServeLogDao{ + group: "default", + table: "hg_sys_serve_log", + columns: sysServeLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysServeLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysServeLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysServeLogDao) Columns() SysServeLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysServeLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysServeLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysServeLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/sys_sms_log.go b/server/internal/dao/internal/sys_sms_log.go new file mode 100755 index 0000000..da73d87 --- /dev/null +++ b/server/internal/dao/internal/sys_sms_log.go @@ -0,0 +1,95 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// SysSmsLogDao is the data access object for the table hg_sys_sms_log. +type SysSmsLogDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns SysSmsLogColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// SysSmsLogColumns defines and stores column names for the table hg_sys_sms_log. +type SysSmsLogColumns struct { + Id string // 主键 + Event string // 事件 + Mobile string // 手机号 + Code string // 验证码或短信内容 + Times string // 验证次数 + Ip string // ip地址 + Status string // 状态(1未验证,2已验证) + CreatedAt string // 创建时间 + UpdatedAt string // 更新时间 +} + +// sysSmsLogColumns holds the columns for the table hg_sys_sms_log. +var sysSmsLogColumns = SysSmsLogColumns{ + Id: "id", + Event: "event", + Mobile: "mobile", + Code: "code", + Times: "times", + Ip: "ip", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +// NewSysSmsLogDao creates and returns a new DAO object for table data access. +func NewSysSmsLogDao(handlers ...gdb.ModelHandler) *SysSmsLogDao { + return &SysSmsLogDao{ + group: "default", + table: "hg_sys_sms_log", + columns: sysSmsLogColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *SysSmsLogDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *SysSmsLogDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *SysSmsLogDao) Columns() SysSmsLogColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *SysSmsLogDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *SysSmsLogDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *SysSmsLogDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/internal/test_category.go b/server/internal/dao/internal/test_category.go new file mode 100755 index 0000000..1ebbc81 --- /dev/null +++ b/server/internal/dao/internal/test_category.go @@ -0,0 +1,97 @@ +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// TestCategoryDao is the data access object for the table hg_test_category. +type TestCategoryDao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns TestCategoryColumns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// TestCategoryColumns defines and stores column names for the table hg_test_category. +type TestCategoryColumns struct { + Id string // 分类ID + Name string // 分类名称 + ShortName string // 简称 + Description string // 描述 + Sort string // 排序 + Remark string // 备注 + Status string // 状态 + CreatedAt string // 创建时间 + UpdatedAt string // 修改时间 + DeletedAt string // 删除时间 +} + +// testCategoryColumns holds the columns for the table hg_test_category. +var testCategoryColumns = TestCategoryColumns{ + Id: "id", + Name: "name", + ShortName: "short_name", + Description: "description", + Sort: "sort", + Remark: "remark", + Status: "status", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +// NewTestCategoryDao creates and returns a new DAO object for table data access. +func NewTestCategoryDao(handlers ...gdb.ModelHandler) *TestCategoryDao { + return &TestCategoryDao{ + group: "default", + table: "hg_test_category", + columns: testCategoryColumns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *TestCategoryDao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *TestCategoryDao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *TestCategoryDao) Columns() TestCategoryColumns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *TestCategoryDao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *TestCategoryDao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *TestCategoryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} diff --git a/server/internal/dao/pay_log.go b/server/internal/dao/pay_log.go new file mode 100755 index 0000000..eef43f9 --- /dev/null +++ b/server/internal/dao/pay_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// payLogDao is the data access object for the table hg_pay_log. +// You can define custom methods on it to extend its functionality as needed. +type payLogDao struct { + *internal.PayLogDao +} + +var ( + // PayLog is a globally accessible object for table hg_pay_log operations. + PayLog = payLogDao{internal.NewPayLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/pay_refund.go b/server/internal/dao/pay_refund.go new file mode 100755 index 0000000..bc52795 --- /dev/null +++ b/server/internal/dao/pay_refund.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// payRefundDao is the data access object for the table hg_pay_refund. +// You can define custom methods on it to extend its functionality as needed. +type payRefundDao struct { + *internal.PayRefundDao +} + +var ( + // PayRefund is a globally accessible object for table hg_pay_refund operations. + PayRefund = payRefundDao{internal.NewPayRefundDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_addons_config.go b/server/internal/dao/sys_addons_config.go new file mode 100755 index 0000000..47c7893 --- /dev/null +++ b/server/internal/dao/sys_addons_config.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysAddonsConfigDao is the data access object for the table hg_sys_addons_config. +// You can define custom methods on it to extend its functionality as needed. +type sysAddonsConfigDao struct { + *internal.SysAddonsConfigDao +} + +var ( + // SysAddonsConfig is a globally accessible object for table hg_sys_addons_config operations. + SysAddonsConfig = sysAddonsConfigDao{internal.NewSysAddonsConfigDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_addons_install.go b/server/internal/dao/sys_addons_install.go new file mode 100644 index 0000000..3eb6bac --- /dev/null +++ b/server/internal/dao/sys_addons_install.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysAddonsInstallDao is the data access object for the table hg_sys_addons_install. +// You can define custom methods on it to extend its functionality as needed. +type sysAddonsInstallDao struct { + *internal.SysAddonsInstallDao +} + +var ( + // SysAddonsInstall is a globally accessible object for table hg_sys_addons_install operations. + SysAddonsInstall = sysAddonsInstallDao{internal.NewSysAddonsInstallDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_attachment.go b/server/internal/dao/sys_attachment.go new file mode 100755 index 0000000..2e40b56 --- /dev/null +++ b/server/internal/dao/sys_attachment.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysAttachmentDao is the data access object for the table hg_sys_attachment. +// You can define custom methods on it to extend its functionality as needed. +type sysAttachmentDao struct { + *internal.SysAttachmentDao +} + +var ( + // SysAttachment is a globally accessible object for table hg_sys_attachment operations. + SysAttachment = sysAttachmentDao{internal.NewSysAttachmentDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_blacklist.go b/server/internal/dao/sys_blacklist.go new file mode 100755 index 0000000..84bdec1 --- /dev/null +++ b/server/internal/dao/sys_blacklist.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysBlacklistDao is the data access object for the table hg_sys_blacklist. +// You can define custom methods on it to extend its functionality as needed. +type sysBlacklistDao struct { + *internal.SysBlacklistDao +} + +var ( + // SysBlacklist is a globally accessible object for table hg_sys_blacklist operations. + SysBlacklist = sysBlacklistDao{internal.NewSysBlacklistDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_config.go b/server/internal/dao/sys_config.go new file mode 100755 index 0000000..a868a27 --- /dev/null +++ b/server/internal/dao/sys_config.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysConfigDao is the data access object for the table hg_sys_config. +// You can define custom methods on it to extend its functionality as needed. +type sysConfigDao struct { + *internal.SysConfigDao +} + +var ( + // SysConfig is a globally accessible object for table hg_sys_config operations. + SysConfig = sysConfigDao{internal.NewSysConfigDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_cron.go b/server/internal/dao/sys_cron.go new file mode 100755 index 0000000..1e0f647 --- /dev/null +++ b/server/internal/dao/sys_cron.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysCronDao is the data access object for the table hg_sys_cron. +// You can define custom methods on it to extend its functionality as needed. +type sysCronDao struct { + *internal.SysCronDao +} + +var ( + // SysCron is a globally accessible object for table hg_sys_cron operations. + SysCron = sysCronDao{internal.NewSysCronDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_cron_group.go b/server/internal/dao/sys_cron_group.go new file mode 100755 index 0000000..25e1578 --- /dev/null +++ b/server/internal/dao/sys_cron_group.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysCronGroupDao is the data access object for the table hg_sys_cron_group. +// You can define custom methods on it to extend its functionality as needed. +type sysCronGroupDao struct { + *internal.SysCronGroupDao +} + +var ( + // SysCronGroup is a globally accessible object for table hg_sys_cron_group operations. + SysCronGroup = sysCronGroupDao{internal.NewSysCronGroupDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_dict_data.go b/server/internal/dao/sys_dict_data.go new file mode 100755 index 0000000..05e78a3 --- /dev/null +++ b/server/internal/dao/sys_dict_data.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysDictDataDao is the data access object for the table hg_sys_dict_data. +// You can define custom methods on it to extend its functionality as needed. +type sysDictDataDao struct { + *internal.SysDictDataDao +} + +var ( + // SysDictData is a globally accessible object for table hg_sys_dict_data operations. + SysDictData = sysDictDataDao{internal.NewSysDictDataDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_dict_type.go b/server/internal/dao/sys_dict_type.go new file mode 100755 index 0000000..102c167 --- /dev/null +++ b/server/internal/dao/sys_dict_type.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysDictTypeDao is the data access object for the table hg_sys_dict_type. +// You can define custom methods on it to extend its functionality as needed. +type sysDictTypeDao struct { + *internal.SysDictTypeDao +} + +var ( + // SysDictType is a globally accessible object for table hg_sys_dict_type operations. + SysDictType = sysDictTypeDao{internal.NewSysDictTypeDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_ems_log.go b/server/internal/dao/sys_ems_log.go new file mode 100755 index 0000000..cad6a7b --- /dev/null +++ b/server/internal/dao/sys_ems_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysEmsLogDao is the data access object for the table hg_sys_ems_log. +// You can define custom methods on it to extend its functionality as needed. +type sysEmsLogDao struct { + *internal.SysEmsLogDao +} + +var ( + // SysEmsLog is a globally accessible object for table hg_sys_ems_log operations. + SysEmsLog = sysEmsLogDao{internal.NewSysEmsLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_gen_codes.go b/server/internal/dao/sys_gen_codes.go new file mode 100755 index 0000000..4e82527 --- /dev/null +++ b/server/internal/dao/sys_gen_codes.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysGenCodesDao is the data access object for the table hg_sys_gen_codes. +// You can define custom methods on it to extend its functionality as needed. +type sysGenCodesDao struct { + *internal.SysGenCodesDao +} + +var ( + // SysGenCodes is a globally accessible object for table hg_sys_gen_codes operations. + SysGenCodes = sysGenCodesDao{internal.NewSysGenCodesDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_gen_curd_demo.go b/server/internal/dao/sys_gen_curd_demo.go new file mode 100755 index 0000000..e67ce1e --- /dev/null +++ b/server/internal/dao/sys_gen_curd_demo.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysGenCurdDemoDao is the data access object for the table hg_sys_gen_curd_demo. +// You can define custom methods on it to extend its functionality as needed. +type sysGenCurdDemoDao struct { + *internal.SysGenCurdDemoDao +} + +var ( + // SysGenCurdDemo is a globally accessible object for table hg_sys_gen_curd_demo operations. + SysGenCurdDemo = sysGenCurdDemoDao{internal.NewSysGenCurdDemoDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_gen_tree_demo.go b/server/internal/dao/sys_gen_tree_demo.go new file mode 100755 index 0000000..8cc7d86 --- /dev/null +++ b/server/internal/dao/sys_gen_tree_demo.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysGenTreeDemoDao is the data access object for the table hg_sys_gen_tree_demo. +// You can define custom methods on it to extend its functionality as needed. +type sysGenTreeDemoDao struct { + *internal.SysGenTreeDemoDao +} + +var ( + // SysGenTreeDemo is a globally accessible object for table hg_sys_gen_tree_demo operations. + SysGenTreeDemo = sysGenTreeDemoDao{internal.NewSysGenTreeDemoDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_log.go b/server/internal/dao/sys_log.go new file mode 100755 index 0000000..fd4f72a --- /dev/null +++ b/server/internal/dao/sys_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysLogDao is the data access object for the table hg_sys_log. +// You can define custom methods on it to extend its functionality as needed. +type sysLogDao struct { + *internal.SysLogDao +} + +var ( + // SysLog is a globally accessible object for table hg_sys_log operations. + SysLog = sysLogDao{internal.NewSysLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_login_log.go b/server/internal/dao/sys_login_log.go new file mode 100755 index 0000000..f158383 --- /dev/null +++ b/server/internal/dao/sys_login_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysLoginLogDao is the data access object for the table hg_sys_login_log. +// You can define custom methods on it to extend its functionality as needed. +type sysLoginLogDao struct { + *internal.SysLoginLogDao +} + +var ( + // SysLoginLog is a globally accessible object for table hg_sys_login_log operations. + SysLoginLog = sysLoginLogDao{internal.NewSysLoginLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_provinces.go b/server/internal/dao/sys_provinces.go new file mode 100755 index 0000000..7e35c53 --- /dev/null +++ b/server/internal/dao/sys_provinces.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysProvincesDao is the data access object for the table hg_sys_provinces. +// You can define custom methods on it to extend its functionality as needed. +type sysProvincesDao struct { + *internal.SysProvincesDao +} + +var ( + // SysProvinces is a globally accessible object for table hg_sys_provinces operations. + SysProvinces = sysProvincesDao{internal.NewSysProvincesDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_serve_license.go b/server/internal/dao/sys_serve_license.go new file mode 100755 index 0000000..6397152 --- /dev/null +++ b/server/internal/dao/sys_serve_license.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysServeLicenseDao is the data access object for the table hg_sys_serve_license. +// You can define custom methods on it to extend its functionality as needed. +type sysServeLicenseDao struct { + *internal.SysServeLicenseDao +} + +var ( + // SysServeLicense is a globally accessible object for table hg_sys_serve_license operations. + SysServeLicense = sysServeLicenseDao{internal.NewSysServeLicenseDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_serve_log.go b/server/internal/dao/sys_serve_log.go new file mode 100755 index 0000000..1fb569d --- /dev/null +++ b/server/internal/dao/sys_serve_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysServeLogDao is the data access object for the table hg_sys_serve_log. +// You can define custom methods on it to extend its functionality as needed. +type sysServeLogDao struct { + *internal.SysServeLogDao +} + +var ( + // SysServeLog is a globally accessible object for table hg_sys_serve_log operations. + SysServeLog = sysServeLogDao{internal.NewSysServeLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/sys_sms_log.go b/server/internal/dao/sys_sms_log.go new file mode 100755 index 0000000..aa2c686 --- /dev/null +++ b/server/internal/dao/sys_sms_log.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// sysSmsLogDao is the data access object for the table hg_sys_sms_log. +// You can define custom methods on it to extend its functionality as needed. +type sysSmsLogDao struct { + *internal.SysSmsLogDao +} + +var ( + // SysSmsLog is a globally accessible object for table hg_sys_sms_log operations. + SysSmsLog = sysSmsLogDao{internal.NewSysSmsLogDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/dao/test_category.go b/server/internal/dao/test_category.go new file mode 100755 index 0000000..d9e5ab7 --- /dev/null +++ b/server/internal/dao/test_category.go @@ -0,0 +1,22 @@ +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package dao + +import ( + "hotgo/internal/dao/internal" +) + +// testCategoryDao is the data access object for the table hg_test_category. +// You can define custom methods on it to extend its functionality as needed. +type testCategoryDao struct { + *internal.TestCategoryDao +} + +var ( + // TestCategory is a globally accessible object for table hg_test_category operations. + TestCategory = testCategoryDao{internal.NewTestCategoryDao()} +) + +// Add your custom methods and functionality below. diff --git a/server/internal/global/cluster.go b/server/internal/global/cluster.go new file mode 100644 index 0000000..6a12ca1 --- /dev/null +++ b/server/internal/global/cluster.go @@ -0,0 +1,51 @@ +// Package global +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package global + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "hotgo/internal/consts" + "hotgo/internal/library/hgrds/lock" + "hotgo/internal/library/hgrds/pubsub" + "hotgo/internal/service" + "hotgo/utility/simple" +) + +// SubscribeClusterSync 订阅集群同步,可以用来集中同步数据、状态等 +func SubscribeClusterSync(ctx context.Context) { + if !simple.IsCluster(ctx) { + return + } + + err := pubsub.SubscribeMap(map[string]pubsub.SubHandler{ + consts.ClusterSyncSysconfig: service.SysConfig().ClusterSync, // 系统配置 + consts.ClusterSyncSysBlacklist: service.SysBlacklist().ClusterSync, // 系统黑名单 + consts.ClusterSyncSysSuperAdmin: service.AdminMember().ClusterSyncSuperAdmin, // 超管 + }) + + if err != nil { + g.Log().Fatal(ctx, err) + } +} + +// PublishClusterSync 推送集群同步消息,如果没有开启集群部署,则不进行推送 +func PublishClusterSync(ctx context.Context, channel string, message interface{}) { + if !simple.IsCluster(ctx) { + return + } + + mutex := lock.Mutex(fmt.Sprintf("%s:%s", "lock", channel)) + err := mutex.LockFunc(ctx, func() { + if _, err := pubsub.Publish(ctx, channel, message); err != nil { + g.Log().Warningf(ctx, "PublishClusterSync %v err:%v", channel, err) + } + }) + if err != nil { + g.Log().Warningf(ctx, "PublishClusterSync %v LockFunc err:%v", channel, err) + } +} diff --git a/server/internal/global/global.go b/server/internal/global/global.go new file mode 100644 index 0000000..bc2b3e1 --- /dev/null +++ b/server/internal/global/global.go @@ -0,0 +1,8 @@ +// Package global +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package global + +// 在这里可以配置一些全局公用的变量 diff --git a/server/internal/global/httproutes.go b/server/internal/global/httproutes.go new file mode 100644 index 0000000..8a4e535 --- /dev/null +++ b/server/internal/global/httproutes.go @@ -0,0 +1,125 @@ +// Package global +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2024 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package global + +import ( + "reflect" + "strings" + "sync" + + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/util/gmeta" + "github.com/gogf/gf/v2/util/gtag" +) + +// HTTPRouter http路由 +type HTTPRouter struct { + ghttp.RouterItem + Tags string `json:"tags" dc:"接口所属的标签,用于接口分类"` + Summary string `json:"summary" dc:"接口/参数概要描述"` + Description string `json:"description" dc:"接口/参数详细描述"` +} + +var ( + httpRoutes map[string]*HTTPRouter + routeMutex sync.Mutex + shortTypeMapForTag = map[string]string{ + gtag.SummaryShort: gtag.Summary, + gtag.SummaryShort2: gtag.Summary, + gtag.DescriptionShort: gtag.Description, + gtag.DescriptionShort2: gtag.Description, + } +) + +// GetRequestRoute 获取当前请求路由属性 +func GetRequestRoute(r *ghttp.Request) *HTTPRouter { + key := GenFilterRequestKey(r) + routes := LoadHTTPRoutes(r) + router, ok := routes[key] + if !ok { + return nil + } + return router +} + +// GenFilterRequestKey 根据请求生成唯一key +func GenFilterRequestKey(r *ghttp.Request) string { + return GenRouteKey(r.Method, r.Request.URL.Path) +} + +// GenFilterRouteKey 根据路由生成唯一key +func GenFilterRouteKey(r *ghttp.Router) string { + return GenRouteKey(r.Method, r.Uri) +} + +// GenRouteKey 生成唯一key +func GenRouteKey(method, path string) string { + return strings.ToUpper(method) + " " + path +} + +func LoadHTTPRoutes(r *ghttp.Request) map[string]*HTTPRouter { + if httpRoutes == nil { + routeMutex.Lock() + defer routeMutex.Unlock() + + if httpRoutes != nil { + return httpRoutes + } + + httpRoutes = make(map[string]*HTTPRouter, len(r.Server.GetRoutes())) + for _, v := range r.Server.GetRoutes() { + key := GenFilterRouteKey(v.Handler.Router) + if _, ok := httpRoutes[key]; !ok { + router := new(HTTPRouter) + router.RouterItem = v + httpRoutes[key] = setRouterMeta(router) + } + } + } + return httpRoutes +} + +func setRouterMeta(router *HTTPRouter) *HTTPRouter { + if !router.RouterItem.Handler.Info.IsStrictRoute { + return router + } + + var reflectValue = reflect.ValueOf(router.Handler.Info.Value.Interface()) + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + + if reflectValue.Kind() != reflect.Func { + return router + } + + var reflectType = reflect.TypeOf(router.Handler.Info.Value.Interface()) + if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 { + return router + } + + var inputObject reflect.Value + if reflectType.In(1).Kind() == reflect.Ptr { + inputObject = reflect.New(reflectType.In(1).Elem()).Elem() + } else { + inputObject = reflect.New(reflectType.In(1)).Elem() + } + + inputMetaMap := fillMapWithShortTags(gmeta.Data(inputObject.Interface())) + router.Tags = inputMetaMap["tags"] + router.Summary = inputMetaMap[gtag.Summary] + router.Description = inputMetaMap[gtag.Description] + return router +} + +func fillMapWithShortTags(m map[string]string) map[string]string { + for k, v := range shortTypeMapForTag { + if m[v] == "" && m[k] != "" { + m[v] = m[k] + } + } + return m +} diff --git a/server/internal/global/init.go b/server/internal/global/init.go new file mode 100644 index 0000000..3666117 --- /dev/null +++ b/server/internal/global/init.go @@ -0,0 +1,168 @@ +// Package global +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package global + +import ( + "context" + "fmt" + "github.com/gogf/gf/contrib/trace/jaeger/v2" + "github.com/gogf/gf/v2" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gmode" + "hotgo/internal/consts" + "hotgo/internal/library/cache" + "hotgo/internal/library/queue" + "hotgo/internal/model/entity" + "hotgo/internal/service" + "hotgo/utility/charset" + "hotgo/utility/simple" + "hotgo/utility/validate" + "runtime" + "strings" +) + +func Init(ctx context.Context) { + // 设置gf运行模式 + SetGFMode(ctx) + + // 设置服务日志处理 + glog.SetDefaultHandler(LoggingServeLogHandler) + + // 默认上海时区 + if err := gtime.SetTimeZone("Asia/Shanghai"); err != nil { + g.Log().Fatalf(ctx, "时区设置异常 err:%+v", err) + return + } + + fmt.Printf("欢迎使用HotGo!\r\n当前运行环境:%v, 运行根路径为:%v \r\nHotGo版本:v%v, gf版本:%v \n", runtime.GOOS, gfile.Pwd(), consts.VersionApp, gf.VERSION) + + // 初始化链路追踪 + InitTrace(ctx) + + // 设置缓存适配器 + cache.SetAdapter(ctx) + + // 初始化功能库配置 + service.SysConfig().InitConfig(ctx) + + // 加载超管数据 + service.AdminMember().LoadSuperAdmin(ctx) + + // 订阅集群同步 + SubscribeClusterSync(ctx) +} + +// LoggingServeLogHandler 服务日志处理 +// 需要将异常日志保存到服务日志时可以通过SetHandlers设置此方法 +func LoggingServeLogHandler(ctx context.Context, in *glog.HandlerInput) { + in.Next(ctx) + + err := g.Try(ctx, func(ctx context.Context) { + var err error + defer func() { + if err != nil { + panic(err) + } + }() + + // web服务日志不做记录,因为会导致重复记录 + r := g.RequestFromCtx(ctx) + if r != nil && r.Server != nil && in.Logger.GetConfig().Path == r.Server.Logger().GetConfig().Path { + return + } + + conf, err := service.SysConfig().GetLoadServeLog(ctx) + if err != nil { + return + } + + if conf == nil { + return + } + + if !conf.Switch { + return + } + + if in.LevelFormat == "" || !gstr.InArray(conf.LevelFormat, in.LevelFormat) { + return + } + + if in.Stack == "" { + in.Stack = in.Logger.GetStack() + } + + if len(in.Content) == 0 { + in.Content = gstr.StrLimit(gvar.New(in.Values).String(), consts.MaxServeLogContentLen) + } + + var data entity.SysServeLog + data.TraceId = gctx.CtxId(ctx) + data.LevelFormat = in.LevelFormat + data.Content = in.Content + data.Stack = gjson.New(charset.ParseStack(in.Stack)) + data.Line = strings.TrimRight(in.CallerPath, ":") + data.TriggerNs = in.Time.UnixNano() + data.Status = consts.StatusEnabled + + if gstr.Contains(in.Content, `exception recovered`) { + data.LevelFormat = "PANI" + } + + if data.Stack.IsNil() { + data.Stack = gjson.New(consts.NilJsonToString) + } + + if conf.Queue { + err = queue.Push(consts.QueueServeLogTopic, data) + } else { + err = service.SysServeLog().RealWrite(ctx, data) + } + }) + + if err != nil { + g.Dump("LoggingServeLogHandler err:", err) + } +} + +// InitTrace 初始化链路追踪 +func InitTrace(ctx context.Context) { + if !g.Cfg().MustGet(ctx, "jaeger.switch").Bool() { + return + } + + tp, err := jaeger.Init(simple.AppName(ctx), g.Cfg().MustGet(ctx, "jaeger.endpoint").String()) + if err != nil { + g.Log().Fatal(ctx, err) + } + + simple.Event().Register(consts.EventServerClose, func(ctx context.Context, args ...interface{}) { + _ = tp.Shutdown(ctx) + g.Log().Debug(ctx, "jaeger closed ..") + }) +} + +// SetGFMode 设置gf运行模式 +func SetGFMode(ctx context.Context) { + mode := g.Cfg().MustGet(ctx, "system.mode").String() + if len(mode) == 0 { + mode = gmode.NOT_SET + } + + var modes = []string{gmode.DEVELOP, gmode.TESTING, gmode.STAGING, gmode.PRODUCT} + + // 如果是有效的运行模式,就进行设置 + if validate.InSlice(modes, mode) { + gmode.Set(mode) + } +} diff --git a/server/internal/library/addons/addons.go b/server/internal/library/addons/addons.go new file mode 100644 index 0000000..dd5f128 --- /dev/null +++ b/server/internal/library/addons/addons.go @@ -0,0 +1,61 @@ +// Package addons +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package addons + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "hotgo/internal/consts" +) + +var cacheResourcePath string + +// GetResourcePath 获取插件资源路径 +func GetResourcePath(ctx context.Context) string { + if len(cacheResourcePath) > 0 { + return cacheResourcePath + } + basePath := g.Cfg().MustGet(ctx, "system.addonsResourcePath").String() + if basePath == "" { + g.Log().Warning(ctx, "addons GetResourcePath not config found:'system.addonsResourcePath', use default values:'resource'") + basePath = "resource" + } + + cacheResourcePath = basePath + return basePath +} + +// GetModulePath 获取指定模块相对路径 +func GetModulePath(name string) string { + return "./" + consts.AddonsDir + "/" + name +} + +// ViewPath 默认的插件模板路径 +// 模板路径:resource/addons/插件模块名称/template +// 例如:resource/addons/hgexample/template +// 如果你不喜欢现在的风格,可以自行调整 +func ViewPath(name, basePath string) string { + return basePath + "/" + consts.AddonsDir + "/" + name + "/template" +} + +// StaticPath 默认的插件静态路映射关系 +// 静态资源路径:resource/public/addons/插件模块名称/public +// 例如访问:http://127.0.0.1:8000/addons/hgexample/default 则指向文件-> resource/addons/hgexample/public/default +// 如果你不喜欢现在的风格,可以自行调整 +func StaticPath(name, basePath string) (string, string) { + return "/" + consts.AddonsDir + "/" + name, basePath + "/" + consts.AddonsDir + "/" + name + "/public" +} + +// RouterPrefix 路由前缀 +// 最终效果:/应用名称/插件模块名称/xxx/xxx。 +// 如果你不喜欢现在的风格,可以自行调整 +func RouterPrefix(ctx context.Context, app, name string) string { + var prefix = "/" + if app != "" { + prefix = g.Cfg().MustGet(ctx, "router."+app+".prefix", "/"+app+"").String() + } + return prefix + "/" + name +} diff --git a/server/internal/library/addons/build.go b/server/internal/library/addons/build.go new file mode 100644 index 0000000..6fab466 --- /dev/null +++ b/server/internal/library/addons/build.go @@ -0,0 +1,138 @@ +// Package addons +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package addons + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + "hotgo/internal/consts" + "hotgo/internal/model" + "hotgo/utility/validate" + "strconv" + "strings" +) + +type BuildOption struct { + Skeleton Skeleton + Config *model.BuildAddonConfig + Extend []string `json:"extend" dc:"扩展功能"` +} + +// Build 构建新插件 +func Build(ctx context.Context, option *BuildOption) (err error) { + var ( + resourcePath = GetResourcePath(ctx) + buildPath = "./" + consts.AddonsDir + "/" + option.Skeleton.Name + modulesPath = "./" + consts.AddonsDir + "/modules/" + option.Skeleton.Name + ".go" + webApiPath = gstr.Replace(option.Config.WebApiPath, "{$name}", option.Skeleton.Name) + webViewsPath = gstr.Replace(option.Config.WebViewsPath, "{$name}", option.Skeleton.Name) + replaces = map[string]string{ + "@{.label}": option.Skeleton.Label, + "@{.name}": option.Skeleton.Name, + "@{.group}": strconv.Itoa(option.Skeleton.Group), + "@{.brief}": option.Skeleton.Brief, + "@{.description}": option.Skeleton.Description, + "@{.author}": option.Skeleton.Author, + "@{.version}": option.Skeleton.Version, + "@{.hgVersion}": consts.VersionApp, // HG 版本 + } + ) + + if resourcePath == "" { + err = gerror.New("请先设置一个有效的插件资源路径,配置名称:'system.addonsResourcePath'") + return + } + + if err = checkBuildDir(buildPath, modulesPath, webApiPath, webViewsPath); err != nil { + return + } + + // scans directory recursively + list, err := gfile.ScanDirFunc(option.Config.SrcPath, "*", true, func(path string) string { + return path + }) + + if err != nil { + return + } + + for _, path := range list { + if !gfile.IsReadable(path) { + err = gerror.Newf("file:%v is unreadable, please check permissions", path) + return + } + + if gfile.IsDir(path) { + continue + } + + flowFile := gstr.ReplaceByMap(path, map[string]string{ + gfile.RealPath(option.Config.SrcPath): "", + ".template": "", + }) + flowFile = buildPath + "/" + flowFile + + content := gstr.ReplaceByMap(gfile.GetContents(path), replaces) + + if err = gfile.PutContents(flowFile, content); err != nil { + break + } + } + + // 隐式注入插件 + if err = gfile.PutContents(modulesPath, gstr.ReplaceByMap(importModules, replaces)); err != nil { + return + } + + // webApi + if err = gfile.PutContents(webApiPath+"/config/index.ts", gstr.ReplaceByMap(webApiLayout, replaces)); err != nil { + return + } + + // web插件配置主页面 + if err = gfile.PutContents(webViewsPath+"/config/BasicSetting.vue", gstr.ReplaceByMap(webConfigBasicSetting, replaces)); err != nil { + return + } + + // web插件基础配置页面 + if err = gfile.PutContents(webViewsPath+"/config/system.vue", gstr.ReplaceByMap(webConfigSystem, replaces)); err != nil { + return + } + + // 创建静态目录 + if validate.InSlice(option.Extend, consts.AddonsExtendResourcePublic) { + _, staticPath := StaticPath(option.Skeleton.Name, resourcePath) + content := fmt.Sprintf(resourcePublicDefaultFile, option.Skeleton.Label) + if err = gfile.PutContents(staticPath+"/default", content); err != nil { + return + } + } + + // 创建模板目录 + if validate.InSlice(option.Extend, consts.AddonsExtendResourceTemplate) { + viewPath := ViewPath(option.Skeleton.Name, resourcePath) + if err = gfile.PutContents(viewPath+"/home/index.html", resourceTemplateHomeFile); err != nil { + return + } + } + return +} + +func checkBuildDir(paths ...string) (err error) { + if len(paths) == 0 { + return + } + + for _, path := range paths { + if gfile.Exists(path) { + return gerror.Newf("插件已存在,请换一个插件名称或者经确认无误后依次删除文件夹: [%v] 后重新生成", strings.Join(paths, "、\t")) + } + } + return +} diff --git a/server/internal/library/addons/build_layout.go b/server/internal/library/addons/build_layout.go new file mode 100644 index 0000000..dd85e96 --- /dev/null +++ b/server/internal/library/addons/build_layout.go @@ -0,0 +1,232 @@ +// Package addons +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package addons + +const ( + importModules = `// Package modules +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package modules + +import _ "hotgo/addons/@{.name}" +` + + webApiLayout = `import { http } from '@/utils/http/axios'; + +export function getConfig(params) { + return http.request({ + url: '/@{.name}/config/get', + method: 'get', + params, + }); +} + +export function updateConfig(params) { + return http.request({ + url: '/@{.name}/config/update', + method: 'post', + params, + }); +} +` + + webConfigBasicSetting = ` + + +` + + webConfigSystem = ` + + +` + + resourcePublicDefaultFile = "Hello!这是创建插件 [%v] 时默认生成的一个静态目录文件,用于测试,当你看到这个提示时,说明已经联调成功啦!" + + resourceTemplateHomeFile = ` + + + + + + + + @{.Title} + + + + +
+

Hello,@{.Data.name}!!

+

@{.Data.module}

+

服务器时间:@{.Data.time}

+
+ + + +` +) diff --git a/server/internal/library/addons/install.go b/server/internal/library/addons/install.go new file mode 100644 index 0000000..976aadd --- /dev/null +++ b/server/internal/library/addons/install.go @@ -0,0 +1,120 @@ +// Package addons +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package addons + +import ( + "context" + "hotgo/internal/consts" + "hotgo/internal/dao" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +// InstallRecord 安装记录 +type InstallRecord struct { + Id int64 `json:"id" description:"安装ID"` + Version string `json:"version" description:"安装版本"` + Status int `json:"status" description:"安装状态"` + CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"` + UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"` +} + +func GetModel(ctx context.Context) *gdb.Model { + return dao.SysAddonsInstall.Ctx(ctx) +} + +func ScanInstall(m Module) (record *InstallRecord, err error) { + err = GetModel(m.Ctx()).Where(dao.SysAddonsInstall.Columns().Name, m.GetSkeleton().Name).Scan(&record) + return +} + +// IsInstall 模块是否已安装 +func IsInstall(m Module) bool { + record, err := ScanInstall(m) + if err != nil { + g.Log().Debugf(m.Ctx(), "addons.IsInstall err:%+v", err) + return false + } + if record == nil { + return false + } + return record.Status == consts.AddonsInstallStatusOk +} + +// Install 安装模块 +func Install(m Module) (err error) { + record, err := ScanInstall(m) + if err != nil { + return + } + if record != nil && record.Status == consts.AddonsInstallStatusOk { + return gerror.New("插件已安装,无需重复操作!") + } + + data := g.Map{ + dao.SysAddonsInstall.Columns().Name: m.GetSkeleton().Name, + dao.SysAddonsInstall.Columns().Version: m.GetSkeleton().Version, + dao.SysAddonsInstall.Columns().Status: consts.AddonsInstallStatusOk, + } + return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error { + if record != nil { + _, _ = GetModel(ctx).Where(dao.SysAddonsInstall.Columns().Id, record.Id).Delete() + } + + if _, err = GetModel(ctx).Data(data).OmitEmptyData().Insert(); err != nil { + return err + } + return m.Install(ctx) + }) +} + +// Upgrade 更新模块 +func Upgrade(m Module) (err error) { + record, err := ScanInstall(m) + if err != nil { + return + } + + if record == nil || record.Status != consts.AddonsInstallStatusOk { + return gerror.New("插件未安装,请安装后操作!") + } + + data := g.Map{ + dao.SysAddonsInstall.Columns().Version: m.GetSkeleton().Version, + } + return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error { + if _, err = GetModel(ctx).Where(dao.SysAddonsInstall.Columns().Id, record.Id).Data(data).Update(); err != nil { + return err + } + return m.Upgrade(ctx) + }) +} + +// UnInstall 卸载模块 +func UnInstall(m Module) (err error) { + record, err := ScanInstall(m) + if err != nil { + return + } + + if record == nil || record.Status != consts.AddonsInstallStatusOk { + return gerror.New("插件未安装,请安装后操作!") + } + + data := g.Map{ + dao.SysAddonsInstall.Columns().Version: m.GetSkeleton().Version, + dao.SysAddonsInstall.Columns().Status: consts.AddonsInstallStatusUn, + } + return g.DB().Transaction(m.Ctx(), func(ctx context.Context, tx gdb.TX) error { + if _, err = GetModel(ctx).Where(dao.SysAddonsInstall.Columns().Id, record.Id).Data(data).Update(); err != nil { + return err + } + return m.UnInstall(ctx) + }) +} diff --git a/server/internal/library/addons/module.go b/server/internal/library/addons/module.go new file mode 100644 index 0000000..092887f --- /dev/null +++ b/server/internal/library/addons/module.go @@ -0,0 +1,186 @@ +// Package addons +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package addons + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" + "hotgo/internal/model/input/form" + "sort" + "sync" + "time" +) + +// Option 模块启动选项 +type Option struct { + Server *ghttp.Server // http服务器 + // 更多选项参数 + // .. +} + +// Skeleton 模块骨架 +type Skeleton struct { + Label string `json:"label"` // 标识 + Name string `json:"name"` // 名称 + Group int `json:"group"` // 分组 + Logo string `json:"logo"` // logo + Brief string `json:"brief"` // 简介 + Description string `json:"description"` // 详细描述 + Author string `json:"author"` // 作者 + Version string `json:"version"` // 版本号 + RootPath string `json:"rootPath"` // 根路径 +} + +func (s *Skeleton) GetModule() Module { + return GetModule(s.Name) +} + +// Module 插件模块 +type Module interface { + Start(option *Option) (err error) // 启动模块 + Stop() (err error) // 停止模块 + Ctx() context.Context // 上下文 + GetSkeleton() *Skeleton // 获取模块 + Install(ctx context.Context) (err error) // 安装模块 + Upgrade(ctx context.Context) (err error) // 更新模块 + UnInstall(ctx context.Context) (err error) // 卸载模块 +} + +var ( + modules = make(map[string]Module) + mLock sync.Mutex +) + +// StartModules 启动所有已安装模块 +func StartModules(ctx context.Context, option *Option) (err error) { + for _, module := range filterInstalled() { + if err = module.Start(option); err != nil { + return + } + } + + // 为所有已安装模块设置静态资源路径 + AddStaticPath(ctx, option.Server) + return +} + +// StopModules 停止所有已安装模块 +func StopModules(ctx context.Context) { + for _, module := range filterInstalled() { + if err := module.Stop(); err != nil { + g.Log().Warningf(ctx, "StopModules err:%v, module:%v", err.Error(), module.GetSkeleton().Name) + time.Sleep(time.Second) + } + } + return +} + +// RegisterModule 注册模块 +func RegisterModule(m Module) Module { + mLock.Lock() + defer mLock.Unlock() + name := m.GetSkeleton().Name + _, ok := modules[name] + if ok { + panic("module repeat registration, name:" + name) + } + + sk := m.GetSkeleton() + if sk == nil { + panic("module skeleton not initialized, name:" + name) + } + + sk.RootPath = GetModulePath(name) + modules[name] = m + return m +} + +// GetModule 获取指定名称模块 +func GetModule(name string) Module { + mLock.Lock() + defer mLock.Unlock() + m, ok := modules[name] + if !ok { + panic("implement not found for interface " + name + ", forgot register?") + } + return m +} + +// GetSkeletons 获取所有模块骨架 +func GetSkeletons() (list []*Skeleton) { + var keys []string + for k := range modules { + keys = append(keys, k) + } + + sort.Strings(keys) + + for _, v := range keys { + list = append(list, GetModule(v).GetSkeleton()) + } + return list +} + +// GetModuleRealPath 获取指定模块绝对路径 +func GetModuleRealPath(name string) string { + path := gfile.RealPath(GetModulePath(name)) + if path == "" { + panic("no path is found. please confirm that the path " + GetModulePath(name) + " exists?") + } + return path +} + +// AddStaticPath 设置插件静态目录映射 +func AddStaticPath(ctx context.Context, server *ghttp.Server) { + basePath := GetResourcePath(ctx) + if basePath == "" { + return + } + + for _, module := range filterInstalled() { + name := module.GetSkeleton().Name + prefix, path := StaticPath(name, basePath) + if !gres.Contains(path) { + if _, err := gfile.Search(path); err != nil { + g.Log().Warningf(ctx, `addons AddStaticPath failed: %v`, err) + continue + } + } + server.AddStaticPath(prefix, path) + } +} + +// filterInstalled 过滤已安装模块 +func filterInstalled() []Module { + var ms []Module + for _, module := range modules { + if IsInstall(module) { + ms = append(ms, module) + } + } + return ms +} + +// ModuleSelect 获取插件模块选项 +func ModuleSelect() form.Selects { + sks := GetSkeletons() + lst := make(form.Selects, 0) + if len(sks) == 0 { + return lst + } + + for _, skeleton := range sks { + lst = append(lst, &form.Select{ + Value: skeleton.Name, + Label: skeleton.Label, + Name: skeleton.Label, + }) + } + return lst +} diff --git a/server/internal/library/cache/cache.go b/server/internal/library/cache/cache.go new file mode 100644 index 0000000..d131061 --- /dev/null +++ b/server/internal/library/cache/cache.go @@ -0,0 +1,58 @@ +// Package cache +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package cache + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gfile" + "hotgo/internal/library/cache/file" +) + +// cache 缓存驱动 +var cache *gcache.Cache + +// Instance 缓存实例 +func Instance() *gcache.Cache { + if cache == nil { + panic("cache uninitialized.") + } + return cache +} + +// SetAdapter 设置缓存适配器 +func SetAdapter(ctx context.Context) { + var adapter gcache.Adapter + + switch g.Cfg().MustGet(ctx, "cache.adapter").String() { + case "redis": + adapter = gcache.NewAdapterRedis(g.Redis()) + case "file": + fileDir := g.Cfg().MustGet(ctx, "cache.fileDir").String() + if fileDir == "" { + g.Log().Fatal(ctx, "file path must be configured for file caching.") + return + } + + if !gfile.Exists(fileDir) { + if err := gfile.Mkdir(fileDir); err != nil { + g.Log().Fatalf(ctx, "failed to create the cache directory. procedure, err:%+v", err) + return + } + } + adapter = file.NewAdapterFile(fileDir) + default: + adapter = gcache.NewAdapterMemory() + } + + // 数据库缓存,默认和通用缓冲驱动一致,如果你不想使用默认的,可以自行调整 + g.DB().GetCache().SetAdapter(adapter) + + // 通用缓存 + cache = gcache.New() + cache.SetAdapter(adapter) +} diff --git a/server/internal/library/cache/file/file.go b/server/internal/library/cache/file/file.go new file mode 100644 index 0000000..26138ff --- /dev/null +++ b/server/internal/library/cache/file/file.go @@ -0,0 +1,322 @@ +// Package file +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package file + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/util/gconv" + "os" + "path/filepath" + "time" +) + +type ( + // AdapterFile is the gcache adapter implements using file server. + AdapterFile struct { + dir string + } + + fileContent struct { + Duration int64 `json:"duration"` + Data interface{} `json:"data,omitempty"` + } +) + +const perm = 0o666 + +var ( + CacheExpiredErr = errors.New("cache expired") +) + +// NewAdapterFile creates and returns a new memory cache object. +func NewAdapterFile(dir string) gcache.Adapter { + return &AdapterFile{ + dir: dir, + } +} + +func (c *AdapterFile) Set(ctx context.Context, key interface{}, value interface{}, lifeTime time.Duration) (err error) { + fileKey := gconv.String(key) + if value == nil || lifeTime < 0 { + return c.Delete(fileKey) + } + return c.Save(fileKey, gconv.String(value), lifeTime) +} + +func (c *AdapterFile) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) (err error) { + return gerror.New("implement me") +} + +func (c *AdapterFile) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) { + return false, gerror.New("implement me") +} + +func (c *AdapterFile) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) { + return false, gerror.New("implement me") +} + +func (c *AdapterFile) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) { + return false, gerror.New("implement me") +} + +func (c *AdapterFile) Get(ctx context.Context, key interface{}) (*gvar.Var, error) { + fetch, err := c.Fetch(gconv.String(key)) + if err != nil { + return nil, err + } + return gvar.New(fetch), nil +} + +func (c *AdapterFile) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) { + result, err = c.Get(ctx, key) + if err != nil && !errors.Is(err, CacheExpiredErr) { + return nil, err + } + if result.IsNil() { + return gvar.New(value), c.Set(ctx, key, value, duration) + } + return +} + +func (c *AdapterFile) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) { + v, err := c.Get(ctx, key) + if err != nil && !errors.Is(err, CacheExpiredErr) { + return nil, err + } + if v.IsNil() { + value, err := f(ctx) + if err != nil { + return nil, err + } + if value == nil { + return nil, nil + } + return gvar.New(value), c.Set(ctx, key, value, duration) + } else { + return v, nil + } +} + +func (c *AdapterFile) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) { + return c.GetOrSetFunc(ctx, key, f, duration) +} + +func (c *AdapterFile) Contains(ctx context.Context, key interface{}) (bool, error) { + return c.Has(gconv.String(key)), nil +} + +func (c *AdapterFile) Size(ctx context.Context) (size int, err error) { + return 0, nil +} + +func (c *AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{}, err error) { + return nil, gerror.New("implement me") +} + +func (c *AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) { + return nil, gerror.New("implement me") +} + +func (c *AdapterFile) Values(ctx context.Context) (values []interface{}, err error) { + return nil, gerror.New("implement me") +} + +func (c *AdapterFile) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) { + return nil, false, gerror.New("implement me") +} + +func (c *AdapterFile) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) { + var ( + v *gvar.Var + oldTTL int64 + fileKey = gconv.String(key) + ) + // TTL. + expire, err := c.GetExpire(ctx, fileKey) + if err != nil { + return + } + oldTTL = int64(expire) + if oldTTL == -2 { + // It does not exist. + return + } + oldDuration = time.Duration(oldTTL) * time.Second + // DEL. + if duration < 0 { + err = c.Delete(fileKey) + return + } + v, err = c.Get(ctx, fileKey) + if err != nil { + return + } + err = c.Set(ctx, fileKey, v.Val(), duration) + return +} + +func (c *AdapterFile) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) { + content, err := c.read(gconv.String(key)) + if err != nil { + return -1, nil + } + + if content.Duration <= time.Now().Unix() { + return -1, nil + } + return time.Duration(time.Now().Unix()-content.Duration) * time.Second, nil +} + +func (c *AdapterFile) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) { + if len(keys) == 0 { + return nil, nil + } + // Retrieves the last key value. + if lastValue, err = c.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil { + return nil, err + } + // Deletes all given keys. + err = c.DeleteMulti(gconv.Strings(keys)...) + return +} + +func (c *AdapterFile) Clear(ctx context.Context) error { + return c.Flush() +} + +func (c *AdapterFile) Close(ctx context.Context) error { + return nil +} + +func (c *AdapterFile) createName(key string) string { + h := sha256.New() + _, _ = h.Write([]byte(key)) + hash := hex.EncodeToString(h.Sum(nil)) + return filepath.Join(c.dir, fmt.Sprintf("%s.cache", hash)) +} + +func (c *AdapterFile) read(key string) (*fileContent, error) { + rp := gfile.RealPath(c.createName(key)) + if rp == "" { + return nil, nil + } + + value, err := os.ReadFile(rp) + if err != nil { + return nil, err + } + + content := &fileContent{} + if err := json.Unmarshal(value, content); err != nil { + return nil, err + } + + if content.Duration == 0 { + return content, nil + } + + if content.Duration <= time.Now().Unix() { + _ = c.Delete(key) + return nil, CacheExpiredErr + } + return content, nil +} + +// Has checks if the cached key exists into the File storage +func (c *AdapterFile) Has(key string) bool { + fc, err := c.read(key) + return err == nil && fc != nil +} + +// Delete the cached key from File storage +func (c *AdapterFile) Delete(key string) error { + _, err := os.Stat(c.createName(key)) + if err != nil && os.IsNotExist(err) { + return nil + } + return os.Remove(c.createName(key)) +} + +// DeleteMulti the cached key from File storage +func (c *AdapterFile) DeleteMulti(keys ...string) (err error) { + for _, key := range keys { + if err = c.Delete(key); err != nil { + return + } + } + return +} + +// Fetch retrieves the cached value from key of the File storage +func (c *AdapterFile) Fetch(key string) (interface{}, error) { + content, err := c.read(key) + if err != nil { + return nil, err + } + + if content == nil { + return nil, nil + } + return content.Data, nil +} + +// FetchMulti retrieve multiple cached values from keys of the File storage +func (c *AdapterFile) FetchMulti(keys []string) map[string]interface{} { + result := make(map[string]interface{}) + for _, key := range keys { + if value, err := c.Fetch(key); err == nil { + result[key] = value + } + } + return result +} + +// Flush removes all cached keys of the File storage +func (c *AdapterFile) Flush() error { + dir, err := os.Open(c.dir) + if err != nil { + return err + } + + defer func() { + _ = dir.Close() + }() + + names, _ := dir.Readdirnames(-1) + + for _, name := range names { + _ = os.Remove(filepath.Join(c.dir, name)) + } + return nil +} + +// Save a value in File storage by key +func (c *AdapterFile) Save(key string, value string, lifeTime time.Duration) error { + duration := int64(0) + + if lifeTime > 0 { + duration = time.Now().Unix() + int64(lifeTime.Seconds()) + } + + content := &fileContent{duration, value} + + data, err := json.Marshal(content) + if err != nil { + return err + } + + err = os.WriteFile(c.createName(key), data, perm) + return err +} diff --git a/server/internal/library/captcha/captcha.go b/server/internal/library/captcha/captcha.go new file mode 100644 index 0000000..6cdca8c --- /dev/null +++ b/server/internal/library/captcha/captcha.go @@ -0,0 +1,77 @@ +// Package captcha +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package captcha + +import ( + "context" + "hotgo/internal/consts" + "image/color" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gstr" + "github.com/mojocn/base64Captcha" +) + +// store 验证码存储方式 +var store = base64Captcha.DefaultMemStore + +// Generate 生成验证码 +func Generate(ctx context.Context, captchaType int) (id string, base64 string) { + var err error + + switch captchaType { + // 算数 + case consts.CaptchaTypeMath: + driver := &base64Captcha.DriverMath{ + Height: 42, + Width: 100, + NoiseCount: 0, + ShowLineOptions: 0, + BgColor: &color.RGBA{ + R: 255, + G: 250, + B: 250, + A: 250, + }, + Fonts: []string{"chromohv.ttf"}, + } + c := base64Captcha.NewCaptcha(driver.ConvertFonts(), store) + id, base64, _, err = c.Generate() + // 字符 + default: + driver := &base64Captcha.DriverString{ + Height: 42, + Width: 100, + //NoiseCount: 50, + //ShowLineOptions: 20, + Length: 4, + BgColor: &color.RGBA{ + R: 255, + G: 250, + B: 250, + A: 250, + }, + Source: "abcdefghjkmnpqrstuvwxyz23456789", // abcdefghjkmnpqrstuvwxyz23456789 + Fonts: []string{"chromohv.ttf"}, + } + c := base64Captcha.NewCaptcha(driver.ConvertFonts(), store) + id, base64, _, err = c.Generate() + } + + if err != nil { + g.Log().Errorf(ctx, "captcha.Generate err:%+v", err) + } + + return +} + +// Verify 验证输入的验证码是否正确 +func Verify(id, answer string) bool { + if id == "" || answer == "" { + return false + } + return store.Verify(id, gstr.ToLower(answer), true) +} diff --git a/server/internal/library/casbin/adapter.go b/server/internal/library/casbin/adapter.go new file mode 100644 index 0000000..7264fa4 --- /dev/null +++ b/server/internal/library/casbin/adapter.go @@ -0,0 +1,333 @@ +// Package casbin +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package casbin + +import ( + "context" + "errors" + "fmt" + "hotgo/internal/consts" + "hotgo/internal/dao" + "math" + "strings" + + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + "github.com/gogf/gf/v2/database/gdb" +) + +var defaultTableName = dao.AdminRoleCasbin.Table() + +const ( + dropPolicyTableSql = `DROP TABLE IF EXISTS %s` + createPolicyTableSql = ` +CREATE TABLE IF NOT EXISTS %s ( + id bigint(20) NOT NULL AUTO_INCREMENT, + p_type varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + v0 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + v1 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + v2 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + v3 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + v4 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + v5 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (id) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '管理员_casbin权限表' ROW_FORMAT = Dynamic; +` + createPolicyTablePgSql = `CREATE TABLE IF NOT EXISTS "public"."%s" ( + "id" int8 NOT NULL DEFAULT nextval('hg_admin_role_casbin_id_seq'::regclass), + "p_type" varchar(64) COLLATE "pg_catalog"."default", + "v0" varchar(256) COLLATE "pg_catalog"."default", + "v1" varchar(256) COLLATE "pg_catalog"."default", + "v2" varchar(256) COLLATE "pg_catalog"."default", + "v3" varchar(256) COLLATE "pg_catalog"."default", + "v4" varchar(256) COLLATE "pg_catalog"."default", + "v5" varchar(256) COLLATE "pg_catalog"."default", + CONSTRAINT "hg_admin_role_casbin_pkey" PRIMARY KEY ("id") +) +; + +ALTER TABLE "public"."%s" + OWNER TO "postgres"; + +COMMENT ON TABLE "public"."%s" IS '管理员_casbin权限表';` +) + +type ( + adapter struct { + db gdb.DB + table string + } + + policyColumns struct { + ID string // ID + PType string // PType + V0 string // V0 + V1 string // V1 + V2 string // V2 + V3 string // V3 + V4 string // V4 + V5 string // V5 + } + + // policy rule entity + policyRule struct { + ID int64 `orm:"id" json:"id"` + PType string `orm:"p_type" json:"p_type"` + V0 string `orm:"v0" json:"v0"` + V1 string `orm:"v1" json:"v1"` + V2 string `orm:"v2" json:"v2"` + V3 string `orm:"v3" json:"v3"` + V4 string `orm:"v4" json:"v4"` + V5 string `orm:"v5" json:"v5"` + } +) + +var ( + errInvalidDatabaseLink = errors.New("invalid database link") + policyColumnsName = policyColumns{ + ID: "id", + PType: "p_type", + V0: "v0", + V1: "v1", + V2: "v2", + V3: "v3", + V4: "v4", + V5: "v5", + } +) + +// NewAdapter Create a casbin adapter +func NewAdapter(link string) (adp *adapter, err error) { + adp = &adapter{table: defaultTableName} + config := strings.SplitN(link, ":", 2) + + if len(config) != 2 { + err = errInvalidDatabaseLink + return + } + + if adp.db, err = gdb.New(gdb.ConfigNode{Type: config[0], Link: config[1]}); err != nil { + return + } + + err = adp.createPolicyTable() + + return +} + +func (a *adapter) model() *gdb.Model { + return a.db.Model(a.table).Safe().Ctx(context.TODO()) +} + +// create a policy table when it's not exists. +func (a *adapter) createPolicyTable() (err error) { + if a.db.GetConfig().Type == consts.DBPgsql { + _, err = a.db.Exec(context.TODO(), fmt.Sprintf(createPolicyTablePgSql, a.table, a.table, a.table)) + return + } + _, err = a.db.Exec(context.TODO(), fmt.Sprintf(createPolicyTableSql, a.table)) + return +} + +// drop policy table from the storage. +func (a *adapter) dropPolicyTable() (err error) { + _, err = a.db.Exec(context.TODO(), fmt.Sprintf(dropPolicyTableSql, a.table)) + return +} + +// LoadPolicy loads all policy rules from the storage. +func (a *adapter) LoadPolicy(model model.Model) (err error) { + var rules []policyRule + if err = a.model().Scan(&rules); err != nil { + return + } + + for _, rule := range rules { + a.loadPolicyRule(rule, model) + } + return +} + +// SavePolicy Saves all policy rules to the storage. +func (a *adapter) SavePolicy(model model.Model) (err error) { + if err = a.dropPolicyTable(); err != nil { + return + } + + if err = a.createPolicyTable(); err != nil { + return + } + + policyRules := make([]policyRule, 0) + + for ptype, ast := range model["p"] { + for _, rule := range ast.Policy { + policyRules = append(policyRules, a.buildPolicyRule(ptype, rule)) + } + } + + for ptype, ast := range model["g"] { + for _, rule := range ast.Policy { + policyRules = append(policyRules, a.buildPolicyRule(ptype, rule)) + } + } + + if count := len(policyRules); count > 0 { + if _, err = a.model().OmitEmptyData().FieldsEx(policyColumnsName.ID).Insert(policyRules); err != nil { + return + } + } + return +} + +// AddPolicy adds a policy rule to the storage. +func (a *adapter) AddPolicy(sec string, ptype string, rule []string) (err error) { + _, err = a.model().OmitEmptyData().FieldsEx(policyColumnsName.ID).Insert(a.buildPolicyRule(ptype, rule)) + return +} + +// AddPolicies adds policy rules to the storage. +func (a *adapter) AddPolicies(sec string, ptype string, rules [][]string) (err error) { + if len(rules) == 0 { + return + } + + policyRules := make([]policyRule, 0, len(rules)) + + for _, rule := range rules { + policyRules = append(policyRules, a.buildPolicyRule(ptype, rule)) + } + + _, err = a.model().OmitEmptyData().FieldsEx(policyColumnsName.ID).Insert(policyRules) + return +} + +// RemovePolicy removes a policy rule from the storage. +func (a *adapter) RemovePolicy(sec string, ptype string, rule []string) (err error) { + db := a.model() + db = db.Where(policyColumnsName.PType, ptype) + for index := 0; index < len(rule); index++ { + db = db.Where(fmt.Sprintf("v%d", index), rule[index]) + } + _, err = db.Delete() + return err +} + +// RemoveFilteredPolicy removes policy rules that match the filter from the storage. +func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (err error) { + db := a.model() + db = db.Where(policyColumnsName.PType, ptype) + for index := 0; index <= 5; index++ { + if fieldIndex <= index && index < fieldIndex+len(fieldValues) { + db = db.Where(fmt.Sprintf("v%d", index), fieldValues[index-fieldIndex]) + } + } + _, err = db.Delete() + return +} + +// RemovePolicies removes policy rules from the storage (implements the persist.BatchAdapter interface). +func (a *adapter) RemovePolicies(sec string, ptype string, rules [][]string) (err error) { + db := a.model() + + for _, rule := range rules { + where := map[string]interface{}{policyColumnsName.PType: ptype} + + for i := 0; i <= 5; i++ { + if len(rule) > i { + where[fmt.Sprintf("v%d", i)] = rule[i] + } + } + + db = db.WhereOr(where) + } + + _, err = db.Delete() + return +} + +// UpdatePolicy updates a policy rule from storage. +func (a *adapter) UpdatePolicy(sec string, ptype string, oldRule, newRule []string) (err error) { + _, err = a.model().Update(a.buildPolicyRule(ptype, newRule), a.buildPolicyRule(ptype, oldRule)) + return +} + +// UpdatePolicies updates some policy rules to storage, like db, redis. +func (a *adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (err error) { + if len(oldRules) == 0 || len(newRules) == 0 { + return + } + return a.db.Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error { + for i := 0; i < int(math.Min(float64(len(oldRules)), float64(len(newRules)))); i++ { + if _, err = tx.Model(a.table).Update(a.buildPolicyRule(ptype, newRules[i]), a.buildPolicyRule(ptype, oldRules[i])); err != nil { + return err + } + } + return nil + }) +} + +// 加载策略规则 +func (a *adapter) loadPolicyRule(rule policyRule, model model.Model) { + ruleText := rule.PType + + if rule.V0 != "" { + ruleText += ", " + rule.V0 + } + + if rule.V1 != "" { + ruleText += ", " + rule.V1 + } + + if rule.V2 != "" { + ruleText += ", " + rule.V2 + } + + if rule.V3 != "" { + ruleText += ", " + rule.V3 + } + + if rule.V4 != "" { + ruleText += ", " + rule.V4 + } + + if rule.V5 != "" { + ruleText += ", " + rule.V5 + } + if err := persist.LoadPolicyLine(ruleText, model); err != nil { + panic(err) + } +} + +// 构建策略规则 +func (a *adapter) buildPolicyRule(ptype string, data []string) policyRule { + rule := policyRule{PType: ptype} + + if len(data) > 0 { + rule.V0 = data[0] + } + + if len(data) > 1 { + rule.V1 = data[1] + } + + if len(data) > 2 { + rule.V2 = data[2] + } + + if len(data) > 3 { + rule.V3 = data[3] + } + + if len(data) > 4 { + rule.V4 = data[4] + } + + if len(data) > 5 { + rule.V5 = data[5] + } + return rule +} diff --git a/server/internal/library/casbin/casbin_test.go b/server/internal/library/casbin/casbin_test.go new file mode 100644 index 0000000..6dafe7a --- /dev/null +++ b/server/internal/library/casbin/casbin_test.go @@ -0,0 +1,67 @@ +// Package casbin +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package casbin + +import ( + "context" + "net/http" + "testing" +) + +// TestNew description +func TestNew(t *testing.T) { + + InitEnforcer(context.TODO()) + + user := "admin" + path := "/" + method := http.MethodGet + t.Logf("\nuser:%v\npath:%v\nmethod:%v", user, path, method) + + ok, err := Enforcer.DeletePermissionsForUser(user) + if err != nil { + t.Error(err) + } + t.Logf("delete user premission:%v", ok) + CheckPremission(t, user, path, method) + AddPremission(t, user, "*", ActionAll) + CheckPremission(t, user, path, method) + + user1 := "user1" + path1 := "/api/v1/*" + checkPathTrue := "/api/v1/user/list" + checkPathFalse := "/api/v2/user/list" + AddPremission(t, user1, path1, ActionAll) + CheckPremission(t, user1, checkPathTrue, ActionPost) + CheckPremission(t, user1, checkPathFalse, http.MethodGet) + CheckPremission(t, user1, checkPathTrue, http.MethodGet) + CheckPremission(t, user1, "/api/v1/user/list2", http.MethodGet) + + ok, err = Enforcer.DeletePermissionsForUser(user1) + if err != nil { + t.Error(err) + } + t.Logf("delete user premission:%v", ok) + CheckPremission(t, user1, "/api/v1/user1/list", http.MethodGet) +} + +// CheckPremission description +func CheckPremission(t *testing.T, user string, path string, method string) { + ok, err := Enforcer.Enforce(user, path, method) + if err != nil { + t.Error(err) + } + t.Logf("check \tuser[%s] \tpremission[%s] \tpath[%s] \tallow[%v]", user, method, path, ok) +} + +// Add description +func AddPremission(t *testing.T, user string, path string, method string) { + ok, err := Enforcer.AddPolicy(user, path, method) + if err != nil { + t.Error(err) + } + t.Logf("add \tuser[%s] \tpremission[%s] \tpath[%s] \tresult[%v]", user, method, path, ok) +} diff --git a/server/internal/library/casbin/enforcer.go b/server/internal/library/casbin/enforcer.go new file mode 100644 index 0000000..3676d11 --- /dev/null +++ b/server/internal/library/casbin/enforcer.go @@ -0,0 +1,164 @@ +// Package casbin +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package casbin + +import ( + "context" + "fmt" + "hotgo/internal/consts" + "hotgo/internal/dao" + "net/http" + "strings" + + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" + "github.com/gogf/gf/v2/text/gstr" +) + +const ( + ActionGet = http.MethodGet + ActionPost = http.MethodPost + ActionPut = http.MethodPut + ActionDelete = http.MethodDelete + ActionAll = "GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD" +) + +var Enforcer *casbin.Enforcer + +// InitEnforcer 初始化 +func InitEnforcer(ctx context.Context) { + var ( + link = getDbLink(ctx) + a, err = NewAdapter(link.String()) + ) + + if err != nil { + g.Log().Panicf(ctx, "casbin.NewAdapter err . %v", err) + return + } + + path := "manifest/config/casbin.conf" + + // 优先从本地加载casbin.conf,如果不存在就从资源文件中找 + modelContent := gfile.GetContents(path) + if len(modelContent) == 0 { + if !gres.IsEmpty() && gres.Contains(path) { + modelContent = string(gres.GetContent(path)) + } + } + + if len(modelContent) == 0 { + g.Log().Panicf(ctx, "casbin model file does not exist:%v", path) + } + + m, err := model.NewModelFromString(modelContent) + if err != nil { + g.Log().Panicf(ctx, "casbin NewModelFromString err:%v", err) + } + + Enforcer, err = casbin.NewEnforcer(m, a) + if err != nil { + g.Log().Panicf(ctx, "casbin NewEnforcer err:%v", err) + } + loadPermissions(ctx) +} + +// GetDbLink 获取数据库链接 +func getDbLink(ctx context.Context) *gvar.Var { + + link := g.Cfg().MustGet(ctx, "database.default") + //读写分离 + if !link.IsSlice() { + return g.Cfg().MustGet(ctx, "database.default.link") + } + + for _, v := range link.Array() { + // 只获取主库 + val := v.(map[string]interface{}) + if val["role"] == "master" { + return gvar.New(val["link"]) + } + } + return gvar.New("database.default.0.link") + +} + +func loadPermissions(ctx context.Context) { + type Policy struct { + Key string `json:"key"` + Permissions string `json:"permissions"` + } + var ( + rules [][]string + polices []*Policy + err error + ) + //别名拼接 r.key m.permissions + q := func(alias string, column string) string { + return fmt.Sprintf("%s.%s", alias, column) + } + err = g.Model(gstr.Join([]string{dao.AdminRole.Table(), "r"}, " ")). + LeftJoin(gstr.Join([]string{dao.AdminRoleMenu.Table(), "rm"}, " "), "r.id=rm.role_id"). + LeftJoin(gstr.Join([]string{dao.AdminMenu.Table(), "m"}, " "), "rm.menu_id=m.id"). + Fields(q("r", dao.AdminRole.Columns().Key), q("m", dao.AdminMenu.Columns().Permissions)). + Where(q("r", dao.AdminRole.Columns().Status), consts.StatusEnabled). + Where(q("m", dao.AdminMenu.Columns().Status), consts.StatusEnabled). + WhereNot(q("m", dao.AdminMenu.Columns().Permissions), ""). + WhereNot(q("r", dao.AdminRole.Columns().Key), consts.SuperRoleKey). + Scan(&polices) + if err != nil { + g.Log().Fatalf(ctx, "loadPermissions Scan err:%v", err) + return + } + + for _, policy := range polices { + if strings.Contains(policy.Permissions, ",") { + lst := strings.Split(policy.Permissions, ",") + for _, permissions := range lst { + rules = append(rules, []string{policy.Key, permissions, ActionAll}) + } + } else { + rules = append(rules, []string{policy.Key, policy.Permissions, ActionAll}) + } + } + + if _, err = Enforcer.AddPolicies(rules); err != nil { + g.Log().Fatalf(ctx, "loadPermissions AddPolicies err:%v", err) + return + } +} + +func Clear(ctx context.Context) (err error) { + policy, err := Enforcer.GetPolicy() + if err != nil { + g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err) + return + } + _, err = Enforcer.RemovePolicies(policy) + if err != nil { + g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err) + return + } + + // 检查是否清理干净 + if len(policy) > 0 { + return Clear(ctx) + } + return +} + +func Refresh(ctx context.Context) (err error) { + if err = Clear(ctx); err != nil { + return err + } + loadPermissions(ctx) + return +} diff --git a/server/internal/library/contexts/context.go b/server/internal/library/contexts/context.go new file mode 100644 index 0000000..b9cd6bd --- /dev/null +++ b/server/internal/library/contexts/context.go @@ -0,0 +1,195 @@ +// Package contexts +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package contexts + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "hotgo/internal/consts" + "hotgo/internal/model" +) + +// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改 +func Init(r *ghttp.Request, customCtx *model.Context) { + r.SetCtxVar(consts.ContextHTTPKey, customCtx) +} + +// Get 获得上下文变量,如果没有设置,那么返回nil +func Get(ctx context.Context) *model.Context { + value := ctx.Value(consts.ContextHTTPKey) + if value == nil { + return nil + } + if localCtx, ok := value.(*model.Context); ok { + return localCtx + } + return nil +} + +// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖 +func SetUser(ctx context.Context, user *model.Identity) { + c := Get(ctx) + if c == nil { + g.Log().Warning(ctx, "contexts.SetUser, c == nil ") + return + } + c.User = user +} + +// SetResponse 设置组件响应 用于访问日志使用 +func SetResponse(ctx context.Context, response *model.Response) { + c := Get(ctx) + if c == nil { + g.Log().Warning(ctx, "contexts.SetResponse, c == nil ") + return + } + c.Response = response +} + +// SetModule 设置应用模块 +func SetModule(ctx context.Context, module string) { + c := Get(ctx) + if c == nil { + g.Log().Warning(ctx, "contexts.SetModule, c == nil ") + return + } + c.Module = module +} + +// GetUser 获取用户信息 +func GetUser(ctx context.Context) *model.Identity { + c := Get(ctx) + if c == nil { + return nil + } + return c.User +} + +// GetUserId 获取用户ID +func GetUserId(ctx context.Context) int64 { + user := GetUser(ctx) + if user == nil { + return 0 + } + return user.Id +} + +// GetRoleId 获取用户角色ID +func GetRoleId(ctx context.Context) int64 { + user := GetUser(ctx) + if user == nil { + return 0 + } + return user.RoleId +} + +// GetRoleKey 获取用户角色唯一编码 +func GetRoleKey(ctx context.Context) string { + user := GetUser(ctx) + if user == nil { + return "" + } + return user.RoleKey +} + +// GetDeptType 获取用户部门类型 +func GetDeptType(ctx context.Context) string { + user := GetUser(ctx) + if user == nil { + return "" + } + return user.DeptType +} + +// IsCompanyDept 是否为公司部门 +func IsCompanyDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeCompany +} + +// IsTenantDept 是否为租户部门 +func IsTenantDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeTenant +} + +// IsMerchantDept 是否为商户部门 +func IsMerchantDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeMerchant +} + +// IsUserDept 是否为普通用户部门 +func IsUserDept(ctx context.Context) bool { + return GetDeptType(ctx) == consts.DeptTypeUser +} + +// GetModule 获取应用模块 +func GetModule(ctx context.Context) string { + c := Get(ctx) + if c == nil { + return "" + } + return c.Module +} + +// SetAddonName 设置插件信息 +func SetAddonName(ctx context.Context, name string) { + c := Get(ctx) + if c == nil { + g.Log().Warning(ctx, "contexts.SetAddonName, c == nil ") + return + } + Get(ctx).AddonName = name +} + +// IsAddonRequest 是否为插件模块请求 +func IsAddonRequest(ctx context.Context) bool { + c := Get(ctx) + if c == nil { + return false + } + return GetAddonName(ctx) != "" +} + +// GetAddonName 获取插件信息 +func GetAddonName(ctx context.Context) string { + c := Get(ctx) + if c == nil { + return "" + } + return Get(ctx).AddonName +} + +// SetData 设置额外数据 +func SetData(ctx context.Context, k string, v interface{}) { + c := Get(ctx) + if c == nil { + g.Log().Warning(ctx, "contexts.SetData, c == nil ") + return + } + Get(ctx).Data[k] = v +} + +// SetDataMap 设置额外数据 +func SetDataMap(ctx context.Context, vs g.Map) { + c := Get(ctx) + if c == nil { + g.Log().Warning(ctx, "contexts.SetData, c == nil ") + return + } + + for k, v := range vs { + Get(ctx).Data[k] = v + } +} + +// GetData 获取额外数据 +func GetData(ctx context.Context) g.Map { + c := Get(ctx) + if c == nil { + return nil + } + return c.Data +} diff --git a/server/internal/library/contexts/detached.go b/server/internal/library/contexts/detached.go new file mode 100644 index 0000000..e7c1310 --- /dev/null +++ b/server/internal/library/contexts/detached.go @@ -0,0 +1,35 @@ +// Package contexts +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package contexts + +import ( + "context" + "time" +) + +type detached struct { + ctx context.Context +} + +func (detached) Deadline() (time.Time, bool) { + return time.Time{}, false +} + +func (detached) Done() <-chan struct{} { + return nil +} + +func (detached) Err() error { + return nil +} + +func (d detached) Value(key interface{}) interface{} { + return d.ctx.Value(key) +} + +func Detach(ctx context.Context) context.Context { + return detached{ctx: ctx} +} diff --git a/server/internal/library/cron/cron.go b/server/internal/library/cron/cron.go new file mode 100644 index 0000000..450cf08 --- /dev/null +++ b/server/internal/library/cron/cron.go @@ -0,0 +1,313 @@ +// Package cron +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package cron + +import ( + "bufio" + "context" + "fmt" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "hotgo/internal/consts" + "hotgo/internal/dao" + "hotgo/internal/model/entity" + "hotgo/utility/simple" + "os" + "strings" + "sync" +) + +var crons = &cronManager{ + tasks: make(map[string]*TaskItem), + loggers: make(map[string]*glog.Logger), +} + +// Cron 定时任务接口 +type Cron interface { + // GetName 获取任务名称 + GetName() string + // Execute 执行任务 + Execute(ctx context.Context, parser *Parser) (err error) +} + +// Parser 任务执行参数 +type Parser struct { + Args []string // 任务参数 + Logger *glog.Logger // 日志管理实例 +} + +// Log 任务调度日志 +type Log struct { + FileName string `json:"fileName" dc:"文件名称"` + SizeFormat string `json:"sizeFormat" dc:"文件大小"` + Contents string `json:"contents" dc:"文件内容"` +} + +// consumerManager 任务管理者 +type cronManager struct { + tasks map[string]*TaskItem + loggers map[string]*glog.Logger + sync.RWMutex +} + +type TaskItem struct { + Pattern string // 表达式,参考:https://goframe.org/pages/viewpage.action?pageId=30736411 + Name string // 唯一的任务名称 + Params string // 函数参数,多个用,隔开 + Fun gcron.JobFunc // 执行的函数接口 + Policy int64 // 策略 1:并行 2:单例 3:单次 4:多次 + Count int // 执行次数,仅Policy=4时有效 +} + +func Logger() *glog.Logger { + return g.Log("cron") +} + +// Register 注册任务 +func Register(c Cron) { + crons.Lock() + defer crons.Unlock() + + name := c.GetName() + if _, ok := crons.tasks[name]; ok { + Logger().Debugf(gctx.GetInitCtx(), "cron.Register name:%v duplicate registration.", name) + return + } + + crons.tasks[name] = &TaskItem{Name: c.GetName(), Fun: GenExecuteFun(c.Execute)} +} + +// StopALL 停止所有任务 +func StopALL() { + for _, v := range gcron.Entries() { + gcron.Remove(v.Name) + } +} + +// StartALL 启动所有任务 +func StartALL(sysCron []*entity.SysCron) (err error) { + crons.Lock() + defer crons.Unlock() + + if len(crons.tasks) == 0 { + g.Log().Debug(gctx.GetInitCtx(), "no scheduled task is available.") + return + } + + for _, cron := range sysCron { + f, ok := crons.tasks[cron.Name] + if !ok { + return gerror.Newf("该任务没有加入任务列表:%v", cron.Name) + } + + sn := GenCronSn(cron) + + // 没有则添加 + if gcron.Search(sn) == nil { + var ( + t *gcron.Entry + ctx = GenCronCtx(cron) + ) + switch cron.Policy { + case consts.CronPolicySame: + t, err = gcron.Add(ctx, cron.Pattern, f.Fun, sn) + + case consts.CronPolicySingle: + t, err = gcron.AddSingleton(ctx, cron.Pattern, f.Fun, sn) + + case consts.CronPolicyOnce: + t, err = gcron.AddOnce(ctx, cron.Pattern, f.Fun, sn) + + case consts.CronPolicyTimes: + if f.Count <= 0 { + f.Count = 1 + } + t, err = gcron.AddTimes(ctx, cron.Pattern, int(cron.Count), f.Fun, sn) + + default: + return gerror.Newf("使用无效的策略, cron.Policy=%v", cron.Policy) + } + + if err != nil { + return err + } + if t == nil { + return gerror.New("启动任务失败") + } + } + + gcron.Start(sn) + + // 执行完毕,单次和多次执行的任务更新状态 + if cron.Policy == consts.CronPolicyOnce || cron.Policy == consts.CronPolicyTimes { + if _, err = dao.SysCron.Ctx(gctx.GetInitCtx()).Where(dao.SysCron.Columns().Id, cron.Id).Data(g.Map{dao.SysCron.Columns().Status: consts.StatusDisable, dao.SysCron.Columns().UpdatedAt: gtime.Now()}).Update(); err != nil { + err = gerror.Wrap(err, "定时任务执行失败!") + return err + } + } + } + + Logger().Debug(gctx.GetInitCtx(), "load cron success..") + return nil +} + +// RefreshStatus 刷新状态 +func RefreshStatus(sysCron *entity.SysCron) (err error) { + if sysCron == nil { + return + } + + if sysCron.Status == consts.StatusEnabled { + return ResetStart(sysCron) + } + return Stop(sysCron) +} + +// Stop 停止单个任务 +func Stop(sysCron *entity.SysCron) (err error) { + cr := gcron.Search(GenCronSn(sysCron)) + if cr == nil { + return + } + cr.Stop() + return +} + +// ResetStart 重置任务 +func ResetStart(sysCron *entity.SysCron) (err error) { + if err = Stop(sysCron); err != nil { + return + } + if err = Delete(sysCron); err != nil { + return + } + return Start(sysCron) +} + +// Once 立即执行一次某个任务 +func Once(ctx context.Context, sysCron *entity.SysCron) error { + crons.RLock() + defer crons.RUnlock() + + for _, v := range crons.tasks { + if v.Name == sysCron.Name { + simple.SafeGo(ctx, func(ctx context.Context) { + v.Fun(GenCronCtx(sysCron)) + }) + return nil + } + } + return gerror.Newf("定时任务不存在:%+v", sysCron.Name) +} + +// Delete 删除任务 +func Delete(sysCron *entity.SysCron) (err error) { + if sysCron == nil { + return + } + + for _, v := range gcron.Entries() { + if v.Name == GenCronSn(sysCron) { + gcron.Remove(v.Name) + } + } + return +} + +// Start 启动单个任务 +func Start(sysCron *entity.SysCron) (err error) { + if sysCron == nil { + return + } + + c := gcron.Search(GenCronSn(sysCron)) + if c != nil { + c.Start() + return + } + return StartALL([]*entity.SysCron{sysCron}) +} + +// DispatchLog 查看指定任务的调度日志 +func DispatchLog(sysCron *entity.SysCron) (log *Log, err error) { + path := fmt.Sprintf("%v/%v", Logger().GetConfig().Path, GenCronSn(sysCron)) + file, err := FindLastModifiedFile(path) + if err != nil { + return nil, err + } + + if len(file) == 0 || !gfile.IsFile(file) { + err = gerror.New("未找到日志!") + return + } + + log = new(Log) + log.FileName = file + log.SizeFormat = gfile.SizeFormat(file) + + if gfile.Size(file) > 1024*50 { + log.Contents, err = ReadLastLines(file, 100) + if err != nil { + return nil, err + } + } else { + log.Contents = gfile.GetContents(file) + } + return +} + +func ReadLastLines(filename string, lineCount int) (string, error) { + file, err := os.Open(filename) + if err != nil { + return "", err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + + lines := make([]string, 0, lineCount) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + if len(lines) > lineCount { + lines = lines[1:] + } + } + + if err = scanner.Err(); err != nil { + return "", err + } + + result := strings.Join(lines, "\n") + return result, nil +} + +func FindLastModifiedFile(dirPath string) (string, error) { + if !gfile.Exists(dirPath) { + return "", gerror.New("该任务暂未产生日志!") + } + + files, err := gfile.ScanDir(dirPath, "*.log", true) + if err != nil { + return "", err + } + + var lastModifiedFile string + var lastModifiedTime int64 = 0 + + for _, file := range files { + modTime := gfile.MTimestamp(file) + if modTime > lastModifiedTime { + lastModifiedTime = modTime + lastModifiedFile = file + } + } + return lastModifiedFile, nil +} diff --git a/server/internal/library/cron/cron_gen.go b/server/internal/library/cron/cron_gen.go new file mode 100644 index 0000000..f19c308 --- /dev/null +++ b/server/internal/library/cron/cron_gen.go @@ -0,0 +1,88 @@ +// Package cron +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package cron + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "hotgo/internal/consts" + "hotgo/internal/model/entity" + "strings" +) + +// GenCronSn 生成任务序列号 +func GenCronSn(sysCron *entity.SysCron) string { + return fmt.Sprintf("%s@%d", sysCron.Name, sysCron.Id) +} + +// GenCronCtx 生成任务上下文 +func GenCronCtx(sysCron *entity.SysCron) (ctx context.Context) { + ctx = context.WithValue(gctx.New(), consts.ContextKeyCronArgs, strings.Split(sysCron.Params, consts.CronSplitStr)) + ctx = context.WithValue(ctx, consts.ContextKeyCronSn, GenCronSn(sysCron)) + return ctx +} + +func GenLoggerByCtx(ctx context.Context) *glog.Logger { + sn, ok := ctx.Value(consts.ContextKeyCronSn).(string) + if !ok { + Logger().Panic(ctx, "获取定时任务序列号失败!") + } + + crons.RLock() + logger, ok := crons.loggers[sn] + crons.RUnlock() + if ok { + return logger + } + + logger = glog.New() + if err := logger.SetConfig(Logger().GetConfig()); err != nil { + Logger().Panic(ctx, err) + } + + logger.SetFlags(glog.F_TIME_STD | glog.F_FILE_SHORT) + // 设置子路径 + if err := logger.SetPath(fmt.Sprintf("%v/%v", logger.GetPath(), sn)); err != nil { + Logger().Panic(ctx, err) + } + + crons.Lock() + defer crons.Unlock() + crons.loggers[sn] = logger + return logger +} + +// GenExecuteFun 生成执行过程 +func GenExecuteFun(fun func(ctx context.Context, parser *Parser) (err error)) func(ctx context.Context) { + return func(ctx context.Context) { + args, ok := ctx.Value(consts.ContextKeyCronArgs).([]string) + if !ok { + Logger().Panic(ctx, "执行定时任务时,参数解析失败!") + return + } + parser := new(Parser) + parser.Args = args + parser.Logger = GenLoggerByCtx(ctx) + + st := gtime.Now() + err := g.Try(ctx, func(ctx context.Context) { + if err := fun(ctx, parser); err != nil { + panic(err) + } + }) + + milliseconds := gtime.Now().Sub(st).Milliseconds() // 执行耗时 + if err != nil { + parser.Logger.Errorf(ctx, "execute failed, took %vms, err:%+v", milliseconds, err) + return + } + parser.Logger.Infof(ctx, "execute success, took %vms.", milliseconds) + } +} diff --git a/server/internal/library/debris/debris.go b/server/internal/library/debris/debris.go new file mode 100644 index 0000000..6defd0e --- /dev/null +++ b/server/internal/library/debris/debris.go @@ -0,0 +1,9 @@ +// Package debris +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +// +package debris + +// 碎片 diff --git a/server/internal/library/dict/dict.go b/server/internal/library/dict/dict.go new file mode 100644 index 0000000..0bb04e5 --- /dev/null +++ b/server/internal/library/dict/dict.go @@ -0,0 +1,81 @@ +// Package dict +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package dict + +import ( + "context" + "errors" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "hash/fnv" + "hotgo/internal/model" + "strconv" +) + +const ( + BuiltinId int64 = -1 // 内置字典ID + EnumsId int64 = -2 // 枚举字典ID + FuncId int64 = -3 // 方法字典ID +) + +var NotExistKeyError = errors.New("not exist key") + +// GetOptions 获取内置选项 +func GetOptions(ctx context.Context, key string) (opts []*model.Option, err error) { + opts = GetEnumsOptions(key) + if opts != nil { + return + } + return GetFuncOptions(ctx, key) +} + +// GetOptionsById 通过类型ID获取内置选项 +func GetOptionsById(ctx context.Context, id int64) (opts []*model.Option, err error) { + for _, v := range GetAllEnums() { + if v.Id == id { + return v.Opts, nil + } + } + + for _, v := range GetAllFunc() { + g.Log().Warningf(ctx, "GetAllFunc GetOptionsById v:%v, %v", v.Id, v.Key) + if v.Id == id { + return LoadFuncOptions(ctx, v) + } + } + + err = NotExistKeyError + return +} + +// GetTypeById 通过类型ID获取内置选项类型 +func GetTypeById(ctx context.Context, id int64) (typ string, err error) { + for _, v := range GetAllEnums() { + if v.Id == id { + return v.Key, nil + } + } + + for _, v := range GetAllFunc() { + if v.Id == id { + return v.Key, nil + } + } + + err = NotExistKeyError + return +} + +// GenIdHash 生成字典id +func GenIdHash(str string, t int64) int64 { + prefix := 10000 * t + h := fnv.New32a() + h.Write([]byte("dict" + str)) + + idStr := fmt.Sprintf("%d%d", prefix, int64(h.Sum32())) + id, _ := strconv.ParseInt(idStr, 10, 64) + return id +} diff --git a/server/internal/library/dict/dict_option.go b/server/internal/library/dict/dict_option.go new file mode 100644 index 0000000..a628e26 --- /dev/null +++ b/server/internal/library/dict/dict_option.go @@ -0,0 +1,181 @@ +// Package dict +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package dict + +import ( + "hash/fnv" + "hotgo/internal/model" + + "github.com/gogf/gf/v2/util/gconv" +) + +// GenDefaultOption 生成默认表格回显样式 +func GenDefaultOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: "default", + Extra: e, + } +} + +func GenSuccessOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: "success", + Extra: e, + } +} + +func GenWarningOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: "warning", + Extra: e, + } +} + +func GenErrorOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: "error", + Extra: e, + } +} + +func GenInfoOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: "info", + Extra: e, + } +} + +func GenPrimaryOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: "primary", + Extra: e, + } +} + +// GenCustomOption 生成自定义表格回显样式 +func GenCustomOption(key interface{}, label string, custom string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: custom, + Extra: e, + } +} + +// GenHashOption 根据不同label以hash算法生成表格回显样式 +func GenHashOption(key interface{}, label string, extra ...any) *model.Option { + e := interface{}(nil) + if len(extra) > 0 { + e = extra[0] + } + strings := []string{"default", "primary", "info", "success", "warning", "error"} + hash := fnv.New32() + + tag := "default" + if _, err := hash.Write(gconv.Bytes(label)); err == nil { + index := uint32(hash.Sum32()) % uint32(len(strings)) + if index < uint32(len(strings)) { + tag = strings[index] + } + } + return &model.Option{ + Key: key, + Label: label, + Value: key, + ListClass: tag, + Extra: e, + } +} + +// GetOption 通过key找到label +func GetOption(ses []*model.Option, key interface{}) *model.Option { + for _, v := range ses { + if gconv.String(v.Key) == gconv.String(key) { + return v + } + } + return nil +} + +// GetOptionLabel 通过key找到label +func GetOptionLabel(ses []*model.Option, key interface{}) string { + for _, v := range ses { + if gconv.String(v.Key) == gconv.String(key) { + return v.Label + } + } + return `` +} + +// HasOptionKey 是否存在指定key +func HasOptionKey(ses []*model.Option, key interface{}) bool { + for _, v := range ses { + if gconv.String(v.Key) == gconv.String(key) { + return true + } + } + return false +} + +// UniqueOption 去重选项 +func UniqueOption(src []*model.Option) (dst []*model.Option) { + temp := map[string]struct{}{} + for _, item := range src { + key := gconv.String(item.Key) + if _, ok := temp[key]; !ok { + temp[key] = struct{}{} + dst = append(dst, item) + } + } + return dst +} diff --git a/server/internal/library/dict/dict_register_enums.go b/server/internal/library/dict/dict_register_enums.go new file mode 100644 index 0000000..434698b --- /dev/null +++ b/server/internal/library/dict/dict_register_enums.go @@ -0,0 +1,70 @@ +// Package dict +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package dict + +import ( + "fmt" + "hotgo/internal/model" + "sync" +) + +type EnumsOption struct { + Id int64 // 字典ID,由系统自动生成 + Key string // 字典选项key + Label string // 字典选项标签名称 + Opts []*model.Option // 数据选项 +} + +var ( + enumsOptions = make(map[string]*EnumsOption) + eLock sync.Mutex +) + +// GetAllEnums 获取所有枚举字典 +func GetAllEnums() map[string]*EnumsOption { + return enumsOptions +} + +// RegisterEnums 注册枚举字典选项 +func RegisterEnums(key, label string, opts []*model.Option) { + eLock.Lock() + defer eLock.Unlock() + + if len(key) == 0 { + panic("字典key不能为空") + } + + if _, ok := enumsOptions[key]; ok { + panic(fmt.Sprintf("重复注册枚举字典选项:%v", key)) + } + + for _, v := range opts { + v.Type = key + } + enumsOptions[key] = &EnumsOption{ + Id: GenIdHash(key, EnumsId), + Key: key, + Label: label, + Opts: opts, + } +} + +// SaveEnums 更新枚举字典选项 +func SaveEnums(key, label string, opts []*model.Option) { + eLock.Lock() + defer eLock.Unlock() + delete(enumsOptions, key) + RegisterEnums(key, label, opts) +} + +// GetEnumsOptions 获取指定枚举字典的数据选项 +func GetEnumsOptions(key string) []*model.Option { + enums, ok := enumsOptions[key] + if !ok { + return nil + } + return enums.Opts +} diff --git a/server/internal/library/dict/dict_register_func.go b/server/internal/library/dict/dict_register_func.go new file mode 100644 index 0000000..cf75dcd --- /dev/null +++ b/server/internal/library/dict/dict_register_func.go @@ -0,0 +1,129 @@ +// Package dict +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package dict + +import ( + "context" + "fmt" + "hotgo/internal/model" + "sync" +) + +// FuncDict 方法字典,实现本接口即可使用内置方法字典 +type FuncDict func(ctx context.Context) (res []*model.Option, err error) + +type FuncOption struct { + Id int64 // 字典ID,由系统自动生成 + Key string // 字典选项key + Label string // 字典选项标签名称 + Fun FuncDict // 字典方法 + Cache bool // 是否缓存数据选项 + Opts []*model.Option // 缓存的数据选项 + sync.Mutex +} + +var ( + funcOptions = make(map[string]*FuncOption) + fLock sync.Mutex +) + +// GetAllFunc 获取所有方法字典 +func GetAllFunc() map[string]*FuncOption { + return funcOptions +} + +// RegisterFunc 注册方法字典选项 +func RegisterFunc(key, label string, fun FuncDict, cache ...bool) { + fLock.Lock() + defer fLock.Unlock() + + if len(key) == 0 { + panic("字典key不能为空") + } + + if _, ok := funcOptions[key]; ok { + panic(fmt.Sprintf("重复注册方法选项:%v", key)) + } + + isCache := false + if len(cache) > 0 { + isCache = cache[0] + } + + funcOptions[key] = &FuncOption{ + Id: GenIdHash(key, FuncId), + Key: key, + Label: label, + Fun: fun, + Cache: isCache, + Opts: nil, + } +} + +// SaveFunc 更新方法字典选项 +func SaveFunc(key, label string, fun FuncDict, cache ...bool) { + fLock.Lock() + defer fLock.Unlock() + delete(funcOptions, key) + RegisterFunc(key, label, fun, cache...) +} + +// ClearFuncCache 清理指定方法缓存选项 +func ClearFuncCache(key string) (err error) { + fun, ok := funcOptions[key] + if !ok { + err = NotExistKeyError + return + } + + fun.Lock() + defer fun.Unlock() + + if fun.Opts != nil { + fun.Opts = nil + } + return +} + +// GetFuncOptions 获取指定方法字典的数据选项 +func GetFuncOptions(ctx context.Context, key string) (res []*model.Option, err error) { + fun, ok := funcOptions[key] + if !ok { + err = NotExistKeyError + return + } + return LoadFuncOptions(ctx, fun) +} + +// LoadFuncOptions 加载指定方法字典的数据选项 +func LoadFuncOptions(ctx context.Context, fun *FuncOption) (res []*model.Option, err error) { + if fun.Cache && fun.Opts != nil { + res = fun.Opts + return + } + + fun.Lock() + defer fun.Unlock() + + if fun.Cache && fun.Opts != nil { + res = fun.Opts + return + } + + res, err = fun.Fun(ctx) + if err != nil { + return nil, err + } + + for k := range res { + res[k].Type = fun.Key + } + + if fun.Cache { + fun.Opts = res + } + return +} diff --git a/server/internal/library/ems/ems.go b/server/internal/library/ems/ems.go new file mode 100644 index 0000000..e26fd45 --- /dev/null +++ b/server/internal/library/ems/ems.go @@ -0,0 +1,50 @@ +// Package ems +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package ems + +import ( + "github.com/gogf/gf/v2/errors/gerror" + "hotgo/internal/model" + "hotgo/utility/validate" + "net/smtp" + "strings" +) + +// Send 发送邮件入口 +func Send(config *model.EmailConfig, to string, subject string, body string) error { + return sendToMail(config, to, subject, body, "html") +} + +func sendToMail(config *model.EmailConfig, to, subject, body, mailType string) error { + if config == nil { + return gerror.New("邮件配置不能为空") + } + + var ( + contentType string + auth = smtp.PlainAuth("", config.User, config.Password, config.Host) + sendTo = strings.Split(to, ";") + ) + + if len(sendTo) == 0 { + return gerror.New("收件人不能为空") + } + + for _, em := range sendTo { + if !validate.IsEmail(em) { + return gerror.Newf("邮件格式不正确,请检查:%v", em) + } + } + + if mailType == "html" { + contentType = "Content-Type: text/" + mailType + "; charset=UTF-8" + } else { + contentType = "Content-Type: text/plain" + "; charset=UTF-8" + } + + msg := []byte("To: " + to + "\r\nFrom: " + config.SendName + "<" + config.User + ">" + "\r\nSubject: " + subject + "\r\n" + contentType + "\r\n\r\n" + body) + return smtp.SendMail(config.Addr, auth, config.User, sendTo, msg) +} diff --git a/server/internal/library/hggen/hggen.go b/server/internal/library/hggen/hggen.go new file mode 100644 index 0000000..cb23db3 --- /dev/null +++ b/server/internal/library/hggen/hggen.go @@ -0,0 +1,290 @@ +// Package hggen +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package hggen + +import ( + _ "hotgo/internal/library/hggen/internal/cmd/gendao" + "hotgo/internal/library/hggen/internal/utility/utils" + _ "unsafe" + + "github.com/gogf/gf/v2/os/gfile" + + "context" + "hotgo/internal/consts" + "hotgo/internal/library/addons" + "hotgo/internal/library/hggen/internal/cmd" + "hotgo/internal/library/hggen/internal/cmd/gendao" + "hotgo/internal/library/hggen/internal/cmd/genservice" + "hotgo/internal/library/hggen/views" + "hotgo/internal/model" + "hotgo/internal/model/input/form" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" + "sort" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +//go:linkname doGenDaoForArray hotgo/internal/library/hggen/internal/cmd/gendao.doGenDaoForArray +func doGenDaoForArray(ctx context.Context, index int, in gendao.CGenDaoInput) + +// Dao 生成数据库实体 +func Dao(ctx context.Context) (err error) { + // 在执行gf gen dao时,先将生成文件放在临时路径,生成完成后再拷贝到项目 + // 目的是希望减少触发gf热编译的几率,防止热编译运行时代码生成流程未结束被自动重启打断 + // gf gen dao 的执行时长主要取决于需要生成数据库表的数量,表越多速度越慢 + tempPathPrefix := views.GetTempGeneratePath(ctx) + "/dao" + + for _, v := range daoConfig { + inp := defaultGenDaoInput + if err = gconv.Scan(v, &inp); err != nil { + return + } + oldPath := inp.Path + inp.ImportPrefix = utils.GetImportPath(inp.Path) + inp.Path = tempPathPrefix + "/" + inp.Path + + if err = gfile.RemoveAll(inp.Path); err != nil { + err = gerror.Newf("清理临时生成目录失败:%v", err) + return err + } + + if err = gfile.Mkdir(inp.Path); err != nil { + err = gerror.Newf("创建临时生成目录失败:%v", err) + return err + } + + doGenDaoForArray(ctx, -1, inp) + + if err = gfile.CopyDir(inp.Path, gfile.Pwd()+"/"+oldPath); err != nil { + err = gerror.Newf("拷贝生成文件失败:%v", err) + return err + } + } + return +} + +// Service 生成业务接口 +func Service(ctx context.Context) (err error) { + return ServiceWithCfg(ctx, GetServiceConfig()) +} + +// ServiceWithCfg 生成业务接口 +func ServiceWithCfg(ctx context.Context, cfg ...genservice.CGenServiceInput) (err error) { + c := GetServiceConfig() + if len(cfg) > 0 { + c = cfg[0] + } + _, err = cmd.Gen.Service(ctx, c) + return +} + +// TableColumns 获取指定表生成字段列表 +func TableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp) (fields []*sysin.GenCodesColumnListModel, err error) { + return views.DoTableColumns(ctx, in, GetDaoConfig(in.Name)) +} + +func TableSelects(ctx context.Context, in *sysin.GenCodesSelectsInp) (res *sysin.GenCodesSelectsModel, err error) { + res = new(sysin.GenCodesSelectsModel) + res.GenType, err = GenTypeSelect(ctx) + if err != nil { + return + } + + res.Db = DbSelect(ctx) + + for k, v := range consts.GenCodesStatusNameMap { + res.Status = append(res.Status, &form.Select{ + Value: k, + Name: v, + Label: v, + }) + } + sort.Sort(res.Status) + + for k, v := range consts.GenCodesJoinNameMap { + res.LinkMode = append(res.LinkMode, &form.Select{ + Value: k, + Name: v, + Label: v, + }) + } + sort.Sort(res.LinkMode) + + for k, v := range consts.GenCodesBuildMethNameMap { + res.BuildMeth = append(res.BuildMeth, &form.Select{ + Value: k, + Name: v, + Label: v, + }) + } + sort.Sort(res.BuildMeth) + + for _, v := range views.FormModes { + res.FormMode = append(res.FormMode, &form.Select{ + Value: v, + Name: views.FormModeMap[v], + Label: views.FormModeMap[v], + }) + } + sort.Sort(res.FormMode) + + for k, v := range views.FormRoleMap { + res.FormRole = append(res.FormRole, &form.Select{ + Value: k, + Name: v, + Label: v, + }) + } + sort.Sort(res.FormRole) + + dictMode, err := service.SysDictType().TreeSelect(ctx, &sysin.DictTreeSelectInp{}) + if err != nil { + return + } + res.DictMode = dictMode + + for _, v := range views.WhereModes { + res.WhereMode = append(res.WhereMode, &form.Select{ + Value: v, + Name: v, + Label: v, + }) + } + for _, v := range views.TableAligns { + res.TableAlign = append(res.TableAlign, &form.Select{ + Value: v, + Name: views.TableAlignMap[v], + Label: views.TableAlignMap[v], + }) + } + + res.Addons = addons.ModuleSelect() + res.TreeStyleType = consts.GenCodesTreeStyleTypeOptions + return +} + +// GenTypeSelect 获取生成类型选项 +func GenTypeSelect(ctx context.Context) (res sysin.GenTypeSelects, err error) { + for k, v := range consts.GenCodesTypeNameMap { + row := &sysin.GenTypeSelect{ + Value: k, + Name: v, + Label: v, + Templates: make(sysin.GenTemplateSelects, 0), + } + + km := k + if km == consts.GenCodesTypeTree { + km = consts.GenCodesTypeCurd + } + confName, ok := consts.GenCodesTypeConfMap[km] + if ok { + var temps []*model.GenerateAppCrudTemplate + err = g.Cfg().MustGet(ctx, "hggen.application."+confName+".templates").Scan(&temps) + if err != nil { + return + } + if len(temps) > 0 { + for index, temp := range temps { + row.Templates = append(row.Templates, &sysin.GenTemplateSelect{ + Value: index, + Label: temp.Group, + Name: temp.Group, + IsAddon: temp.IsAddon, + }) + } + sort.Sort(row.Templates) + } + } + + res = append(res, row) + } + sort.Sort(res) + return +} + +// DbSelect db选项 +func DbSelect(ctx context.Context) (res form.Selects) { + dbs := g.Cfg().MustGet(ctx, "hggen.selectDbs") + if len(dbs.Strings()) == 0 { + res = make(form.Selects, 0) + return res + } + + for _, v := range dbs.Strings() { + res = append(res, &form.Select{ + Value: v, + Label: v, + Name: v, + }) + } + return res +} + +// Preview 生成预览 +func Preview(ctx context.Context, in *sysin.GenCodesPreviewInp) (res *sysin.GenCodesPreviewModel, err error) { + genConfig, err := service.SysConfig().GetLoadGenerate(ctx) + if err != nil { + return nil, err + } + + switch in.GenType { + case consts.GenCodesTypeCurd, consts.GenCodesTypeTree: + return views.Curd.DoPreview(ctx, &views.CurdPreviewInput{ + In: in, + DaoConfig: GetDaoConfig(in.DbName), + Config: genConfig, + }) + case consts.GenCodesTypeQueue: + err = gerror.Newf("生成类型开发中!") + return + default: + err = gerror.Newf("生成类型暂不支持!") + return + } +} + +// Build 提交生成 +func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) { + genConfig, err := service.SysConfig().GetLoadGenerate(ctx) + if err != nil { + return err + } + + switch in.GenType { + case consts.GenCodesTypeCurd, consts.GenCodesTypeTree: + pin := &sysin.GenCodesPreviewInp{SysGenCodes: in.SysGenCodes} + return views.Curd.DoBuild(ctx, &views.CurdBuildInput{ + PreviewIn: &views.CurdPreviewInput{ + In: pin, + DaoConfig: GetDaoConfig(in.DbName), + Config: genConfig, + }, + BeforeEvent: views.CurdBuildEvent{"runDao": Dao}, + AfterEvent: views.CurdBuildEvent{"runService": func(ctx context.Context) (err error) { + cfg := GetServiceConfig() + + // 插件模块,切换到插件下运行gen service + if genConfig.Application.Crud.Templates[pin.GenTemplate].IsAddon { + // 依然使用配置中的参数,只是将生成路径指向插件模块路径 + cfg.SrcFolder = "addons/" + pin.AddonName + "/logic" + cfg.DstFolder = "addons/" + pin.AddonName + "/service" + } + err = ServiceWithCfg(ctx, cfg) + return + }}, + }) + case consts.GenCodesTypeQueue: + err = gerror.Newf("生成类型开发中!") + return + default: + err = gerror.Newf("生成类型暂不支持!") + return + } +} diff --git a/server/internal/library/hggen/init.go b/server/internal/library/hggen/init.go new file mode 100644 index 0000000..7ff306c --- /dev/null +++ b/server/internal/library/hggen/init.go @@ -0,0 +1,130 @@ +// Package hggen +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package hggen + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/util/gconv" + "gopkg.in/yaml.v3" + "hotgo/internal/library/hggen/internal/cmd/gendao" + "hotgo/internal/library/hggen/internal/cmd/genservice" +) + +const ( + cliFolderName = `hack/config.yaml` + RequiredErrorTag = `the cli configuration file must be configured %s` +) + +var ( + config g.Map + daoConfig []interface{} + serviceConfig g.Map + + // 生成service默认参数,请不要直接修改以下配置,如需调整请到/hack/config.yaml,可参考:https://goframe.org/pages/viewpage.action?pageId=49770772 + defaultGenServiceInput = genservice.CGenServiceInput{ + SrcFolder: "internal/logic", + DstFolder: "internal/service", + DstFileNameCase: "Snake", + StPattern: `s([A-Z]\w+)`, + Clear: false, + } + + // 生成dao默认参数,请不要直接修改以下配置,如需调整请到/hack/config.yaml,可参考:https://goframe.org/pages/viewpage.action?pageId=3673173 + defaultGenDaoInput = gendao.CGenDaoInput{ + Path: "internal", + Group: "default", + JsonCase: "CamelLower", + DaoPath: "dao", + DoPath: "model/do", + EntityPath: "model/entity", + StdTime: false, + WithTime: false, + GJsonSupport: false, + OverwriteDao: false, + DescriptionTag: true, + NoJsonTag: false, + NoModelComment: false, + Clear: false, + } +) + +func GetServiceConfig() genservice.CGenServiceInput { + inp := defaultGenServiceInput + _ = gconv.Scan(serviceConfig, &inp) + return inp +} + +func GetDaoConfig(group string) gendao.CGenDaoInput { + find := func(group string) g.Map { + for _, v := range daoConfig { + if v.(g.Map)["group"].(string) == group { + return v.(g.Map) + } + } + return nil + } + + v := find(group) + inp := defaultGenDaoInput + if v != nil { + if err := gconv.Scan(v, &inp); err != nil { + panic(err) + } + } + return inp +} + +func InIt(ctx context.Context) { + path, err := gfile.Search(cliFolderName) + if err != nil { + g.Log().Fatalf(ctx, "get cli configuration file:%v, err:%+v", cliFolderName, err) + } + if path == "" { + g.Log().Fatalf(ctx, "get cli configuration file:%v fail", cliFolderName) + } + if config == nil { + config = make(g.Map) + } + err = yaml.Unmarshal(gfile.GetBytes(path), &config) + if err != nil { + g.Log().Fatalf(ctx, "load cli configuration file:%v, yaml err:%+v", cliFolderName, err) + } + loadConfig(ctx) +} + +func loadConfig(ctx context.Context) { + if _, ok := config["gfcli"]; !ok { + g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli") + } + + if _, ok := config["gfcli"].(g.Map)["gen"]; !ok { + g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen") + } + + dao, ok := config["gfcli"].(g.Map)["gen"].(map[string]interface{})["dao"] + if !ok { + g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen.dao") + } + daoConf, ok := dao.([]interface{}) + if !ok { + g.Log().Fatalf(ctx, RequiredErrorTag, "gfcli.gen.dao format error") + } + daoConfig = daoConf + for _, v := range daoConfig { + if _, ok := v.(g.Map)["group"].(string); !ok { + g.Log().Fatalf(ctx, "group must be configured in %s: `gfcli.gen.dao` and must be the same as the database group", cliFolderName) + } + } + + if serviceConf, ok := config["gfcli"].(g.Map)["gen"].(map[string]interface{})["service"]; ok { + if serviceConfig == nil { + serviceConfig = make(g.Map) + } + serviceConfig = serviceConf.(g.Map) + } +} diff --git a/server/internal/library/hggen/internal/cmd/cmd.go b/server/internal/library/hggen/internal/cmd/cmd.go new file mode 100644 index 0000000..d3668ef --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd.go @@ -0,0 +1,79 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package cmd provides the management of CLI commands for `gf` tool. +package cmd + +import ( + "context" + "strings" + + "github.com/gogf/gf/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/service" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +// GF is the management object for `gf` command line tool. +var GF = cGF{} + +type cGF struct { + g.Meta `name:"gf" ad:"{cGFAd}"` +} + +const ( + cGFAd = ` +ADDITIONAL + Use "gf COMMAND -h" for details about a command. +` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cGFAd`: cGFAd, + }) +} + +type cGFInput struct { + g.Meta `name:"gf"` + Yes bool `short:"y" name:"yes" brief:"all yes for all command without prompt ask" orphan:"true"` + Version bool `short:"v" name:"version" brief:"show version information of current binary" orphan:"true"` + Debug bool `short:"d" name:"debug" brief:"show internal detailed debugging information" orphan:"true"` +} + +type cGFOutput struct{} + +func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error) { + // Version. + if in.Version { + _, err = Version.Index(ctx, cVersionInput{}) + return + } + + answer := "n" + // No argument or option, do installation checks. + if data, isInstalled := service.Install.IsInstalled(); !isInstalled { + mlog.Print("hi, it seems it's the first time you installing gf cli.") + answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION) + } else if !data.IsSelf { + mlog.Print("hi, you have installed gf cli.") + answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION) + } + if strings.EqualFold(answer, "y") { + if err = service.Install.Run(ctx); err != nil { + return + } + gcmd.Scan("press `Enter` to exit...") + return + } + + // Print help content. + gcmd.CommandFromCtx(ctx).Print() + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_build.go b/server/internal/library/hggen/internal/cmd/cmd_build.go new file mode 100644 index 0000000..7a7e245 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_build.go @@ -0,0 +1,399 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "encoding/json" + "fmt" + "os" + "regexp" + "runtime" + "strings" + + "github.com/gogf/gf/v2/encoding/gbase64" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gbuild" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/genv" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var Build = cBuild{ + nodeNameInConfigFile: "gfcli.build", + packedGoFileName: "internal/packed/build_pack_data.go", +} + +type cBuild struct { + g.Meta `name:"build" brief:"{cBuildBrief}" dc:"{cBuildDc}" eg:"{cBuildEg}" ad:"{cBuildAd}"` + nodeNameInConfigFile string // nodeNameInConfigFile is the node name for compiler configurations in configuration file. + packedGoFileName string // packedGoFileName specifies the file name for packing common folders into one single go file. +} + +const ( + cBuildDefaultFile = "main.go" + cBuildBrief = `cross-building go project for lots of platforms` + cBuildEg = ` +gf build main.go +gf build main.go --ps public,template +gf build main.go --cgo +gf build main.go -m none +gf build main.go -n my-app -a all -s all +gf build main.go -n my-app -a amd64,386 -s linux -p . +gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin +` + cBuildDc = ` +The "build" command is most commonly used command, which is designed as a powerful wrapper for +"go build" command for convenience cross-compiling usage. +It provides much more features for building binary: +1. Cross-Compiling for many platforms and architectures. +2. Configuration file support for compiling. +3. Build-In Variables. +` + cBuildAd = ` +PLATFORMS + aix ppc64 + android 386,amd64,arm,arm64 + darwin amd64,arm64 + dragonfly amd64 + freebsd 386,amd64,arm + illumos amd64 + ios arm64 + js wasm + linux 386,amd64,arm,arm64,loong64,mips,mipsle,mips64,mips64le,ppc64,ppc64le,riscv64,s390x + netbsd 386,amd64,arm + openbsd 386,amd64,arm,arm64 + plan9 386,amd64,arm + solaris amd64 + wasip1 wasm + windows 386,amd64,arm,arm64 +` + // https://golang.google.cn/doc/install/source + cBuildPlatforms = ` +aix ppc64 +android 386 +android amd64 +android arm +android arm64 +darwin amd64 +darwin arm64 +dragonfly amd64 +freebsd 386 +freebsd amd64 +freebsd arm +illumos amd64 +ios arm64 +js wasm +linux 386 +linux amd64 +linux arm +linux arm64 +linux loong64 +linux mips +linux mipsle +linux mips64 +linux mips64le +linux ppc64 +linux ppc64le +linux riscv64 +linux s390x +netbsd 386 +netbsd amd64 +netbsd arm +openbsd 386 +openbsd amd64 +openbsd arm +openbsd arm64 +plan9 386 +plan9 amd64 +plan9 arm +solaris amd64 +wasip1 wasm +windows 386 +windows amd64 +windows arm +windows arm64 +` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cBuildBrief`: cBuildBrief, + `cBuildDc`: cBuildDc, + `cBuildEg`: cBuildEg, + `cBuildAd`: cBuildAd, + }) +} + +type cBuildInput struct { + g.Meta `name:"build" config:"gfcli.build"` + File string `name:"FILE" arg:"true" brief:"building file path"` + Name string `short:"n" name:"name" brief:"output binary name"` + Version string `short:"v" name:"version" brief:"output binary version"` + Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"` + System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"` + Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"` + Path string `short:"p" name:"path" brief:"output binary directory path, default is '.'" d:"."` + Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"` + Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"` + Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"` + VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"` + PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"` + PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"` + ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, specially for multiple arch and system buildings. default is false" orphan:"true"` + DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"` +} + +type cBuildOutput struct{} + +func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) { + mlog.SetHeaderPrint(true) + + mlog.Debugf(`build command input: %+v`, in) + // Necessary check. + if gproc.SearchBinary("go") == "" { + mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`) + } + + var ( + parser = gcmd.ParserFromCtx(ctx) + file = in.File + ) + if file == "" { + file = parser.GetArg(2).String() + // Check and use the main.go file. + if gfile.Exists(cBuildDefaultFile) { + file = cBuildDefaultFile + } else { + mlog.Fatal("build file path is empty or main.go not found in current working directory") + } + } + if in.Name == "" { + in.Name = gfile.Name(file) + } + if len(in.Name) < 1 || in.Name == "*" { + mlog.Fatal("name cannot be empty") + } + if in.Mod != "" && in.Mod != "none" { + mlog.Debugf(`mod is %s`, in.Mod) + if in.Extra == "" { + in.Extra = fmt.Sprintf(`-mod=%s`, in.Mod) + } else { + in.Extra = fmt.Sprintf(`-mod=%s %s`, in.Mod, in.Extra) + } + } + if in.Extra != "" { + in.Extra += " " + } + var ( + customSystems = gstr.SplitAndTrim(in.System, ",") + customArches = gstr.SplitAndTrim(in.Arch, ",") + ) + if len(in.Version) > 0 { + in.Path += "/" + in.Version + } + // System and arch checks. + var ( + spaceRegex = regexp.MustCompile(`\s+`) + platformMap = make(map[string]map[string]bool) + ) + for _, line := range strings.Split(strings.TrimSpace(cBuildPlatforms), "\n") { + line = gstr.Trim(line) + line = spaceRegex.ReplaceAllString(line, " ") + var ( + array = strings.Split(line, " ") + system = strings.TrimSpace(array[0]) + arch = strings.TrimSpace(array[1]) + ) + if platformMap[system] == nil { + platformMap[system] = make(map[string]bool) + } + platformMap[system][arch] = true + } + // Auto packing. + if in.PackSrc != "" { + if in.PackDst == "" { + mlog.Fatal(`parameter "packDst" should not be empty when "packSrc" is used`) + } + if gfile.Exists(in.PackDst) && !gfile.IsFile(in.PackDst) { + mlog.Fatalf(`parameter "packDst" path "%s" should be type of file not directory`, in.PackDst) + } + if !gfile.Exists(in.PackDst) { + // Remove the go file that is automatically packed resource. + defer func() { + _ = gfile.RemoveFile(in.PackDst) + mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst) + }() + } + // remove black space in separator. + in.PackSrc, _ = gregex.ReplaceString(`,\s+`, `,`, in.PackSrc) + packCmd := fmt.Sprintf(`gf pack %s %s --keepPath=true`, in.PackSrc, in.PackDst) + mlog.Print(packCmd) + gproc.MustShellRun(ctx, packCmd) + } + + // Injected information by building flags. + ldFlags := fmt.Sprintf( + `-X 'github.com/gogf/gf/v2/os/gbuild.builtInVarStr=%v'`, + c.getBuildInVarStr(ctx, in), + ) + + // start building + mlog.Print("start building...") + if in.Cgo { + genv.MustSet("CGO_ENABLED", "1") + } else { + genv.MustSet("CGO_ENABLED", "0") + } + // print used go env + if in.DumpENV { + _, _ = Env.Index(ctx, cEnvInput{}) + } + for system, item := range platformMap { + if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) { + continue + } + for arch := range item { + if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) { + continue + } + if len(customSystems) == 0 && len(customArches) == 0 { + // Single binary building, output the binary to current working folder. + // For example: + // `gf build` + // `gf build -o main.exe` + c.doBinaryBuild( + ctx, file, + in.Output, in.Path, + runtime.GOOS, runtime.GOARCH, in.Name, ldFlags, in.Extra, + in.ExitWhenError, + true, + ) + } else { + c.doBinaryBuild( + ctx, file, + in.Output, in.Path, + system, arch, in.Name, ldFlags, in.Extra, + in.ExitWhenError, + false, + ) + } + // single binary building. + if len(customSystems) == 0 && len(customArches) == 0 { + goto buildDone + } + } + } + +buildDone: + mlog.Print("done!") + return +} + +func (c cBuild) doBinaryBuild( + ctx context.Context, + filePath string, + outputPath, dirPath string, + system, arch, name, ldFlags, extra string, + exitWhenError bool, + singleBuild bool, +) { + var ( + cmd string + ext string + ) + // Cross-building, output the compiled binary to specified path. + if system == "windows" { + ext = ".exe" + } + genv.MustSet("GOOS", system) + genv.MustSet("GOARCH", arch) + + if outputPath != "" { + outputPath = "-o " + outputPath + } else { + if dirPath == "" { + dirPath = "." + } else { + dirPath = gstr.TrimRight(dirPath, "/") + } + if singleBuild { + outputPath = fmt.Sprintf( + "-o %s/%s%s", + dirPath, name, ext, + ) + } else { + outputPath = fmt.Sprintf( + "-o %s/%s/%s%s", + dirPath, system+"_"+arch, name, ext, + ) + } + } + cmd = fmt.Sprintf( + `go build %s -ldflags "%s" %s%s`, + outputPath, ldFlags, extra, filePath, + ) + mlog.Debug(fmt.Sprintf("build for GOOS=%s GOARCH=%s", system, arch)) + mlog.Debug(cmd) + // It's not necessary printing the complete command string, filtering ldFlags. + cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd) + mlog.Print(cmdShow) + if result, err := gproc.ShellExec(ctx, cmd); err != nil { + mlog.Printf( + "failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n", + system, arch, gstr.Trim(result), + `you may use command option "--debug" to enable debug info and check the details`, + ) + if exitWhenError { + os.Exit(1) + } + } else { + mlog.Debug(gstr.Trim(result)) + } +} + +// getBuildInVarStr retrieves and returns the custom build-in variables in configuration +// file as json. +func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string { + buildInVarMap := in.VarMap + if buildInVarMap == nil { + buildInVarMap = make(g.Map) + } + buildInVarMap[gbuild.BuiltGit] = c.getGitCommit(ctx) + buildInVarMap[gbuild.BuiltTime] = gtime.Now().String() + buildInVarMap[gbuild.BuiltVersion] = in.Version + b, err := json.Marshal(buildInVarMap) + if err != nil { + mlog.Fatal(err) + } + return gbase64.EncodeToString(b) +} + +// getGitCommit retrieves and returns the latest git commit hash string if present. +func (c cBuild) getGitCommit(ctx context.Context) string { + if gproc.SearchBinary("git") == "" { + return "" + } + var ( + cmd = `git log -1 --format="%cd %H" --date=format:"%Y-%m-%d %H:%M:%S"` + s, _ = gproc.ShellExec(ctx, cmd) + ) + mlog.Debug(cmd) + if s != "" { + if !gstr.Contains(s, "fatal") { + return gstr.Trim(s) + } + } + return "" +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_doc.go b/server/internal/library/hggen/internal/cmd/cmd_doc.go new file mode 100644 index 0000000..163732d --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_doc.go @@ -0,0 +1,184 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "path" + "path/filepath" + "time" + + "github.com/gogf/gf/v2/encoding/gcompress" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +const ( + GitName = "gf-site" + BranchName = "gh-pages" + + SiteFileName = GitName + "-" + BranchName + // DocURL is the download address of the document + DocURL = "https://github.com/gogf/" + GitName + "/archive/refs/heads/" + BranchName + ".zip" +) + +var ( + Doc = cDoc{} +) + +type cDoc struct { + g.Meta `name:"doc" brief:"download https://pages.goframe.org/ to run locally"` +} + +type cDocInput struct { + g.Meta `name:"doc" config:"gfcli.doc"` + Path string `short:"p" name:"path" brief:"download docs directory path, default is \"%temp%/goframe\""` + Port int `short:"o" name:"port" brief:"http server port, default is 8080" d:"8080"` + Update bool `short:"u" name:"update" brief:"clean docs directory and update docs"` + Clean bool `short:"c" name:"clean" brief:"clean docs directory"` + Proxy string `short:"x" name:"proxy" brief:"proxy for download, such as https://hub.gitmirror.com/;https://ghproxy.com/;https://ghproxy.net/;https://ghps.cc/"` +} + +type cDocOutput struct{} + +func (c cDoc) Index(ctx context.Context, in cDocInput) (out *cDocOutput, err error) { + docs := NewDocSetting(ctx, in) + mlog.Print("Directory where the document is downloaded:", docs.TempDir) + if in.Clean { + mlog.Print("Cleaning document directory") + err = docs.Clean() + if err != nil { + mlog.Print("Failed to clean document directory:", err) + return + } + return + } + if in.Update { + mlog.Print("Cleaning old document directory") + err = docs.Clean() + if err != nil { + mlog.Print("Failed to clean old document directory:", err) + return + } + } + err = docs.DownloadDoc() + if err != nil { + mlog.Print("Failed to download document:", err) + return + } + + http.Handle("/", http.FileServer(http.Dir(docs.DocDir))) + mlog.Printf("Access address http://127.0.0.1:%d in %s", in.Port, docs.DocDir) + err = http.ListenAndServe(fmt.Sprintf(":%d", in.Port), nil) + if err != nil { + return nil, err + } + return +} + +// DocSetting doc setting +type DocSetting struct { + TempDir string + DocURL string + DocDir string + DocZipFile string +} + +// NewDocSetting new DocSetting +func NewDocSetting(ctx context.Context, in cDocInput) *DocSetting { + fileName := SiteFileName + ".zip" + tempDir := in.Path + if tempDir == "" { + tempDir = gfile.Temp("goframe/docs") + } else { + tempDir = gfile.Abs(path.Join(tempDir, "docs")) + } + + return &DocSetting{ + TempDir: filepath.FromSlash(tempDir), + DocDir: filepath.FromSlash(path.Join(tempDir, SiteFileName)), + DocURL: in.Proxy + DocURL, + DocZipFile: filepath.FromSlash(path.Join(tempDir, fileName)), + } + +} + +// Clean cleans the temporary directory +func (d *DocSetting) Clean() error { + if _, err := os.Stat(d.TempDir); err == nil { + err = gfile.RemoveAll(d.TempDir) + if err != nil { + mlog.Print("Failed to delete temporary directory:", err) + return err + } + } + return nil +} + +// DownloadDoc download the document +func (d *DocSetting) DownloadDoc() error { + if _, err := os.Stat(d.TempDir); err != nil { + err = gfile.Mkdir(d.TempDir) + if err != nil { + mlog.Print("Failed to create temporary directory:", err) + return nil + } + } + // Check if the file exists + if _, err := os.Stat(d.DocDir); err == nil { + mlog.Print("Document already exists, no need to download and unzip") + return nil + } + + if _, err := os.Stat(d.DocZipFile); err == nil { + mlog.Print("File already exists, no need to download") + } else { + mlog.Printf("File does not exist, start downloading: %s", d.DocURL) + startTime := time.Now() + // Download the file + resp, err := http.Get(d.DocURL) + if err != nil { + mlog.Print("Failed to download file:", err) + return err + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create(d.DocZipFile) + if err != nil { + mlog.Print("Failed to create file:", err) + return err + } + defer out.Close() + + // Write the response body to the file + _, err = io.Copy(out, resp.Body) + if err != nil { + mlog.Print("Failed to write file:", err) + return err + } + mlog.Printf("Download successful, time-consuming: %v", time.Since(startTime)) + } + + mlog.Print("Start unzipping the file...") + // Unzip the file + err := gcompress.UnZipFile(d.DocZipFile, d.TempDir) + if err != nil { + mlog.Print("Failed to unzip the file, please run again:", err) + _ = gfile.RemoveFile(d.DocZipFile) + return err + } + + mlog.Print("Download and unzip successful") + return nil +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_docker.go b/server/internal/library/hggen/internal/cmd/cmd_docker.go new file mode 100644 index 0000000..3b53e74 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_docker.go @@ -0,0 +1,179 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "fmt" + "runtime" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Docker = cDocker{} +) + +type cDocker struct { + g.Meta `name:"docker" usage:"{cDockerUsage}" brief:"{cDockerBrief}" eg:"{cDockerEg}" dc:"{cDockerDc}"` +} + +const ( + cDockerUsage = `gf docker [MAIN] [OPTION]` + cDockerBrief = `build docker image for current GoFrame project` + cDockerEg = ` +gf docker +gf docker -t hub.docker.com/john/image:tag +gf docker -p -t hub.docker.com/john/image:tag +gf docker main.go +gf docker main.go -t hub.docker.com/john/image:tag +gf docker main.go -t hub.docker.com/john/image:tag +gf docker main.go -p -t hub.docker.com/john/image:tag +gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag +` + cDockerDc = ` +The "docker" command builds the GF project to a docker images. +It runs "gf build" firstly to compile the project to binary file. +It then runs "docker build" command automatically to generate the docker image. +You should have docker installed, and there must be a Dockerfile in the root of the project. +` + cDockerMainBrief = `main file path for "gf build", it's "main.go" in default. empty string for no binary build` + cDockerBuildBrief = `binary build options before docker image build, it's "-a amd64 -s linux" in default` + cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default` + cDockerShellBrief = `path of the shell file which is executed before docker build` + cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed` + cDockerTagBrief = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"` + cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes` + cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName` + cDockerExtraBrief = `extra build options passed to "docker image"` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cDockerUsage`: cDockerUsage, + `cDockerBrief`: cDockerBrief, + `cDockerEg`: cDockerEg, + `cDockerDc`: cDockerDc, + `cDockerMainBrief`: cDockerMainBrief, + `cDockerFileBrief`: cDockerFileBrief, + `cDockerShellBrief`: cDockerShellBrief, + `cDockerBuildBrief`: cDockerBuildBrief, + `cDockerPushBrief`: cDockerPushBrief, + `cDockerTagBrief`: cDockerTagBrief, + `cDockerTagNameBrief`: cDockerTagNameBrief, + `cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief, + `cDockerExtraBrief`: cDockerExtraBrief, + }) +} + +type cDockerInput struct { + g.Meta `name:"docker" config:"gfcli.docker"` + Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"` + File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"` + Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"` + Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}"` + Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"` + TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"` + TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"` + Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"` + Extra string `name:"extra" short:"e" brief:"{cDockerExtraBrief}"` +} + +type cDockerOutput struct{} + +func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput, err error) { + // Necessary check. + if gproc.SearchBinary("docker") == "" { + mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`) + } + + mlog.Debugf(`docker command input: %+v`, in) + + // Binary build. + if in.Main != "" && in.Build != "" { + in.Build += " --exitWhenError" + if in.Main != "" { + if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil { + mlog.Debugf(`build binary failed with error: %+v`, err) + return + } + } + } + + // Shell executing. + if in.Shell != "" && gfile.Exists(in.Shell) { + if err = c.exeDockerShell(ctx, in.Shell); err != nil { + mlog.Debugf(`build docker failed with error: %+v`, err) + return + } + } + // Docker build. + var ( + dockerBuildOptions string + dockerTags []string + dockerTagBase string + ) + if len(in.TagPrefixes) > 0 { + for _, tagPrefix := range in.TagPrefixes { + tagPrefix = gstr.TrimRight(tagPrefix, "/") + dockerTags = append(dockerTags, fmt.Sprintf(`%s/%s`, tagPrefix, in.TagName)) + } + } + if len(dockerTags) == 0 { + dockerTags = []string{in.Tag} + } + for i, dockerTag := range dockerTags { + if i > 0 { + err = gproc.ShellRun(ctx, fmt.Sprintf(`docker tag %s %s`, dockerTagBase, dockerTag)) + if err != nil { + return + } + continue + } + dockerTagBase = dockerTag + dockerBuildOptions = "" + if dockerTag != "" { + dockerBuildOptions = fmt.Sprintf(`-t %s`, dockerTag) + } + if in.Extra != "" { + dockerBuildOptions = fmt.Sprintf(`%s %s`, dockerBuildOptions, in.Extra) + } + err = gproc.ShellRun(ctx, fmt.Sprintf(`docker build -f %s . %s`, in.File, dockerBuildOptions)) + if err != nil { + return + } + } + + // Docker push. + if !in.Push { + return + } + for _, dockerTag := range dockerTags { + if dockerTag == "" { + continue + } + err = gproc.ShellRun(ctx, fmt.Sprintf(`docker push %s`, dockerTag)) + if err != nil { + return + } + } + return +} + +func (c cDocker) exeDockerShell(ctx context.Context, shellFilePath string) error { + if gfile.ExtName(shellFilePath) == "sh" && runtime.GOOS == "windows" { + mlog.Debugf(`ignore shell file "%s", as it cannot be run on windows system`, shellFilePath) + return nil + } + return gproc.ShellRun(ctx, gfile.GetContents(shellFilePath)) +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_env.go b/server/internal/library/hggen/internal/cmd/cmd_env.go new file mode 100644 index 0000000..88371cb --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_env.go @@ -0,0 +1,85 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "bytes" + "context" + + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Env = cEnv{} +) + +type cEnv struct { + g.Meta `name:"env" brief:"show current Golang environment variables"` +} + +type cEnvInput struct { + g.Meta `name:"env"` +} + +type cEnvOutput struct{} + +func (c cEnv) Index(ctx context.Context, in cEnvInput) (out *cEnvOutput, err error) { + result, err := gproc.ShellExec(ctx, "go env") + if err != nil { + mlog.Fatal(err) + } + if result == "" { + mlog.Fatal(`retrieving Golang environment variables failed, did you install Golang?`) + } + var ( + lines = gstr.Split(result, "\n") + buffer = bytes.NewBuffer(nil) + ) + array := make([][]string, 0) + for _, line := range lines { + line = gstr.Trim(line) + if line == "" { + continue + } + if gstr.Pos(line, "set ") == 0 { + line = line[4:] + } + match, _ := gregex.MatchString(`(.+?)=(.*)`, line) + if len(match) < 3 { + mlog.Fatalf(`invalid Golang environment variable: "%s"`, line) + } + array = append(array, []string{gstr.Trim(match[1]), gstr.Trim(match[2])}) + } + table := tablewriter.NewTable(buffer, + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.On}, + }, + Symbols: tw.NewSymbols(tw.StyleASCII), + })), + tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone}, + Alignment: tw.CellAlignment{PerColumn: []tw.Align{tw.AlignLeft, tw.AlignLeft}}, + ColMaxWidths: tw.CellWidth{Global: 84}, + }, + }), + ) + table.Bulk(array) + table.Render() + mlog.Print(buffer.String()) + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_fix.go b/server/internal/library/hggen/internal/cmd/cmd_fix.go new file mode 100644 index 0000000..0a338aa --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_fix.go @@ -0,0 +1,152 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Fix = cFix{} +) + +type cFix struct { + g.Meta `name:"fix" brief:"auto fixing codes after upgrading to new GoFrame version" usage:"gf fix" ` +} + +type cFixInput struct { + g.Meta `name:"fix"` + Path string `name:"path" short:"p" brief:"directory path, it uses current working directory in default"` + Version string `name:"version" short:"v" brief:"custom specified version to fix, leave it empty to auto detect"` +} + +type cFixOutput struct{} + +type cFixItem struct { + Version string + Func func(version string) error +} + +func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) { + + if in.Path == "" { + in.Path = gfile.Pwd() + } + if in.Version == "" { + in.Version, err = c.autoDetectVersion(in) + if err != nil { + mlog.Fatal(err) + } + if in.Version == "" { + mlog.Print(`no GoFrame usage found, exit fixing`) + return + } + mlog.Debugf(`current GoFrame version auto detect "%s"`, in.Version) + } + + if !gproc.IsChild() { + mlog.Printf(`start auto fixing directory path "%s"...`, in.Path) + defer mlog.Print(`done!`) + } + + err = c.doFix(in) + return +} + +func (c cFix) doFix(in cFixInput) (err error) { + + var items = []cFixItem{ + {Version: "v2.3", Func: c.doFixV23}, + {Version: "v2.5", Func: c.doFixV25}, + } + for _, item := range items { + if gstr.CompareVersionGo(in.Version, item.Version) < 0 { + mlog.Debugf( + `current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`, + in.Version, item.Version, + ) + continue + } + if err = item.Func(in.Version); err != nil { + return + } + } + return +} + +// doFixV23 fixes code when upgrading to GoFrame v2.3. +func (c cFix) doFixV23(version string) error { + replaceFunc := func(path, content string) string { + // gdb.TX from struct to interface. + content = gstr.Replace(content, "*gdb.TX", "gdb.TX") + // function name changes for package gtcp/gudp. + if gstr.Contains(content, "/gf/v2/net/gtcp") || gstr.Contains(content, "/gf/v2/net/gudp") { + content = gstr.ReplaceByMap(content, g.MapStrStr{ + ".SetSendDeadline": ".SetDeadlineSend", + ".SetReceiveDeadline": ".SetDeadlineRecv", + ".SetReceiveBufferWait": ".SetBufferWaitRecv", + }) + } + return content + } + return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true) +} + +// doFixV25 fixes code when upgrading to GoFrame v2.5. +func (c cFix) doFixV25(version string) (err error) { + replaceFunc := func(path, content string) string { + content, err = c.doFixV25Content(content) + return content + } + return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true) +} + +func (c cFix) doFixV25Content(content string) (newContent string, err error) { + newContent = content + if gstr.Contains(content, `.BindHookHandlerByMap(`) { + var pattern = `\.BindHookHandlerByMap\((.+?), map\[string\]ghttp\.HandlerFunc` + newContent, err = gregex.ReplaceString( + pattern, + `.BindHookHandlerByMap($1, map[ghttp.HookName]ghttp.HandlerFunc`, + content, + ) + } + return +} + +func (c cFix) autoDetectVersion(in cFixInput) (string, error) { + var ( + err error + path = gfile.Join(in.Path, "go.mod") + version string + ) + if !gfile.Exists(path) { + return "", gerror.Newf(`"%s" not found in current working directory`, path) + } + err = gfile.ReadLines(path, func(line string) error { + array := gstr.SplitAndTrim(line, " ") + if len(array) > 0 { + if gstr.HasPrefix(array[0], gfPackage) { + version = array[1] + } + } + return nil + }) + if err != nil { + mlog.Fatal(err) + } + return version, nil +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen.go b/server/internal/library/hggen/internal/cmd/cmd_gen.go new file mode 100644 index 0000000..10f03da --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen.go @@ -0,0 +1,42 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gtag" +) + +var ( + Gen = cGen{} +) + +type cGen struct { + g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"` + cGenDao + cGenEnums + cGenCtrl + cGenPb + cGenPbEntity + cGenService +} + +const ( + cGenBrief = `automatically generate go files for dao/do/entity/pb/pbentity` + cGenDc = ` +The "gen" command is designed for multiple generating purposes. +It's currently supporting generating go files for ORM models, protobuf and protobuf entity files. +Please use "gf gen dao -h" for specified type help. +` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cGenBrief`: cGenBrief, + `cGenDc`: cGenDc, + }) +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go b/server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go new file mode 100644 index 0000000..cd116ac --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen_ctrl.go @@ -0,0 +1,15 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "hotgo/internal/library/hggen/internal/cmd/genctrl" +) + +type ( + cGenCtrl = genctrl.CGenCtrl +) diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_dao.go b/server/internal/library/hggen/internal/cmd/cmd_gen_dao.go new file mode 100644 index 0000000..2781da5 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen_dao.go @@ -0,0 +1,25 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +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" + + // do not add dm in cli pre-compilation, + // the dm driver does not support certain target platforms. + // _ "github.com/gogf/gf/contrib/drivers/dm/v2" + "hotgo/internal/library/hggen/internal/cmd/gendao" +) + +type ( + cGenDao = gendao.CGenDao +) diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_enums.go b/server/internal/library/hggen/internal/cmd/cmd_gen_enums.go new file mode 100644 index 0000000..35fc3b6 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen_enums.go @@ -0,0 +1,15 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "hotgo/internal/library/hggen/internal/cmd/genenums" +) + +type ( + cGenEnums = genenums.CGenEnums +) diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_pb.go b/server/internal/library/hggen/internal/cmd/cmd_gen_pb.go new file mode 100644 index 0000000..1833d89 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen_pb.go @@ -0,0 +1,13 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import "hotgo/internal/library/hggen/internal/cmd/genpb" + +type ( + cGenPb = genpb.CGenPb +) diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go b/server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go new file mode 100644 index 0000000..58b26d8 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen_pbentity.go @@ -0,0 +1,13 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import "hotgo/internal/library/hggen/internal/cmd/genpbentity" + +type ( + cGenPbEntity = genpbentity.CGenPbEntity +) diff --git a/server/internal/library/hggen/internal/cmd/cmd_gen_service.go b/server/internal/library/hggen/internal/cmd/cmd_gen_service.go new file mode 100644 index 0000000..33b5689 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_gen_service.go @@ -0,0 +1,15 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "hotgo/internal/library/hggen/internal/cmd/genservice" +) + +type ( + cGenService = genservice.CGenService +) diff --git a/server/internal/library/hggen/internal/cmd/cmd_init.go b/server/internal/library/hggen/internal/cmd/cmd_init.go new file mode 100644 index 0000000..319f730 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_init.go @@ -0,0 +1,182 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/os/gres" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/allyes" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +var ( + // Init . + Init = cInit{} +) + +type cInit struct { + g.Meta `name:"init" brief:"{cInitBrief}" eg:"{cInitEg}"` +} + +const ( + cInitRepoPrefix = `github.com/gogf/` + cInitMonoRepo = `template-mono` + cInitMonoRepoApp = `template-mono-app` + cInitSingleRepo = `template-single` + cInitBrief = `create and initialize an empty GoFrame project` + cInitEg = ` +gf init my-project +gf init my-mono-repo -m +gf init my-mono-repo -a +` + cInitNameBrief = ` +name for the project. It will create a folder with NAME in current directory. +The NAME will also be the module name for the project. +` + // cInitGitDir the git directory + cInitGitDir = ".git" + // cInitGitignore the gitignore file + cInitGitignore = ".gitignore" +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cInitBrief`: cInitBrief, + `cInitEg`: cInitEg, + `cInitNameBrief`: cInitNameBrief, + }) +} + +type cInitInput struct { + g.Meta `name:"init"` + Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"` + Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"` + MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app instead a single-repo" orphan:"true"` + Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"` + Module string `name:"module" short:"g" brief:"custom go module"` +} + +type cInitOutput struct{} + +func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) { + var overwrote = false + if !gfile.IsEmpty(in.Name) && !allyes.Check() { + s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name) + if strings.EqualFold(s, "n") { + return + } + overwrote = true + } + mlog.Print("initializing...") + + // Create project folder and files. + var ( + templateRepoName string + gitignoreFile = in.Name + "/" + cInitGitignore + ) + + if in.Mono { + templateRepoName = cInitMonoRepo + } else if in.MonoApp { + templateRepoName = cInitMonoRepoApp + } else { + templateRepoName = cInitSingleRepo + } + + err = gres.Export(templateRepoName, in.Name, gres.ExportOption{ + RemovePrefix: templateRepoName, + }) + if err != nil { + return + } + + // build ignoreFiles from the .gitignore file + ignoreFiles := make([]string, 0, 10) + ignoreFiles = append(ignoreFiles, cInitGitDir) + // in.MonoApp is a mono-repo-app, it should ignore the .gitignore file + if overwrote && !in.MonoApp { + err = gfile.ReadLines(gitignoreFile, func(line string) error { + // Add only hidden files or directories + // If other directories are added, it may cause the entire directory to be ignored + // such as 'main' in the .gitignore file, but the path is ' D:\main\my-project ' + if line != "" && strings.HasPrefix(line, ".") { + ignoreFiles = append(ignoreFiles, line) + } + return nil + }) + + // if not found the .gitignore file will skip os.ErrNotExist error + if err != nil && !os.IsNotExist(err) { + return + } + } + + // Get template name and module name. + if in.Module == "" { + in.Module = gfile.Basename(gfile.RealPath(in.Name)) + } + if in.MonoApp { + pwd := gfile.Pwd() + string(os.PathSeparator) + in.Name + in.Module = utils.GetImportPath(pwd) + } + + // Replace template name to project name. + err = gfile.ReplaceDirFunc(func(path, content string) string { + for _, ignoreFile := range ignoreFiles { + if strings.Contains(path, ignoreFile) { + return content + } + } + return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, in.Module) + }, in.Name, "*", true) + if err != nil { + return + } + + // Update the GoFrame version. + if in.Update { + mlog.Print("update goframe...") + // go get -u github.com/gogf/gf/v2@latest + updateCommand := `go get -u github.com/gogf/gf/v2@latest` + if in.Name != "." { + updateCommand = fmt.Sprintf(`cd %s && %s`, in.Name, updateCommand) + } + if err = gproc.ShellRun(ctx, updateCommand); err != nil { + mlog.Fatal(err) + } + // go mod tidy + gomModTidyCommand := `go mod tidy` + if in.Name != "." { + gomModTidyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, gomModTidyCommand) + } + if err = gproc.ShellRun(ctx, gomModTidyCommand); err != nil { + mlog.Fatal(err) + } + } + + mlog.Print("initialization done! ") + if !in.Mono { + enjoyCommand := `gf run main.go` + if in.Name != "." { + enjoyCommand = fmt.Sprintf(`cd %s && %s`, in.Name, enjoyCommand) + } + mlog.Printf(`you can now run "%s" to start your journey, enjoy!`, enjoyCommand) + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_install.go b/server/internal/library/hggen/internal/cmd/cmd_install.go new file mode 100644 index 0000000..0dd30ad --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_install.go @@ -0,0 +1,34 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + + "hotgo/internal/library/hggen/internal/service" +) + +var ( + Install = cInstall{} +) + +type cInstall struct { + g.Meta `name:"install" brief:"install gf binary to system (might need root/admin permission)"` +} + +type cInstallInput struct { + g.Meta `name:"install"` +} + +type cInstallOutput struct{} + +func (c cInstall) Index(ctx context.Context, in cInstallInput) (out *cInstallOutput, err error) { + err = service.Install.Run(ctx) + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_pack.go b/server/internal/library/hggen/internal/cmd/cmd_pack.go new file mode 100644 index 0000000..b6addd0 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_pack.go @@ -0,0 +1,104 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "strings" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/allyes" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Pack = cPack{} +) + +type cPack struct { + g.Meta `name:"pack" usage:"{cPackUsage}" brief:"{cPackBrief}" eg:"{cPackEg}"` +} + +const ( + cPackUsage = `gf pack SRC DST` + cPackBrief = `packing any file/directory to a resource file, or a go file` + cPackEg = ` +gf pack public data.bin +gf pack public,template data.bin +gf pack public,template packed/data.go +gf pack public,template,config packed/data.go +gf pack public,template,config packed/data.go -n=packed -p=/var/www/my-app +gf pack /var/www/public packed/data.go -n=packed +` + cPackSrcBrief = `source path for packing, which can be multiple source paths.` + cPackDstBrief = ` +destination file path for packed file. if extension of the filename is ".go" and "-n" option is given, +it enables packing SRC to go file, or else it packs SRC into a binary file. +` + cPackNameBrief = `package name for output go file, it's set as its directory name if no name passed` + cPackPrefixBrief = `prefix for each file packed into the resource file` + cPackKeepPathBrief = `keep the source path from system to resource file, usually for relative path` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cPackUsage`: cPackUsage, + `cPackBrief`: cPackBrief, + `cPackEg`: cPackEg, + `cPackSrcBrief`: cPackSrcBrief, + `cPackDstBrief`: cPackDstBrief, + `cPackNameBrief`: cPackNameBrief, + `cPackPrefixBrief`: cPackPrefixBrief, + `cPackKeepPathBrief`: cPackKeepPathBrief, + }) +} + +type cPackInput struct { + g.Meta `name:"pack" config:"gfcli.pack"` + Src string `name:"SRC" arg:"true" v:"required" brief:"{cPackSrcBrief}"` + Dst string `name:"DST" arg:"true" v:"required" brief:"{cPackDstBrief}"` + Name string `name:"name" short:"n" brief:"{cPackNameBrief}"` + Prefix string `name:"prefix" short:"p" brief:"{cPackPrefixBrief}"` + KeepPath bool `name:"keepPath" short:"k" brief:"{cPackKeepPathBrief}" orphan:"true"` +} + +type cPackOutput struct{} + +func (c cPack) Index(ctx context.Context, in cPackInput) (out *cPackOutput, err error) { + if gfile.Exists(in.Dst) && gfile.IsDir(in.Dst) { + mlog.Fatalf("DST path '%s' cannot be a directory", in.Dst) + } + if !gfile.IsEmpty(in.Dst) && !allyes.Check() { + s := gcmd.Scanf("path '%s' is not empty, files might be overwrote, continue? [y/n]: ", in.Dst) + if strings.EqualFold(s, "n") { + return + } + } + if in.Name == "" && gfile.ExtName(in.Dst) == "go" { + in.Name = gfile.Basename(gfile.Dir(in.Dst)) + } + var option = gres.Option{ + Prefix: in.Prefix, + KeepPath: in.KeepPath, + } + if in.Name != "" { + if err = gres.PackToGoFileWithOption(in.Src, in.Dst, in.Name, option); err != nil { + mlog.Fatalf("pack failed: %v", err) + } + } else { + if err = gres.PackToFileWithOption(in.Src, in.Dst, option); err != nil { + mlog.Fatalf("pack failed: %v", err) + } + } + mlog.Print("done!") + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_run.go b/server/internal/library/hggen/internal/cmd/cmd_run.go new file mode 100644 index 0000000..7eef8fb --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_run.go @@ -0,0 +1,283 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/gogf/gf/v2/container/gtype" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gfsnotify" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var Run = cRun{} + +type cRun struct { + g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"` +} + +type cRunApp struct { + File string // Go run file name. + Path string // Directory storing built binary. + Options string // Extra "go run" options. + Args string // Custom arguments. + WatchPaths []string // Watch paths for live reload. +} + +const ( + cRunUsage = `gf run FILE [OPTION]` + cRunBrief = `running go codes with hot-compiled-like feature` + cRunEg = ` +gf run main.go +gf run main.go --args "server -p 8080" +gf run main.go -mod=vendor +gf run main.go -w "manifest/config/*.yaml" +` + cRunDc = ` +The "run" command is used for running go codes with hot-compiled-like feature, +which compiles and runs the go codes asynchronously when codes change. +` + cRunFileBrief = `building file path.` + cRunPathBrief = `output directory path for built binary file. it's "./" in default` + cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined` + cRunArgsBrief = `custom arguments for your process` + cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml"` +) + +var process *gproc.Process + +func init() { + gtag.Sets(g.MapStrStr{ + `cRunUsage`: cRunUsage, + `cRunBrief`: cRunBrief, + `cRunEg`: cRunEg, + `cRunDc`: cRunDc, + `cRunFileBrief`: cRunFileBrief, + `cRunPathBrief`: cRunPathBrief, + `cRunExtraBrief`: cRunExtraBrief, + `cRunArgsBrief`: cRunArgsBrief, + `cRunWatchPathsBrief`: cRunWatchPathsBrief, + }) +} + +type ( + cRunInput struct { + g.Meta `name:"run" config:"gfcli.run"` + File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"` + Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"` + Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"` + Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"` + WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"` + } + cRunOutput struct{} +) + +func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err error) { + if !gfile.Exists(in.File) { + mlog.Fatalf(`given file "%s" not found`, in.File) + } + if !gfile.IsFile(in.File) { + mlog.Fatalf(`given "%s" is not a file`, in.File) + } + // Necessary check. + if gproc.SearchBinary("go") == "" { + mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`) + } + + if len(in.WatchPaths) == 1 { + in.WatchPaths = strings.Split(in.WatchPaths[0], ",") + mlog.Printf("watchPaths: %v", in.WatchPaths) + } + + app := &cRunApp{ + File: in.File, + Path: filepath.FromSlash(in.Path), + Options: in.Extra, + Args: in.Args, + WatchPaths: in.WatchPaths, + } + dirty := gtype.NewBool() + + outputPath := app.genOutputPath() + callbackFunc := func(event *gfsnotify.Event) { + if !event.IsWrite() && !event.IsCreate() && !event.IsRemove() && !event.IsRename() { + return + } + + if gfile.ExtName(event.Path) != "go" { + return + } + + // Variable `dirty` is used for running the changes only one in one second. + if !dirty.Cas(false, true) { + return + } + + // With some delay in case of multiple code changes in very short interval. + gtimer.SetTimeout(ctx, 1500*gtime.MS, func(ctx context.Context) { + defer dirty.Set(false) + mlog.Printf(`watched file changes: %s`, event.String()) + app.Run(ctx, outputPath) + }) + } + + if len(app.WatchPaths) > 0 { + for _, path := range app.WatchPaths { + _, err = gfsnotify.Add(gfile.RealPath(path), callbackFunc) + if err != nil { + mlog.Fatal(err) + } + } + } else { + _, err = gfsnotify.Add(gfile.RealPath("."), callbackFunc) + if err != nil { + mlog.Fatal(err) + } + } + + go app.Run(ctx, outputPath) + + gproc.AddSigHandlerShutdown(func(sig os.Signal) { + app.End(ctx, sig, outputPath) + os.Exit(0) + }) + gproc.Listen() + + select {} +} + +func (app *cRunApp) Run(ctx context.Context, outputPath string) { + // Rebuild and run the codes. + mlog.Printf("build: %s", app.File) + + // In case of `pipe: too many open files` error. + // Build the app. + buildCommand := fmt.Sprintf( + `go build -o %s %s %s`, + outputPath, + app.Options, + app.File, + ) + mlog.Print(buildCommand) + result, err := gproc.ShellExec(ctx, buildCommand) + if err != nil { + mlog.Printf("build error: \n%s%s", result, err.Error()) + return + } + // Kill the old process if build successfully. + if process != nil { + if err := process.Kill(); err != nil { + mlog.Debugf("kill process error: %s", err.Error()) + } + } + // Run the binary file. + runCommand := fmt.Sprintf(`%s %s`, outputPath, app.Args) + mlog.Print(runCommand) + if runtime.GOOS == "windows" { + // Special handling for windows platform. + // DO NOT USE "cmd /c" command. + process = gproc.NewProcess(outputPath, strings.Fields(app.Args)) + } else { + process = gproc.NewProcessCmd(runCommand, nil) + } + if pid, err := process.Start(ctx); err != nil { + mlog.Printf("build running error: %s", err.Error()) + } else { + mlog.Printf("build running pid: %d", pid) + } +} + +func (app *cRunApp) End(ctx context.Context, sig os.Signal, outputPath string) { + // Delete the binary file. + // firstly, kill the process. + if process != nil { + if sig != nil && runtime.GOOS != "windows" { + if err := process.Signal(sig); err != nil { + mlog.Debugf("send signal to process error: %s", err.Error()) + if err := process.Kill(); err != nil { + mlog.Debugf("kill process error: %s", err.Error()) + } + } else { + waitCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + done := make(chan error, 1) + go func() { + select { + case <-waitCtx.Done(): + done <- waitCtx.Err() + case done <- process.Wait(): + } + }() + err := <-done + if err != nil { + mlog.Debugf("process wait error: %s", err.Error()) + if err := process.Kill(); err != nil { + mlog.Debugf("kill process error: %s", err.Error()) + } + } else { + mlog.Debug("process exited gracefully") + } + } + } else { + if err := process.Kill(); err != nil { + mlog.Debugf("kill process error: %s", err.Error()) + } + } + } + if err := gfile.RemoveFile(outputPath); err != nil { + mlog.Printf("delete binary file error: %s", err.Error()) + } else { + mlog.Printf("deleted binary file: %s", outputPath) + } +} + +func (app *cRunApp) genOutputPath() (outputPath string) { + var renamePath string + outputPath = gfile.Join(app.Path, gfile.Name(app.File)) + if runtime.GOOS == "windows" { + outputPath += ".exe" + if gfile.Exists(outputPath) { + renamePath = outputPath + "~" + if err := gfile.Rename(outputPath, renamePath); err != nil { + mlog.Print(err) + } + } + } + return filepath.FromSlash(outputPath) +} + +func matchWatchPaths(watchPaths []string, eventPath string) bool { + for _, path := range watchPaths { + absPath, err := filepath.Abs(path) + if err != nil { + mlog.Printf("match watchPath '%s' error: %s", path, err.Error()) + continue + } + matched, err := filepath.Match(absPath, eventPath) + if err != nil { + mlog.Printf("match watchPath '%s' error: %s", path, err.Error()) + continue + } + if matched { + return true + } + } + return false +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_tpl.go b/server/internal/library/hggen/internal/cmd/cmd_tpl.go new file mode 100644 index 0000000..4c132e5 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_tpl.go @@ -0,0 +1,174 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gtag" + "github.com/gogf/gf/v2/util/gutil" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Tpl = cTpl{} +) + +type cTpl struct { + g.Meta `name:"tpl" brief:"{cTplBrief}" dc:"{cTplDc}"` +} + +const ( + cTplBrief = `template parsing and building commands` + cTplDc = ` +The "tpl" command is used for template parsing and building purpose. +It can parse either template file or folder with multiple types of values support, +like json/xml/yaml/toml/ini. +` + cTplParseBrief = `parse either template file or folder with multiple types of values` + cTplParseEg = ` +gf tpl parse -p ./template -v values.json -r +gf tpl parse -p ./template -v values.json -n *.tpl -r +gf tpl parse -p ./template -v values.json -d '${{,}}' -r +gf tpl parse -p ./template -v values.json -o ./template.parsed +` + cTplSupportValuesFilePattern = `*.json,*.xml,*.yaml,*.yml,*.toml,*.ini` +) + +type ( + cTplParseInput struct { + g.Meta `name:"parse" config:"gfcli.tpl.parse" brief:"{cTplParseBrief}" eg:"{cTplParseEg}"` + Path string `name:"path" short:"p" brief:"template file or folder path" v:"required"` + Pattern string `name:"pattern" short:"n" brief:"template file pattern when path is a folder, default is:*" d:"*"` + Recursive bool `name:"recursive" short:"c" brief:"recursively parsing files if path is folder, default is:true" d:"true"` + Values string `name:"values" short:"v" brief:"template values file/folder, support file types like: json/xml/yaml/toml/ini" v:"required"` + Output string `name:"output" short:"o" brief:"output file/folder path"` + Delimiters string `name:"delimiters" short:"d" brief:"delimiters for template content parsing, default is:{{,}}" d:"{{,}}"` + Replace bool `name:"replace" short:"r" brief:"replace original files" orphan:"true"` + } + cTplParseOutput struct{} +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cTplBrief`: cTplBrief, + `cTplDc`: cTplDc, + `cTplParseEg`: cTplParseEg, + `cTplParseBrief`: cTplParseBrief, + }) +} + +func (c *cTpl) Parse(ctx context.Context, in cTplParseInput) (out *cTplParseOutput, err error) { + if in.Output == "" && !in.Replace { + return nil, gerror.New(`parameter output and replace should not be both empty`) + } + delimiters := gstr.SplitAndTrim(in.Delimiters, ",") + mlog.Debugf("delimiters input:%s, parsed:%#v", in.Delimiters, delimiters) + if len(delimiters) != 2 { + return nil, gerror.Newf(`invalid delimiters: %s`, in.Delimiters) + } + g.View().SetDelimiters(delimiters[0], delimiters[1]) + valuesMap, err := c.loadValues(ctx, in.Values) + if err != nil { + return nil, err + } + if len(valuesMap) == 0 { + return nil, gerror.Newf(`empty values loaded from values file/folder "%s"`, in.Values) + } + err = c.parsePath(ctx, valuesMap, in) + if err == nil { + mlog.Print("done!") + } + return +} + +func (c *cTpl) parsePath(ctx context.Context, values g.Map, in cTplParseInput) (err error) { + if !gfile.Exists(in.Path) { + return gerror.Newf(`path "%s" does not exist`, in.Path) + } + var ( + path string + files []string + relativePath string + outputPath string + ) + path = gfile.RealPath(in.Path) + if gfile.IsDir(path) { + files, err = gfile.ScanDirFile(path, in.Pattern, in.Recursive) + if err != nil { + return err + } + for _, file := range files { + relativePath = gstr.Replace(file, path, "") + if in.Output != "" { + outputPath = gfile.Join(in.Output, relativePath) + } + if err = c.parseFile(ctx, file, outputPath, values, in); err != nil { + return + } + } + return + } + if in.Output != "" { + outputPath = in.Output + } + err = c.parseFile(ctx, path, outputPath, values, in) + return +} + +func (c *cTpl) parseFile(ctx context.Context, file string, output string, values g.Map, in cTplParseInput) (err error) { + output = gstr.ReplaceByMap(output, g.MapStrStr{ + `\\`: `\`, + `//`: `/`, + }) + content, err := g.View().Parse(ctx, file, values) + if err != nil { + return err + } + if output != "" { + mlog.Printf(`parse file "%s" to "%s"`, file, output) + return gfile.PutContents(output, content) + } + if in.Replace { + mlog.Printf(`parse and replace file "%s"`, file) + return gfile.PutContents(file, content) + } + return nil +} + +func (c *cTpl) loadValues(ctx context.Context, valuesPath string) (data g.Map, err error) { + if !gfile.Exists(valuesPath) { + return nil, gerror.Newf(`values file/folder "%s" does not exist`, valuesPath) + } + var j *gjson.Json + if gfile.IsDir(valuesPath) { + var valueFiles []string + valueFiles, err = gfile.ScanDirFile(valuesPath, cTplSupportValuesFilePattern, true) + if err != nil { + return nil, err + } + data = make(g.Map) + for _, file := range valueFiles { + if j, err = gjson.Load(file); err != nil { + return nil, err + } + gutil.MapMerge(data, j.Map()) + } + return + } + if j, err = gjson.Load(valuesPath); err != nil { + return nil, err + } + data = j.Map() + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_up.go b/server/internal/library/hggen/internal/cmd/cmd_up.go new file mode 100644 index 0000000..1b12686 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_up.go @@ -0,0 +1,279 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "context" + "fmt" + "runtime" + + "github.com/gogf/selfupdate" + + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/genv" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +var ( + Up = cUp{} +) + +type cUp struct { + g.Meta `name:"up" brief:"upgrade GoFrame version/tool to latest one in current project" eg:"{cUpEg}" ` +} + +const ( + gfPackage = `github.com/gogf/gf/` + cUpEg = ` +gf up +gf up -a +gf up -c +gf up -cf +gf up -a -m=install +gf up -a -m=install -p=github.com/gogf/gf/cmd/gf/v2@latest +` + cliMethodHttpDownload = "http" + cliMethodGoInstall = "install" +) + +func init() { + gtag.Sets(g.MapStrStr{ + `cUpEg`: cUpEg, + }) +} + +type cUpInput struct { + g.Meta `name:"up" config:"gfcli.up"` + All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"` + Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" orphan:"true"` + Fix bool `name:"fix" short:"f" brief:"auto fix codes(it only make sense if cli is to be upgraded)" orphan:"true"` + CliDownloadingMethod string `name:"cli-download-method" short:"m" brief:"cli upgrade method: http=download binary via HTTP GET, install=upgrade via go install" d:"http"` + // CliModulePath specifies the module path for CLI installation via go install. + // This is used when CliDownloadingMethod is set to "install". + CliModulePath string `name:"cli-module-path" short:"p" brief:"custom cli module path for upgrade CLI tool with go install method" d:"github.com/gogf/gf/cmd/gf/v2@latest"` +} + +type cUpOutput struct{} + +func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) { + defer func() { + if err == nil { + mlog.Print() + mlog.Print(`👏congratulations! you've upgraded to the latest version of GoFrame! enjoy it!👏`) + mlog.Print() + } + }() + + var doUpgradeVersionOut *doUpgradeVersionOutput + if in.All { + in.Cli = true + in.Fix = true + } + if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil { + return nil, err + } + + if in.Cli { + if err = c.doUpgradeCLI(ctx, in); err != nil { + return nil, err + } + } + + if in.Cli && in.Fix { + if doUpgradeVersionOut != nil && len(doUpgradeVersionOut.Items) > 0 { + upgradedPathSet := gset.NewStrSet() + for _, item := range doUpgradeVersionOut.Items { + if !upgradedPathSet.AddIfNotExist(item.DirPath) { + continue + } + if err = c.doAutoFixing(ctx, item.DirPath, item.Version); err != nil { + return nil, err + } + } + } + } + return +} + +type doUpgradeVersionOutput struct { + Items []doUpgradeVersionOutputItem +} + +type doUpgradeVersionOutputItem struct { + DirPath string + Version string +} + +func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeVersionOutput, err error) { + mlog.Print(`start upgrading version...`) + out = &doUpgradeVersionOutput{ + Items: make([]doUpgradeVersionOutputItem, 0), + } + type Package struct { + Name string + Version string + } + + var ( + temp string + dirPath = gfile.Pwd() + goModPath = gfile.Join(dirPath, "go.mod") + ) + // It recursively upgrades the go.mod from sub folder to its parent folders. + for { + if gfile.Exists(goModPath) { + var packages []Package + err = gfile.ReadLines(goModPath, func(line string) error { + line = gstr.Trim(line) + line = gstr.TrimLeftStr(line, "require ") + line = gstr.Trim(line) + if gstr.HasPrefix(line, gfPackage) { + array := gstr.SplitAndTrim(line, " ") + packages = append(packages, Package{ + Name: array[0], + Version: array[1], + }) + } + return nil + }) + if err != nil { + return + } + for _, pkg := range packages { + mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version) + mlog.Printf(`running command: go get %s@latest`, pkg.Name) + // go get @latest + command := fmt.Sprintf(`cd %s && go get %s@latest`, dirPath, pkg.Name) + if err = gproc.ShellRun(ctx, command); err != nil { + return + } + // go mod tidy + if err = utils.GoModTidy(ctx, dirPath); err != nil { + return nil, err + } + out.Items = append(out.Items, doUpgradeVersionOutputItem{ + DirPath: dirPath, + Version: pkg.Version, + }) + } + return + } + temp = gfile.Dir(dirPath) + if temp == "" || temp == dirPath { + return + } + dirPath = temp + goModPath = gfile.Join(dirPath, "go.mod") + } +} + +// doUpgradeCLI downloads the new version binary with process. +func (c cUp) doUpgradeCLI(ctx context.Context, in cUpInput) (err error) { + mlog.Print(`start upgrading cli...`) + fmt.Println(` cli upgrade method:`, in.CliDownloadingMethod) + switch in.CliDownloadingMethod { + case cliMethodHttpDownload: + return c.doUpgradeCLIWithHttpDownload(ctx) + case cliMethodGoInstall: + return c.doUpgradeCLIWithGoInstall(ctx, in) + default: + mlog.Fatalf(`invalid cli upgrade method: "%s", please use "http" or "install"`, in.CliDownloadingMethod) + } + return +} + +func (c cUp) doUpgradeCLIWithHttpDownload(ctx context.Context) (err error) { + mlog.Print(`start upgrading cli with http get download...`) + var ( + downloadUrl = fmt.Sprintf( + `https://github.com/gogf/gf/releases/latest/download/gf_%s_%s`, + runtime.GOOS, runtime.GOARCH, + ) + localSaveFilePath = gfile.SelfPath() + "~" + ) + + if runtime.GOOS == "windows" { + downloadUrl += ".exe" + } + + mlog.Printf(`start downloading "%s" to "%s", it may take some time`, downloadUrl, localSaveFilePath) + err = utils.HTTPDownloadFileWithPercent(downloadUrl, localSaveFilePath) + if err != nil { + return err + } + + defer func() { + mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath()) + mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath) + _ = gfile.RemoveFile(localSaveFilePath) + }() + + // It fails if file not exist or its size is less than 1MB. + if !gfile.Exists(localSaveFilePath) || gfile.Size(localSaveFilePath) < 1024*1024 { + mlog.Fatalf(`download "%s" to "%s" failed`, downloadUrl, localSaveFilePath) + } + + newFile, err := gfile.Open(localSaveFilePath) + if err != nil { + return err + } + // selfupdate + err = selfupdate.Apply(newFile, selfupdate.Options{}) + if err != nil { + return err + } + return +} + +func (c cUp) doUpgradeCLIWithGoInstall(ctx context.Context, in cUpInput) (err error) { + mlog.Print(`upgrading cli with go install...`) + if !genv.Contains("GOPATH") { + mlog.Fatal(`"GOPATH" environment variable does not exist, please check your go installation`) + } + + command := fmt.Sprintf(`go install %s`, in.CliModulePath) + mlog.Printf(`running command: %s`, command) + err = gproc.ShellRun(ctx, command) + if err != nil { + return err + } + + cliFilePath := gfile.Join(genv.Get("GOPATH").String(), "bin/gf") + if runtime.GOOS == "windows" { + cliFilePath += ".exe" + } + + // It fails if file not exist or its size is less than 1MB. + if !gfile.Exists(cliFilePath) || gfile.Size(cliFilePath) < 1024*1024 { + mlog.Fatalf(`go install %s failed, "%s" does not exist or its size is less than 1MB`, in.CliModulePath, cliFilePath) + } + + newFile, err := gfile.Open(cliFilePath) + if err != nil { + return err + } + // selfupdate + err = selfupdate.Apply(newFile, selfupdate.Options{}) + if err != nil { + return err + } + return +} + +func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) { + mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version) + command := fmt.Sprintf(`gf fix -p %s`, dirPath) + _ = gproc.ShellRun(ctx, command) + return +} diff --git a/server/internal/library/hggen/internal/cmd/cmd_version.go b/server/internal/library/hggen/internal/cmd/cmd_version.go new file mode 100644 index 0000000..420d588 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/cmd_version.go @@ -0,0 +1,162 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "bytes" + "context" + "fmt" + "runtime" + "strings" + "time" + + "github.com/gogf/gf/v2" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gbuild" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Version = cVersion{} +) + +const ( + defaultIndent = "{{indent}}" +) + +type cVersion struct { + g.Meta `name:"version" brief:"show version information of current binary"` +} + +type cVersionInput struct { + g.Meta `name:"version"` +} + +type cVersionOutput struct{} + +func (c cVersion) Index(ctx context.Context, in cVersionInput) (*cVersionOutput, error) { + detailBuffer := &detailBuffer{} + detailBuffer.WriteString(fmt.Sprintf("%s", gf.VERSION)) + + detailBuffer.appendLine(0, "Welcome to GoFrame!") + + detailBuffer.appendLine(0, "Env Detail:") + goVersion, ok := getGoVersion() + if ok { + detailBuffer.appendLine(1, fmt.Sprintf("Go Version: %s", goVersion)) + detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", getGoFrameVersion(2))) + } else { + v, err := c.getGFVersionOfCurrentProject() + if err == nil { + detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", v)) + } else { + detailBuffer.appendLine(1, fmt.Sprintf("GF Version(go.mod): %s", err.Error())) + } + } + + detailBuffer.appendLine(0, "CLI Detail:") + detailBuffer.appendLine(1, fmt.Sprintf("Installed At: %s", gfile.SelfPath())) + info := gbuild.Info() + if info.GoFrame == "" { + detailBuffer.appendLine(1, fmt.Sprintf("Built Go Version: %s", runtime.Version())) + detailBuffer.appendLine(1, fmt.Sprintf("Built GF Version: %s", gf.VERSION)) + } else { + if info.Git == "" { + info.Git = "none" + } + detailBuffer.appendLine(1, fmt.Sprintf("Built Go Version: %s", info.Golang)) + detailBuffer.appendLine(1, fmt.Sprintf("Built GF Version: %s", info.GoFrame)) + detailBuffer.appendLine(1, fmt.Sprintf("Git Commit: %s", info.Git)) + detailBuffer.appendLine(1, fmt.Sprintf("Built Time: %s", info.Time)) + } + + detailBuffer.appendLine(0, "Others Detail:") + detailBuffer.appendLine(1, "Docs: https://goframe.org") + detailBuffer.appendLine(1, fmt.Sprintf("Now : %s", time.Now().Format(time.RFC3339))) + + mlog.Print(detailBuffer.replaceAllIndent(" ")) + return nil, nil +} + +// detailBuffer is a buffer for detail information. +type detailBuffer struct { + bytes.Buffer +} + +// appendLine appends a line to the buffer with given indent level. +func (d *detailBuffer) appendLine(indentLevel int, line string) { + d.WriteString(fmt.Sprintf("\n%s%s", strings.Repeat(defaultIndent, indentLevel), line)) +} + +// replaceAllIndent replaces the tab with given indent string and prints the buffer content. +func (d *detailBuffer) replaceAllIndent(indentStr string) string { + return strings.ReplaceAll(d.String(), defaultIndent, indentStr) +} + +// getGoFrameVersion returns the goframe version of current project using. +func getGoFrameVersion(indentLevel int) (gfVersion string) { + pkgInfo, err := gproc.ShellExec(context.Background(), `go list -f "{{if (not .Main)}}{{.Path}}@{{.Version}}{{end}}" -m all`) + if err != nil { + return "cannot find go.mod" + } + pkgList := gstr.Split(pkgInfo, "\n") + for _, v := range pkgList { + if strings.HasPrefix(v, "github.com/gogf/gf") { + gfVersion += fmt.Sprintf("\n%s%s", strings.Repeat(defaultIndent, indentLevel), v) + } + } + return +} + +// getGoVersion returns the go version +func getGoVersion() (goVersion string, ok bool) { + goVersion, err := gproc.ShellExec(context.Background(), "go version") + if err != nil { + return "", false + } + goVersion = gstr.TrimLeftStr(goVersion, "go version ") + goVersion = gstr.TrimRightStr(goVersion, "\n") + return goVersion, true +} + +// getGFVersionOfCurrentProject checks and returns the GoFrame version current project using. +func (c cVersion) getGFVersionOfCurrentProject() (string, error) { + goModPath := gfile.Join(gfile.Pwd(), "go.mod") + if gfile.Exists(goModPath) { + lines := gstr.SplitAndTrim(gfile.GetContents(goModPath), "\n") + for _, line := range lines { + line = gstr.Trim(line) + line = gstr.TrimLeftStr(line, "require ") + line = gstr.Trim(line) + // Version 1. + match, err := gregex.MatchString(`^github\.com/gogf/gf\s+(.+)$`, line) + if err != nil { + return "", err + } + if len(match) <= 1 { + // Version > 1. + match, err = gregex.MatchString(`^github\.com/gogf/gf/v\d\s+(.+)$`, line) + if err != nil { + return "", err + } + } + if len(match) > 1 { + return gstr.Trim(match[1]), nil + } + } + + return "", gerror.New("cannot find goframe requirement in go.mod") + } else { + return "", gerror.New("cannot find go.mod") + } +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl.go new file mode 100644 index 0000000..c62ff22 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl.go @@ -0,0 +1,238 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "context" + + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +const ( + CGenCtrlConfig = `gfcli.gen.ctrl` + CGenCtrlUsage = `gf gen ctrl [OPTION]` + CGenCtrlBrief = `parse api definitions to generate controller/sdk go files` + CGenCtrlEg = ` +gf gen ctrl +` + CGenCtrlBriefSrcFolder = `source folder path to be parsed. default: api` + CGenCtrlBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/controller` + CGenCtrlBriefWatchFile = `used in file watcher, it re-generates go files only if given file is under srcFolder` + CGenCtrlBriefSdkPath = `also generate SDK go files for api definitions to specified directory` + CGenCtrlBriefSdkStdVersion = `use standard version prefix for generated sdk request path` + CGenCtrlBriefSdkNoV1 = `do not add version suffix for interface module name if version is v1` + CGenCtrlBriefClear = `auto delete generated and unimplemented controller go files if api definitions are missing` + CGenCtrlControllerMerge = `generate all controller files into one go file by name of api definition source go file` +) + +const ( + PatternCtrlDefinition = `func\s+\(.+?\)\s+\w+\(.+?\*(\w+)\.(\w+)Req\)\s+\(.+?\*(\w+)\.(\w+)Res,\s+\w+\s+error\)\s+{` +) + +const ( + genCtrlFileLockSeconds = 10 +) + +func init() { + gtag.Sets(g.MapStrStr{ + `CGenCtrlConfig`: CGenCtrlConfig, + `CGenCtrlUsage`: CGenCtrlUsage, + `CGenCtrlBrief`: CGenCtrlBrief, + `CGenCtrlEg`: CGenCtrlEg, + `CGenCtrlBriefSrcFolder`: CGenCtrlBriefSrcFolder, + `CGenCtrlBriefDstFolder`: CGenCtrlBriefDstFolder, + `CGenCtrlBriefWatchFile`: CGenCtrlBriefWatchFile, + `CGenCtrlBriefSdkPath`: CGenCtrlBriefSdkPath, + `CGenCtrlBriefSdkStdVersion`: CGenCtrlBriefSdkStdVersion, + `CGenCtrlBriefSdkNoV1`: CGenCtrlBriefSdkNoV1, + `CGenCtrlBriefClear`: CGenCtrlBriefClear, + `CGenCtrlControllerMerge`: CGenCtrlControllerMerge, + }) +} + +type ( + CGenCtrl struct{} + CGenCtrlInput struct { + g.Meta `name:"ctrl" config:"{CGenCtrlConfig}" usage:"{CGenCtrlUsage}" brief:"{CGenCtrlBrief}" eg:"{CGenCtrlEg}"` + SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenCtrlBriefSrcFolder}" d:"api"` + DstFolder string `short:"d" name:"dstFolder" brief:"{CGenCtrlBriefDstFolder}" d:"internal/controller"` + WatchFile string `short:"w" name:"watchFile" brief:"{CGenCtrlBriefWatchFile}"` + SdkPath string `short:"k" name:"sdkPath" brief:"{CGenCtrlBriefSdkPath}"` + SdkStdVersion bool `short:"v" name:"sdkStdVersion" brief:"{CGenCtrlBriefSdkStdVersion}" orphan:"true"` + SdkNoV1 bool `short:"n" name:"sdkNoV1" brief:"{CGenCtrlBriefSdkNoV1}" orphan:"true"` + Clear bool `short:"c" name:"clear" brief:"{CGenCtrlBriefClear}" orphan:"true"` + Merge bool `short:"m" name:"merge" brief:"{CGenCtrlControllerMerge}" orphan:"true"` + } + CGenCtrlOutput struct{} +) + +func (c CGenCtrl) Ctrl(ctx context.Context, in CGenCtrlInput) (out *CGenCtrlOutput, err error) { + if in.WatchFile != "" { + err = c.generateByWatchFile( + in.WatchFile, in.SdkPath, in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge, + ) + mlog.Print(`done!`) + return + } + + if !gfile.Exists(in.SrcFolder) { + mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder) + } + // retrieve all api modules. + apiModuleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false) + if err != nil { + return nil, err + } + for _, apiModuleFolderPath := range apiModuleFolderPaths { + if !gfile.IsDir(apiModuleFolderPath) { + continue + } + // generate go files by api module. + var ( + module = gfile.Basename(apiModuleFolderPath) + dstModuleFolderPath = gfile.Join(in.DstFolder, module) + ) + err = c.generateByModule( + apiModuleFolderPath, dstModuleFolderPath, in.SdkPath, + in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge, + ) + if err != nil { + return nil, err + } + } + + mlog.Print(`done!`) + return +} + +func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion, sdkNoV1, clear, merge bool) (err error) { + // File lock to avoid multiple processes. + var ( + flockFilePath = gfile.Temp("gf.cli.gen.service.lock") + flockContent = gfile.GetContents(flockFilePath) + ) + if flockContent != "" { + if gtime.Timestamp()-gconv.Int64(flockContent) < genCtrlFileLockSeconds { + // If another generating process is running, it just exits. + mlog.Debug(`another "gen service" process is running, exit`) + return + } + } + defer gfile.RemoveFile(flockFilePath) + _ = gfile.PutContents(flockFilePath, gtime.TimestampStr()) + + // check this updated file is an api file. + // watch file should be in standard goframe project structure. + var ( + apiVersionPath = gfile.Dir(watchFile) + apiModuleFolderPath = gfile.Dir(apiVersionPath) + shouldBeNameOfAPi = gfile.Basename(gfile.Dir(apiModuleFolderPath)) + ) + if shouldBeNameOfAPi != "api" { + return nil + } + // watch file should have api definitions. + if gfile.Exists(watchFile) { + structsInfo, err := c.getStructsNameInSrc(watchFile) + if err != nil { + return err + } + if len(structsInfo) == 0 { + return nil + } + } + + var ( + projectRootPath = gfile.Dir(gfile.Dir(apiModuleFolderPath)) + module = gfile.Basename(apiModuleFolderPath) + dstModuleFolderPath = gfile.Join(projectRootPath, "internal", "controller", module) + ) + return c.generateByModule( + apiModuleFolderPath, dstModuleFolderPath, sdkPath, sdkStdVersion, sdkNoV1, clear, merge, + ) +} + +// parseApiModule parses certain api and generate associated go files by certain module, not all api modules. +func (c CGenCtrl) generateByModule( + apiModuleFolderPath, dstModuleFolderPath, sdkPath string, + sdkStdVersion, sdkNoV1, clear, merge bool, +) (err error) { + // parse src and dst folder go files. + apiItemsInSrc, err := c.getApiItemsInSrc(apiModuleFolderPath) + if err != nil { + return err + } + apiItemsInDst, err := c.getApiItemsInDst(dstModuleFolderPath) + if err != nil { + return err + } + + // generate api interface go files. + if err = newApiInterfaceGenerator().Generate(apiModuleFolderPath, apiItemsInSrc); err != nil { + return + } + + // generate controller go files. + // api filtering for already implemented api controllers. + var ( + alreadyImplementedCtrlSet = gset.NewStrSet() + toBeImplementedApiItems = make([]apiItem, 0) + ) + for _, item := range apiItemsInDst { + alreadyImplementedCtrlSet.Add(item.String()) + } + for _, item := range apiItemsInSrc { + if alreadyImplementedCtrlSet.Contains(item.String()) { + continue + } + toBeImplementedApiItems = append(toBeImplementedApiItems, item) + } + if len(toBeImplementedApiItems) > 0 { + err = newControllerGenerator().Generate(dstModuleFolderPath, toBeImplementedApiItems, merge) + if err != nil { + return + } + } + + // delete unimplemented controllers if api definitions are missing. + if clear { + var ( + apiDefinitionSet = gset.NewStrSet() + extraApiItemsInCtrl = make([]apiItem, 0) + ) + for _, item := range apiItemsInSrc { + apiDefinitionSet.Add(item.String()) + } + for _, item := range apiItemsInDst { + if apiDefinitionSet.Contains(item.String()) { + continue + } + extraApiItemsInCtrl = append(extraApiItemsInCtrl, item) + } + if len(extraApiItemsInCtrl) > 0 { + err = newControllerClearer().Clear(dstModuleFolderPath, extraApiItemsInCtrl) + if err != nil { + return + } + } + } + + // generate sdk go files. + if sdkPath != "" { + if err = newApiSdkGenerator().Generate(apiItemsInSrc, sdkPath, sdkStdVersion, sdkNoV1); err != nil { + return + } + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go new file mode 100644 index 0000000..c502e3e --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_api_item.go @@ -0,0 +1,37 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "fmt" + + "github.com/gogf/gf/v2/text/gstr" +) + +type apiItem struct { + Import string `eg:"demo.com/api/user/v1"` + FileName string `eg:"user"` + Module string `eg:"user"` + Version string `eg:"v1"` + MethodName string `eg:"GetList"` + Comment string `eg:"GetList get list"` +} + +func (a apiItem) String() string { + return gstr.Join([]string{ + a.Import, a.Module, a.Version, a.MethodName, + }, ",") +} + +// GetComment returns the comment of apiItem. +func (a apiItem) GetComment() string { + if a.Comment == "" { + return "" + } + // format for handling comments + return fmt.Sprintf("\n// %s %s", a.MethodName, a.Comment) +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse.go new file mode 100644 index 0000000..9f87bf5 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse.go @@ -0,0 +1,95 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" +) + +type structInfo struct { + structName string + comment string +} + +// getStructsNameInSrc retrieves all struct names and comment +// that end in "Req" and have "g.Meta" in their body. +func (c CGenCtrl) getStructsNameInSrc(filePath string) (structInfos []*structInfo, err error) { + var ( + fileContent = gfile.GetContents(filePath) + fileSet = token.NewFileSet() + ) + + node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return + } + + ast.Inspect(node, func(n ast.Node) bool { + if typeSpec, ok := n.(*ast.TypeSpec); ok { + structName := typeSpec.Name.Name + if !gstr.HasSuffix(structName, "Req") { + // ignore struct name that do not end in "Req" + return true + } + if structType, ok := typeSpec.Type.(*ast.StructType); ok { + var buf bytes.Buffer + if err := printer.Fprint(&buf, fileSet, structType); err != nil { + return false + } + // ignore struct name that match a request, but has no g.Meta in its body. + if !gstr.Contains(buf.String(), `g.Meta`) { + return true + } + + comment := typeSpec.Doc.Text() + // remove the struct name from the comment + if gstr.HasPrefix(comment, structName) { + comment = gstr.TrimLeftStr(comment, structName, 1) + } + // remove the comment \n or space + comment = gstr.Trim(comment) + + structInfos = append(structInfos, &structInfo{ + structName: structName, + comment: comment, + }) + } + } + return true + }) + + return +} + +// getImportsInDst retrieves all import paths in the file. +func (c CGenCtrl) getImportsInDst(filePath string) (imports []string, err error) { + var ( + fileContent = gfile.GetContents(filePath) + fileSet = token.NewFileSet() + ) + + node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return + } + + ast.Inspect(node, func(n ast.Node) bool { + if imp, ok := n.(*ast.ImportSpec); ok { + imports = append(imports, imp.Path.Value) + } + return true + }) + + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.go new file mode 100644 index 0000000..a1bd739 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_ast_parse_clear.go @@ -0,0 +1,43 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + + "github.com/gogf/gf/v2/os/gfile" +) + +// getFuncInDst retrieves all function declarations and bodies in the file. +func (c *controllerClearer) getFuncInDst(filePath string) (funcs []string, err error) { + var ( + fileContent = gfile.GetContents(filePath) + fileSet = token.NewFileSet() + ) + + node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return + } + + ast.Inspect(node, func(n ast.Node) bool { + if fun, ok := n.(*ast.FuncDecl); ok { + var buf bytes.Buffer + if err := printer.Fprint(&buf, fileSet, fun); err != nil { + return false + } + funcs = append(funcs, buf.String()) + } + return true + }) + + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go new file mode 100644 index 0000000..62818a4 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_calculate.go @@ -0,0 +1,137 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/utils" +) + +func (c CGenCtrl) getApiItemsInSrc(apiModuleFolderPath string) (items []apiItem, err error) { + var importPath string + // The second level folders: versions. + apiVersionFolderPaths, err := gfile.ScanDir(apiModuleFolderPath, "*", false) + if err != nil { + return nil, err + } + for _, apiVersionFolderPath := range apiVersionFolderPaths { + if !gfile.IsDir(apiVersionFolderPath) { + continue + } + // The second level folders: versions. + apiFileFolderPaths, err := gfile.ScanDir(apiVersionFolderPath, "*.go", false) + if err != nil { + return nil, err + } + importPath = utils.GetImportPath(apiVersionFolderPath) + for _, apiFileFolderPath := range apiFileFolderPaths { + if gfile.IsDir(apiFileFolderPath) { + continue + } + structsInfo, err := c.getStructsNameInSrc(apiFileFolderPath) + if err != nil { + return nil, err + } + for _, s := range structsInfo { + // remove end "Req" + methodName := gstr.TrimRightStr(s.structName, "Req", 1) + item := apiItem{ + Import: gstr.Trim(importPath, `"`), + FileName: gfile.Name(apiFileFolderPath), + Module: gfile.Basename(apiModuleFolderPath), + Version: gfile.Basename(apiVersionFolderPath), + MethodName: methodName, + Comment: s.comment, + } + items = append(items, item) + } + } + } + return +} + +func (c CGenCtrl) getApiItemsInDst(dstFolder string) (items []apiItem, err error) { + if !gfile.Exists(dstFolder) { + return nil, nil + } + type importItem struct { + Path string + Alias string + } + filePaths, err := gfile.ScanDir(dstFolder, "*.go", true) + if err != nil { + return nil, err + } + for _, filePath := range filePaths { + var ( + array []string + importItems []importItem + importLines []string + module = gfile.Basename(gfile.Dir(filePath)) + ) + importLines, err = c.getImportsInDst(filePath) + if err != nil { + return nil, err + } + + // retrieve all imports. + for _, importLine := range importLines { + array = gstr.SplitAndTrim(importLine, " ") + if len(array) == 2 { + importItems = append(importItems, importItem{ + Path: gstr.Trim(array[1], `"`), + Alias: array[0], + }) + } else { + importItems = append(importItems, importItem{ + Path: gstr.Trim(array[0], `"`), + }) + } + } + // retrieve all api usages. + // retrieve it without using AST, but use regular expressions to retrieve. + // It's because the api definition is simple and regular. + // Use regular expressions to get better performance. + fileContent := gfile.GetContents(filePath) + matches, err := gregex.MatchAllString(PatternCtrlDefinition, fileContent) + if err != nil { + return nil, err + } + for _, match := range matches { + // try to find the import path of the api. + var ( + importPath string + version = match[1] + methodName = match[2] // not the function name, but the method name in api definition. + ) + for _, item := range importItems { + if item.Alias != "" { + if item.Alias == version { + importPath = item.Path + break + } + continue + } + if gfile.Basename(item.Path) == version { + importPath = item.Path + break + } + } + item := apiItem{ + Import: gstr.Trim(importPath, `"`), + Module: module, + Version: gfile.Basename(importPath), + MethodName: methodName, + } + items = append(items, item) + } + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go new file mode 100644 index 0000000..08cb33e --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl.go @@ -0,0 +1,231 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +type controllerGenerator struct{} + +func newControllerGenerator() *controllerGenerator { + return &controllerGenerator{} +} + +func (c *controllerGenerator) Generate(dstModuleFolderPath string, apiModuleApiItems []apiItem, merge bool) (err error) { + var ( + doneApiItemSet = gset.NewStrSet() + ) + for _, item := range apiModuleApiItems { + if doneApiItemSet.Contains(item.String()) { + continue + } + // retrieve all api items of the same module. + var ( + subItems = c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version) + importPath = gstr.Replace(gfile.Dir(item.Import), "\\", "/", -1) + ) + if err = c.doGenerateCtrlNewByModuleAndVersion( + dstModuleFolderPath, item.Module, item.Version, importPath, + ); err != nil { + return + } + + // use -merge + if merge { + err = c.doGenerateCtrlMergeItem(dstModuleFolderPath, subItems, doneApiItemSet) + continue + } + + for _, subItem := range subItems { + err = c.doGenerateCtrlItem(dstModuleFolderPath, subItem) + if err != nil { + return + } + doneApiItemSet.Add(subItem.String()) + } + } + return +} + +func (c *controllerGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) { + for _, item := range items { + if item.Module == module && item.Version == version { + subItems = append(subItems, item) + } + } + return +} + +func (c *controllerGenerator) doGenerateCtrlNewByModuleAndVersion( + dstModuleFolderPath, module, version, importPath string, +) (err error) { + var ( + moduleFilePath = filepath.FromSlash(gfile.Join(dstModuleFolderPath, module+".go")) + moduleFilePathNew = filepath.FromSlash(gfile.Join(dstModuleFolderPath, module+"_new.go")) + ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(version)) + interfaceName = fmt.Sprintf(`%s.I%s%s`, module, gstr.CaseCamel(module), gstr.UcFirst(version)) + newFuncName = fmt.Sprintf(`New%s`, gstr.UcFirst(version)) + newFuncNameDefinition = fmt.Sprintf(`func %s()`, newFuncName) + alreadyCreated bool + ) + if !gfile.Exists(moduleFilePath) { + content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerEmpty, g.MapStrStr{ + "{Module}": module, + }) + if err = gfile.PutContents(moduleFilePath, gstr.TrimLeft(content)); err != nil { + return err + } + mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePath)) + } + if !gfile.Exists(moduleFilePathNew) { + content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewEmpty, g.MapStrStr{ + "{Module}": module, + "{ImportPath}": fmt.Sprintf(`"%s"`, importPath), + }) + if err = gfile.PutContents(moduleFilePathNew, gstr.TrimLeft(content)); err != nil { + return err + } + mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePathNew)) + } + filePaths, err := gfile.ScanDir(dstModuleFolderPath, "*.go", false) + if err != nil { + return err + } + for _, filePath := range filePaths { + if gstr.Contains(gfile.GetContents(filePath), newFuncNameDefinition) { + alreadyCreated = true + break + } + } + if !alreadyCreated { + content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewFunc, g.MapStrStr{ + "{CtrlName}": ctrlName, + "{NewFuncName}": newFuncName, + "{InterfaceName}": interfaceName, + }) + err = gfile.PutContentsAppend(moduleFilePathNew, content) + if err != nil { + return err + } + } + return +} + +func (c *controllerGenerator) doGenerateCtrlItem(dstModuleFolderPath string, item apiItem) (err error) { + var ( + methodNameSnake = gstr.CaseSnake(item.MethodName) + ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(item.Version)) + methodFilePath = filepath.FromSlash(gfile.Join(dstModuleFolderPath, fmt.Sprintf( + `%s_%s_%s.go`, item.Module, item.Version, methodNameSnake, + ))) + ) + var content string + + if gfile.Exists(methodFilePath) { + content = gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{ + "{Module}": item.Module, + "{CtrlName}": ctrlName, + "{Version}": item.Version, + "{MethodName}": item.MethodName, + "{MethodComment}": item.GetComment(), + }) + + if gstr.Contains(gfile.GetContents(methodFilePath), fmt.Sprintf(`func (c *%v) %v(`, ctrlName, item.MethodName)) { + return + } + if err = gfile.PutContentsAppend(methodFilePath, gstr.TrimLeft(content)); err != nil { + return err + } + } else { + content = gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFunc, g.MapStrStr{ + "{Module}": item.Module, + "{ImportPath}": item.Import, + "{CtrlName}": ctrlName, + "{Version}": item.Version, + "{MethodName}": item.MethodName, + "{MethodComment}": item.GetComment(), + }) + if err = gfile.PutContents(methodFilePath, gstr.TrimLeft(content)); err != nil { + return err + } + } + mlog.Printf(`generated: %s`, gfile.RealPath(methodFilePath)) + return +} + +// use -merge +func (c *controllerGenerator) doGenerateCtrlMergeItem(dstModuleFolderPath string, apiItems []apiItem, doneApiSet *gset.StrSet) (err error) { + + type controllerFileItem struct { + module string + version string + importPath string + // Each ctrlFileItem has multiple CTRLs + controllers strings.Builder + } + // It is possible that there are multiple files under one module + ctrlFileItemMap := make(map[string]*controllerFileItem) + + for _, api := range apiItems { + ctrlFileItem, found := ctrlFileItemMap[api.FileName] + if !found { + ctrlFileItem = &controllerFileItem{ + module: api.Module, + version: api.Version, + controllers: strings.Builder{}, + importPath: api.Import, + } + ctrlFileItemMap[api.FileName] = ctrlFileItem + } + + ctrl := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{ + "{Module}": api.Module, + "{CtrlName}": fmt.Sprintf(`Controller%s`, gstr.UcFirst(api.Version)), + "{Version}": api.Version, + "{MethodName}": api.MethodName, + "{MethodComment}": api.GetComment(), + })) + ctrlFileItem.controllers.WriteString(ctrl) + doneApiSet.Add(api.String()) + } + + for ctrlFileName, ctrlFileItem := range ctrlFileItemMap { + ctrlFilePath := gfile.Join(dstModuleFolderPath, fmt.Sprintf( + `%s_%s_%s.go`, ctrlFileItem.module, ctrlFileItem.version, ctrlFileName, + )) + + // This logic is only followed when a new ctrlFileItem is generated + // Most of the rest of the time, the following logic is followed + if !gfile.Exists(ctrlFilePath) { + ctrlFileHeader := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerHeader, g.MapStrStr{ + "{Module}": ctrlFileItem.module, + "{ImportPath}": ctrlFileItem.importPath, + })) + err = gfile.PutContents(ctrlFilePath, ctrlFileHeader) + if err != nil { + return err + } + } + + if err = gfile.PutContentsAppend(ctrlFilePath, ctrlFileItem.controllers.String()); err != nil { + return err + } + mlog.Printf(`generated: %s`, gfile.RealPath(ctrlFilePath)) + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go new file mode 100644 index 0000000..b3fbcc8 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_ctrl_clear.go @@ -0,0 +1,57 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +type controllerClearer struct{} + +func newControllerClearer() *controllerClearer { + return &controllerClearer{} +} + +func (c *controllerClearer) Clear(dstModuleFolderPath string, extraApiItemsInCtrl []apiItem) (err error) { + for _, item := range extraApiItemsInCtrl { + if err = c.doClear(dstModuleFolderPath, item); err != nil { + return err + } + } + return +} + +func (c *controllerClearer) doClear(dstModuleFolderPath string, item apiItem) (err error) { + var ( + methodNameSnake = gstr.CaseSnake(item.MethodName) + methodFilePath = gfile.Join(dstModuleFolderPath, fmt.Sprintf( + `%s_%s_%s.go`, item.Module, item.Version, methodNameSnake, + )) + ) + + funcs, err := c.getFuncInDst(methodFilePath) + if err != nil { + return err + } + + if len(funcs) > 1 { + // One line. + if !gstr.Contains(funcs[0], "\n") && gstr.Contains(funcs[0], `CodeNotImplemented`) { + mlog.Printf( + `remove unimplemented and of no api definitions controller file: %s`, + methodFilePath, + ) + err = gfile.RemoveFile(methodFilePath) + } + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go new file mode 100644 index 0000000..a6cc5eb --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_interface.go @@ -0,0 +1,118 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "fmt" + "path/filepath" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +type apiInterfaceGenerator struct{} + +func newApiInterfaceGenerator() *apiInterfaceGenerator { + return &apiInterfaceGenerator{} +} + +func (c *apiInterfaceGenerator) Generate(apiModuleFolderPath string, apiModuleApiItems []apiItem) (err error) { + if len(apiModuleApiItems) == 0 { + return nil + } + var firstApiItem = apiModuleApiItems[0] + if err = c.doGenerate(apiModuleFolderPath, firstApiItem.Module, apiModuleApiItems); err != nil { + return + } + return +} + +func (c *apiInterfaceGenerator) doGenerate(apiModuleFolderPath string, module string, items []apiItem) (err error) { + var ( + moduleFilePath = filepath.FromSlash(gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.go`, module))) + importPathMap = gmap.NewListMap() + importPaths []string + ) + // if there's already exist file that with the same but not auto generated go file, + // it uses another file name. + if !utils.IsFileDoNotEdit(moduleFilePath) { + moduleFilePath = filepath.FromSlash(gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.if.go`, module))) + } + // all import paths. + importPathMap.Set("\t"+`"context"`+"\n", 1) + for _, item := range items { + importPathMap.Set(fmt.Sprintf("\t"+`"%s"`, item.Import), 1) + } + importPaths = gconv.Strings(importPathMap.Keys()) + // interface definitions. + var ( + doneApiItemSet = gset.NewStrSet() + interfaceDefinition string + interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlApiInterface, g.MapStrStr{ + "{Module}": module, + "{ImportPaths}": gstr.Join(importPaths, "\n"), + })) + ) + for _, item := range items { + if doneApiItemSet.Contains(item.String()) { + continue + } + // retrieve all api items of the same module. + subItems := c.getSubItemsByModuleAndVersion(items, item.Module, item.Version) + var ( + method string + methods = make([]string, 0) + interfaceName = fmt.Sprintf(`I%s%s`, gstr.CaseCamel(item.Module), gstr.UcFirst(item.Version)) + ) + for _, subItem := range subItems { + method = fmt.Sprintf( + "\t%s(ctx context.Context, req *%s.%sReq) (res *%s.%sRes, err error)", + subItem.MethodName, subItem.Version, subItem.MethodName, subItem.Version, subItem.MethodName, + ) + methods = append(methods, method) + doneApiItemSet.Add(subItem.String()) + } + interfaceDefinition += fmt.Sprintf("type %s interface {", interfaceName) + interfaceDefinition += "\n" + interfaceDefinition += gstr.Join(methods, "\n") + interfaceDefinition += "\n" + interfaceDefinition += fmt.Sprintf("}") + interfaceDefinition += "\n\n" + } + interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(interfaceContent, g.MapStrStr{ + "{Interfaces}": gstr.TrimRightStr(interfaceDefinition, "\n", 2), + })) + err = gfile.PutContents(moduleFilePath, interfaceContent) + mlog.Printf(`generated: %s`, gfile.RealPath(moduleFilePath)) + return +} + +func (c *apiInterfaceGenerator) getSubItemsByModule(items []apiItem, module string) (subItems []apiItem) { + for _, item := range items { + if item.Module == module { + subItems = append(subItems, item) + } + } + return +} + +func (c *apiInterfaceGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) { + for _, item := range items { + if item.Module == module && item.Version == version { + subItems = append(subItems, item) + } + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go new file mode 100644 index 0000000..17bb64a --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genctrl/genctrl_generate_sdk.go @@ -0,0 +1,199 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genctrl + +import ( + "fmt" + "path/filepath" + + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +type apiSdkGenerator struct{} + +func newApiSdkGenerator() *apiSdkGenerator { + return &apiSdkGenerator{} +} + +func (c *apiSdkGenerator) Generate(apiModuleApiItems []apiItem, sdkFolderPath string, sdkStdVersion, sdkNoV1 bool) (err error) { + if err = c.doGenerateSdkPkgFile(sdkFolderPath); err != nil { + return + } + + var doneApiItemSet = gset.NewStrSet() + for _, item := range apiModuleApiItems { + if doneApiItemSet.Contains(item.String()) { + continue + } + // retrieve all api items of the same module. + subItems := c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version) + if err = c.doGenerateSdkIClient(sdkFolderPath, item.Import, item.Module, item.Version, sdkNoV1); err != nil { + return + } + if err = c.doGenerateSdkImplementer( + subItems, sdkFolderPath, item.Import, item.Module, item.Version, sdkStdVersion, sdkNoV1, + ); err != nil { + return + } + for _, subItem := range subItems { + doneApiItemSet.Add(subItem.String()) + } + } + return +} + +func (c *apiSdkGenerator) doGenerateSdkPkgFile(sdkFolderPath string) (err error) { + var ( + pkgName = gfile.Basename(sdkFolderPath) + pkgFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.go`, pkgName))) + fileContent string + ) + if gfile.Exists(pkgFilePath) { + return nil + } + fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkPkgNew, g.MapStrStr{ + "{PkgName}": pkgName, + })) + err = gfile.PutContents(pkgFilePath, fileContent) + mlog.Printf(`generated: %s`, gfile.RealPath(pkgFilePath)) + return +} + +func (c *apiSdkGenerator) doGenerateSdkIClient( + sdkFolderPath, versionImportPath, module, version string, sdkNoV1 bool, +) (err error) { + var ( + fileContent string + isDirty bool + isExist bool + pkgName = gfile.Basename(sdkFolderPath) + funcName = gstr.CaseCamel(module) + gstr.UcFirst(version) + interfaceName = fmt.Sprintf(`I%s`, funcName) + moduleImportPath = gstr.Replace(fmt.Sprintf(`"%s"`, gfile.Dir(versionImportPath)), "\\", "/", -1) + iClientFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.iclient.go`, pkgName))) + interfaceFuncDefinition = fmt.Sprintf( + `%s() %s.%s`, + gstr.CaseCamel(module)+gstr.UcFirst(version), module, interfaceName, + ) + ) + if sdkNoV1 && version == "v1" { + interfaceFuncDefinition = fmt.Sprintf( + `%s() %s.%s`, + gstr.CaseCamel(module), module, interfaceName, + ) + } + if isExist = gfile.Exists(iClientFilePath); isExist { + fileContent = gfile.GetContents(iClientFilePath) + } else { + fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkIClient, g.MapStrStr{ + "{PkgName}": pkgName, + })) + } + + // append the import path to current import paths. + if !gstr.Contains(fileContent, moduleImportPath) { + isDirty = true + // It is without using AST, because it is from a template. + fileContent, err = gregex.ReplaceString( + `(import \([\s\S]*?)\)`, + fmt.Sprintf("$1\t%s\n)", moduleImportPath), + fileContent, + ) + if err != nil { + return + } + } + + // append the function definition to interface definition. + if !gstr.Contains(fileContent, interfaceFuncDefinition) { + isDirty = true + // It is without using AST, because it is from a template. + fileContent, err = gregex.ReplaceString( + `(type IClient interface {[\s\S]*?)}`, + fmt.Sprintf("$1\t%s\n}", interfaceFuncDefinition), + fileContent, + ) + if err != nil { + return + } + } + if isDirty { + err = gfile.PutContents(iClientFilePath, fileContent) + if isExist { + mlog.Printf(`updated: %s`, gfile.RealPath(iClientFilePath)) + } else { + mlog.Printf(`generated: %s`, gfile.RealPath(iClientFilePath)) + } + } + return +} + +func (c *apiSdkGenerator) doGenerateSdkImplementer( + items []apiItem, sdkFolderPath, versionImportPath, module, version string, sdkStdVersion, sdkNoV1 bool, +) (err error) { + var ( + pkgName = gfile.Basename(sdkFolderPath) + moduleNameCamel = gstr.CaseCamel(module) + moduleNameSnake = gstr.CaseSnake(module) + moduleImportPath = gstr.Replace(gfile.Dir(versionImportPath), "\\", "/", -1) + versionPrefix = "" + implementerName = moduleNameCamel + gstr.UcFirst(version) + implementerFilePath = filepath.FromSlash(gfile.Join(sdkFolderPath, fmt.Sprintf( + `%s_%s_%s.go`, pkgName, moduleNameSnake, version, + ))) + ) + if sdkNoV1 && version == "v1" { + implementerName = moduleNameCamel + } + // implementer file template. + var importPaths = make([]string, 0) + importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", moduleImportPath)) + importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", versionImportPath)) + implementerFileContent := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementer, g.MapStrStr{ + "{PkgName}": pkgName, + "{ImportPaths}": gstr.Join(importPaths, "\n"), + "{ImplementerName}": implementerName, + })) + // implementer new function definition. + if sdkStdVersion { + versionPrefix = fmt.Sprintf(`/api/%s`, version) + } + implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerNew, g.MapStrStr{ + "{Module}": module, + "{VersionPrefix}": versionPrefix, + "{ImplementerName}": implementerName, + })) + // implementer functions definitions. + for _, item := range items { + implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerFunc, g.MapStrStr{ + "{Version}": item.Version, + "{MethodName}": item.MethodName, + "{ImplementerName}": implementerName, + "{MethodComment}": item.GetComment(), + })) + implementerFileContent += "\n" + } + err = gfile.PutContents(implementerFilePath, implementerFileContent) + mlog.Printf(`generated: %s`, gfile.RealPath(implementerFilePath)) + return +} + +func (c *apiSdkGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) { + for _, item := range items { + if item.Module == module && item.Version == version { + subItems = append(subItems, item) + } + } + return +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao.go b/server/internal/library/hggen/internal/cmd/gendao/gendao.go new file mode 100644 index 0000000..1c01c39 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao.go @@ -0,0 +1,413 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "context" + "fmt" + "strings" + + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "golang.org/x/mod/modfile" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +type ( + CGenDao struct{} + CGenDaoInput struct { + g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"` + Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"` + Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"` + Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"` + TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"` + ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"` + Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"` + Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"` + RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"` + RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenDaoBriefRemoveFieldPrefix}"` + JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"` + ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"` + DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"` + TablePath string `name:"tablePath" short:"tp" brief:"{CGenDaoBriefTablePath}" d:"table"` + DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"` + EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"` + TplDaoTablePath string `name:"tplDaoTablePath" short:"t0" brief:"{CGenDaoBriefTplDaoTablePath}"` + TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"` + TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"` + TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"` + TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"` + StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"` + WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"` + GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"` + OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"` + DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"` + NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"` + NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"` + Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"` + GenTable bool `name:"genTable" short:"gt" brief:"{CGenDaoBriefGenTable}" orphan:"true"` + + TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"` + FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"` + + // internal usage purpose. + genItems *CGenDaoInternalGenItems + } + CGenDaoOutput struct{} + + CGenDaoInternalInput struct { + CGenDaoInput + DB gdb.DB + TableNames []string + NewTableNames []string + ShardingTableSet *gset.StrSet + } + DBTableFieldName = string + DBFieldTypeName = string + CustomAttributeType struct { + Type string `brief:"custom attribute type name"` + Import string `brief:"custom import for this type"` + } +) + +var ( + createdAt = gtime.Now() + tplView = gview.New() + defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{ + "decimal": { + Type: "float64", + }, + "money": { + Type: "float64", + }, + "numeric": { + Type: "float64", + }, + "smallmoney": { + Type: "float64", + }, + } + + // tablewriter Options + twRenderer = tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ + Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.Off, Right: tw.Off}, + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off}, + }, + Symbols: tw.NewSymbols(tw.StyleASCII), + })) + twConfig = tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone}, + }, + }) +) + +func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) { + in.genItems = newCGenDaoInternalGenItems() + if in.Link != "" { + doGenDaoForArray(ctx, -1, in) + } else if g.Cfg().Available(ctx) { + v := g.Cfg().MustGet(ctx, CGenDaoConfig) + if v.IsSlice() { + for i := 0; i < len(v.Interfaces()); i++ { + doGenDaoForArray(ctx, i, in) + } + } else { + doGenDaoForArray(ctx, -1, in) + } + } else { + doGenDaoForArray(ctx, -1, in) + } + doClear(in.genItems) + mlog.Print("done!") + return +} + +// doGenDaoForArray implements the "gen dao" command for configuration array. +func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) { + if in.genItems == nil { + in.genItems = newCGenDaoInternalGenItems() + } + + var ( + err error + db gdb.DB + ) + if index >= 0 { + err = g.Cfg().MustGet( + ctx, + fmt.Sprintf(`%s.%d`, CGenDaoConfig, index), + ).Scan(&in) + if err != nil { + mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenDaoConfig, err) + } + } + if dirRealPath := gfile.RealPath(in.Path); dirRealPath == "" { + mlog.Fatalf(`path "%s" does not exist`, in.Path) + } + removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",") + + // It uses user passed database configuration. + if in.Link != "" { + var tempGroup = gtime.TimestampNanoStr() + err = gdb.AddConfigNode(tempGroup, gdb.ConfigNode{ + Link: in.Link, + }) + if err != nil { + mlog.Fatalf(`database configuration failed: %+v`, err) + } + if db, err = gdb.Instance(tempGroup); err != nil { + mlog.Fatalf(`database initialization failed: %+v`, err) + } + } else { + db = g.DB(in.Group) + } + if db == nil { + mlog.Fatal(`database initialization failed, may be invalid database configuration`) + } + + var tableNames []string + if in.Tables != "" { + tableNames = gstr.SplitAndTrim(in.Tables, ",") + } else { + tableNames, err = db.Tables(context.TODO()) + if err != nil { + mlog.Fatalf("fetching tables failed: %+v", err) + } + } + // Table excluding. + if in.TablesEx != "" { + array := garray.NewStrArrayFrom(tableNames) + for _, p := range gstr.SplitAndTrim(in.TablesEx, ",") { + if gstr.Contains(p, "*") || gstr.Contains(p, "?") { + p = gstr.ReplaceByMap(p, map[string]string{ + "\r": "", + "\n": "", + }) + p = gstr.ReplaceByMap(p, map[string]string{ + "*": "\r", + "?": "\n", + }) + p = gregex.Quote(p) + p = gstr.ReplaceByMap(p, map[string]string{ + "\r": ".*", + "\n": ".", + }) + for _, v := range array.Clone().Slice() { + if gregex.IsMatchString(p, v) { + array.RemoveValue(v) + } + } + } else { + array.RemoveValue(p) + } + } + tableNames = array.Slice() + } + + // merge default typeMapping to input typeMapping. + if in.TypeMapping == nil { + in.TypeMapping = defaultTypeMapping + } else { + for key, typeMapping := range defaultTypeMapping { + if _, ok := in.TypeMapping[key]; !ok { + in.TypeMapping[key] = typeMapping + } + } + } + + // Generating dao & model go files one by one according to given table name. + var ( + newTableNames = make([]string, len(tableNames)) + shardingNewTableSet = gset.NewStrSet() + ) + for i, tableName := range tableNames { + newTableName := tableName + for _, v := range removePrefixArray { + newTableName = gstr.TrimLeftStr(newTableName, v, 1) + } + if len(in.ShardingPattern) > 0 { + for _, pattern := range in.ShardingPattern { + var ( + match []string + regPattern = gstr.Replace(pattern, "?", `(.+)`) + ) + match, err = gregex.MatchString(regPattern, newTableName) + if err != nil { + mlog.Fatalf(`invalid sharding pattern "%s": %+v`, pattern, err) + } + if len(match) < 2 { + continue + } + newTableName = gstr.Replace(pattern, "?", "") + newTableName = gstr.Trim(newTableName, `_.-`) + if shardingNewTableSet.Contains(newTableName) { + tableNames[i] = "" + continue + } + // Add prefix to sharding table name, if not, the isSharding check would not match. + shardingNewTableSet.Add(in.Prefix + newTableName) + } + } + newTableName = in.Prefix + newTableName + if tableNames[i] != "" { + // If shardingNewTableSet contains newTableName (tableName is empty), it should not be added to tableNames, make it empty and filter later. + newTableNames[i] = newTableName + } + } + tableNames = garray.NewStrArrayFrom(tableNames).FilterEmpty().Slice() + newTableNames = garray.NewStrArrayFrom(newTableNames).FilterEmpty().Slice() // Filter empty table names. make sure that newTableNames and tableNames have the same length. + in.genItems.Scale() + + // Dao: index and internal. + generateDao(ctx, CGenDaoInternalInput{ + CGenDaoInput: in, + DB: db, + TableNames: tableNames, + NewTableNames: newTableNames, + ShardingTableSet: shardingNewTableSet, + }) + // Table: table fields. + generateTable(ctx, CGenDaoInternalInput{ + CGenDaoInput: in, + DB: db, + TableNames: tableNames, + NewTableNames: newTableNames, + ShardingTableSet: shardingNewTableSet, + }) + // Do. + generateDo(ctx, CGenDaoInternalInput{ + CGenDaoInput: in, + DB: db, + TableNames: tableNames, + NewTableNames: newTableNames, + }) + // Entity. + generateEntity(ctx, CGenDaoInternalInput{ + CGenDaoInput: in, + DB: db, + TableNames: tableNames, + NewTableNames: newTableNames, + }) + + in.genItems.SetClear(in.Clear) +} + +func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string { + var packageImportsArray = garray.NewStrArray() + if isDo { + packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`) + } + + // Time package recognition. + if strings.Contains(source, "gtime.Time") { + packageImportsArray.Append(`"github.com/gogf/gf/v2/os/gtime"`) + } else if strings.Contains(source, "time.Time") { + packageImportsArray.Append(`"time"`) + } + + // Json type. + if strings.Contains(source, "gjson.Json") { + packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`) + } + + // Check and update imports in go.mod + if len(appendImports) > 0 { + goModPath := utils.GetModPath() + if goModPath == "" { + mlog.Fatal("go.mod not found in current project") + } + mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil) + if err != nil { + mlog.Fatalf("parse go.mod failed: %+v", err) + } + for _, appendImport := range appendImports { + found := false + for _, require := range mod.Require { + if gstr.Contains(appendImport, require.Mod.Path) { + found = true + break + } + } + if !found { + if err = gproc.ShellRun(ctx, `go get `+appendImport); err != nil { + mlog.Fatalf(`%+v`, err) + } + } + packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport)) + } + } + + // Generate and write content to golang file. + packageImportsStr := "" + if packageImportsArray.Len() > 0 { + packageImportsStr = fmt.Sprintf("import(\n%s\n)", packageImportsArray.Join("\n")) + } + return packageImportsStr +} + +func assignDefaultVar(view *gview.View, in CGenDaoInternalInput) { + var ( + tplCreatedAtDatetimeStr string + tplDatetimeStr = createdAt.String() + ) + if in.WithTime { + tplCreatedAtDatetimeStr = fmt.Sprintf(`Created at %s`, tplDatetimeStr) + } + view.Assigns(g.Map{ + tplVarDatetimeStr: tplDatetimeStr, + tplVarCreatedAtDatetimeStr: tplCreatedAtDatetimeStr, + }) +} + +func sortFieldKeyForDao(fieldMap map[string]*gdb.TableField) []string { + names := make(map[int]string) + for _, field := range fieldMap { + names[field.Index] = field.Name + } + var ( + i = 0 + j = 0 + result = make([]string, len(names)) + ) + for { + if len(names) == 0 { + break + } + if val, ok := names[i]; ok { + result[j] = val + j++ + delete(names, i) + } + i++ + } + return result +} + +func getTemplateFromPathOrDefault(filePath string, def string) string { + if filePath != "" { + if contents := gfile.GetContents(filePath); contents != "" { + return contents + } + } + return def +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go new file mode 100644 index 0000000..ae297e0 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_clear.go @@ -0,0 +1,48 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +func doClear(items *CGenDaoInternalGenItems) { + var allGeneratedFilePaths = make([]string, 0) + for _, item := range items.Items { + allGeneratedFilePaths = append(allGeneratedFilePaths, item.GeneratedFilePaths...) + } + for i, v := range allGeneratedFilePaths { + allGeneratedFilePaths[i] = gfile.RealPath(v) + } + for _, item := range items.Items { + if !item.Clear { + continue + } + doClearItem(item, allGeneratedFilePaths) + } +} + +func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) { + var generatedFilePaths = make([]string, 0) + for _, dirPath := range item.StorageDirPaths { + filePaths, err := gfile.ScanDirFile(dirPath, "*.go", true) + if err != nil { + mlog.Fatal(err) + } + generatedFilePaths = append(generatedFilePaths, filePaths...) + } + for _, filePath := range generatedFilePaths { + if !gstr.InArray(allGeneratedFilePaths, filePath) { + if err := gfile.RemoveFile(filePath); err != nil { + mlog.Print(err) + } + } + } +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go new file mode 100644 index 0000000..d1ebe85 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_dao.go @@ -0,0 +1,260 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + "strings" + + "github.com/olekukonko/tablewriter" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +func generateDao(ctx context.Context, in CGenDaoInternalInput) { + var ( + dirPathDao = gfile.Join(in.Path, in.DaoPath) + dirPathDaoInternal = gfile.Join(dirPathDao, "internal") + ) + in.genItems.AppendDirPath(dirPathDao) + for i := 0; i < len(in.TableNames); i++ { + var ( + realTableName = in.TableNames[i] + newTableName = in.NewTableNames[i] + ) + generateDaoSingle(ctx, generateDaoSingleInput{ + CGenDaoInternalInput: in, + TableName: realTableName, + NewTableName: newTableName, + DirPathDao: dirPathDao, + DirPathDaoInternal: dirPathDaoInternal, + IsSharding: in.ShardingTableSet.Contains(newTableName), + }) + } +} + +type generateDaoSingleInput struct { + CGenDaoInternalInput + // TableName specifies the table name of the table. + TableName string + // NewTableName specifies the prefix-stripped or custom edited name of the table. + NewTableName string + DirPathDao string + DirPathDaoInternal string + IsSharding bool +} + +// generateDaoSingle generates the dao and model content of given table. +func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) { + // Generating table data preparing. + fieldMap, err := in.DB.TableFields(ctx, in.TableName) + if err != nil { + mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err) + } + var ( + tableNameCamelCase = formatFieldName(in.NewTableName, FieldNameCaseCamel) + tableNameCamelLowerCase = formatFieldName(in.NewTableName, FieldNameCaseCamelLower) + tableNameSnakeCase = gstr.CaseSnake(in.NewTableName) + importPrefix = in.ImportPrefix + ) + if importPrefix == "" { + importPrefix = utils.GetImportPath(gfile.Join(in.Path, in.DaoPath)) + } else { + importPrefix = gstr.Join(g.SliceStr{importPrefix, in.DaoPath}, "/") + } + + fileName := gstr.Trim(tableNameSnakeCase, "-_.") + if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" { + // Add suffix to avoid the table name which contains "_test", + // which would make the go file a testing file. + fileName += "_table" + } + + // dao - index + generateDaoIndex(generateDaoIndexInput{ + generateDaoSingleInput: in, + TableNameCamelCase: tableNameCamelCase, + TableNameCamelLowerCase: tableNameCamelLowerCase, + ImportPrefix: importPrefix, + FileName: fileName, + }) + + // dao - internal + generateDaoInternal(generateDaoInternalInput{ + generateDaoSingleInput: in, + TableNameCamelCase: tableNameCamelCase, + TableNameCamelLowerCase: tableNameCamelLowerCase, + ImportPrefix: importPrefix, + FileName: fileName, + FieldMap: fieldMap, + }) +} + +type generateDaoIndexInput struct { + generateDaoSingleInput + TableNameCamelCase string + TableNameCamelLowerCase string + ImportPrefix string + FileName string +} + +func generateDaoIndex(in generateDaoIndexInput) { + path := filepath.FromSlash(gfile.Join(in.DirPathDao, in.FileName+".go")) + // It should add path to result slice whenever it would generate the path file or not. + in.genItems.AppendGeneratedFilePath(path) + if in.OverwriteDao || !gfile.Exists(path) { + var ( + ctx = context.Background() + tplContent = getTemplateFromPathOrDefault( + in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent, + ) + ) + tplView.ClearAssigns() + tplView.Assigns(gview.Params{ + tplVarTableSharding: in.IsSharding, + tplVarTableShardingPrefix: in.NewTableName + "_", + tplVarImportPrefix: in.ImportPrefix, + tplVarTableName: in.TableName, + tplVarTableNameCamelCase: in.TableNameCamelCase, + tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase, + tplVarPackageName: filepath.Base(in.DaoPath), + }) + indexContent, err := tplView.ParseContent(ctx, tplContent) + if err != nil { + mlog.Fatalf("parsing template content failed: %v", err) + } + if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil { + mlog.Fatalf("writing content to '%s' failed: %v", path, err) + } else { + utils.GoFmt(path) + mlog.Print("generated:", gfile.RealPath(path)) + } + } +} + +type generateDaoInternalInput struct { + generateDaoSingleInput + TableNameCamelCase string + TableNameCamelLowerCase string + ImportPrefix string + FileName string + FieldMap map[string]*gdb.TableField +} + +func generateDaoInternal(in generateDaoInternalInput) { + var ( + ctx = context.Background() + removeFieldPrefixArray = gstr.SplitAndTrim(in.RemoveFieldPrefix, ",") + tplContent = getTemplateFromPathOrDefault( + in.TplDaoInternalPath, consts.TemplateGenDaoInternalContent, + ) + ) + tplView.ClearAssigns() + tplView.Assigns(gview.Params{ + tplVarImportPrefix: in.ImportPrefix, + tplVarTableName: in.TableName, + tplVarGroupName: in.Group, + tplVarTableNameCamelCase: in.TableNameCamelCase, + tplVarTableNameCamelLowerCase: in.TableNameCamelLowerCase, + tplVarColumnDefine: gstr.Trim(generateColumnDefinitionForDao(in.FieldMap, removeFieldPrefixArray)), + tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap, removeFieldPrefixArray)), + }) + assignDefaultVar(tplView, in.CGenDaoInternalInput) + modelContent, err := tplView.ParseContent(ctx, tplContent) + if err != nil { + mlog.Fatalf("parsing template content failed: %v", err) + } + path := filepath.FromSlash(gfile.Join(in.DirPathDaoInternal, in.FileName+".go")) + in.genItems.AppendGeneratedFilePath(path) + if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil { + mlog.Fatalf("writing content to '%s' failed: %v", path, err) + } else { + utils.GoFmt(path) + mlog.Print("generated:", gfile.RealPath(path)) + } +} + +// generateColumnNamesForDao generates and returns the column names assignment content of column struct +// for specified table. +func generateColumnNamesForDao(fieldMap map[string]*gdb.TableField, removeFieldPrefixArray []string) string { + var ( + buffer = bytes.NewBuffer(nil) + array = make([][]string, len(fieldMap)) + names = sortFieldKeyForDao(fieldMap) + ) + + for index, name := range names { + field := fieldMap[name] + + newFiledName := field.Name + for _, v := range removeFieldPrefixArray { + newFiledName = gstr.TrimLeftStr(newFiledName, v, 1) + } + + array[index] = []string{ + " #" + formatFieldName(newFiledName, FieldNameCaseCamel) + ":", + fmt.Sprintf(` #"%s",`, field.Name), + } + } + table := tablewriter.NewTable(buffer, twRenderer, twConfig) + table.Bulk(array) + table.Render() + namesContent := buffer.String() + // Let's do this hack of table writer for indent! + namesContent = gstr.Replace(namesContent, " #", "") + buffer.Reset() + buffer.WriteString(namesContent) + return buffer.String() +} + +// generateColumnDefinitionForDao generates and returns the column names definition for specified table. +func generateColumnDefinitionForDao(fieldMap map[string]*gdb.TableField, removeFieldPrefixArray []string) string { + var ( + buffer = bytes.NewBuffer(nil) + array = make([][]string, len(fieldMap)) + names = sortFieldKeyForDao(fieldMap) + ) + + for index, name := range names { + var ( + field = fieldMap[name] + comment = gstr.Trim(gstr.ReplaceByArray(field.Comment, g.SliceStr{ + "\n", " ", + "\r", " ", + })) + ) + newFiledName := field.Name + for _, v := range removeFieldPrefixArray { + newFiledName = gstr.TrimLeftStr(newFiledName, v, 1) + } + array[index] = []string{ + " #" + formatFieldName(newFiledName, FieldNameCaseCamel), + " # " + "string", + " #" + fmt.Sprintf(`// %s`, comment), + } + } + table := tablewriter.NewTable(buffer, twRenderer, twConfig) + table.Bulk(array) + table.Render() + defineContent := buffer.String() + // Let's do this hack of table writer for indent! + defineContent = gstr.Replace(defineContent, " #", "") + buffer.Reset() + buffer.WriteString(defineContent) + return buffer.String() +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_do.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_do.go new file mode 100644 index 0000000..fd296cf --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_do.go @@ -0,0 +1,100 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "context" + "fmt" + "path/filepath" + "strings" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +func generateDo(ctx context.Context, in CGenDaoInternalInput) { + var dirPathDo = filepath.FromSlash(gfile.Join(in.Path, in.DoPath)) + in.genItems.AppendDirPath(dirPathDo) + in.NoJsonTag = true + in.DescriptionTag = false + in.NoModelComment = false + // Model content. + for i, tableName := range in.TableNames { + fieldMap, err := in.DB.TableFields(ctx, tableName) + if err != nil { + mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err) + } + var ( + newTableName = in.NewTableNames[i] + doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go") + structDefinition, _ = generateStructDefinition(ctx, generateStructDefinitionInput{ + CGenDaoInternalInput: in, + TableName: tableName, + StructName: formatFieldName(newTableName, FieldNameCaseCamel), + FieldMap: fieldMap, + IsDo: true, + }) + ) + // replace all types to any. + structDefinition, _ = gregex.ReplaceStringFuncMatch( + "([A-Z]\\w*?)\\s+([\\w\\*\\.]+?)\\s+(//)", + structDefinition, + func(match []string) string { + // If the type is already a pointer/slice/map, it does nothing. + if !gstr.HasPrefix(match[2], "*") && !gstr.HasPrefix(match[2], "[]") && !gstr.HasPrefix(match[2], "map") { + return fmt.Sprintf(`%s any %s`, match[1], match[3]) + } + return match[0] + }, + ) + modelContent := generateDoContent( + ctx, + in, + tableName, + formatFieldName(newTableName, FieldNameCaseCamel), + structDefinition, + ) + in.genItems.AppendGeneratedFilePath(doFilePath) + err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent)) + if err != nil { + mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err) + } else { + utils.GoFmt(doFilePath) + mlog.Print("generated:", gfile.RealPath(doFilePath)) + } + } +} + +func generateDoContent( + ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string, +) string { + var ( + tplContent = getTemplateFromPathOrDefault( + in.TplDaoDoPath, consts.TemplateGenDaoDoContent, + ) + ) + tplView.ClearAssigns() + tplView.Assigns(gview.Params{ + tplVarTableName: tableName, + tplVarPackageImports: getImportPartContent(ctx, structDefine, true, nil), + tplVarTableNameCamelCase: tableNameCamelCase, + tplVarStructDefine: structDefine, + tplVarPackageName: filepath.Base(in.DoPath), + }) + assignDefaultVar(tplView, in) + doContent, err := tplView.ParseContent(ctx, tplContent) + if err != nil { + mlog.Fatalf("parsing template content failed: %v", err) + } + return doContent +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go new file mode 100644 index 0000000..5eeafe7 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_entity.go @@ -0,0 +1,85 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "context" + "path/filepath" + "strings" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +func generateEntity(ctx context.Context, in CGenDaoInternalInput) { + var dirPathEntity = gfile.Join(in.Path, in.EntityPath) + in.genItems.AppendDirPath(dirPathEntity) + // Model content. + for i, tableName := range in.TableNames { + fieldMap, err := in.DB.TableFields(ctx, tableName) + if err != nil { + mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err) + } + + var ( + newTableName = in.NewTableNames[i] + entityFilePath = filepath.FromSlash(gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go")) + structDefinition, appendImports = generateStructDefinition(ctx, generateStructDefinitionInput{ + CGenDaoInternalInput: in, + TableName: tableName, + StructName: formatFieldName(newTableName, FieldNameCaseCamel), + FieldMap: fieldMap, + IsDo: false, + }) + entityContent = generateEntityContent( + ctx, + in, + newTableName, + formatFieldName(newTableName, FieldNameCaseCamel), + structDefinition, + appendImports, + ) + ) + in.genItems.AppendGeneratedFilePath(entityFilePath) + err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent)) + if err != nil { + mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err) + } else { + utils.GoFmt(entityFilePath) + mlog.Print("generated:", gfile.RealPath(entityFilePath)) + } + } +} + +func generateEntityContent( + ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string, appendImports []string, +) string { + var ( + tplContent = getTemplateFromPathOrDefault( + in.TplDaoEntityPath, consts.TemplateGenDaoEntityContent, + ) + ) + tplView.ClearAssigns() + tplView.Assigns(gview.Params{ + tplVarTableName: tableName, + tplVarPackageImports: getImportPartContent(ctx, structDefine, false, appendImports), + tplVarTableNameCamelCase: tableNameCamelCase, + tplVarStructDefine: structDefine, + tplVarPackageName: filepath.Base(in.EntityPath), + }) + assignDefaultVar(tplView, in) + entityContent, err := tplView.ParseContent(ctx, tplContent) + if err != nil { + mlog.Fatalf("parsing template content failed: %v", err) + } + return entityContent +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go new file mode 100644 index 0000000..fcb7bf9 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_gen_item.go @@ -0,0 +1,53 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +type ( + CGenDaoInternalGenItems struct { + index int + Items []CGenDaoInternalGenItem + } + CGenDaoInternalGenItem struct { + Clear bool + StorageDirPaths []string + GeneratedFilePaths []string + } +) + +func newCGenDaoInternalGenItems() *CGenDaoInternalGenItems { + return &CGenDaoInternalGenItems{ + index: -1, + Items: make([]CGenDaoInternalGenItem, 0), + } +} + +func (i *CGenDaoInternalGenItems) Scale() { + i.Items = append(i.Items, CGenDaoInternalGenItem{ + StorageDirPaths: make([]string, 0), + GeneratedFilePaths: make([]string, 0), + Clear: false, + }) + i.index++ +} + +func (i *CGenDaoInternalGenItems) SetClear(clear bool) { + i.Items[i.index].Clear = clear +} + +func (i *CGenDaoInternalGenItems) AppendDirPath(storageDirPath string) { + i.Items[i.index].StorageDirPaths = append( + i.Items[i.index].StorageDirPaths, + storageDirPath, + ) +} + +func (i *CGenDaoInternalGenItems) AppendGeneratedFilePath(generatedFilePath string) { + i.Items[i.index].GeneratedFilePaths = append( + i.Items[i.index].GeneratedFilePaths, + generatedFilePath, + ) +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go new file mode 100644 index 0000000..9508489 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_structure.go @@ -0,0 +1,229 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "bytes" + "context" + "fmt" + "strings" + + "github.com/olekukonko/tablewriter" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" +) + +type generateStructDefinitionInput struct { + CGenDaoInternalInput + TableName string // Table name. + StructName string // Struct name. + FieldMap map[string]*gdb.TableField // Table field map. + IsDo bool // Is generating DTO struct. +} + +func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) (string, []string) { + var appendImports []string + buffer := bytes.NewBuffer(nil) + array := make([][]string, len(in.FieldMap)) + names := sortFieldKeyForDao(in.FieldMap) + for index, name := range names { + var imports string + field := in.FieldMap[name] + array[index], imports = generateStructFieldDefinition(ctx, field, in) + if imports != "" { + appendImports = append(appendImports, imports) + } + } + table := tablewriter.NewTable(buffer, twRenderer, twConfig) + table.Bulk(array) + table.Render() + stContent := buffer.String() + // Let's do this hack of table writer for indent! + stContent = gstr.Replace(stContent, " #", "") + stContent = gstr.Replace(stContent, "` ", "`") + stContent = gstr.Replace(stContent, "``", "") + buffer.Reset() + fmt.Fprintf(buffer, "type %s struct {\n", in.StructName) + if in.IsDo { + fmt.Fprintf(buffer, "g.Meta `orm:\"table:%s, do:true\"`\n", in.TableName) + } + buffer.WriteString(stContent) + buffer.WriteString("}") + return buffer.String(), appendImports +} + +func getTypeMappingInfo( + ctx context.Context, fieldType string, inTypeMapping map[DBFieldTypeName]CustomAttributeType, +) (typeNameStr, importStr string) { + if typeMapping, ok := inTypeMapping[strings.ToLower(fieldType)]; ok { + typeNameStr = typeMapping.Type + importStr = typeMapping.Import + return + } + tryTypeMatch, _ := gregex.MatchString(`(.+?)\(([^\(\)]+)\)([\s\)]*)`, fieldType) + var ( + tryTypeName string + moreTry bool + ) + if len(tryTypeMatch) == 4 { + tryTypeMatch3, _ := gregex.ReplaceString(`\s+`, "", tryTypeMatch[3]) + tryTypeName = gstr.Trim(tryTypeMatch[1]) + tryTypeMatch3 + moreTry = tryTypeMatch3 != "" + } else { + tryTypeName = gstr.Split(fieldType, " ")[0] + } + if tryTypeName != "" { + if typeMapping, ok := inTypeMapping[strings.ToLower(tryTypeName)]; ok { + typeNameStr = typeMapping.Type + importStr = typeMapping.Import + } else if moreTry { + typeNameStr, importStr = getTypeMappingInfo(ctx, tryTypeName, inTypeMapping) + } + } + return +} + +// generateStructFieldDefinition generates and returns the attribute definition for specified field. +func generateStructFieldDefinition( + ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput, +) (attrLines []string, appendImport string) { + var ( + err error + localTypeName gdb.LocalType + localTypeNameStr string + jsonTag = gstr.CaseConvert(field.Name, gstr.CaseTypeMatch(in.JsonCase)) + ) + + if in.TypeMapping != nil && len(in.TypeMapping) > 0 { + localTypeNameStr, appendImport = getTypeMappingInfo(ctx, field.Type, in.TypeMapping) + } + + if localTypeNameStr == "" { + localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil) + if err != nil { + panic(err) + } + localTypeNameStr = string(localTypeName) + switch localTypeName { + case gdb.LocalTypeDate, gdb.LocalTypeTime, gdb.LocalTypeDatetime: + if in.StdTime { + localTypeNameStr = "time.Time" + } else { + localTypeNameStr = "*gtime.Time" + } + + case gdb.LocalTypeInt64Bytes: + localTypeNameStr = "int64" + + case gdb.LocalTypeUint64Bytes: + localTypeNameStr = "uint64" + + // Special type handle. + case gdb.LocalTypeJson, gdb.LocalTypeJsonb: + if in.GJsonSupport { + localTypeNameStr = "*gjson.Json" + } else { + localTypeNameStr = "string" + } + } + } + + var ( + tagKey = "`" + descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`) + ) + removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",") + newFiledName := field.Name + for _, v := range removeFieldPrefixArray { + newFiledName = gstr.TrimLeftStr(newFiledName, v, 1) + } + + if in.FieldMapping != nil && len(in.FieldMapping) > 0 { + if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok { + localTypeNameStr = typeMapping.Type + appendImport = typeMapping.Import + } + } + + attrLines = []string{ + " #" + formatFieldName(newFiledName, FieldNameCaseCamel), + " #" + localTypeNameStr, + } + attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag)) + // orm tag + if !in.IsDo { + // entity + attrLines = append(attrLines, fmt.Sprintf(` #orm:"%s"`, field.Name)) + } + attrLines = append(attrLines, fmt.Sprintf(` #description:"%s"%s`, descriptionTag, tagKey)) + attrLines = append(attrLines, fmt.Sprintf(` #// %s`, formatComment(field.Comment))) + + for k, v := range attrLines { + if in.NoJsonTag { + v, _ = gregex.ReplaceString(`json:".+"`, ``, v) + } + if !in.DescriptionTag { + v, _ = gregex.ReplaceString(`description:".*"`, ``, v) + } + if in.NoModelComment { + v, _ = gregex.ReplaceString(`//.+`, ``, v) + } + attrLines[k] = v + } + return attrLines, appendImport +} + +type FieldNameCase string + +const ( + FieldNameCaseCamel FieldNameCase = "CaseCamel" + FieldNameCaseCamelLower FieldNameCase = "CaseCamelLower" +) + +// formatFieldName formats and returns a new field name that is used for golang codes generating. +func formatFieldName(fieldName string, nameCase FieldNameCase) string { + // For normal databases like mysql, pgsql, sqlite, + // field/table names of that are in normal case. + var newFieldName = fieldName + if isAllUpper(fieldName) { + // For special databases like dm, oracle, + // field/table names of that are in upper case. + newFieldName = strings.ToLower(fieldName) + } + switch nameCase { + case FieldNameCaseCamel: + return gstr.CaseCamel(newFieldName) + case FieldNameCaseCamelLower: + return gstr.CaseCamelLower(newFieldName) + default: + return "" + } +} + +// isAllUpper checks and returns whether given `fieldName` all letters are upper case. +func isAllUpper(fieldName string) bool { + for _, b := range fieldName { + if b >= 'a' && b <= 'z' { + return false + } + } + return true +} + +// formatComment formats the comment string to fit the golang code without any lines. +func formatComment(comment string) string { + comment = gstr.ReplaceByArray(comment, g.SliceStr{ + "\n", " ", + "\r", " ", + }) + comment = gstr.Replace(comment, `\n`, " ") + comment = gstr.Trim(comment) + return comment +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_table.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_table.go new file mode 100644 index 0000000..f6587b0 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_table.go @@ -0,0 +1,147 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "bytes" + "context" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +// generateTable generates dao files for given tables. +func generateTable(ctx context.Context, in CGenDaoInternalInput) { + dirPathTable := gfile.Join(in.Path, in.TablePath) + if !in.GenTable { + if gfile.Exists(dirPathTable) { + in.genItems.AppendDirPath(dirPathTable) + } + return + } + in.genItems.AppendDirPath(dirPathTable) + for i := 0; i < len(in.TableNames); i++ { + var ( + realTableName = in.TableNames[i] + newTableName = in.NewTableNames[i] + ) + generateTableSingle(ctx, generateTableSingleInput{ + CGenDaoInternalInput: in, + TableName: realTableName, + NewTableName: newTableName, + DirPathTable: dirPathTable, + }) + } +} + +// generateTableSingleInput is the input parameter for generateTableSingle. +type generateTableSingleInput struct { + CGenDaoInternalInput + // TableName specifies the table name of the table. + TableName string + // NewTableName specifies the prefix-stripped or custom edited name of the table. + NewTableName string + DirPathTable string +} + +// generateTableSingle generates dao files for a single table. +func generateTableSingle(ctx context.Context, in generateTableSingleInput) { + // Generating table data preparing. + fieldMap, err := in.DB.TableFields(ctx, in.TableName) + if err != nil { + mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err) + } + + tableNameSnakeCase := gstr.CaseSnake(in.NewTableName) + fileName := gstr.Trim(tableNameSnakeCase, "-_.") + if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" { + // Add suffix to avoid the table name which contains "_test", + // which would make the go file a testing file. + fileName += "_table" + } + path := filepath.FromSlash(gfile.Join(in.DirPathTable, fileName+".go")) + in.genItems.AppendGeneratedFilePath(path) + if in.OverwriteDao || !gfile.Exists(path) { + var ( + ctx = context.Background() + tplContent = getTemplateFromPathOrDefault( + in.TplDaoTablePath, consts.TemplateGenTableContent, + ) + ) + tplView.ClearAssigns() + tplView.Assigns(gview.Params{ + tplVarGroupName: in.Group, + tplVarTableName: in.TableName, + tplVarTableNameCamelCase: formatFieldName(in.NewTableName, FieldNameCaseCamel), + tplVarPackageName: filepath.Base(in.TablePath), + tplVarTableFields: generateTableFields(fieldMap), + }) + indexContent, err := tplView.ParseContent(ctx, tplContent) + if err != nil { + mlog.Fatalf("parsing template content failed: %v", err) + } + if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil { + mlog.Fatalf("writing content to '%s' failed: %v", path, err) + } else { + utils.GoFmt(path) + mlog.Print("generated:", gfile.RealPath(path)) + } + } +} + +// generateTableFields generates and returns the field definition content for specified table. +func generateTableFields(fields map[string]*gdb.TableField) string { + var buf bytes.Buffer + fieldNames := make([]string, 0, len(fields)) + for fieldName := range fields { + fieldNames = append(fieldNames, fieldName) + } + sort.Slice(fieldNames, func(i, j int) bool { + return fields[fieldNames[i]].Index < fields[fieldNames[j]].Index // asc + }) + for index, fieldName := range fieldNames { + field := fields[fieldName] + buf.WriteString(" " + strconv.Quote(field.Name) + ": {\n") + buf.WriteString(" Index: " + gconv.String(field.Index) + ",\n") + buf.WriteString(" Name: " + strconv.Quote(field.Name) + ",\n") + buf.WriteString(" Type: " + strconv.Quote(field.Type) + ",\n") + buf.WriteString(" Null: " + gconv.String(field.Null) + ",\n") + buf.WriteString(" Key: " + strconv.Quote(field.Key) + ",\n") + buf.WriteString(" Default: " + generateDefaultValue(field.Default) + ",\n") + buf.WriteString(" Extra: " + strconv.Quote(field.Extra) + ",\n") + buf.WriteString(" Comment: " + strconv.Quote(field.Comment) + ",\n") + buf.WriteString(" },") + if index != len(fieldNames)-1 { + buf.WriteString("\n") + } + } + return buf.String() +} + +// generateDefaultValue generates and returns the default value definition for specified field. +func generateDefaultValue(value interface{}) string { + if value == nil { + return "nil" + } + switch v := value.(type) { + case string: + return strconv.Quote(v) + default: + return gconv.String(v) + } +} diff --git a/server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go b/server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go new file mode 100644 index 0000000..25bc842 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/gendao/gendao_tag.go @@ -0,0 +1,155 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gtag" +) + +const ( + CGenDaoConfig = `gfcli.gen.dao` + CGenDaoUsage = `gf gen dao [OPTION]` + CGenDaoBrief = `automatically generate go files for dao/do/entity` + CGenDaoEg = ` +gf gen dao +gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +gf gen dao -p ./model -g user-center -t user,user_detail,user_login +gf gen dao -r user_ +` + + CGenDaoAd = ` +CONFIGURATION SUPPORT + Options are also supported by configuration file. + It's suggested using configuration file instead of command line arguments making producing. + The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml): + gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "order,products" + jsonCase: "CamelLower" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "./my-app" + prefix: "primary_" + tables: "user, userDetail" + typeMapping: + decimal: + type: decimal.Decimal + import: github.com/shopspring/decimal + numeric: + type: string + fieldMapping: + table_name.field_name: + type: decimal.Decimal + import: github.com/shopspring/decimal +` + CGenDaoBriefPath = `directory path for generated files` + CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame` + CGenDaoBriefTables = `generate models only for given tables, multiple table names separated with ','` + CGenDaoBriefTablesEx = `generate models excluding given tables, multiple table names separated with ','` + CGenDaoBriefPrefix = `add prefix for all table of specified link/database tables` + CGenDaoBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','` + CGenDaoBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','` + CGenDaoBriefStdTime = `use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables` + CGenDaoBriefWithTime = `add created time for auto produced go files` + CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables` + CGenDaoBriefImportPrefix = `custom import prefix for generated go files` + CGenDaoBriefDaoPath = `directory path for storing generated dao files under path` + CGenDaoBriefTablePath = `directory path for storing generated table files under path` + CGenDaoBriefDoPath = `directory path for storing generated do files under path` + CGenDaoBriefEntityPath = `directory path for storing generated entity files under path` + CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder` + CGenDaoBriefModelFile = `custom file name for storing generated model content` + CGenDaoBriefModelFileForDao = `custom file name generating model for DAO operations like Where/Data. It's empty in default` + CGenDaoBriefDescriptionTag = `add comment to description tag for each field` + CGenDaoBriefNoJsonTag = `no json tag will be added for each field` + CGenDaoBriefNoModelComment = `no model comment will be added for each field` + CGenDaoBriefClear = `delete all generated go files that do not exist in database` + CGenDaoBriefGenTable = `generate table files` + CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table` + CGenDaoBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table` + CGenDaoBriefShardingPattern = `sharding pattern for table name, e.g. "users_?" will be replace tables "users_001,users_002,..." to "users" dao` + CGenDaoBriefGroup = ` +specifying the configuration group name of database for generated ORM instance, +it's not necessary and the default value is "default" +` + CGenDaoBriefJsonCase = ` +generated json tag case for model struct, cases are as follows: +| Case | Example | +|---------------- |--------------------| +| Camel | AnyKindOfString | +| CamelLower | anyKindOfString | default +| Snake | any_kind_of_string | +| SnakeScreaming | ANY_KIND_OF_STRING | +| SnakeFirstUpper | rgb_code_md5 | +| Kebab | any-kind-of-string | +| KebabScreaming | ANY-KIND-OF-STRING | +` + CGenDaoBriefTplDaoIndexPath = `template file path for dao index file` + CGenDaoBriefTplDaoInternalPath = `template file path for dao internal file` + CGenDaoBriefTplDaoDoPathPath = `template file path for dao do file` + CGenDaoBriefTplDaoEntityPath = `template file path for dao entity file` + + tplVarTableName = `TplTableName` + tplVarTableNameCamelCase = `TplTableNameCamelCase` + tplVarTableNameCamelLowerCase = `TplTableNameCamelLowerCase` + tplVarTableSharding = `TplTableSharding` + tplVarTableShardingPrefix = `TplTableShardingPrefix` + tplVarTableFields = `TplTableFields` + tplVarPackageImports = `TplPackageImports` + tplVarImportPrefix = `TplImportPrefix` + tplVarStructDefine = `TplStructDefine` + tplVarColumnDefine = `TplColumnDefine` + tplVarColumnNames = `TplColumnNames` + tplVarGroupName = `TplGroupName` + tplVarDatetimeStr = `TplDatetimeStr` + tplVarCreatedAtDatetimeStr = `TplCreatedAtDatetimeStr` + tplVarPackageName = `TplPackageName` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `CGenDaoConfig`: CGenDaoConfig, + `CGenDaoUsage`: CGenDaoUsage, + `CGenDaoBrief`: CGenDaoBrief, + `CGenDaoEg`: CGenDaoEg, + `CGenDaoAd`: CGenDaoAd, + `CGenDaoBriefPath`: CGenDaoBriefPath, + `CGenDaoBriefLink`: CGenDaoBriefLink, + `CGenDaoBriefTables`: CGenDaoBriefTables, + `CGenDaoBriefTablesEx`: CGenDaoBriefTablesEx, + `CGenDaoBriefPrefix`: CGenDaoBriefPrefix, + `CGenDaoBriefRemovePrefix`: CGenDaoBriefRemovePrefix, + `CGenDaoBriefRemoveFieldPrefix`: CGenDaoBriefRemoveFieldPrefix, + `CGenDaoBriefStdTime`: CGenDaoBriefStdTime, + `CGenDaoBriefWithTime`: CGenDaoBriefWithTime, + `CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath, + `CGenDaoBriefTablePath`: CGenDaoBriefTablePath, + `CGenDaoBriefDoPath`: CGenDaoBriefDoPath, + `CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath, + `CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport, + `CGenDaoBriefImportPrefix`: CGenDaoBriefImportPrefix, + `CGenDaoBriefOverwriteDao`: CGenDaoBriefOverwriteDao, + `CGenDaoBriefModelFile`: CGenDaoBriefModelFile, + `CGenDaoBriefModelFileForDao`: CGenDaoBriefModelFileForDao, + `CGenDaoBriefDescriptionTag`: CGenDaoBriefDescriptionTag, + `CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag, + `CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment, + `CGenDaoBriefClear`: CGenDaoBriefClear, + `CGenDaoBriefGenTable`: CGenDaoBriefGenTable, + `CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping, + `CGenDaoBriefFieldMapping`: CGenDaoBriefFieldMapping, + `CGenDaoBriefShardingPattern`: CGenDaoBriefShardingPattern, + `CGenDaoBriefGroup`: CGenDaoBriefGroup, + `CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase, + `CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath, + `CGenDaoBriefTplDaoInternalPath`: CGenDaoBriefTplDaoInternalPath, + `CGenDaoBriefTplDaoDoPathPath`: CGenDaoBriefTplDaoDoPathPath, + `CGenDaoBriefTplDaoEntityPath`: CGenDaoBriefTplDaoEntityPath, + }) +} diff --git a/server/internal/library/hggen/internal/cmd/genenums/genenums.go b/server/internal/library/hggen/internal/cmd/genenums/genenums.go new file mode 100644 index 0000000..18a34cd --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genenums/genenums.go @@ -0,0 +1,85 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genenums + +import ( + "context" + + "golang.org/x/tools/go/packages" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +type ( + CGenEnums struct{} + CGenEnumsInput struct { + g.Meta `name:"enums" config:"{CGenEnumsConfig}" brief:"{CGenEnumsBrief}" eg:"{CGenEnumsEg}"` + Src string `name:"src" short:"s" dc:"source folder path to be parsed" d:"api"` + Path string `name:"path" short:"p" dc:"output go file path storing enums content" d:"internal/packed/packed_enums.go"` + Prefixes []string `name:"prefixes" short:"x" dc:"only exports packages that starts with specified prefixes"` + } + CGenEnumsOutput struct{} +) + +const ( + CGenEnumsConfig = `gfcli.gen.enums` + CGenEnumsBrief = `parse go files in current project and generate enums go file` + CGenEnumsEg = ` +gf gen enums +gf gen enums -p internal/packed/packed_enums.go +gf gen enums -p internal/packed/packed_enums.go -s . +gf gen enums -x github.com/gogf +` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `CGenEnumsEg`: CGenEnumsEg, + `CGenEnumsBrief`: CGenEnumsBrief, + `CGenEnumsConfig`: CGenEnumsConfig, + }) +} + +func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnumsOutput, err error) { + realPath := gfile.RealPath(in.Src) + if realPath == "" { + mlog.Fatalf(`source folder path "%s" does not exist`, in.Src) + } + err = gfile.Chdir(realPath) + if err != nil { + mlog.Fatal(err) + } + mlog.Printf(`scanning for enums: %s`, realPath) + cfg := &packages.Config{ + Dir: realPath, + Mode: pkgLoadMode, + Tests: false, + } + pkgs, err := packages.Load(cfg) + if err != nil { + mlog.Fatal(err) + } + p := NewEnumsParser(in.Prefixes) + p.ParsePackages(pkgs) + var enumsContent = gstr.ReplaceByMap(consts.TemplateGenEnums, g.MapStrStr{ + "{PackageName}": gfile.Basename(gfile.Dir(in.Path)), + "{EnumsJson}": "`" + p.Export() + "`", + }) + enumsContent = gstr.Trim(enumsContent) + if err = gfile.PutContents(in.Path, enumsContent); err != nil { + return + } + mlog.Printf(`generated enums go file: %s`, in.Path) + mlog.Print("done!") + return +} diff --git a/server/internal/library/hggen/internal/cmd/genenums/genenums_parser.go b/server/internal/library/hggen/internal/cmd/genenums/genenums_parser.go new file mode 100644 index 0000000..e0ab942 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genenums/genenums_parser.go @@ -0,0 +1,149 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genenums + +import ( + "go/constant" + "go/types" + + "golang.org/x/tools/go/packages" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" +) + +const pkgLoadMode = 0xffffff + +type EnumsParser struct { + enums []EnumItem + parsedPkg map[string]struct{} + prefixes []string + standardPackages map[string]struct{} +} + +type EnumItem struct { + Name string + Value string + Kind constant.Kind // String/Int/Bool/Float/Complex/Unknown + Type string // Pkg.ID + TypeName +} + +func NewEnumsParser(prefixes []string) *EnumsParser { + return &EnumsParser{ + enums: make([]EnumItem, 0), + parsedPkg: make(map[string]struct{}), + prefixes: prefixes, + standardPackages: getStandardPackages(), + } +} + +func (p *EnumsParser) ParsePackages(pkgs []*packages.Package) { + for _, pkg := range pkgs { + p.ParsePackage(pkg) + } +} + +func (p *EnumsParser) ParsePackage(pkg *packages.Package) { + // Ignore std packages. + if _, ok := p.standardPackages[pkg.ID]; ok { + return + } + // Ignore pared packages. + if _, ok := p.parsedPkg[pkg.ID]; ok { + return + } + p.parsedPkg[pkg.ID] = struct{}{} + + // Only parse specified prefixes. + if len(p.prefixes) > 0 { + var hasPrefix bool + for _, prefix := range p.prefixes { + if hasPrefix = gstr.HasPrefix(pkg.ID, prefix); hasPrefix { + break + } + } + if !hasPrefix { + return + } + } + + var ( + scope = pkg.Types.Scope() + names = scope.Names() + ) + for _, name := range names { + con, ok := scope.Lookup(name).(*types.Const) + if !ok { + // Only constants can be enums. + continue + } + if !con.Exported() { + // Ignore unexported values. + continue + } + + var enumType = con.Type().String() + if !gstr.Contains(enumType, "/") { + // Ignore std types. + continue + } + var ( + enumName = con.Name() + enumValue = con.Val().ExactString() + enumKind = con.Val().Kind() + ) + if con.Val().Kind() == constant.String { + enumValue = constant.StringVal(con.Val()) + } + p.enums = append(p.enums, EnumItem{ + Name: enumName, + Value: enumValue, + Type: enumType, + Kind: enumKind, + }) + } + for _, im := range pkg.Imports { + p.ParsePackage(im) + } +} + +func (p *EnumsParser) Export() string { + var typeEnumMap = make(map[string][]any) + for _, enum := range p.enums { + if typeEnumMap[enum.Type] == nil { + typeEnumMap[enum.Type] = make([]any, 0) + } + var value any + switch enum.Kind { + case constant.Int: + value = gconv.Int64(enum.Value) + case constant.String: + value = enum.Value + case constant.Float: + value = gconv.Float64(enum.Value) + case constant.Bool: + value = gconv.Bool(enum.Value) + default: + value = enum.Value + } + typeEnumMap[enum.Type] = append(typeEnumMap[enum.Type], value) + } + return gjson.MustEncodeString(typeEnumMap) +} + +func getStandardPackages() map[string]struct{} { + standardPackages := make(map[string]struct{}) + stdPackages, err := packages.Load(nil, "std") + if err != nil { + panic(err) + } + for _, p := range stdPackages { + standardPackages[p.ID] = struct{}{} + } + return standardPackages +} diff --git a/server/internal/library/hggen/internal/cmd/genpb/genpb.go b/server/internal/library/hggen/internal/cmd/genpb/genpb.go new file mode 100644 index 0000000..da4da07 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genpb/genpb.go @@ -0,0 +1,132 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genpb + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +type ( + CGenPb struct{} + CGenPbInput struct { + g.Meta `name:"pb" config:"{CGenPbConfig}" brief:"{CGenPbBrief}" eg:"{CGenPbEg}"` + Path string `name:"path" short:"p" dc:"protobuf file folder path" d:"manifest/protobuf"` + OutputApi string `name:"api" short:"a" dc:"output folder path storing generated go files of api" d:"api"` + OutputCtrl string `name:"ctrl" short:"c" dc:"output folder path storing generated go files of controller" d:"internal/controller"` + } + CGenPbOutput struct{} +) + +const ( + CGenPbConfig = `gfcli.gen.pb` + CGenPbBrief = `parse proto files and generate protobuf go files` + CGenPbEg = ` +gf gen pb +gf gen pb -p . -a . -p . +` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `CGenPbEg`: CGenPbEg, + `CGenPbBrief`: CGenPbBrief, + `CGenPbConfig`: CGenPbConfig, + }) +} + +func (c CGenPb) Pb(ctx context.Context, in CGenPbInput) (out *CGenPbOutput, err error) { + // Necessary check. + protoc := gproc.SearchBinary("protoc") + if protoc == "" { + mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first: https://grpc.io/docs/languages/go/quickstart/`) + } + + // protocol fold checks. + var ( + protoPath = gfile.RealPath(in.Path) + isParsingPWD bool + ) + if protoPath == "" { + // Use current working directory as protoPath if there are proto files under. + currentPath := gfile.Pwd() + currentFiles, _ := gfile.ScanDirFile(currentPath, "*.proto") + if len(currentFiles) > 0 { + protoPath = currentPath + isParsingPWD = true + } else { + mlog.Fatalf(`proto files folder "%s" does not exist`, in.Path) + } + } + // output path checks. + outputApiPath := gfile.RealPath(in.OutputApi) + if outputApiPath == "" { + if isParsingPWD { + outputApiPath = protoPath + } else { + mlog.Fatalf(`output api folder "%s" does not exist`, in.OutputApi) + } + } + outputCtrlPath := gfile.RealPath(in.OutputCtrl) + if outputCtrlPath == "" { + if isParsingPWD { + outputCtrlPath = "" + } else { + mlog.Fatalf(`output controller folder "%s" does not exist`, in.OutputCtrl) + } + } + + // folder scanning. + files, err := gfile.ScanDirFile(protoPath, "*.proto", true) + if err != nil { + mlog.Fatal(err) + } + if len(files) == 0 { + mlog.Fatalf(`no proto files found in folder "%s"`, in.Path) + } + + var originPwd = gfile.Pwd() + defer gfile.Chdir(originPwd) + + if err = gfile.Chdir(protoPath); err != nil { + mlog.Fatal(err) + } + for _, file := range files { + var command = gproc.NewProcess(protoc, nil) + command.Args = append(command.Args, "--proto_path="+gfile.Pwd()) + command.Args = append(command.Args, "--go_out=paths=source_relative:"+outputApiPath) + command.Args = append(command.Args, "--go-grpc_out=paths=source_relative:"+outputApiPath) + command.Args = append(command.Args, file) + mlog.Print(command.String()) + if err = command.Run(ctx); err != nil { + mlog.Fatal(err) + } + } + // Generate struct tag according comment rules. + err = c.generateStructTag(ctx, generateStructTagInput{OutputApiPath: outputApiPath}) + if err != nil { + return + } + // Generate controllers according comment rules. + if outputCtrlPath != "" { + err = c.generateController(ctx, generateControllerInput{ + OutputApiPath: outputApiPath, + OutputCtrlPath: outputCtrlPath, + }) + if err != nil { + return + } + } + mlog.Print("done!") + return +} diff --git a/server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go b/server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go new file mode 100644 index 0000000..205e0c4 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genpb/genpb_controller.go @@ -0,0 +1,197 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genpb + +import ( + "context" + "fmt" + "strings" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/utils" +) + +type generateControllerInput struct { + OutputApiPath string + OutputCtrlPath string +} + +type generateCtrl struct { + Name string + Package string + Version string + Methods []generateCtrlMethod +} + +type generateCtrlMethod struct { + Name string + Definition string +} + +const ( + controllerTemplate = ` +package {Package} + +type Controller struct { + {Version}.Unimplemented{Name}Server +} + +func Register(s *grpcx.GrpcServer) { + {Version}.Register{Name}Server(s.Server, &Controller{}) +} +` + controllerMethodTemplate = ` +func (*Controller) {Definition} { + return nil, gerror.NewCode(gcode.CodeNotImplemented) +} +` +) + +func (c CGenPb) generateController(ctx context.Context, in generateControllerInput) (err error) { + files, err := gfile.ScanDirFile(in.OutputApiPath, "*_grpc.pb.go", true) + if err != nil { + return err + } + var controllers []generateCtrl + for _, file := range files { + fileControllers, err := c.parseControllers(file) + if err != nil { + return err + } + controllers = append(controllers, fileControllers...) + } + if len(controllers) == 0 { + return nil + } + // Generate controller files. + err = c.doGenerateControllers(in, controllers) + return +} + +func (c CGenPb) parseControllers(filePath string) ([]generateCtrl, error) { + var ( + controllers []generateCtrl + content = gfile.GetContents(filePath) + ) + _, err := gregex.ReplaceStringFuncMatch( + `type (\w+)Server interface {([\s\S]+?)}`, + content, + func(match []string) string { + ctrl := generateCtrl{ + Name: match[1], + Package: strings.ReplaceAll(gfile.Basename(gfile.Dir(gfile.Dir(filePath))), "-", "_"), + Version: gfile.Basename(gfile.Dir(filePath)), + Methods: make([]generateCtrlMethod, 0), + } + lines := gstr.Split(match[2], "\n") + for _, line := range lines { + line = gstr.Trim(line) + if line == "" || !gstr.IsLetterUpper(line[0]) { + continue + } + // Comment. + if gregex.IsMatchString(`^//.+`, line) { + continue + } + line, _ = gregex.ReplaceStringFuncMatch( + `^(\w+)\(context\.Context, \*(\w+)\) \(\*(\w+), error\)$`, + line, + func(match []string) string { + return fmt.Sprintf( + `%s(ctx context.Context, req *%s.%s) (res *%s.%s, err error)`, + match[1], ctrl.Version, match[2], ctrl.Version, match[3], + ) + }, + ) + ctrl.Methods = append(ctrl.Methods, generateCtrlMethod{ + Name: gstr.Split(line, "(")[0], + Definition: line, + }) + } + if len(ctrl.Methods) > 0 { + controllers = append(controllers, ctrl) + } + return match[0] + }, + ) + return controllers, err +} + +func (c CGenPb) doGenerateControllers(in generateControllerInput, controllers []generateCtrl) (err error) { + for _, controller := range controllers { + err = c.doGenerateController(in, controller) + if err != nil { + return err + } + } + err = utils.ReplaceGeneratedContentGFV2(in.OutputCtrlPath) + return nil +} + +func (c CGenPb) doGenerateController(in generateControllerInput, controller generateCtrl) (err error) { + var ( + folderPath = gfile.Join(in.OutputCtrlPath, controller.Package) + filePath = gfile.Join(folderPath, controller.Package+".go") + isDirty bool + ) + if !gfile.Exists(folderPath) { + if err = gfile.Mkdir(folderPath); err != nil { + return err + } + } + if !gfile.Exists(filePath) { + templateContent := gstr.ReplaceByMap(controllerTemplate, g.MapStrStr{ + "{Name}": controller.Name, + "{Version}": controller.Version, + "{Package}": controller.Package, + }) + if err = gfile.PutContents(filePath, templateContent); err != nil { + return err + } + isDirty = true + } + // Exist controller content. + var ctrlContent string + files, err := gfile.ScanDirFile(folderPath, "*.go", false) + if err != nil { + return err + } + for _, file := range files { + if ctrlContent != "" { + ctrlContent += "\n" + } + ctrlContent += gfile.GetContents(file) + } + // Generate method content. + var generatedContent string + for _, method := range controller.Methods { + if gstr.Contains(ctrlContent, fmt.Sprintf(`%s(`, method.Name)) { + continue + } + if generatedContent != "" { + generatedContent += "\n" + } + generatedContent += gstr.ReplaceByMap(controllerMethodTemplate, g.MapStrStr{ + "{Definition}": method.Definition, + }) + } + if generatedContent != "" { + err = gfile.PutContentsAppend(filePath, generatedContent) + if err != nil { + return err + } + isDirty = true + } + if isDirty { + utils.GoFmt(filePath) + } + return nil +} diff --git a/server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go b/server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go new file mode 100644 index 0000000..3dd9481 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genpb/genpb_tag.go @@ -0,0 +1,123 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genpb + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + + "hotgo/internal/library/hggen/internal/utility/utils" +) + +type generateStructTagInput struct { + OutputApiPath string +} + +func (c CGenPb) generateStructTag(ctx context.Context, in generateStructTagInput) (err error) { + files, err := gfile.ScanDirFile(in.OutputApiPath, "*.pb.go", true) + if err != nil { + return err + } + var content string + for _, file := range files { + content = gfile.GetContents(file) + content, err = c.doTagReplacement(ctx, content) + if err != nil { + return err + } + if err = gfile.PutContents(file, content); err != nil { + return err + } + utils.GoFmt(file) + } + return +} + +func (c CGenPb) doTagReplacement(ctx context.Context, content string) (string, error) { + content, err := gregex.ReplaceStringFuncMatch(`type (\w+) struct {([\s\S]+?)}`, content, func(match []string) string { + var ( + topCommentMatch []string + tailCommentMatch []string + lines = gstr.Split(match[2], "\n") + lineTagMap = gmap.NewListMap() + ) + for index, line := range lines { + line = gstr.Trim(line) + if line == "" { + continue + } + // Top comment. + topCommentMatch, _ = gregex.MatchString(`^/[/|\*](.+)`, line) + if len(topCommentMatch) > 1 { + c.tagCommentIntoListMap(gstr.Trim(topCommentMatch[1]), lineTagMap) + continue + } + // Tail comment. + tailCommentMatch, _ = gregex.MatchString(".+?`.+?`.+?//(.+)", line) + if len(tailCommentMatch) > 1 { + c.tagCommentIntoListMap(gstr.Trim(tailCommentMatch[1]), lineTagMap) + } + // Tag injection. + if !lineTagMap.IsEmpty() { + tagContent := c.listMapToStructTag(lineTagMap) + lineTagMap.Clear() + // If already have it, don't add it anymore + if gstr.Contains(gstr.StrTill(line, "` //"), tagContent) { + continue + } + line, _ = gregex.ReplaceString("`(.+)`", fmt.Sprintf("`$1 %s`", tagContent), line) + } + lines[index] = line + } + match[2] = gstr.Join(lines, "\n") + return fmt.Sprintf("type %s struct {%s}", match[1], match[2]) + }) + return content, err +} + +func (c CGenPb) tagCommentIntoListMap(comment string, lineTagMap *gmap.ListMap) { + tagCommentMatch, _ := gregex.MatchString(`^(\w+):(.+)`, comment) + if len(tagCommentMatch) > 1 { + var ( + tagName = gstr.Trim(tagCommentMatch[1]) + tagContent = gstr.Trim(tagCommentMatch[2]) + ) + lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent) + } else { + var ( + tagName = "dc" + // Convert backticks and double quotes to single quotes. + tagContent = gstr.ReplaceByMap(comment, g.MapStrStr{ + "`": `'`, + `"`: `'`, + }) + ) + lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent) + } +} + +func (c CGenPb) listMapToStructTag(lineTagMap *gmap.ListMap) string { + var tag string + lineTagMap.Iterator(func(key, value any) bool { + if tag != "" { + tag += " " + } + tag += fmt.Sprintf( + `%s:"%s"`, + key, gstr.Replace(gconv.String(value), `"`, `\"`), + ) + return true + }) + return tag +} diff --git a/server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go b/server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go new file mode 100644 index 0000000..a80ec17 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genpbentity/genpbentity.go @@ -0,0 +1,591 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genpbentity + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + "regexp" + "strings" + + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +type ( + CGenPbEntity struct{} + CGenPbEntityInput struct { + g.Meta `name:"pbentity" config:"{CGenPbEntityConfig}" brief:"{CGenPbEntityBrief}" eg:"{CGenPbEntityEg}" ad:"{CGenPbEntityAd}"` + Path string `name:"path" short:"p" brief:"{CGenPbEntityBriefPath}" d:"manifest/protobuf/pbentity"` + Package string `name:"package" short:"k" brief:"{CGenPbEntityBriefPackage}"` + GoPackage string `name:"goPackage" short:"g" brief:"{CGenPbEntityBriefGoPackage}"` + Link string `name:"link" short:"l" brief:"{CGenPbEntityBriefLink}"` + Tables string `name:"tables" short:"t" brief:"{CGenPbEntityBriefTables}"` + Prefix string `name:"prefix" short:"f" brief:"{CGenPbEntityBriefPrefix}"` + RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenPbEntityBriefRemovePrefix}"` + RemoveFieldPrefix string `name:"removeFieldPrefix" short:"rf" brief:"{CGenPbEntityBriefRemoveFieldPrefix}"` + TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"` + NameCase string `name:"nameCase" short:"n" brief:"{CGenPbEntityBriefNameCase}" d:"Camel"` + JsonCase string `name:"jsonCase" short:"j" brief:"{CGenPbEntityBriefJsonCase}" d:"none"` + Option string `name:"option" short:"o" brief:"{CGenPbEntityBriefOption}"` + ShardingPattern []string `name:"shardingPattern" short:"sp" brief:"{CGenDaoBriefShardingPattern}"` + + TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenPbEntityBriefTypeMapping}" orphan:"true"` + FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenPbEntityBriefFieldMapping}" orphan:"true"` + } + CGenPbEntityOutput struct{} + + CGenPbEntityInternalInput struct { + CGenPbEntityInput + DB gdb.DB + TableName string // TableName specifies the table name of the table. + NewTableName string // NewTableName specifies the prefix-stripped name of the table. + } + + DBTableFieldName = string + DBFieldTypeName = string + CustomAttributeType struct { + Type string `brief:"custom attribute type name"` + Import string `brief:"custom import for this type"` + } +) + +const ( + defaultPackageSuffix = `api/pbentity` + CGenPbEntityConfig = `gfcli.gen.pbentity` + CGenPbEntityBrief = `generate entity message files in protobuf3 format` + CGenPbEntityEg = ` +gf gen pbentity +gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login +gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf +gf gen pbentity -r user_ +` + + CGenPbEntityAd = ` +CONFIGURATION SUPPORT + Options are also supported by configuration file. + It's suggested using configuration file instead of command line arguments making producing. + The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml): + gfcli: + gen: + - pbentity: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + path: "protocol/demos/entity" + tables: "order,products" + package: "demos" + - pbentity: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "protocol/demos/entity" + prefix: "primary_" + tables: "user, userDetail" + package: "demos" + option: | + option go_package = "protobuf/demos"; + option java_package = "protobuf/demos"; + option php_namespace = "protobuf/demos"; + typeMapping: + json: + type: google.protobuf.Value + import: google/protobuf/struct.proto + jsonb: + type: google.protobuf.Value + import: google/protobuf/struct.proto +` + CGenPbEntityBriefPath = `directory path for generated files storing` + CGenPbEntityBriefPackage = `package path for all entity proto files` + CGenPbEntityBriefGoPackage = `go package path for all entity proto files` + CGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame` + CGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','` + CGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files` + CGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','` + CGenPbEntityBriefTablesEx = `generate all models exclude the specified tables, multiple prefix separated with ','` + CGenPbEntityBriefRemoveFieldPrefix = `remove specified prefix of the field, multiple prefix separated with ','` + CGenPbEntityBriefOption = `extra protobuf options` + CGenPbEntityBriefShardingPattern = `sharding pattern for table name, e.g. "users_?" will replace tables "users_001,users_002,..." to "users" pbentity` + CGenPbEntityBriefGroup = ` +specifying the configuration group name of database for generated ORM instance, +it's not necessary and the default value is "default" +` + + CGenPbEntityBriefNameCase = ` +case for message attribute names, default is "Camel": +| Case | Example | +|---------------- |--------------------| +| Camel | AnyKindOfString | +| CamelLower | anyKindOfString | default +| Snake | any_kind_of_string | +| SnakeScreaming | ANY_KIND_OF_STRING | +| SnakeFirstUpper | rgb_code_md5 | +| Kebab | any-kind-of-string | +| KebabScreaming | ANY-KIND-OF-STRING | +` + + CGenPbEntityBriefJsonCase = ` +case for message json tag, cases are the same as "nameCase", default "CamelLower". +set it to "none" to ignore json tag generating. +` + + CGenPbEntityBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table` + CGenPbEntityBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table` +) + +var defaultTypeMapping = map[DBFieldTypeName]CustomAttributeType{ + // gdb.LocalTypeString + "string": { + Type: "string", + }, + // gdb.LocalTypeTime + // "time": { + // Type: "google.protobuf.Duration", + // Import: "google/protobuf/duration.proto", + // }, + // gdb.LocalTypeDate + "date": { + Type: "google.protobuf.Timestamp", + Import: "google/protobuf/timestamp.proto", + }, + // gdb.LocalTypeDatetime + "datetime": { + Type: "google.protobuf.Timestamp", + Import: "google/protobuf/timestamp.proto", + }, + // gdb.LocalTypeInt + "int": { + Type: "int32", + }, + // gdb.LocalTypeUint + "uint": { + Type: "uint32", + }, + // gdb.LocalTypeInt64 + "int64": { + Type: "int64", + }, + // gdb.LocalTypeUint64 + "uint64": { + Type: "uint64", + }, + // gdb.LocalTypeIntSlice + "[]int": { + Type: "repeated int32", + }, + // gdb.LocalTypeInt64Slice + "[]int64": { + Type: "repeated int64", + }, + // gdb.LocalTypeUint64Slice + "[]uint64": { + Type: "repeated uint64", + }, + // gdb.LocalTypeInt64Bytes + "int64-bytes": { + Type: "repeated int64", + }, + // gdb.LocalTypeUint64Bytes + "uint64-bytes": { + Type: "repeated uint64", + }, + // gdb.LocalTypeFloat32 + "float32": { + Type: "float", + }, + // gdb.LocalTypeFloat64 + "float64": { + Type: "double", + }, + // gdb.LocalTypeBytes + "[]byte": { + Type: "bytes", + }, + // gdb.LocalTypeBool + "bool": { + Type: "bool", + }, + // gdb.LocalTypeJson + // "json": { + // Type: "google.protobuf.Value", + // Import: "google/protobuf/struct.proto", + // }, + // gdb.LocalTypeJsonb + // "jsonb": { + // Type: "google.protobuf.Value", + // Import: "google/protobuf/struct.proto", + // }, +} + +func init() { + gtag.Sets(g.MapStrStr{ + `CGenPbEntityConfig`: CGenPbEntityConfig, + `CGenPbEntityBrief`: CGenPbEntityBrief, + `CGenPbEntityEg`: CGenPbEntityEg, + `CGenPbEntityAd`: CGenPbEntityAd, + `CGenPbEntityBriefPath`: CGenPbEntityBriefPath, + `CGenPbEntityBriefPackage`: CGenPbEntityBriefPackage, + `CGenPbEntityBriefGoPackage`: CGenPbEntityBriefGoPackage, + `CGenPbEntityBriefLink`: CGenPbEntityBriefLink, + `CGenPbEntityBriefTables`: CGenPbEntityBriefTables, + `CGenPbEntityBriefPrefix`: CGenPbEntityBriefPrefix, + `CGenPbEntityBriefRemovePrefix`: CGenPbEntityBriefRemovePrefix, + `CGenPbEntityBriefTablesEx`: CGenPbEntityBriefTablesEx, + `CGenPbEntityBriefRemoveFieldPrefix`: CGenPbEntityBriefRemoveFieldPrefix, + `CGenPbEntityBriefGroup`: CGenPbEntityBriefGroup, + `CGenPbEntityBriefNameCase`: CGenPbEntityBriefNameCase, + `CGenPbEntityBriefJsonCase`: CGenPbEntityBriefJsonCase, + `CGenPbEntityBriefOption`: CGenPbEntityBriefOption, + `CGenPbEntityBriefShardingPattern`: CGenPbEntityBriefShardingPattern, + `CGenPbEntityBriefTypeMapping`: CGenPbEntityBriefTypeMapping, + `CGenPbEntityBriefFieldMapping`: CGenPbEntityBriefFieldMapping, + }) +} + +func (c CGenPbEntity) PbEntity(ctx context.Context, in CGenPbEntityInput) (out *CGenPbEntityOutput, err error) { + var ( + config = g.Cfg() + ) + if config.Available(ctx) { + v := config.MustGet(ctx, CGenPbEntityConfig) + if v.IsSlice() { + for i := 0; i < len(v.Interfaces()); i++ { + doGenPbEntityForArray(ctx, i, in) + } + } else { + doGenPbEntityForArray(ctx, -1, in) + } + } else { + doGenPbEntityForArray(ctx, -1, in) + } + mlog.Print("done!") + return +} + +func doGenPbEntityForArray(ctx context.Context, index int, in CGenPbEntityInput) { + var ( + err error + db gdb.DB + ) + if index >= 0 { + err = g.Cfg().MustGet( + ctx, + fmt.Sprintf(`%s.%d`, CGenPbEntityConfig, index), + ).Scan(&in) + if err != nil { + mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenPbEntityConfig, err) + } + } + if in.Package == "" { + mlog.Debug(`package parameter is empty, trying calculating the package path using go.mod`) + modName := utils.GetImportPath(gfile.Pwd()) + in.Package = modName + "/" + defaultPackageSuffix + } + removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",") + + excludeTables := gset.NewStrSetFrom(gstr.SplitAndTrim(in.TablesEx, ",")) + + // It uses user passed database configuration. + if in.Link != "" { + var ( + tempGroup = gtime.TimestampNanoStr() + match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link) + ) + if len(match) == 3 { + gdb.AddConfigNode(tempGroup, gdb.ConfigNode{ + Type: gstr.Trim(match[1]), + Link: in.Link, + }) + db, _ = gdb.Instance(tempGroup) + } + } else { + db = g.DB() + } + if db == nil { + mlog.Fatal("database initialization failed") + } + + tableNames := ([]string)(nil) + shardingNewTableSet := gset.NewStrSet() + if in.Tables != "" { + tableNames = gstr.SplitAndTrim(in.Tables, ",") + } else { + tableNames, err = db.Tables(context.TODO()) + if err != nil { + mlog.Fatalf("fetching tables failed: \n %v", err) + } + } + // merge default typeMapping to input typeMapping. + if in.TypeMapping == nil { + in.TypeMapping = defaultTypeMapping + } else { + for key, typeMapping := range defaultTypeMapping { + if _, ok := in.TypeMapping[key]; !ok { + in.TypeMapping[key] = typeMapping + } + } + } + + for _, tableName := range tableNames { + if excludeTables.Contains(tableName) { + continue + } + newTableName := tableName + for _, v := range removePrefixArray { + newTableName = gstr.TrimLeftStr(newTableName, v, 1) + } + var shardingTableName string + if len(in.ShardingPattern) > 0 { + for _, pattern := range in.ShardingPattern { + var ( + match []string + regPattern = gstr.Replace(pattern, "?", `(.+)`) + ) + match, err = gregex.MatchString(regPattern, newTableName) + if err != nil { + mlog.Fatalf(`invalid sharding pattern "%s": %+v`, pattern, err) + } + if len(match) < 2 { + continue + } + shardingTableName = gstr.Replace(pattern, "?", "") + shardingTableName = gstr.Trim(shardingTableName, `_.-`) + } + } + if shardingTableName != "" { + if shardingNewTableSet.Contains(shardingTableName) { + continue + } + shardingNewTableSet.Add(shardingTableName) + newTableName = shardingTableName + } + generatePbEntityContentFile(ctx, CGenPbEntityInternalInput{ + CGenPbEntityInput: in, + DB: db, + TableName: tableName, + NewTableName: newTableName, + }) + } +} + +// generatePbEntityContentFile generates the protobuf files for given table. +func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInput) { + fieldMap, err := in.DB.TableFields(ctx, in.TableName) + if err != nil { + mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err) + } + // Change the `newTableName` if `Prefix` is given. + newTableName := in.Prefix + in.NewTableName + var ( + tableNameCamelCase = gstr.CaseCamel(newTableName) + tableNameSnakeCase = gstr.CaseSnake(newTableName) + entityMessageDefine, appendImports = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in) + fileName = gstr.Trim(tableNameSnakeCase, "-_.") + path = filepath.FromSlash(gfile.Join(in.Path, fileName+".proto")) + ) + packageImportStr := "" + var packageImportsArray = garray.NewStrArray() + if len(appendImports) > 0 { + for _, appendImport := range appendImports { + packageImportStr = fmt.Sprintf(`import "%s";`, appendImport) + if packageImportsArray.Search(packageImportStr) == -1 { + packageImportsArray.Append(packageImportStr) + } + } + } + if in.GoPackage == "" { + in.GoPackage = in.Package + } + entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{ + "{Imports}": packageImportsArray.Join("\n"), + "{PackageName}": gfile.Basename(in.Package), + "{GoPackage}": in.GoPackage, + "{OptionContent}": in.Option, + "{EntityMessage}": entityMessageDefine, + }) + if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil { + mlog.Fatalf("writing content to '%s' failed: %v", path, err) + } else { + mlog.Print("generated:", gfile.RealPath(path)) + } +} + +// generateEntityMessageDefinition generates and returns the message definition for specified table. +func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) (string, []string) { + var ( + appendImports []string + buffer = bytes.NewBuffer(nil) + array = make([][]string, len(fieldMap)) + names = sortFieldKeyForPbEntity(fieldMap) + ) + for index, name := range names { + var imports string + array[index], imports = generateMessageFieldForPbEntity(index+1, fieldMap[name], in) + if imports != "" { + appendImports = append(appendImports, imports) + } + } + table := tablewriter.NewTable(buffer, + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ + Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.On, Right: tw.Off}, + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off}, + }, + Symbols: tw.NewSymbolCustom("Proto").WithColumn(" "), + })), + tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone}, + }, + }), + ) + table.Bulk(array) + table.Render() + stContent := buffer.String() + // Let's do this hack of table writer for indent! + stContent = regexp.MustCompile(`\s+\n`).ReplaceAllString(gstr.Replace(stContent, " #", ""), "\n") + buffer.Reset() + buffer.WriteString(fmt.Sprintf("message %s {\n", entityName)) + buffer.WriteString(stContent) + buffer.WriteString("}") + return buffer.String(), appendImports +} + +// generateMessageFieldForPbEntity generates and returns the message definition for specified field. +func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) (attrLines []string, appendImport string) { + var ( + localTypeNameStr string + localTypeName gdb.LocalType + comment string + jsonTagStr string + err error + ctx = gctx.GetInitCtx() + ) + if in.TypeMapping != nil && len(in.TypeMapping) > 0 { + // match typeMapping after local type transform. + // eg: double => string, varchar => string etc. + localTypeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil) + if err != nil { + panic(err) + } + if localTypeName != "" { + if typeMappingLocal, localOk := in.TypeMapping[strings.ToLower(string(localTypeName))]; localOk { + localTypeNameStr = typeMappingLocal.Type + appendImport = typeMappingLocal.Import + } + } + // Try match unknown / string localTypeName with db type. + if localTypeName == "" || localTypeName == gdb.LocalTypeString { + formattedFieldType, _ := in.DB.GetFormattedDBTypeNameForField(field.Type) + if typeMapping, ok := in.TypeMapping[strings.ToLower(formattedFieldType)]; ok { + localTypeNameStr = typeMapping.Type + appendImport = typeMapping.Import + } + } + } + + if localTypeNameStr == "" { + localTypeNameStr = "string" + } + + comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{ + "\n", " ", + "\r", " ", + }) + comment = gstr.Trim(comment) + comment = gstr.Replace(comment, `\n`, " ") + comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment) + if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" { + jsonTagStr = fmt.Sprintf(`[json_name = "%s"]`, jsonTagName) + // beautiful indent. + if index < 10 { + // 3 spaces + jsonTagStr = " " + jsonTagStr + } else if index < 100 { + // 2 spaces + jsonTagStr = " " + jsonTagStr + } else { + // 1 spaces + jsonTagStr = " " + jsonTagStr + } + } + + removeFieldPrefixArray := gstr.SplitAndTrim(in.RemoveFieldPrefix, ",") + newFiledName := field.Name + for _, v := range removeFieldPrefixArray { + newFiledName = gstr.TrimLeftStr(newFiledName, v, 1) + } + + if in.FieldMapping != nil && len(in.FieldMapping) > 0 { + if typeMapping, ok := in.FieldMapping[fmt.Sprintf("%s.%s", in.TableName, newFiledName)]; ok { + localTypeNameStr = typeMapping.Type + appendImport = typeMapping.Import + } + } + + return []string{ + " #" + localTypeNameStr, + " #" + formatCase(newFiledName, in.NameCase), + " #= " + gconv.String(index) + jsonTagStr + ";", + " #" + fmt.Sprintf(`// %s`, comment), + }, appendImport +} + +func getTplPbEntityContent(tplEntityPath string) string { + if tplEntityPath != "" { + return gfile.GetContents(tplEntityPath) + } + return consts.TemplatePbEntityMessageContent +} + +// formatCase call gstr.Case* function to convert the s to specified case. +func formatCase(str, caseStr string) string { + if caseStr == "none" { + return "" + } + return gstr.CaseConvert(str, gstr.CaseTypeMatch(caseStr)) +} + +func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string { + names := make(map[int]string) + for _, field := range fieldMap { + names[field.Index] = field.Name + } + var ( + result = make([]string, len(names)) + i = 0 + j = 0 + ) + for { + if len(names) == 0 { + break + } + if val, ok := names[i]; ok { + result[j] = val + j++ + delete(names, i) + } + i++ + } + return result +} diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice.go b/server/internal/library/hggen/internal/cmd/genservice/genservice.go new file mode 100644 index 0000000..79f66ab --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genservice/genservice.go @@ -0,0 +1,391 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genservice + +import ( + "context" + "fmt" + "path/filepath" + "sync" + "sync/atomic" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/gtag" + + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +const ( + CGenServiceConfig = `gfcli.gen.service` + CGenServiceUsage = `gf gen service [OPTION]` + CGenServiceBrief = `parse struct and associated functions from packages to generate service go file` + CGenServiceEg = ` +gf gen service +gf gen service -f Snake +` + CGenServiceBriefSrcFolder = `source folder path to be parsed. default: internal/logic` + CGenServiceBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/service` + CGenServiceBriefFileNameCase = ` +destination file name storing automatically generated go files, cases are as follows: +| Case | Example | +|---------------- |--------------------| +| Lower | anykindofstring | +| Camel | AnyKindOfString | +| CamelLower | anyKindOfString | +| Snake | any_kind_of_string | default +| SnakeScreaming | ANY_KIND_OF_STRING | +| SnakeFirstUpper | rgb_code_md5 | +| Kebab | any-kind-of-string | +| KebabScreaming | ANY-KIND-OF-STRING | +` + CGenServiceBriefWatchFile = `used in file watcher, it re-generates all service go files only if given file is under srcFolder` + CGenServiceBriefStPattern = `regular expression matching struct name for generating service. default: ^s([A-Z]\\\\w+)$` + CGenServiceBriefPackages = `produce go files only for given source packages(source folders)` + CGenServiceBriefImportPrefix = `custom import prefix to calculate import path for generated importing go file of logic` + CGenServiceBriefClear = `delete all generated go files that are not used any further` +) + +func init() { + gtag.Sets(g.MapStrStr{ + `CGenServiceConfig`: CGenServiceConfig, + `CGenServiceUsage`: CGenServiceUsage, + `CGenServiceBrief`: CGenServiceBrief, + `CGenServiceEg`: CGenServiceEg, + `CGenServiceBriefSrcFolder`: CGenServiceBriefSrcFolder, + `CGenServiceBriefDstFolder`: CGenServiceBriefDstFolder, + `CGenServiceBriefFileNameCase`: CGenServiceBriefFileNameCase, + `CGenServiceBriefWatchFile`: CGenServiceBriefWatchFile, + `CGenServiceBriefStPattern`: CGenServiceBriefStPattern, + `CGenServiceBriefPackages`: CGenServiceBriefPackages, + `CGenServiceBriefImportPrefix`: CGenServiceBriefImportPrefix, + `CGenServiceBriefClear`: CGenServiceBriefClear, + }) +} + +type ( + CGenService struct{} + CGenServiceInput struct { + g.Meta `name:"service" config:"{CGenServiceConfig}" usage:"{CGenServiceUsage}" brief:"{CGenServiceBrief}" eg:"{CGenServiceEg}"` + SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenServiceBriefSrcFolder}" d:"internal/logic"` + DstFolder string `short:"d" name:"dstFolder" brief:"{CGenServiceBriefDstFolder}" d:"internal/service"` + DstFileNameCase string `short:"f" name:"dstFileNameCase" brief:"{CGenServiceBriefFileNameCase}" d:"Snake"` + WatchFile string `short:"w" name:"watchFile" brief:"{CGenServiceBriefWatchFile}"` + StPattern string `short:"a" name:"stPattern" brief:"{CGenServiceBriefStPattern}" d:"^s([A-Z]\\w+)$"` + Packages []string `short:"p" name:"packages" brief:"{CGenServiceBriefPackages}"` + ImportPrefix string `short:"i" name:"importPrefix" brief:"{CGenServiceBriefImportPrefix}"` + Clear bool `short:"l" name:"clear" brief:"{CGenServiceBriefClear}" orphan:"true"` + } + CGenServiceOutput struct{} +) + +const ( + genServiceFileLockSeconds = 10 +) + +type fileInfo struct { + PkgItems []pkgItem + FuncItems []funcItem +} + +type folderInfo struct { + SrcPackageName string + SrcImportedPackages *garray.SortedStrArray + SrcStructFunctions *gmap.ListMap + DstFilePath string + + FileInfos []*fileInfo +} + +func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) { + in.SrcFolder = filepath.ToSlash(in.SrcFolder) + in.SrcFolder = gstr.TrimRight(in.SrcFolder, `/`) + in.WatchFile = filepath.ToSlash(in.WatchFile) + in.WatchFile = gstr.TrimRight(in.WatchFile, `/`) + + // Watch file handling. + if in.WatchFile != "" { + // File lock to avoid multiple processes. + var ( + flockFilePath = gfile.Temp("gf.cli.gen.service.lock") + flockContent = gfile.GetContents(flockFilePath) + ) + if flockContent != "" { + if gtime.Timestamp()-gconv.Int64(flockContent) < genServiceFileLockSeconds { + // If another "gen service" process is running, it just exits. + mlog.Debug(`another "gen service" process is running, exit`) + return + } + } + defer gfile.RemoveFile(flockFilePath) + _ = gfile.PutContents(flockFilePath, gtime.TimestampStr()) + + // It works only if given WatchFile is in SrcFolder. + var ( + watchFileDir = gfile.Dir(in.WatchFile) + srcFolderDir = gfile.Dir(watchFileDir) + ) + mlog.Debug("watchFileDir:", watchFileDir) + mlog.Debug("logicFolderDir:", srcFolderDir) + if !gstr.HasSuffix(gstr.Replace(srcFolderDir, `\`, `/`), in.SrcFolder) { + mlog.Printf(`ignore watch file "%s", not in source path "%s"`, in.WatchFile, in.SrcFolder) + return + } + var newWorkingDir = gfile.Dir(gfile.Dir(srcFolderDir)) + if err = gfile.Chdir(newWorkingDir); err != nil { + mlog.Fatalf(`%+v`, err) + } + mlog.Debug("Chdir:", newWorkingDir) + + in.WatchFile = "" + in.Packages = []string{gfile.Basename(watchFileDir)} + return c.Service(ctx, in) + } + + if !gfile.Exists(in.SrcFolder) { + mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder) + } + + if in.ImportPrefix == "" { + in.ImportPrefix = utils.GetImportPath(in.SrcFolder) + } + + var ( + isDirty atomic.Value // Temp boolean. + files []string // Temp file array. + initImportSrcPackages []string // Used for generating logic.go. + inputPackages = in.Packages // Custom packages. + dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder)) // Package name for generated go files. + generatedDstFilePathSet = gset.NewStrSet() // All generated file path set. + ) + isDirty.Store(false) + + // The first level folders. + srcFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false) + if err != nil { + return nil, err + } + // it will use goroutine to generate service files for each package. + var ( + folderInfos []folderInfo + wg = sync.WaitGroup{} + allStructItems = make(map[string][]string) + ) + + for _, srcFolderPath := range srcFolderPaths { + if !gfile.IsDir(srcFolderPath) { + continue + } + // Only retrieve sub files, no recursively. + if files, err = gfile.ScanDir(srcFolderPath, "*.go", false); err != nil { + return nil, err + } + if len(files) == 0 { + continue + } + + var ( + srcPackageName = gfile.Basename(srcFolderPath) + srcImportedPackages = garray.NewSortedStrArray().SetUnique(true) + srcStructFunctions = gmap.NewListMap() + dstFilePath = gfile.Join(in.DstFolder, + c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go", + ) + ) + + folder := folderInfo{ + SrcPackageName: srcPackageName, + SrcImportedPackages: srcImportedPackages, + SrcStructFunctions: srcStructFunctions, + DstFilePath: dstFilePath, + } + + for _, file := range files { + pkgItems, structItems, funcItems, err := c.parseItemsInSrc(file) + if err != nil { + return nil, err + } + for k, v := range structItems { + allStructItems[k] = v + } + folder.FileInfos = append(folder.FileInfos, &fileInfo{ + PkgItems: pkgItems, + FuncItems: funcItems, + }) + } + + folderInfos = append(folderInfos, folder) + } + + folderInfos = c.calculateStructEmbeddedFuncInfos(folderInfos, allStructItems) + + for _, folder := range folderInfos { + // Parse single logic package folder. + var ( + srcPackageName = folder.SrcPackageName + srcImportedPackages = folder.SrcImportedPackages + srcStructFunctions = folder.SrcStructFunctions + dstFilePath = folder.DstFilePath + ) + generatedDstFilePathSet.Add(dstFilePath) + // if it were to use goroutine, + // it would cause the order of the generated functions in the file to be disordered. + for _, file := range folder.FileInfos { + pkgItems, funcItems := file.PkgItems, file.FuncItems + + // Calculate imported packages for service generating. + err = c.calculateImportedItems(in, pkgItems, funcItems, srcImportedPackages) + if err != nil { + return nil, err + } + + // Calculate functions and interfaces for service generating. + err = c.calculateFuncItems(in, funcItems, srcStructFunctions) + if err != nil { + return nil, err + } + } + + initImportSrcPackages = append( + initImportSrcPackages, + fmt.Sprintf(`%s/%s`, in.ImportPrefix, srcPackageName), + ) + // Ignore source packages if input packages given. + if len(inputPackages) > 0 && !gstr.InArray(inputPackages, srcPackageName) { + mlog.Debugf( + `ignore source package "%s" as it is not in desired packages: %+v`, + srcPackageName, inputPackages, + ) + continue + } + + // Generating service go file for single logic package. + wg.Add(1) + go func(generateServiceFilesInput generateServiceFilesInput) { + defer wg.Done() + ok, err := c.generateServiceFile(generateServiceFilesInput) + if err != nil { + mlog.Printf(`error generating service file "%s": %v`, generateServiceFilesInput.DstFilePath, err) + } + if !isDirty.Load().(bool) && ok { + isDirty.Store(true) + } + }(generateServiceFilesInput{ + CGenServiceInput: in, + SrcPackageName: srcPackageName, + SrcImportedPackages: srcImportedPackages.Slice(), + SrcStructFunctions: srcStructFunctions, + DstPackageName: dstPackageName, + DstFilePath: dstFilePath, + }) + } + wg.Wait() + + if in.Clear { + files, err = gfile.ScanDirFile(in.DstFolder, "*.go", false) + if err != nil { + return nil, err + } + var relativeFilePath string + for _, file := range files { + relativeFilePath = gstr.SubStrFromR(file, in.DstFolder) + if !generatedDstFilePathSet.Contains(relativeFilePath) && + utils.IsFileDoNotEdit(relativeFilePath) { + + mlog.Printf(`remove no longer used service file: %s`, relativeFilePath) + if err = gfile.RemoveFile(file); err != nil { + return nil, err + } + } + } + } + + if isDirty.Load().(bool) { + // Generate initialization go file. + if len(initImportSrcPackages) > 0 { + if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil { + return + } + } + + // Replace v1 to v2 for GoFrame. + if err = utils.ReplaceGeneratedContentGFV2(in.DstFolder); err != nil { + return nil, err + } + mlog.Printf(`gofmt go files in "%s"`, in.DstFolder) + utils.GoFmt(in.DstFolder) + } + + // auto update main.go. + if err = c.checkAndUpdateMain(in.SrcFolder); err != nil { + return nil, err + } + + mlog.Print(`done!`) + return +} + +func (c CGenService) checkAndUpdateMain(srcFolder string) (err error) { + var ( + logicPackageName = gstr.ToLower(gfile.Basename(srcFolder)) + logicFilePath = gfile.Join(srcFolder, logicPackageName+".go") + importPath = utils.GetImportPath(srcFolder) + importStr = fmt.Sprintf(`_ "%s"`, importPath) + mainFilePath = gfile.Join(gfile.Dir(gfile.Dir(gfile.Dir(logicFilePath))), "main.go") + mainFileContent = gfile.GetContents(mainFilePath) + ) + // No main content found. + if mainFileContent == "" { + return nil + } + if gstr.Contains(mainFileContent, importStr) { + return nil + } + match, err := gregex.MatchString(`import \(([\s\S]+?)\)`, mainFileContent) + if err != nil { + return err + } + // No match. + if len(match) < 2 { + return nil + } + lines := garray.NewStrArrayFrom(gstr.Split(match[1], "\n")) + for i, line := range lines.Slice() { + line = gstr.Trim(line) + if len(line) == 0 { + continue + } + if line[0] == '_' { + continue + } + // Insert the logic import into imports. + if err = lines.InsertBefore(i, fmt.Sprintf("\t%s\n\n", importStr)); err != nil { + return err + } + break + } + mainFileContent, err = gregex.ReplaceString( + `import \(([\s\S]+?)\)`, + fmt.Sprintf(`import (%s)`, lines.Join("\n")), + mainFileContent, + ) + if err != nil { + return err + } + mlog.Print(`update main.go`) + err = gfile.PutContents(mainFilePath, mainFileContent) + utils.GoFmt(mainFilePath) + return +} diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go new file mode 100644 index 0000000..974ec7e --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_parse.go @@ -0,0 +1,319 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genservice + +import ( + "go/ast" + "go/parser" + "go/token" + "strings" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gstructs" + "github.com/gogf/gf/v2/text/gstr" +) + +type pkgItem struct { + Alias string `eg:"gdbas"` + Path string `eg:"github.com/gogf/gf/v2/database/gdb"` + RawImport string `eg:"gdbas github.com/gogf/gf/v2/database/gdb"` +} + +type funcItem struct { + Receiver string `eg:"sUser"` + MethodName string `eg:"GetList"` + Params []map[string]string `eg:"ctx: context.Context, cond: *SearchInput"` + Results []map[string]string `eg:"list: []*User, err: error"` + Comment string `eg:"Get user list"` +} + +// parseItemsInSrc parses the pkgItem and funcItem from the specified file. +// It can't skip the private methods. +// It can't skip the imported packages of import alias equal to `_`. +func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, structItems map[string][]string, funcItems []funcItem, err error) { + var ( + fileContent = gfile.GetContents(filePath) + fileSet = token.NewFileSet() + ) + + node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments) + if err != nil { + return + } + + structItems = make(map[string][]string) + pkg := node.Name.Name + pkgAliasMap := make(map[string]string) + ast.Inspect(node, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.ImportSpec: + // parse the imported packages. + pkgItem := c.parseImportPackages(x) + pkgItems = append(pkgItems, pkgItem) + pkgPath := strings.Trim(pkgItem.Path, "\"") + pkgPath = strings.ReplaceAll(pkgPath, "\\", "/") + tmp := strings.Split(pkgPath, "/") + srcPkg := tmp[len(tmp)-1] + if srcPkg != pkgItem.Alias { + pkgAliasMap[pkgItem.Alias] = srcPkg + } + case *ast.TypeSpec: // type define + switch xType := x.Type.(type) { + case *ast.StructType: // define struct + // parse the struct declaration. + var structName = pkg + "." + x.Name.Name + var structEmbeddedStruct []string + for _, field := range xType.Fields.List { + if len(field.Names) > 0 || field.Tag == nil { // not anonymous field + continue + } + + tagValue := strings.Trim(field.Tag.Value, "`") + tagValue = strings.TrimSpace(tagValue) + if len(tagValue) == 0 { // not set tag + continue + } + tags := gstructs.ParseTag(tagValue) + + if v, ok := tags["gen"]; !ok || v != "extend" { + continue + } + + var embeddedStruct string + switch v := field.Type.(type) { + case *ast.Ident: + if embeddedStruct, err = c.astExprToString(v); err != nil { + embeddedStruct = "" + break + } + embeddedStruct = pkg + "." + embeddedStruct + case *ast.StarExpr: + if embeddedStruct, err = c.astExprToString(v.X); err != nil { + embeddedStruct = "" + break + } + embeddedStruct = pkg + "." + embeddedStruct + case *ast.SelectorExpr: + var pkg string + if pkg, err = c.astExprToString(v.X); err != nil { + embeddedStruct = "" + break + } + if v, ok := pkgAliasMap[pkg]; ok { + pkg = v + } + if embeddedStruct, err = c.astExprToString(v.Sel); err != nil { + embeddedStruct = "" + break + } + embeddedStruct = pkg + "." + embeddedStruct + } + + if embeddedStruct == "" { + continue + } + structEmbeddedStruct = append(structEmbeddedStruct, embeddedStruct) + + } + if len(structEmbeddedStruct) > 0 { + structItems[structName] = structEmbeddedStruct + } + case *ast.Ident: // define ident + var ( + structName = pkg + "." + x.Name.Name + typeName = pkg + "." + xType.Name + ) + structItems[structName] = []string{typeName} + case *ast.SelectorExpr: // define selector + var ( + structName = pkg + "." + x.Name.Name + selecotrPkg string + typeName string + ) + if selecotrPkg, err = c.astExprToString(xType.X); err != nil { + break + } + if v, ok := pkgAliasMap[selecotrPkg]; ok { + selecotrPkg = v + } + if typeName, err = c.astExprToString(xType.Sel); err != nil { + break + } + typeName = selecotrPkg + "." + typeName + structItems[structName] = []string{typeName} + } + + case *ast.FuncDecl: + // parse the function items. + if x.Recv == nil { + return true + } + + var funcName = x.Name.Name + funcItems = append(funcItems, funcItem{ + Receiver: c.parseFuncReceiverTypeName(x), + MethodName: funcName, + Params: c.parseFuncParams(x), + Results: c.parseFuncResults(x), + Comment: c.parseFuncComment(x), + }) + } + return true + }) + return +} + +// parseImportPackages retrieves the imported packages from the specified ast.ImportSpec. +func (c CGenService) parseImportPackages(node *ast.ImportSpec) (packages pkgItem) { + if node.Path == nil { + return + } + var ( + alias string + path = node.Path.Value + rawImport string + ) + + if node.Name != nil { + alias = node.Name.Name + rawImport = node.Name.Name + " " + path + } else { + rawImport = path + } + + // if the alias is empty, it will further retrieve the real alias. + if alias == "" { + alias = c.getRealAlias(path) + } + + return pkgItem{ + Alias: alias, + Path: path, + RawImport: rawImport, + } +} + +// getRealAlias retrieves the real alias of the package. +// If package is "github.com/gogf/gf", the alias is "gf". +// If package is "github.com/gogf/gf/v2", the alias is "gf" instead of "v2". +func (c CGenService) getRealAlias(importPath string) (pkgName string) { + importPath = gstr.Trim(importPath, `"`) + parts := gstr.Split(importPath, "/") + if len(parts) == 0 { + return + } + + pkgName = parts[len(parts)-1] + + if !gstr.HasPrefix(pkgName, "v") { + return pkgName + } + + if len(parts) < 2 { + return pkgName + } + + if gstr.IsNumeric(gstr.SubStr(pkgName, 1)) { + pkgName = parts[len(parts)-2] + } + + return pkgName +} + +// parseFuncReceiverTypeName retrieves the receiver type of the function. +// For example: +// +// func(s *sArticle) -> *sArticle +// func(s sArticle) -> sArticle +func (c CGenService) parseFuncReceiverTypeName(node *ast.FuncDecl) (receiverType string) { + if node.Recv == nil { + return "" + } + receiverType, err := c.astExprToString(node.Recv.List[0].Type) + if err != nil { + return "" + } + return +} + +// parseFuncParams retrieves the input parameters of the function. +// It returns the name and type of the input parameters. +// For example: +// +// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}} +func (c CGenService) parseFuncParams(node *ast.FuncDecl) (params []map[string]string) { + if node.Type.Params == nil { + return + } + for _, param := range node.Type.Params.List { + if param.Names == nil { + // No name for the return value. + resultType, err := c.astExprToString(param.Type) + if err != nil { + continue + } + params = append(params, map[string]string{ + "paramName": "", + "paramType": resultType, + }) + continue + } + for _, name := range param.Names { + paramType, err := c.astExprToString(param.Type) + if err != nil { + continue + } + params = append(params, map[string]string{ + "paramName": name.Name, + "paramType": paramType, + }) + } + } + return +} + +// parseFuncResults retrieves the output parameters of the function. +// It returns the name and type of the output parameters. +// For example: +// +// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error} +// []map[string]string{resultName: "", resultType: error} +func (c CGenService) parseFuncResults(node *ast.FuncDecl) (results []map[string]string) { + if node.Type.Results == nil { + return + } + for _, result := range node.Type.Results.List { + if result.Names == nil { + // No name for the return value. + resultType, err := c.astExprToString(result.Type) + if err != nil { + continue + } + results = append(results, map[string]string{ + "resultName": "", + "resultType": resultType, + }) + continue + } + for _, name := range result.Names { + resultType, err := c.astExprToString(result.Type) + if err != nil { + continue + } + results = append(results, map[string]string{ + "resultName": name.Name, + "resultType": resultType, + }) + } + } + return +} + +// parseFuncComment retrieves the comment of the function. +func (c CGenService) parseFuncComment(node *ast.FuncDecl) string { + return c.astCommentToString(node.Doc) +} diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go new file mode 100644 index 0000000..8fc12e2 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_ast_utils.go @@ -0,0 +1,48 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genservice + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + "strings" +) + +// exprToString converts ast.Expr to string. +// For example: +// +// ast.Expr -> "context.Context" +// ast.Expr -> "*v1.XxxReq" +// ast.Expr -> "error" +// ast.Expr -> "int" +func (c CGenService) astExprToString(expr ast.Expr) (string, error) { + var ( + buf bytes.Buffer + err error + ) + err = format.Node(&buf, token.NewFileSet(), expr) + if err != nil { + return "", err + } + return buf.String(), nil +} + +// astCommentToString returns the raw (original) text of the comment. +// It includes the comment markers (//, /*, and */). +// It adds a newline at the end of the comment. +func (c CGenService) astCommentToString(node *ast.CommentGroup) string { + if node == nil { + return "" + } + var b strings.Builder + for _, c := range node.List { + b.WriteString(c.Text + "\n") + } + return b.String() +} diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go new file mode 100644 index 0000000..4225fd9 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_calculate.go @@ -0,0 +1,228 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genservice + +import ( + "fmt" + "strings" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +func (c CGenService) calculateImportedItems( + in CGenServiceInput, + pkgItems []pkgItem, funcItems []funcItem, + srcImportedPackages *garray.SortedStrArray, +) (err error) { + // allFuncParamType saves all the param and result types of the functions. + var allFuncParamType strings.Builder + + for _, item := range funcItems { + for _, param := range item.Params { + allFuncParamType.WriteString(param["paramType"] + ",") + } + for _, result := range item.Results { + allFuncParamType.WriteString(result["resultType"] + ",") + } + } + + for _, item := range pkgItems { + alias := item.Alias + + // If the alias is _, it means that the package is not generated. + if alias == "_" { + mlog.Debugf(`ignore anonymous package: %s`, item.RawImport) + continue + } + // If the alias is empty, it will use the package name as the alias. + if alias == "" { + alias = gfile.Basename(gstr.Trim(item.Path, `"`)) + } + if !gstr.Contains(allFuncParamType.String(), alias) { + mlog.Debugf(`ignore unused package: %s`, item.RawImport) + continue + } + srcImportedPackages.Add(item.RawImport) + } + return nil +} + +func (c CGenService) calculateFuncItems( + in CGenServiceInput, + funcItems []funcItem, + srcPkgInterfaceMap *gmap.ListMap, +) (err error) { + var srcPkgInterfaceFunc []map[string]string + + for _, item := range funcItems { + var ( + // eg: "sArticle" + receiverName string + receiverMatch []string + + // eg: "GetList(ctx context.Context, req *v1.ArticleListReq) (list []*v1.Article, err error)" + funcHead string + ) + + // handle the receiver name. + if item.Receiver == "" { + continue + } + receiverName = item.Receiver + receiverName = gstr.Trim(receiverName, "*") + // Match and pick the struct name from receiver. + if receiverMatch, err = gregex.MatchString(in.StPattern, receiverName); err != nil { + return err + } + if len(receiverMatch) < 1 { + continue + } + receiverName = gstr.CaseCamel(receiverMatch[1]) + + // check if the func name is public. + if !gstr.IsLetterUpper(item.MethodName[0]) { + continue + } + + if !srcPkgInterfaceMap.Contains(receiverName) { + srcPkgInterfaceFunc = make([]map[string]string, 0) + srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc) + } else { + srcPkgInterfaceFunc = srcPkgInterfaceMap.Get(receiverName).([]map[string]string) + } + + // make the func head. + paramsStr := c.tidyParam(item.Params) + resultsStr := c.tidyResult(item.Results) + funcHead = fmt.Sprintf("%s(%s) (%s)", item.MethodName, paramsStr, resultsStr) + + srcPkgInterfaceFunc = append(srcPkgInterfaceFunc, map[string]string{ + "funcHead": funcHead, + "funcComment": item.Comment, + }) + srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc) + } + return nil +} + +// tidyParam tidies the input parameters. +// For example: +// +// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}} +// -> ctx context.Context, info struct{} +func (c CGenService) tidyParam(paramSlice []map[string]string) (paramStr string) { + for i, param := range paramSlice { + if i > 0 { + paramStr += ", " + } + paramStr += fmt.Sprintf("%s %s", param["paramName"], param["paramType"]) + } + return +} + +// tidyResult tidies the output parameters. +// For example: +// +// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error} +// -> list []*User, err error +// +// []map[string]string{resultName: "", resultType: error} +// -> error +func (c CGenService) tidyResult(resultSlice []map[string]string) (resultStr string) { + for i, result := range resultSlice { + if i > 0 { + resultStr += ", " + } + if result["resultName"] != "" { + resultStr += fmt.Sprintf("%s %s", result["resultName"], result["resultType"]) + } else { + resultStr += result["resultType"] + } + } + return +} + +func (c CGenService) getStructFuncItems(structName string, allStructItems map[string][]string, funcItemsWithoutEmbed map[string][]*funcItem) (funcItems []*funcItem) { + funcItemNameSet := map[string]struct{}{} + + if items, ok := funcItemsWithoutEmbed[structName]; ok { + funcItems = append(funcItems, items...) + for _, item := range items { + funcItemNameSet[item.MethodName] = struct{}{} + } + } + + embeddedStructNames, ok := allStructItems[structName] + if !ok { + return + } + + for _, embeddedStructName := range embeddedStructNames { + items := c.getStructFuncItems(embeddedStructName, allStructItems, funcItemsWithoutEmbed) + + for _, item := range items { + if _, ok := funcItemNameSet[item.MethodName]; ok { + continue + } + funcItemNameSet[item.MethodName] = struct{}{} + funcItems = append(funcItems, item) + } + } + + return +} + +func (c CGenService) calculateStructEmbeddedFuncInfos(folderInfos []folderInfo, allStructItems map[string][]string) (newFolerInfos []folderInfo) { + funcItemsWithoutEmbed := make(map[string][]*funcItem) + funcItemMap := make(map[string]*([]funcItem)) + funcItemsWithoutEmbedMap := make(map[string]*funcItem) + + newFolerInfos = append(newFolerInfos, folderInfos...) + + for _, folder := range newFolerInfos { + for k := range folder.FileInfos { + fi := folder.FileInfos[k] + for k := range fi.FuncItems { + item := &fi.FuncItems[k] + receiver := folder.SrcPackageName + "." + strings.ReplaceAll(item.Receiver, "*", "") + funcItemMap[receiver] = &fi.FuncItems + funcItemsWithoutEmbed[receiver] = append(funcItemsWithoutEmbed[receiver], item) + funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiver, item.MethodName)] = item + } + } + } + + for receiver, structItems := range allStructItems { + receiverName := strings.ReplaceAll(receiver, "*", "") + for _, structName := range structItems { + // Get the list of methods for the corresponding structName. + for _, funcItem := range c.getStructFuncItems(structName, allStructItems, funcItemsWithoutEmbed) { + if _, ok := funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiverName, funcItem.MethodName)]; ok { + continue + } + if funcItemsPtr, ok := funcItemMap[receiverName]; ok { + newFuncItem := *funcItem + newFuncItem.Receiver = getReceiverName(receiver) + (*funcItemsPtr) = append((*funcItemsPtr), newFuncItem) + } + } + } + } + + return +} + +func getReceiverName(receiver string) string { + ss := strings.Split(receiver, ".") + return ss[len(ss)-1] +} diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go new file mode 100644 index 0000000..c18249a --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate.go @@ -0,0 +1,87 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genservice + +import ( + "bytes" + "fmt" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" + "hotgo/internal/library/hggen/internal/utility/utils" +) + +type generateServiceFilesInput struct { + CGenServiceInput + SrcPackageName string + SrcImportedPackages []string + SrcStructFunctions *gmap.ListMap + DstPackageName string + DstFilePath string // Absolute file path for generated service go file. +} + +func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool, err error) { + var generatedContent bytes.Buffer + + c.generatePackageImports(&generatedContent, in.DstPackageName, in.SrcImportedPackages) + c.generateType(&generatedContent, in.SrcStructFunctions, in.DstPackageName) + c.generateVar(&generatedContent, in.SrcStructFunctions) + c.generateFunc(&generatedContent, in.SrcStructFunctions) + + // Write file content to disk. + if gfile.Exists(in.DstFilePath) { + if !utils.IsFileDoNotEdit(in.DstFilePath) { + mlog.Printf(`ignore file as it is manually maintained: %s`, in.DstFilePath) + return false, nil + } + } + mlog.Printf(`generating service go file: %s`, in.DstFilePath) + if err = gfile.PutBytes(in.DstFilePath, generatedContent.Bytes()); err != nil { + return true, err + } + return true, nil +} + +// generateInitializationFile generates `logic.go`. +func (c CGenService) generateInitializationFile(in CGenServiceInput, importSrcPackages []string) (err error) { + var ( + logicPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder)) + logicFilePath = gfile.Join(in.SrcFolder, logicPackageName+".go") + logicImports string + generatedContent string + ) + if !utils.IsFileDoNotEdit(logicFilePath) { + mlog.Debugf(`ignore file as it is manually maintained: %s`, logicFilePath) + return nil + } + for _, importSrcPackage := range importSrcPackages { + logicImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n") + } + generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{ + "{PackageName}": logicPackageName, + "{Imports}": logicImports, + }) + mlog.Printf(`generating init go file: %s`, logicFilePath) + if err = gfile.PutContents(logicFilePath, generatedContent); err != nil { + return err + } + utils.GoFmt(logicFilePath) + return nil +} + +// getDstFileNameCase call gstr.Case* function to convert the s to specified case. +func (c CGenService) getDstFileNameCase(str, caseStr string) (newStr string) { + if newStr := gstr.CaseConvert(str, gstr.CaseTypeMatch(caseStr)); newStr != str { + return newStr + } + return gstr.CaseSnake(str) +} diff --git a/server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go new file mode 100644 index 0000000..21ecac4 --- /dev/null +++ b/server/internal/library/hggen/internal/cmd/genservice/genservice_generate_template.go @@ -0,0 +1,105 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package genservice + +import ( + "bytes" + "fmt" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" +) + +func (c CGenService) generatePackageImports(generatedContent *bytes.Buffer, packageName string, imports []string) { + generatedContent.WriteString(gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{ + "{PackageName}": packageName, + "{Imports}": fmt.Sprintf( + "import (\n%s\n)", gstr.Join(imports, "\n"), + ), + })) +} + +// generateType type definitions. +// See: const.TemplateGenServiceContentInterface +func (c CGenService) generateType(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap, dstPackageName string) { + generatedContent.WriteString("type(") + generatedContent.WriteString("\n") + + srcStructFunctions.Iterator(func(key, value any) bool { + var ( + funcContents = make([]string, 0) + funcContent string + ) + structName, funcSlice := key.(string), value.([]map[string]string) + // Generating interface content. + for _, funcInfo := range funcSlice { + // Remove package name calls of `dstPackageName` in produced codes. + funcHead, _ := gregex.ReplaceString( + fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), + ``, funcInfo["funcHead"], + ) + funcContent = funcInfo["funcComment"] + funcHead + funcContents = append(funcContents, funcContent) + } + + // funcContents to string. + generatedContent.WriteString( + gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{ + "{InterfaceName}": "I" + structName, + "{FuncDefinition}": gstr.Join(funcContents, "\n\t"), + })), + ) + generatedContent.WriteString("\n") + return true + }) + + generatedContent.WriteString(")") + generatedContent.WriteString("\n") +} + +// generateVar variable definitions. +// See: const.TemplateGenServiceContentVariable +func (c CGenService) generateVar(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) { + // Generating variable and register definitions. + var variableContent string + + srcStructFunctions.Iterator(func(key, value any) bool { + structName := key.(string) + variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{ + "{StructName}": structName, + "{InterfaceName}": "I" + structName, + })) + variableContent += "\n" + return true + }) + if variableContent != "" { + generatedContent.WriteString("var(") + generatedContent.WriteString("\n") + generatedContent.WriteString(variableContent) + generatedContent.WriteString(")") + generatedContent.WriteString("\n") + } +} + +// generateFunc function definitions. +// See: const.TemplateGenServiceContentRegister +func (c CGenService) generateFunc(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) { + // Variable register function definitions. + srcStructFunctions.Iterator(func(key, value any) bool { + structName := key.(string) + generatedContent.WriteString(gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{ + "{StructName}": structName, + "{InterfaceName}": "I" + structName, + }))) + generatedContent.WriteString("\n\n") + return true + }) +} diff --git a/server/internal/library/hggen/internal/consts/consts.go b/server/internal/library/hggen/internal/consts/consts.go new file mode 100644 index 0000000..cece25d --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts.go @@ -0,0 +1,13 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const ( + // DoNotEditKey is used in generated files, + // which marks the files will be overwritten by CLI tool. + DoNotEditKey = `DO NOT EDIT` +) diff --git a/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go new file mode 100644 index 0000000..ac6ee7d --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template.go @@ -0,0 +1,87 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenCtrlControllerEmpty = ` +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package {Module} +` + +const TemplateGenCtrlControllerNewEmpty = ` +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package {Module} + +import ( + {ImportPath} +) +` + +const TemplateGenCtrlControllerNewFunc = ` +type {CtrlName} struct{} + +func {NewFuncName}() {InterfaceName} { + return &{CtrlName}{} +} +` + +const TemplateGenCtrlControllerMethodFunc = ` +package {Module} + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "{ImportPath}" +) +{MethodComment} +func (c *{CtrlName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) { + return nil, gerror.NewCode(gcode.CodeNotImplemented) +} +` + +const TemplateGenCtrlControllerHeader = ` +package {Module} + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "{ImportPath}" +) + +` + +const TemplateGenCtrlControllerMethodFuncMerge = ` +{MethodComment} +func (c *{CtrlName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) { + return nil, gerror.NewCode(gcode.CodeNotImplemented) +} +` + +const TemplateGenCtrlApiInterface = ` +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package {Module} + +import ( +{ImportPaths} +) + +{Interfaces} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go new file mode 100644 index 0000000..f2c4670 --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_ctrl_template_sdk.go @@ -0,0 +1,89 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenCtrlSdkPkgNew = ` +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package {PkgName} + +import ( + "fmt" + + "github.com/gogf/gf/contrib/sdk/httpclient/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gstr" +) + +type implementer struct { + config httpclient.Config +} + +func New(config httpclient.Config) IClient { + return &implementer{ + config: config, + } +} + +` + +const TemplateGenCtrlSdkIClient = ` +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package {PkgName} + +import ( +) + +type IClient interface { +} +` + +const TemplateGenCtrlSdkImplementer = ` +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package {PkgName} + +import ( + "context" + + "github.com/gogf/gf/contrib/sdk/httpclient/v2" + "github.com/gogf/gf/v2/text/gstr" + +{ImportPaths} +) + +type implementer{ImplementerName} struct { + *httpclient.Client +} + +` + +const TemplateGenCtrlSdkImplementerNew = ` +func (i *implementer) {ImplementerName}() {Module}.I{ImplementerName} { + var ( + client = httpclient.New(i.config) + prefix = gstr.TrimRight(i.config.URL, "/") + "{VersionPrefix}" + ) + client.Client = client.Prefix(prefix) + return &implementer{ImplementerName}{client} +} + +` + +const TemplateGenCtrlSdkImplementerFunc = `{MethodComment} +func (i *implementer{ImplementerName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) { + err = i.Request(ctx, req, &res) + return +} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_dao.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_dao.go new file mode 100644 index 0000000..cffa30e --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_dao.go @@ -0,0 +1,139 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenDaoIndexContent = ` +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package {{.TplPackageName}} + +import ( + "{{.TplImportPrefix}}/internal" +) + +// {{.TplTableNameCamelLowerCase}}Dao is the data access object for the table {{.TplTableName}}. +// You can define custom methods on it to extend its functionality as needed. +type {{.TplTableNameCamelLowerCase}}Dao struct { + *internal.{{.TplTableNameCamelCase}}Dao +} + +var ( + // {{.TplTableNameCamelCase}} is a globally accessible object for table {{.TplTableName}} operations. + {{.TplTableNameCamelCase}} = {{.TplTableNameCamelLowerCase}}Dao{ +{{- if .TplTableSharding -}} + internal.New{{.TplTableNameCamelCase}}Dao({{.TplTableNameCamelLowerCase}}ShardingHandler), +{{- else -}} + internal.New{{.TplTableNameCamelCase}}Dao(), +{{- end -}} + } +) + +{{if .TplTableSharding -}} +// {{.TplTableNameCamelLowerCase}}ShardingHandler is the handler for sharding operations. +// You can fill this sharding handler with your custom implementation. +func {{.TplTableNameCamelLowerCase}}ShardingHandler(m *gdb.Model) *gdb.Model { + m = m.Sharding(gdb.ShardingConfig{ + Table: gdb.ShardingTableConfig{ + Enable: true, + Prefix: "{{.TplTableShardingPrefix}}", + // Replace Rule field with your custom sharding rule. + // Or you can use "&gdb.DefaultShardingRule{}" for default sharding rule. + Rule: nil, + }, + Schema: gdb.ShardingSchemaConfig{}, + }) + return m +} +{{- end}} + +// Add your custom methods and functionality below. + +` + +const TemplateGenDaoInternalContent = ` +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}} +// ========================================================================== + +package internal + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// {{.TplTableNameCamelCase}}Dao is the data access object for the table {{.TplTableName}}. +type {{.TplTableNameCamelCase}}Dao struct { + table string // table is the underlying table name of the DAO. + group string // group is the database configuration group name of the current DAO. + columns {{.TplTableNameCamelCase}}Columns // columns contains all the column names of Table for convenient usage. + handlers []gdb.ModelHandler // handlers for customized model modification. +} + +// {{.TplTableNameCamelCase}}Columns defines and stores column names for the table {{.TplTableName}}. +type {{.TplTableNameCamelCase}}Columns struct { + {{.TplColumnDefine}} +} + +// {{.TplTableNameCamelLowerCase}}Columns holds the columns for the table {{.TplTableName}}. +var {{.TplTableNameCamelLowerCase}}Columns = {{.TplTableNameCamelCase}}Columns{ + {{.TplColumnNames}} +} + +// New{{.TplTableNameCamelCase}}Dao creates and returns a new DAO object for table data access. +func New{{.TplTableNameCamelCase}}Dao(handlers ...gdb.ModelHandler) *{{.TplTableNameCamelCase}}Dao { + return &{{.TplTableNameCamelCase}}Dao{ + group: "{{.TplGroupName}}", + table: "{{.TplTableName}}", + columns: {{.TplTableNameCamelLowerCase}}Columns, + handlers: handlers, + } +} + +// DB retrieves and returns the underlying raw database management object of the current DAO. +func (dao *{{.TplTableNameCamelCase}}Dao) DB() gdb.DB { + return g.DB(dao.group) +} + +// Table returns the table name of the current DAO. +func (dao *{{.TplTableNameCamelCase}}Dao) Table() string { + return dao.table +} + +// Columns returns all column names of the current DAO. +func (dao *{{.TplTableNameCamelCase}}Dao) Columns() {{.TplTableNameCamelCase}}Columns { + return dao.columns +} + +// Group returns the database configuration group name of the current DAO. +func (dao *{{.TplTableNameCamelCase}}Dao) Group() string { + return dao.group +} + +// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation. +func (dao *{{.TplTableNameCamelCase}}Dao) Ctx(ctx context.Context) *gdb.Model { + model := dao.DB().Model(dao.table) + for _, handler := range dao.handlers { + model = handler(model) + } + return model.Safe().Ctx(ctx) +} + +// Transaction wraps the transaction logic using function f. +// It rolls back the transaction and returns the error if function f returns a non-nil error. +// It commits the transaction and returns nil if function f returns nil. +// +// Note: Do not commit or roll back the transaction in function f, +// as it is automatically handled by this function. +func (dao *{{.TplTableNameCamelCase}}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) { + return dao.Ctx(ctx).Transaction(ctx, f) +} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go new file mode 100644 index 0000000..97a404e --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_do.go @@ -0,0 +1,20 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenDaoDoContent = ` +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}} +// ================================================================================= + +package {{.TplPackageName}} + +{{.TplPackageImports}} + +// {{.TplTableNameCamelCase}} is the golang structure of table {{.TplTableName}} for DAO operations like Where/Data. +{{.TplStructDefine}} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go new file mode 100644 index 0000000..c54713a --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_entity.go @@ -0,0 +1,20 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenDaoEntityContent = ` +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {{.TplCreatedAtDatetimeStr}} +// ================================================================================= + +package {{.TplPackageName}} + +{{.TplPackageImports}} + +// {{.TplTableNameCamelCase}} is the golang structure for table {{.TplTableName}}. +{{.TplStructDefine}} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go new file mode 100644 index 0000000..a60c92d --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_dao_template_table.go @@ -0,0 +1,35 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenTableContent = ` +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package {{.TplPackageName}} + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" +) + +// {{.TplTableNameCamelCase}} defines the fields of table "{{.TplTableName}}" with their properties. +// This map is used internally by GoFrame ORM to understand table structure. +var {{.TplTableNameCamelCase}} = map[string]*gdb.TableField{ +{{.TplTableFields}} +} + +// Set{{.TplTableNameCamelCase}}TableFields registers the table fields definition to the database instance. +// db: database instance that implements gdb.DB interface. +// schema: optional schema/namespace name, especially for databases that support schemas. +func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db gdb.DB, schema ...string) error { + return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...) +} + +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_enums_template.go b/server/internal/library/hggen/internal/consts/consts_gen_enums_template.go new file mode 100644 index 0000000..9477787 --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_enums_template.go @@ -0,0 +1,23 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenEnums = ` +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================ + +package {PackageName} + +import ( + "github.com/gogf/gf/v2/util/gtag" +) + +func init() { + gtag.SetGlobalEnums({EnumsJson}) +} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go b/server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go new file mode 100644 index 0000000..73b693e --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_pbentity_template.go @@ -0,0 +1,23 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplatePbEntityMessageContent = ` +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +syntax = "proto3"; + +package {PackageName}; + +option go_package = "{GoPackage}"; +{OptionContent} +{Imports} + +{EntityMessage} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_service_template.go b/server/internal/library/hggen/internal/consts/consts_gen_service_template.go new file mode 100644 index 0000000..f5c4f5a --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_service_template.go @@ -0,0 +1,41 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenServiceContentHead = ` +// ================================================================================ +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// You can delete these comments if you wish manually maintain this interface file. +// ================================================================================ + +package {PackageName} + +{Imports} +` + +const TemplateGenServiceContentInterface = ` +{InterfaceName} interface { + {FuncDefinition} +} +` + +const TemplateGenServiceContentVariable = ` +local{StructName} {InterfaceName} +` + +const TemplateGenServiceContentRegister = ` +func {StructName}() {InterfaceName} { + if local{StructName} == nil { + panic("implement not found for interface {InterfaceName}, forgot register?") + } + return local{StructName} +} + +func Register{StructName}(i {InterfaceName}) { + local{StructName} = i +} +` diff --git a/server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go b/server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go new file mode 100644 index 0000000..eb43a03 --- /dev/null +++ b/server/internal/library/hggen/internal/consts/consts_gen_service_template_logic.go @@ -0,0 +1,19 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenServiceLogicContent = ` +// ========================================================================== +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ========================================================================== + +package {PackageName} + +import( + {Imports} +) +` diff --git a/server/internal/library/hggen/internal/service/install.go b/server/internal/library/hggen/internal/service/install.go new file mode 100644 index 0000000..0c901b1 --- /dev/null +++ b/server/internal/library/hggen/internal/service/install.go @@ -0,0 +1,246 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package service + +import ( + "context" + "runtime" + "strings" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/genv" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + + "hotgo/internal/library/hggen/internal/utility/allyes" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +var ( + Install = serviceInstall{} +) + +type serviceInstall struct{} + +type serviceInstallAvailablePath struct { + dirPath string + filePath string + writable bool + installed bool + IsSelf bool +} + +func (s serviceInstall) Run(ctx context.Context) (err error) { + // Ask where to install. + paths := s.getAvailablePaths() + if len(paths) <= 0 { + mlog.Printf("no path detected, you can manually install gf by copying the binary to path folder.") + return + } + mlog.Printf("I found some installable paths for you(from $PATH): ") + mlog.Printf(" %2s | %8s | %9s | %s", "Id", "Writable", "Installed", "Path") + + // Print all paths status and determine the default selectedID value. + var ( + selectedID = -1 + newPaths []serviceInstallAvailablePath + pathSet = gset.NewStrSet() // Used for repeated items filtering. + ) + for _, path := range paths { + if !pathSet.AddIfNotExist(path.dirPath) { + continue + } + newPaths = append(newPaths, path) + } + paths = newPaths + for id, path := range paths { + mlog.Printf(" %2d | %8t | %9t | %s", id, path.writable, path.installed, path.dirPath) + if selectedID == -1 { + // Use the previously installed path as the most priority choice. + if path.installed { + selectedID = id + } + } + } + // If there's no previously installed path, use the first writable path. + if selectedID == -1 { + // Order by choosing priority. + commonPaths := garray.NewStrArrayFrom(g.SliceStr{ + s.getGoPathBin(), + `/usr/local/bin`, + `/usr/bin`, + `/usr/sbin`, + `C:\Windows`, + `C:\Windows\system32`, + `C:\Go\bin`, + `C:\Program Files`, + `C:\Program Files (x86)`, + }) + // Check the common installation directories. + commonPaths.Iterator(func(k int, v string) bool { + for id, aPath := range paths { + if strings.EqualFold(aPath.dirPath, v) { + selectedID = id + return false + } + } + return true + }) + if selectedID == -1 { + selectedID = 0 + } + } + + if allyes.Check() { + // Use the default selectedID. + mlog.Printf("please choose one installation destination [default %d]: %d", selectedID, selectedID) + } else { + for { + // Get input and update selectedID. + var ( + inputID int + input = gcmd.Scanf("please choose one installation destination [default %d]: ", selectedID) + ) + if input != "" { + inputID = gconv.Int(input) + } else { + break + } + // Check if out of range. + if inputID >= len(paths) || inputID < 0 { + mlog.Printf("invalid install destination Id: %d", inputID) + continue + } + selectedID = inputID + break + } + } + + // Get selected destination path. + dstPath := paths[selectedID] + + // Install the new binary. + mlog.Debugf(`copy file from "%s" to "%s"`, gfile.SelfPath(), dstPath.filePath) + err = gfile.CopyFile(gfile.SelfPath(), dstPath.filePath) + if err != nil { + mlog.Printf("install gf binary to '%s' failed: %v", dstPath.dirPath, err) + mlog.Printf("you can manually install gf by copying the binary to folder: %s", dstPath.dirPath) + } else { + mlog.Printf("gf binary is successfully installed to: %s", dstPath.filePath) + } + return +} + +// IsInstalled checks and returns whether the binary is installed. +func (s serviceInstall) IsInstalled() (*serviceInstallAvailablePath, bool) { + paths := s.getAvailablePaths() + for _, aPath := range paths { + if aPath.installed { + return &aPath, true + } + } + return nil, false +} + +// getGoPathBin retrieves ad returns the GOPATH/bin path for binary. +func (s serviceInstall) getGoPathBin() string { + if goPath := genv.Get(`GOPATH`).String(); goPath != "" { + return gfile.Join(goPath, "bin") + } + return "" +} + +// getAvailablePaths returns the installation paths data for the binary. +func (s serviceInstall) getAvailablePaths() []serviceInstallAvailablePath { + var ( + folderPaths []serviceInstallAvailablePath + binaryFileName = "gf" + ) + + // Windows binary file name suffix. + if runtime.GOOS == "windows" { + binaryFileName += ".exe" + } + + // $GOPATH/bin + if goPathBin := s.getGoPathBin(); goPathBin != "" { + folderPaths = s.checkAndAppendToAvailablePath( + folderPaths, goPathBin, binaryFileName, + ) + } + switch runtime.GOOS { + case "darwin": + darwinInstallationCheckPaths := []string{"/usr/local/bin"} + for _, v := range darwinInstallationCheckPaths { + folderPaths = s.checkAndAppendToAvailablePath( + folderPaths, v, binaryFileName, + ) + } + fallthrough + + default: + // Search and find the writable directory path. + envPath := genv.Get("PATH", genv.Get("Path").String()).String() + if gstr.Contains(envPath, ";") { + // windows. + for _, v := range gstr.SplitAndTrim(envPath, ";") { + if v == "." { + continue + } + folderPaths = s.checkAndAppendToAvailablePath( + folderPaths, v, binaryFileName, + ) + } + } else if gstr.Contains(envPath, ":") { + // *nix. + for _, v := range gstr.SplitAndTrim(envPath, ":") { + if v == "." { + continue + } + folderPaths = s.checkAndAppendToAvailablePath( + folderPaths, v, binaryFileName, + ) + } + } else if envPath != "" { + folderPaths = s.checkAndAppendToAvailablePath( + folderPaths, envPath, binaryFileName, + ) + } else { + folderPaths = s.checkAndAppendToAvailablePath( + folderPaths, "/usr/local/bin", binaryFileName, + ) + } + } + return folderPaths +} + +// checkAndAppendToAvailablePath checks if `path` is writable and already installed. +// It adds the `path` to `folderPaths` if it is writable or already installed, or else it ignores the `path`. +func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInstallAvailablePath, dirPath string, binaryFileName string) []serviceInstallAvailablePath { + var ( + filePath = gfile.Join(dirPath, binaryFileName) + writable = gfile.IsWritable(dirPath) + installed = gfile.Exists(filePath) + self = gfile.SelfPath() == filePath + ) + if !writable && !installed { + return folderPaths + } + return append( + folderPaths, + serviceInstallAvailablePath{ + dirPath: dirPath, + writable: writable, + filePath: filePath, + installed: installed, + IsSelf: self, + }) +} diff --git a/server/internal/library/hggen/internal/utility/allyes/allyes.go b/server/internal/library/hggen/internal/utility/allyes/allyes.go new file mode 100644 index 0000000..f8f7d1e --- /dev/null +++ b/server/internal/library/hggen/internal/utility/allyes/allyes.go @@ -0,0 +1,28 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package allyes + +import ( + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/genv" +) + +const ( + EnvName = "GF_CLI_ALL_YES" +) + +// Init initializes the package manually. +func Init() { + if gcmd.GetOpt("y") != nil { + genv.MustSet(EnvName, "1") + } +} + +// Check checks whether option allow all yes for command. +func Check() bool { + return genv.Get(EnvName).String() == "1" +} diff --git a/server/internal/library/hggen/internal/utility/mlog/mlog.go b/server/internal/library/hggen/internal/utility/mlog/mlog.go new file mode 100644 index 0000000..2181501 --- /dev/null +++ b/server/internal/library/hggen/internal/utility/mlog/mlog.go @@ -0,0 +1,76 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package mlog + +import ( + "context" + + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/genv" + "github.com/gogf/gf/v2/os/glog" +) + +const ( + headerPrintEnvName = "GF_CLI_MLOG_HEADER" +) + +var ( + ctx = context.TODO() + logger = glog.New() +) + +func init() { + if genv.Get(headerPrintEnvName).String() == "1" { + logger.SetHeaderPrint(true) + } else { + logger.SetHeaderPrint(false) + } + + if gcmd.GetOpt("debug") != nil || gcmd.GetOpt("gf.debug") != nil { + logger.SetHeaderPrint(true) + logger.SetStackSkip(4) + logger.SetFlags(logger.GetFlags() | glog.F_FILE_LONG) + logger.SetDebug(true) + } else { + logger.SetStack(false) + logger.SetDebug(false) + } +} + +// SetHeaderPrint enables/disables header printing to stdout. +func SetHeaderPrint(enabled bool) { + logger.SetHeaderPrint(enabled) + if enabled { + _ = genv.Set(headerPrintEnvName, "1") + } else { + _ = genv.Set(headerPrintEnvName, "0") + } +} + +func Print(v ...any) { + logger.Print(ctx, v...) +} + +func Printf(format string, v ...any) { + logger.Printf(ctx, format, v...) +} + +func Fatal(v ...any) { + logger.Fatal(ctx, v...) +} + +func Fatalf(format string, v ...any) { + logger.Fatalf(ctx, format, v...) +} + +func Debug(v ...any) { + logger.Debug(ctx, v...) +} + +func Debugf(format string, v ...any) { + logger.Debugf(ctx, format, v...) +} diff --git a/server/internal/library/hggen/internal/utility/utils/utils.go b/server/internal/library/hggen/internal/utility/utils/utils.go new file mode 100644 index 0000000..05361fd --- /dev/null +++ b/server/internal/library/hggen/internal/utility/utils/utils.go @@ -0,0 +1,135 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package utils + +import ( + "context" + "fmt" + + "golang.org/x/tools/imports" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + + "hotgo/internal/library/hggen/internal/consts" + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +// GoFmt formats the source file and adds or removes import statements as necessary. +func GoFmt(path string) { + replaceFunc := func(path, content string) string { + res, err := imports.Process(path, []byte(content), nil) + if err != nil { + mlog.Printf(`error format "%s" go files: %v`, path, err) + return content + } + return string(res) + } + + var err error + if gfile.IsFile(path) { + // File format. + if gfile.ExtName(path) != "go" { + return + } + err = gfile.ReplaceFileFunc(replaceFunc, path) + } else { + // Folder format. + err = gfile.ReplaceDirFunc(replaceFunc, path, "*.go", true) + } + if err != nil { + mlog.Printf(`error format "%s" go files: %v`, path, err) + } +} + +// GoModTidy executes `go mod tidy` at specified directory `dirPath`. +func GoModTidy(ctx context.Context, dirPath string) error { + command := fmt.Sprintf(`cd %s && go mod tidy`, dirPath) + err := gproc.ShellRun(ctx, command) + return err +} + +// IsFileDoNotEdit checks and returns whether file contains `do not edit` key. +func IsFileDoNotEdit(filePath string) bool { + if !gfile.Exists(filePath) { + return true + } + return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey) +} + +// ReplaceGeneratedContentGFV2 replaces generated go content from goframe v1 to v2. +func ReplaceGeneratedContentGFV2(folderPath string) (err error) { + return gfile.ReplaceDirFunc(func(path, content string) string { + if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) { + content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`) + content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`) + content = gstr.Replace(content, `"github.com/gogf/gf/v2/contrib/`, `"github.com/gogf/gf/contrib/`) + return content + } + return content + }, folderPath, "*.go", true) +} + +// GetImportPath calculates and returns the golang import path for given `dirPath`. +// Note that it needs a `go.mod` in current working directory or parent directories to detect the path. +func GetImportPath(dirPath string) string { + // If `filePath` does not exist, create it firstly to find the import path. + var realPath = gfile.RealPath(dirPath) + if realPath == "" { + _ = gfile.Mkdir(dirPath) + realPath = gfile.RealPath(dirPath) + } + + var ( + newDir = gfile.Dir(realPath) + oldDir string + suffix = gfile.Basename(dirPath) + goModName = "go.mod" + goModPath string + importPath string + ) + for { + goModPath = gfile.Join(newDir, goModName) + if gfile.Exists(goModPath) { + match, _ := gregex.MatchString(`^module\s+(.+)\s*`, gfile.GetContents(goModPath)) + importPath = gstr.Trim(match[1]) + "/" + suffix + importPath = gstr.Replace(importPath, `\`, `/`) + importPath = gstr.TrimRight(importPath, `/`) + return importPath + } + oldDir = newDir + newDir = gfile.Dir(oldDir) + if newDir == oldDir { + return "" + } + suffix = gfile.Basename(oldDir) + "/" + suffix + } +} + +// GetModPath retrieves and returns the file path of go.mod for current project. +func GetModPath() string { + var ( + oldDir = gfile.Pwd() + newDir = oldDir + goModName = "go.mod" + goModPath string + ) + for { + goModPath = gfile.Join(newDir, goModName) + if gfile.Exists(goModPath) { + return goModPath + } + newDir = gfile.Dir(oldDir) + if newDir == oldDir { + break + } + oldDir = newDir + } + return "" +} diff --git a/server/internal/library/hggen/internal/utility/utils/utils_http_download.go b/server/internal/library/hggen/internal/utility/utils/utils_http_download.go new file mode 100644 index 0000000..ecedc00 --- /dev/null +++ b/server/internal/library/hggen/internal/utility/utils/utils_http_download.go @@ -0,0 +1,55 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package utils + +import ( + "io" + "net/http" + "os" + "time" + + "github.com/schollz/progressbar/v3" + + "github.com/gogf/gf/v2/errors/gerror" + + "hotgo/internal/library/hggen/internal/utility/mlog" +) + +// HTTPDownloadFileWithPercent downloads target url file to local path with percent process printing. +func HTTPDownloadFileWithPercent(url string, localSaveFilePath string) error { + start := time.Now() + out, err := os.Create(localSaveFilePath) + if err != nil { + return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath) + } + defer out.Close() + + headResp, err := http.Head(url) + if err != nil { + return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath) + } + defer headResp.Body.Close() + + resp, err := http.Get(url) + if err != nil { + return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath) + } + defer resp.Body.Close() + + bar := progressbar.NewOptions(int(resp.ContentLength), progressbar.OptionShowBytes(true), progressbar.OptionShowCount()) + writer := io.MultiWriter(out, bar) + _, err = io.Copy(writer, resp.Body) + + elapsed := time.Since(start) + if elapsed > time.Minute { + mlog.Printf(`download completed in %.0fm`, float64(elapsed)/float64(time.Minute)) + } else { + mlog.Printf(`download completed in %.0fs`, elapsed.Seconds()) + } + + return nil +} diff --git a/server/internal/library/hggen/views/column.go b/server/internal/library/hggen/views/column.go new file mode 100644 index 0000000..761f848 --- /dev/null +++ b/server/internal/library/hggen/views/column.go @@ -0,0 +1,305 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "context" + "fmt" + "strings" + + "hotgo/internal/consts" + "hotgo/internal/library/hggen/internal/cmd/gendao" + "hotgo/internal/model/input/sysin" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gregex" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" +) + +// DoTableColumns 获取指定表生成字段列表 +func DoTableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp, config gendao.CGenDaoInput) (fields []*sysin.GenCodesColumnListModel, err error) { + var ( + sql string + conf = g.DB(in.Name).GetConfig() + ) + + // 根据数据库类型使用不同的SQL + if conf.Type == consts.DBPgsql { + // PostgreSQL: 使用pg_catalog查询列详细信息 + sql = ` + SELECT + a.attnum as id, + a.attname as name, + COALESCE(col_description(a.attrelid, a.attnum), '') as dc, + t.typname as dataType, + CASE + WHEN a.atttypmod > 0 THEN t.typname || '(' || (a.atttypmod - 4) || ')' + ELSE t.typname + END as sqlType, + CASE + WHEN a.atttypmod > 0 THEN a.atttypmod - 4 + ELSE NULL + END as length, + CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END as isAllowNull, + pg_get_expr(ad.adbin, ad.adrelid) as defaultValue, + CASE + WHEN pk.contype = 'p' THEN 'PRI' + WHEN uk.contype = 'u' THEN 'UNI' + ELSE '' + END as "index", + CASE + WHEN ad.adbin IS NOT NULL AND ad.adbin LIKE '%nextval%' THEN 'auto_increment' + ELSE '' + END as extra + FROM pg_attribute a + JOIN pg_class c ON a.attrelid = c.oid + JOIN pg_namespace n ON c.relnamespace = n.oid + JOIN pg_type t ON a.atttypid = t.oid + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + LEFT JOIN pg_constraint pk ON pk.conrelid = c.oid AND pk.contype = 'p' AND a.attnum = ANY(pk.conkey) + LEFT JOIN pg_constraint uk ON uk.conrelid = c.oid AND uk.contype = 'u' AND a.attnum = ANY(uk.conkey) + WHERE n.nspname = 'public' + AND c.relname = '` + in.Table + `' + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a.attnum` + } else { + // MySQL: 使用information_schema.COLUMNS + sql = fmt.Sprintf("SELECT ORDINAL_POSITION as id, COLUMN_NAME as name, COLUMN_COMMENT as dc, DATA_TYPE as dataType, COLUMN_TYPE as sqlType, CHARACTER_MAXIMUM_LENGTH as length, IS_NULLABLE as isAllowNull, COLUMN_DEFAULT as defaultValue, COLUMN_KEY as `index`, EXTRA as extra FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' ORDER BY id ASC", conf.Name, in.Table) + } + + err = g.DB(in.Name).Ctx(ctx).Raw(sql).Scan(&fields) + if err != nil { + return nil, err + } + + if len(fields) == 0 { + return + } + + for _, field := range fields { + if in.IsLink == 1 { + CustomLinkAttributes(ctx, in.Alias, field, config) + } else { + CustomAttributes(ctx, field, config) + } + } + return +} + +// CustomLinkAttributes 可自定义关联表的字段属性 +func CustomLinkAttributes(ctx context.Context, alias string, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) { + field.GoName, field.GoType, field.TsName, field.TsType = GenGotype(ctx, field, in) + + field.GoName = gstr.UcFirst(alias + field.GoName) + field.TsName = gstr.LcFirst(field.GoName) + + setDefaultQueryWhere(field) + setDefaultValue(field) +} + +// CustomAttributes 可自定义的字段属性 +func CustomAttributes(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) { + field.GoName, field.GoType, field.TsName, field.TsType = GenGotype(ctx, field, in) + setDefault(field) +} + +// GenGotype 生成字段的go类型 +func GenGotype(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) (goName, typeName, tsName string, tsType string) { + var err error + tsName = getJsonTagFromCase(field.Name, in.JsonCase) + goName = gstr.CaseCamel(field.Name) + + typeName, err = CheckLocalTypeForField(ctx, field.DataType, nil) + if err != nil { + panic(err) + } + + switch gdb.LocalType(typeName) { + case gdb.LocalTypeDate, gdb.LocalTypeDatetime: + if in.StdTime { + typeName = "time.Time" + } else { + typeName = "*gtime.Time" + } + + case gdb.LocalTypeInt64Bytes: + typeName = "int64" + + case gdb.LocalTypeUint64Bytes: + typeName = "uint64" + + // Special type handle. + case gdb.LocalTypeJson, gdb.LocalTypeJsonb: + if in.GJsonSupport { + typeName = "*gjson.Json" + } else { + typeName = "string" + } + } + + tsType = ShiftMap[typeName] + return +} + +// CheckLocalTypeForField checks and returns corresponding type for given db type. +func CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (string, error) { + var ( + typeName string + typePattern string + ) + match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType) + if len(match) == 3 { + typeName = gstr.Trim(match[1]) + typePattern = gstr.Trim(match[2]) + } else { + typeName = gstr.Split(fieldType, " ")[0] + } + typeName = strings.ToLower(typeName) + switch typeName { + case + "binary", + "varbinary", + "blob", + "tinyblob", + "mediumblob", + "longblob": + return string(gdb.LocalTypeBytes), nil + + case + "int", + "tinyint", + "small_int", + "smallint", + "medium_int", + "mediumint", + "serial": + if gstr.ContainsI(fieldType, "unsigned") { + return string(gdb.LocalTypeUint), nil + } + return string(gdb.LocalTypeInt), nil + + case + "big_int", + "bigint", + "bigserial": + if gstr.ContainsI(fieldType, "unsigned") { + return string(gdb.LocalTypeUint64), nil + } + return string(gdb.LocalTypeInt64), nil + + case + "real": + return string(gdb.LocalTypeFloat32), nil + + case + "float", + "double", + "decimal", + "money", + "numeric", + "smallmoney": + return string(gdb.LocalTypeFloat64), nil + + case + "bit": + // It is suggested using bit(1) as boolean. + if typePattern == "1" { + return string(gdb.LocalTypeBool), nil + } + s := gconv.String(fieldValue) + // mssql is true|false string. + if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") { + return string(gdb.LocalTypeBool), nil + } + if gstr.ContainsI(fieldType, "unsigned") { + return string(gdb.LocalTypeUint64Bytes), nil + } + return string(gdb.LocalTypeInt64Bytes), nil + + case + "bool": + return string(gdb.LocalTypeBool), nil + + case + "date": + return string(gdb.LocalTypeDate), nil + + case + "datetime", + "timestamp", + "timestamptz": + return string(gdb.LocalTypeDatetime), nil + + case + "json": + return string(gdb.LocalTypeJson), nil + + case + "jsonb": + return string(gdb.LocalTypeJsonb), nil + + default: + // Auto-detect field type, using key match. + switch { + case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"): + return string(gdb.LocalTypeString), nil + + case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"): + return string(gdb.LocalTypeFloat64), nil + + case strings.Contains(typeName, "bool"): + return string(gdb.LocalTypeBool), nil + + case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"): + return string(gdb.LocalTypeBytes), nil + + case strings.Contains(typeName, "int"): + if gstr.ContainsI(fieldType, "unsigned") { + return string(gdb.LocalTypeUint), nil + } + return string(gdb.LocalTypeInt), nil + + case strings.Contains(typeName, "time"): + return string(gdb.LocalTypeDatetime), nil + + case strings.Contains(typeName, "date"): + return string(gdb.LocalTypeDatetime), nil + + default: + return string(gdb.LocalTypeString), nil + } + } +} + +// getJsonTagFromCase call gstr.Case* function to convert the s to specified case. +func getJsonTagFromCase(str, caseStr string) string { + switch gstr.ToLower(caseStr) { + case gstr.ToLower("Camel"): + return gstr.CaseCamel(str) + + case gstr.ToLower("CamelLower"): + return gstr.CaseCamelLower(str) + + case gstr.ToLower("Kebab"): + return gstr.CaseKebab(str) + + case gstr.ToLower("KebabScreaming"): + return gstr.CaseKebabScreaming(str) + + case gstr.ToLower("Snake"): + return gstr.CaseSnake(str) + + case gstr.ToLower("SnakeFirstUpper"): + return gstr.CaseSnakeFirstUpper(str) + + case gstr.ToLower("SnakeScreaming"): + return gstr.CaseSnakeScreaming(str) + } + return str +} diff --git a/server/internal/library/hggen/views/column_default.go b/server/internal/library/hggen/views/column_default.go new file mode 100644 index 0000000..ca44e60 --- /dev/null +++ b/server/internal/library/hggen/views/column_default.go @@ -0,0 +1,326 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "github.com/gogf/gf/v2/text/gstr" + "hotgo/internal/consts" + "hotgo/internal/model/input/sysin" +) + +// 默认表单组件映射 Ts -> 表单组件 +var defaultFormModeMap = map[string]string{ + TsTypeString: FormModeInput, + TsTypeNumber: FormModeInputNumber, + TsTypeBoolean: FormModeInputNumber, + TsTypeArray: FormModeInputDynamic, + TsTypeTuple: FormModeInputDynamic, + TsTypeAny: FormModeInput, +} + +var defaultEditFields = map[string]bool{ + "id": false, + "created_at": false, + "updated_at": false, + "deleted_at": false, +} + +var defaultEditSwitch = map[string]bool{ + "id": false, + "level": false, + "tree": false, + "created_by": false, + "updated_by": false, + "deleted_by": false, + "created_at": false, + "updated_at": false, + "deleted_at": false, +} + +var defaultListSwitch = map[string]bool{ + "level": false, + "tree": false, + "deleted_at": false, +} + +var defaultExportSwitch = map[string]bool{ + "level": false, + "tree": false, + "deleted_at": false, +} + +var defaultQuerySwitch = map[string]bool{ + "level": false, + "tree": false, + "deleted_at": false, +} + +var defaultSort = map[string]bool{ + "id": true, + "sort": true, +} + +var defaultTreeFields = []string{"pid", "level", "tree"} + +// 默认表单验证映射 物理类型命名识别 +var defaultFormRoleMap = map[string]string{ + "mobile": FormRolePhone, + "qq": FormRoleQq, + "email": FormRoleEmail, + "id_card": FormRoleIdCard, + "bank_card": FormRoleBankCard, + "password": FormRolePassword, + "pass": FormRolePassword, + "price": FormRoleAmount, +} + +// 默认查询条件映射 go类型识别 +var defaultWhereModeMap = map[string]string{ + GoTypeString: WhereModeLike, + GoTypeDate: WhereModeEq, + GoTypeDatetime: WhereModeEq, + GoTypeInt: WhereModeEq, + GoTypeUint: WhereModeEq, + GoTypeInt64: WhereModeEq, + GoTypeUint64: WhereModeEq, + GoTypeIntSlice: WhereModeIn, + GoTypeInt64Slice: WhereModeIn, + GoTypeUint64Slice: WhereModeIn, + GoTypeFloat32: WhereModeEq, + GoTypeFloat64: WhereModeEq, + GoTypeBytes: WhereModeEq, + GoTypeTime: WhereModeEq, + GoTypeGTime: WhereModeEq, + GoTypeJson: WhereModeJsonContains, +} + +// setDefault 设置默认属性 +func setDefault(field *sysin.GenCodesColumnListModel) { + setDefaultEdit(field) + + setDefaultFormMode(field) + + setDefaultFormRole(field) + + setDefaultDictType(field) + + setDefaultList(field) + + setDefaultExport(field) + + setDefaultQuery(field) + + setDefaultQueryWhere(field) + + setDefaultValue(field) + + if field.IsAllowNull == "NO" { + field.Required = true + } + + if IsIndexUNI(field.Index) { + field.Unique = true + } + + if df, ok := defaultSort[field.Name]; ok { + field.IsSort = df + } + + if field.Dc == "" { + field.Dc = field.Name + } +} + +// setDefaultEdit 设置默认编辑 +func setDefaultEdit(field *sysin.GenCodesColumnListModel) { + field.IsEdit = true + + if IsIndexPK(field.Index) { + field.IsEdit = false + return + } + + if df, ok := defaultEditFields[field.Name]; ok { + field.IsEdit = df + } + + if df, ok := defaultEditSwitch[field.Name]; ok { + field.IsEdit = df + } +} + +// setDefaultFormMode 设置默认表单组件 +func setDefaultFormMode(field *sysin.GenCodesColumnListModel) { + field.FormMode = FormModeInput + if df, ok := defaultFormModeMap[field.TsType]; ok { + field.FormMode = df + } + + if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) { + field.FormMode = FormModeSelect + return + } + + if field.GoName == "CreatedAt" { + field.FormMode = FormModeTimeRange + return + } + + if (field.GoName == "ProvinceId" || field.GoName == "CityId") && IsNumberType(field.GoType) { + field.FormMode = FormModeCitySelector + return + } + + if field.DataType == "datetime" || field.DataType == "timestamp" || field.DataType == "timestamptz" { + field.FormMode = FormModeTime + return + } + + if field.DataType == "date" { + field.FormMode = FormModeDate + return + } + + if field.GoType == GoTypeString && field.Length >= 256 && field.Length <= 512 { + field.FormMode = FormModeInputTextarea + return + } + + if field.GoType == GoTypeString && field.Length > 512 { + field.FormMode = FormModeInputEditor + return + } +} + +// setDefaultFormRole 设置默认表单验证 +func setDefaultFormRole(field *sysin.GenCodesColumnListModel) { + field.FormRole = FormRoleNone + + switch field.GoType { + case GoTypeUint, GoTypeUint64: + field.FormRole = FormRoleNum + return + } + + if df, ok := defaultFormRoleMap[field.Name]; ok { + field.FormRole = df + } +} + +// setDefaultDictType 设置默认字典类型 +func setDefaultDictType(field *sysin.GenCodesColumnListModel) { + if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) { + field.DictType = 3 // 默认系统状态ID + return + } +} + +// setDefaultList 设置默认列表 +func setDefaultList(field *sysin.GenCodesColumnListModel) { + field.IsList = true + switch field.GoType { + case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeBytes, GoTypeJson: + field.IsList = false + return + } + + if field.Length >= 500 { + field.IsList = false + return + } + + if df, ok := defaultListSwitch[field.Name]; ok { + field.IsList = df + } +} + +// setDefaultExport 设置默认导出 +func setDefaultExport(field *sysin.GenCodesColumnListModel) { + field.IsExport = true + switch field.GoType { + case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeBytes, GoTypeJson: + field.IsExport = false + return + } + + if field.Length >= 500 { + field.IsExport = false + return + } + + if df, ok := defaultExportSwitch[field.Name]; ok { + field.IsExport = df + } +} + +// setDefaultQuery 设置默认查询 +func setDefaultQuery(field *sysin.GenCodesColumnListModel) { + field.IsQuery = false + + if IsIndexPK(field.Index) { + field.IsQuery = true + return + } + + if gstr.HasSuffix(field.GoName, "Status") && IsNumberType(field.GoType) { + field.IsQuery = true + return + } + + if field.GoName == "CreatedAt" { + field.IsQuery = true + return + } + + if df, ok := defaultQuerySwitch[field.Name]; ok { + field.IsQuery = df + } +} + +// setDefaultQueryWhere 设置默认查询条件 +func setDefaultQueryWhere(field *sysin.GenCodesColumnListModel) { + field.QueryWhere = WhereModeEq + + if field.GoName == "CreatedAt" { + field.QueryWhere = WhereModeBetween + return + } + + if field.Length >= 500 { + field.QueryWhere = WhereModeLikeAll + return + } + + if df, ok := defaultWhereModeMap[field.GoType]; ok { + field.QueryWhere = df + } +} + +// setDefaultValue 设置默认value +func setDefaultValue(field *sysin.GenCodesColumnListModel) { + var value interface{} + if field.DefaultValue == nil { + switch field.GoType { + case GoTypeString, GoTypeBytes, GoTypeDate, GoTypeDatetime, GoTypeTime, GoTypeGTime: + value = "" + case GoTypeIntSlice, GoTypeInt64Slice, GoTypeUint64Slice, GoTypeJson: + value = nil + case GoTypeInt, GoTypeUint, GoTypeInt64, GoTypeUint64: + value = 0 + case GoTypeBool: + value = false + } + } else { + value = consts.ConvType(field.DefaultValue, field.GoType) + } + + // 时间类型不做默认值处理 + if field.GoType == GoTypeGTime { + value = "" + } + + field.DefaultValue = value +} diff --git a/server/internal/library/hggen/views/column_map.go b/server/internal/library/hggen/views/column_map.go new file mode 100644 index 0000000..1cbdfda --- /dev/null +++ b/server/internal/library/hggen/views/column_map.go @@ -0,0 +1,343 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "github.com/gogf/gf/v2/text/gstr" + "hotgo/internal/model/input/sysin" + "hotgo/utility/validate" +) + +// 字段映射关系 + +// go类型 +const ( + GoTypeString = "string" + GoTypeDate = "date" + GoTypeDatetime = "datetime" + GoTypeInt = "int" + GoTypeUint = "uint" + GoTypeInt64 = "int64" + GoTypeUint64 = "uint64" + GoTypeIntSlice = "[]int" + GoTypeInt64Slice = "[]int64" + GoTypeUint64Slice = "[]uint64" + GoTypeFloat32 = "float32" + GoTypeFloat64 = "float64" + GoTypeBytes = "[]byte" + GoTypeBool = "bool" + GoTypeTime = "time.Time" + GoTypeGTime = "*gtime.Time" + GoTypeJson = "*gjson.Json" +) + +var GoTypeNameMap = map[string]string{ + GoTypeString: GoTypeString, + GoTypeDate: GoTypeDate, + GoTypeDatetime: GoTypeDatetime, + GoTypeInt: GoTypeInt, + GoTypeUint: GoTypeUint, + GoTypeInt64: GoTypeInt64, + GoTypeUint64: GoTypeUint64, + GoTypeIntSlice: GoTypeIntSlice, + GoTypeInt64Slice: GoTypeInt64Slice, + GoTypeUint64Slice: GoTypeUint64Slice, + GoTypeFloat32: GoTypeFloat32, + GoTypeFloat64: GoTypeFloat64, + GoTypeBytes: GoTypeBytes, + GoTypeBool: GoTypeBool, + GoTypeTime: GoTypeTime, + GoTypeGTime: GoTypeGTime, + GoTypeJson: GoTypeJson, +} + +// ts类型 +const ( + TsTypeString = "string" + TsTypeNumber = "number" + TsTypeBoolean = "boolean" + TsTypeArray = "array" + TsTypeTuple = "tuple" + TsTypeAny = "any" +) + +var TsTypeNameMap = map[string]string{ + TsTypeString: TsTypeString, + TsTypeNumber: TsTypeNumber, + TsTypeBoolean: TsTypeBoolean, + TsTypeArray: TsTypeArray, + TsTypeTuple: TsTypeTuple, + TsTypeAny: TsTypeAny, +} + +// ShiftMap Go -> Ts 类型转换 +var ShiftMap = map[string]string{ + GoTypeString: TsTypeString, + GoTypeDate: TsTypeString, + GoTypeDatetime: TsTypeString, + GoTypeInt: TsTypeNumber, + GoTypeUint: TsTypeNumber, + GoTypeInt64: TsTypeNumber, + GoTypeUint64: TsTypeNumber, + GoTypeIntSlice: TsTypeArray, + GoTypeInt64Slice: TsTypeArray, + GoTypeUint64Slice: TsTypeArray, + GoTypeFloat32: TsTypeNumber, + GoTypeFloat64: TsTypeNumber, + GoTypeBytes: TsTypeString, + GoTypeBool: TsTypeBoolean, + GoTypeTime: TsTypeString, + GoTypeGTime: TsTypeString, + GoTypeJson: TsTypeAny, +} + +// 表单组件 +const ( + FormModeInput = "Input" // 文本输入 + FormModeInputNumber = "InputNumber" // 数字输入 + FormModeInputTextarea = "InputTextarea" // 文本域 + FormModeInputEditor = "InputEditor" // 富文本 + FormModeInputYaml = "InputYaml" // YAML配置 + FormModeInputDynamic = "InputDynamic" // 动态键值对 + FormModeDate = "Date" // 日期选择(Y-M-D) + FormModeDateRange = "DateRange" // 日期范围选择 + FormModeTime = "Time" // 时间选择(Y-M-D H:i:s) + FormModeTimeRange = "TimeRange" // 时间范围选择 + FormModeRadio = "Radio" // 单选按钮 + FormModeCheckbox = "Checkbox" // 复选按钮 + FormModeSelect = "Select" // 单选下拉框 + FormModeSelectMultiple = "SelectMultiple" // 多选下拉框 + FormModeTreeSelect = "TreeSelect" // 树型选择 + FormModeCascader = "Cascader" // 级联选择 + FormModeUploadImage = "UploadImage" // 单图上传 + FormModeUploadImages = "UploadImages" // 多图上传 + FormModeUploadFile = "UploadFile" // 单文件上传 + FormModeUploadFiles = "UploadFiles" // 多文件上传 + FormModeSwitch = "Switch" // 开关 + FormModeRate = "Rate" // 评分 + FormModeCitySelector = "CitySelector" // 省市区选择 + FormModePidTreeSelect = "PidTreeSelect" // 树型上级选择,树表生成专用 +) + +var FormModes = []string{ + FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputYaml, FormModeInputDynamic, + FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange, + FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeTreeSelect, FormModeCascader, + FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles, + FormModeSwitch, + FormModeRate, + FormModeCitySelector, +} + +var FormModeMap = map[string]string{ + FormModeInput: "文本输入", + FormModeInputNumber: "数字输入", + FormModeInputTextarea: "文本域", + FormModeInputEditor: "富文本", + FormModeInputYaml: "YAML配置", + FormModeInputDynamic: "动态键值对", + FormModeDate: "日期选择(Y-M-D)", + FormModeDateRange: "日期范围选择", + FormModeTime: "时间选择(Y-M-D H:i:s)", + FormModeTimeRange: "时间范围选择", + FormModeRadio: "单选按钮", + FormModeCheckbox: "复选按钮", + FormModeSelect: "单选下拉框", + FormModeSelectMultiple: "多选下拉框", + FormModeTreeSelect: "树型选择", + FormModeCascader: "级联选择", + FormModeUploadImage: "单图上传", + FormModeUploadImages: "多图上传", + FormModeUploadFile: "单文件上传", + FormModeUploadFiles: "多文件上传", + FormModeSwitch: "开关", + FormModeRate: "评分", + FormModeCitySelector: "省市区选择", +} + +// 表单验证 +const ( + FormRoleNone = "none" + FormRoleIp = "ip" + FormRolePercentage = "percentage" + FormRoleTel = "tel" + FormRolePhone = "phone" + FormRoleQq = "qq" + FormRoleEmail = "email" + FormRoleIdCard = "idCard" + FormRoleNum = "num" + FormRoleBankCard = "bankCard" + FormRoleWeibo = "weibo" + FormRoleUserName = "userName" + FormRoleAccount = "account" + FormRolePassword = "password" + FormRoleAmount = "amount" + FormRoleYaml = "yaml" +) + +var FormRoleMap = map[string]string{ + FormRoleNone: "不验证", + FormRoleIp: "Ipv4或Ipv6", + FormRolePercentage: "0-100百分比", + FormRoleTel: "固话格式", + FormRolePhone: "手机号", + FormRoleQq: "QQ号码", + FormRoleEmail: "邮箱", + FormRoleIdCard: "身份证", + FormRoleNum: "非零正整数", + FormRoleBankCard: "银行卡", + FormRoleWeibo: "微博号", + FormRoleUserName: "用户名", + FormRoleAccount: "账号", + FormRolePassword: "密码", + FormRoleAmount: "金额", + FormRoleYaml: "YAML格式", +} + +// 查询条件 +const ( + WhereModeEq = "=" // = + WhereModeNeq = "!=" // != + WhereModeGt = ">" // > + WhereModeGte = ">=" // >= + WhereModeLt = "<" // < + WhereModeLte = "<=" // <= + WhereModeIn = "IN" // IN (...) + WhereModeNotIn = "NOT IN" // NOT IN (...) + WhereModeBetween = "BETWEEN" // BETWEEN + WhereModeNotBetween = "NOT BETWEEN" // NOT BETWEEN + WhereModeLike = "LIKE" // LIKE + WhereModeLikeAll = "LIKE %...%" // LIKE %...% + WhereModeNotLike = "NOT LIKE" // NOT LIKE + WhereModeJsonContains = "JSON_CONTAINS(doc, val)" // JSON_CONTAINS(json_doc, val[, path]) // 判断是否包含某个json值 +) + +var WhereModes = []string{WhereModeEq, + WhereModeNeq, WhereModeGt, WhereModeGte, WhereModeLt, WhereModeLte, + WhereModeIn, WhereModeNotIn, + WhereModeBetween, WhereModeNotBetween, + WhereModeLike, WhereModeLikeAll, WhereModeNotLike, + WhereModeJsonContains, +} + +// 表格列的排序方式 +const ( + TableAlignLeft = "left" + TableAlignRight = "right" + TableAlignCenter = "center" +) + +var TableAligns = []string{TableAlignLeft, TableAlignRight, TableAlignCenter} + +var TableAlignMap = map[string]string{ + TableAlignLeft: "居左", + TableAlignRight: "居右", + TableAlignCenter: "居中", +} + +// IsNumberType 是否是数字类型 +func IsNumberType(goType string) bool { + switch goType { + case GoTypeInt, GoTypeUint, GoTypeInt64, GoTypeUint64: + return true + case GoTypeFloat32, GoTypeFloat64: + return true + } + return false +} + +// IsSelectFormMode 是否是选择器组件 +func IsSelectFormMode(formMode string) bool { + switch formMode { + case FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeCitySelector, FormModeTreeSelect, FormModeCascader: + return true + } + return false +} + +func HasColumn(fields []*sysin.GenCodesColumnListModel, column string) bool { + for _, field := range fields { + if field.Name == column { + return true + } + } + return false +} + +func HasColumnWithFormMode(fields []*sysin.GenCodesColumnListModel, formMode string) bool { + for _, field := range fields { + if field.FormMode == formMode { + return true + } + } + return false +} + +func HasMaxSort(fields []*sysin.GenCodesColumnListModel) bool { + return HasColumn(fields, "sort") +} + +func HasStatus(headOps []string, fields []*sysin.GenCodesColumnListModel) bool { + if !gstr.InArray(headOps, "status") { + return false + } + return HasColumn(fields, "status") +} + +func HasSwitch(fields []*sysin.GenCodesColumnListModel) bool { + return HasColumnWithFormMode(fields, FormModeSwitch) +} + +func HasHookMemberSummary(fields []*sysin.GenCodesColumnListModel) bool { + for _, field := range fields { + if IsMemberSummaryField(field.Name) { + if field.IsList { + return true + } + } + } + return false +} + +func HasQueryMemberSummary(fields []*sysin.GenCodesColumnListModel) bool { + for _, field := range fields { + if IsMemberSummaryField(field.Name) { + if field.IsQuery { + return true + } + } + } + return false +} + +func IsMemberSummaryField(name string) bool { + switch name { + case "created_by", "updated_by", "deleted_by": + return true + } + return false +} + +// ReviseFields 校正字段值,兼容版本升级前的老数据格式 +func ReviseFields(fields []*sysin.GenCodesColumnListModel) []*sysin.GenCodesColumnListModel { + for _, field := range fields { + if !validate.InSlice(TableAligns, field.Align) { + field.Align = TableAlignLeft + } + + if field.Width < 1 { + field.Width = -1 + } + if field.Width > 2000 { + field.Width = 2000 + } + + if field.FormGridSpan < 1 { + field.FormGridSpan = 1 + } + } + return fields +} diff --git a/server/internal/library/hggen/views/curd.go b/server/internal/library/hggen/views/curd.go new file mode 100644 index 0000000..c0e31b8 --- /dev/null +++ b/server/internal/library/hggen/views/curd.go @@ -0,0 +1,1009 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "context" + "hotgo/internal/consts" + "hotgo/internal/dao" + "hotgo/internal/library/hggen/internal/cmd/gendao" + "hotgo/internal/library/hgorm" + "hotgo/internal/model" + "hotgo/internal/model/input/sysin" + "hotgo/internal/service" + "hotgo/utility/convert" + "hotgo/utility/file" + "hotgo/utility/tree" + "runtime" + "strings" + + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" +) + +var Curd = gCurd{} + +type gCurd struct{} + +type CurdStep struct { + HasMaxSort bool // 最大排序 + HasAdd bool // 表单添加 + HasBatchDel bool // 批量删除 + HasExport bool // 表格导出 + HasNotFilterAuth bool // 不过滤认证权限 + HasEdit bool // 表单编辑 + HasDel bool // 删除 + HasView bool // 查看详情 + HasStatus bool // 修改状态 + HasSwitch bool // 数值开关 + HasCheck bool // 勾选列 + HasMenu bool // 菜单权限 + IsTreeTable bool // 树型列表 + IsOptionTreeTable bool // 选项式树型列表 + HasRules bool // 表单验证规则 + HasRulesValidator bool // 表单验证器 + HasSearchForm bool // 列表搜索 + HasDict bool // 字典 + HasFuncDict bool // 注册方法字典 + HasQueryMemberSummary bool // 查询用户摘要 + HasHookMemberSummary bool // hook用户摘要 + ImportModel ImportModel // 公用导包 - model.ts + ActionColumnWidth int64 // 列表操作栏宽度 + IsAddon bool // 是否是插件 +} + +// ImportModel 导包 - model.ts +type ImportModel struct { + NaiveUI []string + UtilsIs []string + UtilsUrl []string + UtilsDate []string + UtilsValidate []string + UtilsHotGo []string + UtilsIndex []string +} + +type CurdOptionsJoin struct { + Uuid string `json:"uuid"` + LinkTable string `json:"linkTable"` + Alias string `json:"alias"` + LinkMode int `json:"linkMode"` + Field string `json:"field"` + MasterField string `json:"masterField"` + DaoName string `json:"daoName"` + Columns []*sysin.GenCodesColumnListModel `json:"columns"` +} + +type CurdOptionsMenu struct { + Icon string `json:"icon"` + Pid int `json:"pid"` + Sort int `json:"sort"` +} + +type OptionsTree struct { + TitleColumn string `json:"titleColumn"` + StyleType int `json:"styleType"` + TitleField *sysin.GenCodesColumnListModel +} + +// PresetStep 预设生成流程参数 +type PresetStep struct { + FormGridCols int `json:"formGridCols" dc:"表单显示的栅格数量"` +} + +type CurdOptions struct { + AutoOps []string `json:"autoOps"` + ColumnOps []string `json:"columnOps"` + HeadOps []string `json:"headOps"` + Join []*CurdOptionsJoin `json:"join"` + Menu *CurdOptionsMenu `json:"menu"` + Tree *OptionsTree `json:"tree"` + TemplateGroup string `json:"templateGroup"` + ApiPrefix string `json:"apiPrefix"` + ImportWebApi string `json:"importWebApi"` + FuncDict *FuncDict `json:"funcDict"` + PresetStep *PresetStep `json:"presetStep"` + Step *CurdStep // 转换后的流程控制条件 + DictOps CurdOptionsDict // 字典选项 + dictMap g.Map // 字典选项 -> 字段映射关系 +} + +type FuncDict struct { + ValueColumn string // 选项值 + LabelColumn string // 选项名称 + Value *sysin.GenCodesColumnListModel + Label *sysin.GenCodesColumnListModel +} + +type CurdOptionsDict struct { + Has bool + Types []string + Schemas []*OptionsSchemasField +} + +type OptionsSchemasField struct { + Field string + Type string +} + +type CurdPreviewInput struct { + In *sysin.GenCodesPreviewInp // 提交参数 + DaoConfig gendao.CGenDaoInput // 生成dao配置 + Config *model.GenerateConfig // 生成配置 + view *gview.View // 视图模板 + content *sysin.GenCodesPreviewModel // 页面代码 + masterFields []*sysin.GenCodesColumnListModel // 主表字段属性 + pk *sysin.GenCodesColumnListModel // 主键属性 + options *CurdOptions // 生成选项 +} + +type CurdBuildEvent map[string]func(ctx context.Context) (err error) + +type CurdBuildInput struct { + PreviewIn *CurdPreviewInput // 预览参数 + BeforeEvent CurdBuildEvent // 前置事件 + AfterEvent CurdBuildEvent // 后置事件 +} + +func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error) { + in.content = new(sysin.GenCodesPreviewModel) + in.content.Views = make(map[string]*sysin.GenFile) + + // 初始化生成选项 + if err = initOptions(in); err != nil { + return err + } + + // 初始化表字段配置 + if err = initTableField(ctx, in); err != nil { + return err + } + + // 初始化树表 + if err = initTableTree(in); err != nil { + return err + } + + initStep(in) + + // 初始化方法字典 + if err = initFuncDict(in); err != nil { + return err + } + + // 初始化生成模板 + if err = initTemplate(in); err != nil { + return err + } + return +} + +func initOptions(in *CurdPreviewInput) (err error) { + if err = in.In.Options.Scan(&in.options); err != nil { + return + } + in.options.dictMap = make(g.Map) + return +} + +func initTemplate(in *CurdPreviewInput) (err error) { + if len(in.Config.Application.Crud.Templates)-1 < in.In.GenTemplate { + return gerror.New("没有找到生成模板的配置,请检查!") + } + + // api前缀 + apiPrefix := gstr.LcFirst(in.In.VarName) + if in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon { + apiPrefix = in.In.AddonName + "/" + apiPrefix + } + in.options.ApiPrefix = apiPrefix + + if err = checkCurdPath(in.Config.Application.Crud.Templates[in.In.GenTemplate], in.In.AddonName); err != nil { + return + } + in.options.TemplateGroup = in.Config.Application.Crud.Templates[in.In.GenTemplate].MasterPackage + return +} + +func initFuncDict(in *CurdPreviewInput) (err error) { + if !in.options.Step.HasFuncDict || in.options.FuncDict == nil { + return + } + + if len(in.options.FuncDict.LabelColumn) == 0 || len(in.options.FuncDict.ValueColumn) == 0 { + err = gerror.New("生成字典选项必须设置选项值和选项名称") + return err + } + + for _, field := range in.masterFields { + if field.Name == in.options.FuncDict.ValueColumn { + in.options.FuncDict.Value = field + } + + if field.Name == in.options.FuncDict.LabelColumn { + in.options.FuncDict.Label = field + } + } + return +} + +func initTableField(ctx context.Context, in *CurdPreviewInput) (err error) { + // 加载主表配置 + if err = in.In.MasterColumns.Scan(&in.masterFields); err != nil { + return + } + + if len(in.masterFields) == 0 { + if in.masterFields, err = DoTableColumns(ctx, &sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil { + return + } + } + + // 主键属性 + in.pk = getPkField(in) + if in.pk == nil { + return gerror.New("initInput no primary key is set in the table!") + } + + in.masterFields = ReviseFields(in.masterFields) + + // 检查表命名 + var names = []string{in.In.DaoName} + for _, v := range in.options.Join { + v.Columns = ReviseFields(v.Columns) + names = append(names, v.DaoName) + } + if err = CheckIllegalName("数据库表名", names...); err != nil { + return + } + + if err = CheckIllegalName("实体命名", in.In.VarName); err != nil { + return + } + return +} + +func initTableTree(in *CurdPreviewInput) (err error) { + // 检查树表字段 + if in.In.GenType == consts.GenCodesTypeTree { + if err = CheckTreeTableFields(in.masterFields); err != nil { + return err + } + + // 解析选项树名称字段 + has := false + for _, field := range in.masterFields { + if in.options.Tree.TitleColumn == field.Name { + in.options.Tree.TitleField = field + has = true + break + } + } + if !has { + err = gerror.New("请选择一个有效的树名称字段") + return + } + } + return err +} + +func initStep(in *CurdPreviewInput) { + in.options.Step = new(CurdStep) + in.options.Step.HasMaxSort = HasMaxSort(in.masterFields) + in.options.Step.HasAdd = gstr.InArray(in.options.HeadOps, "add") + in.options.Step.HasBatchDel = gstr.InArray(in.options.HeadOps, "batchDel") && gstr.InArray(in.options.ColumnOps, "check") + in.options.Step.HasExport = gstr.InArray(in.options.HeadOps, "export") + in.options.Step.HasNotFilterAuth = gstr.InArray(in.options.ColumnOps, "notFilterAuth") + in.options.Step.HasEdit = gstr.InArray(in.options.ColumnOps, "edit") + in.options.Step.HasDel = gstr.InArray(in.options.ColumnOps, "del") + in.options.Step.HasView = gstr.InArray(in.options.ColumnOps, "view") + in.options.Step.HasStatus = HasStatus(in.options.ColumnOps, in.masterFields) + in.options.Step.HasSwitch = HasSwitch(in.masterFields) + in.options.Step.HasCheck = gstr.InArray(in.options.ColumnOps, "check") + in.options.Step.HasMenu = gstr.InArray(in.options.AutoOps, "genMenuPermissions") + in.options.Step.HasQueryMemberSummary = HasQueryMemberSummary(in.masterFields) + in.options.Step.HasHookMemberSummary = HasHookMemberSummary(in.masterFields) + in.options.Step.IsTreeTable = in.In.GenType == consts.GenCodesTypeTree + if in.options.Step.IsTreeTable { + in.options.Step.IsOptionTreeTable = in.options.Tree.StyleType == consts.GenCodesTreeStyleTypeOption + } + in.options.Step.HasFuncDict = gstr.InArray(in.options.AutoOps, "genFuncDict") + in.options.Step.IsAddon = in.Config.Application.Crud.Templates[in.In.GenTemplate].IsAddon + if in.options.PresetStep.FormGridCols < 1 { + in.options.PresetStep.FormGridCols = 1 + } +} + +// getPkField 获取主键 +func getPkField(in *CurdPreviewInput) *sysin.GenCodesColumnListModel { + if len(in.masterFields) == 0 { + panic("getPkField masterFields uninitialized.") + } + for _, field := range in.masterFields { + if IsIndexPK(field.Index) { + return field + } + } + return nil +} + +func (l *gCurd) loadView(ctx context.Context, in *CurdPreviewInput) (err error) { + temp := in.Config.Application.Crud.Templates[in.In.GenTemplate] + view := gview.New() + err = view.SetConfigWithMap(g.Map{ + "Paths": temp.TemplatePath, + "Delimiters": in.Config.Delimiters, + }) + if err != nil { + return + } + + now := gtime.Now() + view.BindFuncMap(g.Map{ + "NowYear": now.Year, // 当前年 + "ToLower": strings.ToLower, // 全部小写 + "LcFirst": gstr.LcFirst, // 首字母小写 + "UcFirst": gstr.UcFirst, // 首字母大写 + "ToTSArray": ToTSArray, // 转为ts数组格式 + }) + + if err = l.generateWebModelDictOptions(ctx, in); err != nil { + return + } + + modName, err := GetModName(ctx) + if err != nil { + return + } + importApi := gstr.Replace(temp.ApiPath, "./", modName+"/") + "/" + strings.ToLower(in.In.VarName) + importInput := gstr.Replace(temp.InputPath, "./", modName+"/") + importController := gstr.Replace(temp.ControllerPath, "./", modName+"/") + importService := "hotgo/internal/service" + if temp.IsAddon { + importService = "hotgo/addons/" + in.In.AddonName + "/service" + } + + in.options.ImportWebApi = "@/api/" + gstr.LcFirst(in.In.VarName) + if temp.IsAddon { + in.options.ImportWebApi = "@/api/addons/" + in.In.AddonName + "/" + gstr.LcFirst(in.In.VarName) + } + + componentPrefix := gstr.LcFirst(in.In.VarName) + if temp.IsAddon { + componentPrefix = "addons/" + in.In.AddonName + "/" + componentPrefix + } + + nowTime := now.Format("Y-m-d H:i:s") + view.Assigns(gview.Params{ + "templateGroup": in.options.TemplateGroup, // 生成模板分组名称 + "servFunName": l.parseServFunName(in.options.TemplateGroup, in.In.VarName), // 业务服务名称 + "nowTime": nowTime, // 当前时间 + "version": runtime.Version(), // GO 版本 + "hgVersion": consts.VersionApp, // HG 版本 + "varName": in.In.VarName, // 实体名称 + "tableComment": in.In.TableComment, // 对外名称 + "daoName": in.In.DaoName, // ORM模型 + "masterFields": in.masterFields, // 主表字段 + "pk": in.pk, // 主键属性 + "options": in.options, // 提交选项 + "dictOptions": in.options.DictOps, // web字典选项 + "importApi": importApi, // 导入goApi包 + "importInput": importInput, // 导入input包 + "importController": importController, // 导入控制器包 + "importService": importService, // 导入业务服务 + "importWebApi": in.options.ImportWebApi, // 导入webApi + "apiPrefix": in.options.ApiPrefix, // api前缀 + "componentPrefix": componentPrefix, // vue子组件前缀 + "in": in.In, // 在模版中使用`in`参数,如:插件目录名称 + }) + + in.view = view + return +} + +func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) { + st := gtime.Now() + preview, err := l.DoPreview(ctx, in.PreviewIn) + if err != nil { + return + } + + db, err := g.DB().Open(ParseDBConfigNodeLink(&gdb.ConfigNode{Link: in.PreviewIn.DaoConfig.Link})) + if err != nil { + err = gerror.Newf("连接数据库失败,请检查配置文件[server/hack/config.yaml]数据库配置是否正确!err:%v", err.Error()) + return err + } + + defer db.Close() + if err = db.Ping(); err != nil { + err = gerror.Newf("数据库访问异常,请检查配置文件[server/hack/config.yaml]数据库配置是否正确!err:%v", err.Error()) + return + } + + // 前置操作 + if len(in.BeforeEvent) > 0 { + for name, f := range in.BeforeEvent { + if gstr.InArray(in.PreviewIn.options.AutoOps, name) { + if err = f(ctx); err != nil { + return gerror.Newf("in doBuild operation beforeEvent to '%s' failed:%v", name, err) + } + } + } + } + + // 处理sql文件 + handleSqlFile := func(vi *sysin.GenFile) (err error) { + // 无需生成 + if vi.Meth != consts.GenCodesBuildMethCreate && vi.Meth != consts.GenCodesBuildMethCover { + return + } + + if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil { + return gerror.Newf("writing content to '%s' failed: %v", vi.Path, err) + } + + // 导入失败,将sql文件删除 + if err = ImportSql(ctx, vi.Path); err != nil { + _ = gfile.RemoveAll(vi.Path) + } + return + } + + // 将sql文件提取出来优先处理 + // sql执行过程出错是高概率事件,后期在执行前要进行预效验,尽量减少在执行过程中出错的可能性 + sqlGenFile, ok := preview.Views["source.sql"] + if ok { + delete(preview.Views, "source.sql") + if err = handleSqlFile(sqlGenFile); err != nil { + return + } + } + + for _, vi := range preview.Views { + // 无需生成 + if vi.Meth != consts.GenCodesBuildMethCreate && vi.Meth != consts.GenCodesBuildMethCover { + continue + } + + if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil { + return gerror.Newf("writing content to '%s' failed: %v", vi.Path, err) + } + } + + // 后置操作 + if len(in.AfterEvent) > 0 { + for name, f := range in.AfterEvent { + if gstr.InArray(in.PreviewIn.options.AutoOps, name) { + if err = f(ctx); err != nil { + return gerror.Newf("in doBuild operation afterEvent to '%s' failed:%v", name, err) + } + } + } + } + g.Log().Debugf(ctx, "generate code operation completed, %vms", gtime.Now().Sub(st).Milliseconds()) + return +} + +func (l *gCurd) DoPreview(ctx context.Context, in *CurdPreviewInput) (res *sysin.GenCodesPreviewModel, err error) { + // 初始化 + if err = l.initInput(ctx, in); err != nil { + return nil, err + } + + // 加载模板 + if err = l.loadView(ctx, in); err != nil { + return nil, err + } + + if err = l.generateApiContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateInputContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateControllerContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateLogicContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateRouterContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateWebApiContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateWebModelContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateWebIndexContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateWebEditContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateWebViewContent(ctx, in); err != nil { + return nil, err + } + + if err = l.generateSqlContent(ctx, in); err != nil { + return nil, err + } + + in.content.Config = in.Config + res = in.content + return +} + +func (l *gCurd) generateApiContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "api.go" + tplData = g.Map{} + genFile = new(sysin.GenFile) + ) + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].ApiPath, strings.ToLower(in.In.VarName), strings.ToLower(in.In.VarName)+".go") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateInputContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "input.go" + genFile = new(sysin.GenFile) + ) + + tplData, err := l.inputTplData(ctx, in) + if err != nil { + return err + } + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].InputPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateControllerContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "controller.go" + tplData = g.Map{} + genFile = new(sysin.GenFile) + ) + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].ControllerPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateLogicContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "logic.go" + genFile = new(sysin.GenFile) + ) + + tplData, err := l.logicTplData(ctx, in) + if err != nil { + return err + } + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].LogicPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateRouterContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "router.go" + tplData = g.Map{} + genFile = new(sysin.GenFile) + ) + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content, err = FormatGo(ctx, name, genFile.Content) + if err != nil { + return err + } + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].RouterPath, convert.CamelCaseToUnderline(in.In.VarName)+".go") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateWebApiContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "web.api.ts" + tplData = g.Map{} + genFile = new(sysin.GenFile) + ) + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content = FormatTs(genFile.Content) + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebApiPath, gstr.LcFirst(in.In.VarName), "index.ts") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "web.model.ts" + genFile = new(sysin.GenFile) + ) + + tplData, err := l.webModelTplData(ctx, in) + if err != nil { + return + } + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return + } + + genFile.Content = FormatTs(genFile.Content) + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "model.ts") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "web.index.vue" + genFile = new(sysin.GenFile) + ) + + tplData, err := l.webIndexTplData(ctx, in) + if err != nil { + return err + } + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content = FormatVue(genFile.Content) + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "index.vue") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateWebEditContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "web.edit.vue" + genFile = new(sysin.GenFile) + ) + + tplData, err := l.webEditTplData(ctx, in) + if err != nil { + return err + } + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content = FormatVue(genFile.Content) + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "edit.vue") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + if !in.options.Step.HasEdit { + genFile.Meth = consts.GenCodesBuildIgnore + genFile.Required = false + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateWebViewContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "web.view.vue" + genFile = new(sysin.GenFile) + ) + + tplData, err := l.webViewTplData(ctx, in) + if err != nil { + return err + } + + genFile.Content, err = in.view.Parse(ctx, name+".template", tplData) + if err != nil { + return err + } + + genFile.Content = FormatVue(genFile.Content) + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "view.vue") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if genFile.Meth == consts.GenCodesBuildMethSkip && gstr.InArray(in.options.AutoOps, "forcedCover") { + genFile.Meth = consts.GenCodesBuildMethCover + } + + if !in.options.Step.HasView { + genFile.Meth = consts.GenCodesBuildIgnore + genFile.Required = false + } + + in.content.Views[name] = genFile + return +} + +func (l *gCurd) generateSqlContent(ctx context.Context, in *CurdPreviewInput) (err error) { + var ( + name = "source.sql" + config = g.DB("default").GetConfig() + tplData = g.Map{ + "dbName": config.Name, + "menuTable": config.Prefix + "admin_menu", + "mainComponent": "LAYOUT", + } + genFile = new(sysin.GenFile) + templateName = "source.sql.template" + ) + + // 根据数据库类型选择不同的模板 + if config.Type == consts.DBPgsql { + templateName = "source_pgsql.sql.template" + } + + menus, err := service.AdminMenu().GetFastList(ctx) + if err != nil { + return err + } + + tplData["dirPid"], tplData["dirLevel"], tplData["dirTree"], err = hgorm.AutoUpdateTree(ctx, &dao.AdminMenu, 0, int64(in.options.Menu.Pid)) + if err != nil { + return err + } + + tplData["listLevel"] = tplData["dirLevel"].(int) + 1 + tplData["btnLevel"] = tplData["dirLevel"].(int) + 2 + tplData["sortLevel"] = tplData["dirLevel"].(int) + 3 + + pageRedirect := "" + if in.options.Menu.Pid > 0 { + tplData["mainComponent"] = "ParentLayout" + menu, ok := menus[int64(in.options.Menu.Pid)] + if !ok { + err = gerror.New("选择的上级菜单不存在") + return + } + for _, id := range tree.GetIds(menu.Tree) { + if v, ok2 := menus[id]; ok2 { + if !gstr.HasSuffix(pageRedirect, "/") && !gstr.HasPrefix(v.Path, "/") { + pageRedirect += "/" + } + pageRedirect += v.Path + } + } + + if !gstr.HasSuffix(pageRedirect, "/") && !gstr.HasPrefix(menu.Path, "/") { + pageRedirect += "/" + } + pageRedirect += menu.Path + } + pageRedirect += "/" + gstr.LcFirst(in.In.VarName) + "/index" + tplData["pageRedirect"] = pageRedirect + + genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].SqlPath, convert.CamelCaseToUnderline(in.In.VarName)+"_menu.sql") + genFile.Meth = consts.GenCodesBuildMethCreate + if gfile.Exists(genFile.Path) { + genFile.Meth = consts.GenCodesBuildMethSkip + } + genFile.Required = true + + if !in.options.Step.HasMenu { + genFile.Meth = consts.GenCodesBuildIgnore + genFile.Required = false + } + + // 需要生成时,检查菜单命名是否存在 + if genFile.Meth == consts.GenCodesBuildMethCreate { + menuNamePrefix := gstr.LcFirst(in.In.VarName) + menuNames := []string{menuNamePrefix, menuNamePrefix + "Index"} + if in.options.Step.HasEdit { + menuNames = append(menuNames, menuNamePrefix+"Edit") + menuNames = append(menuNames, menuNamePrefix+"View") + } + if in.options.Step.HasView { + menuNames = append(menuNames, menuNamePrefix+"View") + } + if in.options.Step.HasMaxSort { + menuNames = append(menuNames, menuNamePrefix+"MaxSort") + } + if in.options.Step.HasDel { + menuNames = append(menuNames, menuNamePrefix+"Delete") + } + if in.options.Step.HasStatus { + menuNames = append(menuNames, menuNamePrefix+"Status") + } + if in.options.Step.HasSwitch { + menuNames = append(menuNames, menuNamePrefix+"Switch") + } + if in.options.Step.HasExport { + menuNames = append(menuNames, menuNamePrefix+"Export") + } + if in.options.Step.IsTreeTable { + menuNames = append(menuNames, menuNamePrefix+"TreeOption") + } + + menuNames = convert.UniqueSlice(menuNames) + hasMenus, err := service.AdminMenu().Model(ctx).Fields(dao.AdminMenu.Columns().Name).WhereIn(dao.AdminMenu.Columns().Name, menuNames).Array() + if err != nil { + return err + } + + if len(hasMenus) > 0 { + err = gerror.Newf("要生成的菜单中有已存在的路由别名,请检查并删除:%v", strings.Join(gvar.New(hasMenus).Strings(), `、`)) + return err + } + } + + tplData["generatePath"] = genFile.Path + genFile.Content, err = in.view.Parse(ctx, templateName, tplData) + if err != nil { + return err + } + + in.content.Views[name] = genFile + return +} diff --git a/server/internal/library/hggen/views/curd_generate_input.go b/server/internal/library/hggen/views/curd_generate_input.go new file mode 100644 index 0000000..8b4669c --- /dev/null +++ b/server/internal/library/hggen/views/curd_generate_input.go @@ -0,0 +1,354 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "bytes" + "context" + "fmt" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "strings" + + "hotgo/internal/dao" + "hotgo/internal/model/input/sysin" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gstr" + "github.com/olekukonko/tablewriter" +) + +const ( + InputTypeListInp = 1 // 列表输入 + InputTypeListModel = 2 // 列表输出 + InputTypeExportModel = 3 // 列表导出 + InputTypeEditInpValidator = 4 // 添加&编辑验证器 + InputTypeUpdateFields = 5 // 编辑修改过滤字段 + InputTypeInsertFields = 6 // 编辑新增过滤字段 + InputTypeTreeOptionFields = 7 // 关系树查询字段 + EditInpValidatorGenerally = "if err := g.Validator().Rules(\"%s\").Data(in.%s).Messages(\"%s\").Run(ctx); err != nil {\n\t\treturn err.Current()\n\t}\n" + EditInpValidatorYaml = "if err := validate.ValidateYAML(in.%s); err != nil {\n\t\treturn gerror.Newf(\"%s必须为有效的YAML格式: %%s\", err.Error())\n\t}\n" +) + +var ( + // tablewriter Options + twRenderer = tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ + Borders: tw.Border{Top: tw.Off, Bottom: tw.Off, Left: tw.Off, Right: tw.Off}, + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.Off, BetweenColumns: tw.Off}, + }, + Symbols: tw.NewSymbols(tw.StyleASCII), + })) + twConfig = tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{AutoWrap: tw.WrapNone}, + }, + }) +) + +func (l *gCurd) inputTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { + data = make(g.Map) + data["listInpColumns"] = l.generateInputListColumns(ctx, in, InputTypeListInp) + data["listModelColumns"] = l.generateInputListColumns(ctx, in, InputTypeListModel) + data["exportModelColumns"] = l.generateInputListColumns(ctx, in, InputTypeExportModel) + data["editInpValidator"] = l.generateInputListColumns(ctx, in, InputTypeEditInpValidator) + data["updateFieldsColumns"] = l.generateInputListColumns(ctx, in, InputTypeUpdateFields) + data["insertFieldsColumns"] = l.generateInputListColumns(ctx, in, InputTypeInsertFields) + data["viewModelColumns"] = l.generateInputViewColumns(ctx, in) + if in.options.Step.IsTreeTable { + data["treeOptionFields"] = l.generateInputListColumns(ctx, in, InputTypeTreeOptionFields) + } + return +} + +func (l *gCurd) generateInputViewColumns(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + + index := 0 + array := make([][]string, 1000) + // 主表 + for _, field := range in.masterFields { + // 查询用户摘要 + if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + tagKey := "`" + descriptionTag := gstr.Replace(formatComment(field.Dc)+"摘要信息", `"`, `\"`) + result := []string{" #" + field.GoName + "Summa"} + result = append(result, " #*hook.MemberSumma") + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName+"Summa")) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + array[index] = result + index++ + } + } + + table := tablewriter.NewTable(buffer, twRenderer, twConfig) + table.Bulk(array) + table.Render() + stContent := buffer.String() + // Let's do this hack of table writer for indent! + stContent = gstr.Replace(stContent, " #", "") + stContent = gstr.Replace(stContent, "` ", "`") + stContent = gstr.Replace(stContent, "``", "") + stContent = removeEndWrap(stContent) + + buffer.Reset() + buffer.WriteString(stContent) + return "\tentity." + in.In.DaoName + "\n" + buffer.String() +} + +func (l *gCurd) generateInputListColumns(ctx context.Context, in *CurdPreviewInput, inputType int) string { + buffer := bytes.NewBuffer(nil) + index := 0 + array := make([][]string, 1000) + // 主表 + for _, field := range in.masterFields { + row := l.generateStructFieldDefinition(in, field, inputType, true) + if row == nil { + continue + } + array[index] = row + index++ + + switch inputType { + case InputTypeListModel: + // 查询用户摘要 + if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + tagKey := "`" + descriptionTag := gstr.Replace(formatComment(field.Dc)+"摘要信息", `"`, `\"`) + result := []string{" #" + field.GoName + "Summa"} + result = append(result, " #*hook.MemberSumma") + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName+"Summa")) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + array[index] = result + index++ + } + } + } + + // 关联表 + if len(in.options.Join) > 0 { + for _, v := range in.options.Join { + if !isEffectiveJoin(v) { + continue + } + for _, field := range v.Columns { + row := l.generateStructFieldDefinition(in, field, inputType, false) + if row != nil { + array[index] = row + index++ + } + } + } + } + + table := tablewriter.NewTable(buffer, twRenderer, twConfig) + table.Bulk(array) + table.Render() + stContent := buffer.String() + // Let's do this hack of table writer for indent! + stContent = gstr.Replace(stContent, " #", "") + stContent = gstr.Replace(stContent, "` ", "`") + stContent = gstr.Replace(stContent, "``", "") + stContent = removeEndWrap(stContent) + + buffer.Reset() + buffer.WriteString(stContent) + return buffer.String() +} + +// generateStructFieldForModel generates and returns the attribute definition for specified field. +func (l *gCurd) generateStructFieldDefinition(in *CurdPreviewInput, field *sysin.GenCodesColumnListModel, inputType int, isMaster bool) []string { + var ( + tagKey = "`" + result = []string{" #" + field.GoName} + descriptionTag = gstr.Replace(formatComment(field.Dc), `"`, `\"`) + ) + + addResult := func() []string { + result = append(result, " #"+field.GoType) + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + return result + } + + isUpdateOrInsertInputFields := func() bool { + if !isMaster { + return field.IsEdit + } + + if field.IsEdit { + return true + } + + // 树表内部维护字段 + if in.options.Step.IsTreeTable && gstr.InArray(defaultTreeFields, field.Name) { + return true + } + + // 修改人 + if inputType == InputTypeUpdateFields && field.GoName == "UpdatedBy" { + return true + } + + // 创建人 + if inputType == InputTypeInsertFields && field.GoName == "CreatedBy" { + return true + } + return false + } + + isQuery := false + + switch inputType { + case InputTypeListInp: + if in.options.Step.IsTreeTable && IsPidName(field.Name) { + isQuery = true + field.QueryWhere = WhereModeEq + } + if !field.IsQuery && !isQuery { + return nil + } + + if field.QueryWhere == WhereModeBetween { + result = append(result, " #[]"+field.GoType) + } else { + // 查询用户摘要时,固定接收字符串类型 + if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) { + result = append(result, " #string") + } else { + result = append(result, " #"+field.GoType) + } + } + result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, field.TsName)) + result = append(result, " #"+fmt.Sprintf(`dc:"%s"`+tagKey, descriptionTag)) + + case InputTypeListModel: + // 主表的主键 + if IsIndexPK(field.Index) && isMaster { + addResult() + // 树表的pid字段 + } else if in.options.Step.IsTreeTable && IsPidName(field.Name) { + addResult() + } else if field.IsList { + addResult() + } else { + return nil + } + case InputTypeExportModel: + if !field.IsExport { + return nil + } + addResult() + case InputTypeEditInpValidator: + if !field.IsEdit { + return nil + } + if !field.Required && (field.FormRole == "none" || field.FormRole == "") { + return nil + } + rule := "// 验证" + field.Dc + "\n" + if field.Required && (field.FormRole == FormRoleNone || field.FormRole == "") { + field.FormRole = "required" + } + if err, s := makeValidatorFunc(field); err != nil { + return nil + } else { + rule += s + } + result = []string{rule} + case InputTypeUpdateFields: + if !isUpdateOrInsertInputFields() { + return nil + } + addResult() + case InputTypeInsertFields: + if !isUpdateOrInsertInputFields() { + return nil + } + addResult() + case InputTypeTreeOptionFields: + if IsIndexPK(field.Index) { + return addResult() + } + if IsPidName(field.Name) { + return addResult() + } + if in.options.Tree.TitleColumn == field.Name { + return addResult() + } + return nil + default: + panic("inputType is invalid") + } + return result +} + +func makeValidatorFunc(field *sysin.GenCodesColumnListModel) (err error, rule string) { + if field.FormRole == "required" { + rule = fmt.Sprintf(EditInpValidatorGenerally, "required", field.GoName, field.Dc+"不能为空") + } else if field.FormRole == FormRoleIp { + rule = fmt.Sprintf(EditInpValidatorGenerally, "ip", field.GoName, field.Dc+"必须为IPV4或IPV6") + } else if field.FormRole == FormRolePercentage { + rule = fmt.Sprintf(EditInpValidatorGenerally, "min:0|max:100", field.GoName, field.Dc+"必须0-100之间") + } else if field.FormRole == FormRoleTel { + rule = fmt.Sprintf(EditInpValidatorGenerally, "telephone", field.GoName, field.Dc+"不是座机号码") + } else if field.FormRole == FormRolePhone { + rule = fmt.Sprintf(EditInpValidatorGenerally, "phone", field.GoName, field.Dc+"不是手机号码") + } else if field.FormRole == FormRoleQq { + rule = fmt.Sprintf(EditInpValidatorGenerally, "qq", field.GoName, field.Dc+"不是QQ号码") + } else if field.FormRole == FormRoleEmail { + rule = fmt.Sprintf(EditInpValidatorGenerally, "email", field.GoName, field.Dc+"不是邮箱地址") + } else if field.FormRole == FormRoleIdCard { + rule = fmt.Sprintf(EditInpValidatorGenerally, "resident-id", field.GoName, field.Dc+"不是身份证号码") + } else if field.FormRole == FormRoleNum { + rule = fmt.Sprintf(EditInpValidatorGenerally, "min:1", field.GoName, field.Dc+"必须大于0") + } else if field.FormRole == FormRoleBankCard { + rule = fmt.Sprintf(EditInpValidatorGenerally, "bank-card", field.GoName, field.Dc+"不是银行卡号") + } else if field.FormRole == FormRoleWeibo { + rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^[0-9a-zA-Z\\u4e00-\\u9fa5_-]*$", field.GoName, field.Dc+"不是微博号") + } else if field.FormRole == FormRoleUserName { + rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^[0-9a-zA-Z]{6,16}$", field.GoName, field.Dc+"必须为6-16位由字母和数字组成") + } else if field.FormRole == FormRoleAccount { + rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^[\\w_\\d]{6,16}$", field.GoName, field.Dc+"必须为6-16位由字母、数字或下划线组成") + } else if field.FormRole == FormRolePassword { + rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$", field.GoName, field.Dc+"必须包含6-18为字母和数字") + } else if field.FormRole == FormRoleAmount { + rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:(^[0-9]{1,10}$)|(^[0-9]{1,10}[\\\\.]{1}[0-9]{1,2}$)", field.GoName, field.Dc+"最多允许输入10位整数及2位小数") + } else if field.FormRole == FormRoleYaml { + rule = fmt.Sprintf(EditInpValidatorYaml, field.GoName, field.Dc) + } else { + err = gerror.New("not support") + } + + // 生成验证字典 + if field.DictType > 0 { + var ( + ctx = context.Background() + valueType gdb.Value + resultValue gdb.Result + ) + valueType, err = dao.SysDictType.Ctx(ctx).Fields(dao.SysDictType.Columns().Type).Where(dao.SysDictType.Columns().Id, field.DictType).Value() + if err != nil { + return + } + resultValue, err = dao.SysDictData.Ctx(context.Background()).Fields(dao.SysDictData.Columns().Value).Where(dao.SysDictData.Columns().Type, valueType.String()).All() + if err != nil { + return + } + if resultValue.Len() > 0 { + names := make([]string, 0) + for _, item := range resultValue { + names = append(names, item["value"].String()) + } + dictRule := "in:" + strings.Join(names, ",") + rule += fmt.Sprintf(EditInpValidatorGenerally, dictRule, field.GoName, field.Dc+"值不正确") + err = nil + } + } + return +} diff --git a/server/internal/library/hggen/views/curd_generate_logic.go b/server/internal/library/hggen/views/curd_generate_logic.go new file mode 100644 index 0000000..78bc993 --- /dev/null +++ b/server/internal/library/hggen/views/curd_generate_logic.go @@ -0,0 +1,318 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "bytes" + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gstr" + "hotgo/internal/consts" + "hotgo/internal/model/input/sysin" +) + +const ( + LogicWhereComments = "\n\t// 查询%s\n" + LogicWhereNoSupport = "\t// TODO 暂不支持生成[ %s ]查询方式,请自行补充此处代码!" + LogicEditUpdate = "\tif _, err = s.Model(ctx%s).\n\t\t\tFields(%sin.%sUpdateFields{}).\n\t\t\tWherePri(in.%s).Data(in).Update(); err != nil {\n\t\t\terr = gerror.Wrap(err, \"修改%s失败,请稍后重试!\")\n\t\t}\n\t\treturn" + LogicEditInsert = "\tif _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).\n\t\tFields(%sin.%sInsertFields{}).\n\t\tData(in).OmitEmptyData().Insert(); err != nil {\n\t\terr = gerror.Wrap(err, \"新增%s失败,请稍后重试!\")\n\t}" + LogicEditUnique = "\t// 验证'%s'唯一\n\tif err = hgorm.IsUnique(ctx, &dao.%s, g.Map{dao.%s.Columns().%s: in.%s}, \"%s已存在\", in.%s); err != nil {\n\t\treturn\n\t}\n" + LogicSwitchUpdate = "g.Map{\n\t\tin.Key: in.Value,\n%s}" + LogicStatusUpdate = "g.Map{\n\t\tdao.%s.Columns().Status: in.Status,\n%s}" + LogicDeletedUpdate = "g.Map{\n%s}" +) + +func (l *gCurd) logicTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { + data = make(g.Map) + data["listWhere"] = l.generateLogicListWhere(ctx, in) + data["listJoin"] = l.generateLogicListJoin(ctx, in) + data["listFields"] = l.generateLogicListFields(ctx, in) + data["listOrder"] = l.generateLogicListOrder(ctx, in) + data["edit"] = l.generateLogicEdit(ctx, in) + data["switchFields"] = l.generateLogicSwitchFields(ctx, in) + data["switchUpdate"] = l.generateLogicSwitchUpdate(ctx, in) + data["statusUpdate"] = l.generateLogicStatusUpdate(ctx, in) + data["deletedUpdate"] = l.generateLogicDeletedUpdate(ctx, in) + return +} + +func (l *gCurd) generateLogicDeletedUpdate(ctx context.Context, in *CurdPreviewInput) string { + isDestroy := false + var update string + for _, field := range in.masterFields { + if field.GoName == "DeletedBy" { + update += "\t\tdao." + in.In.DaoName + ".Columns().DeletedBy: contexts.GetUserId(ctx),\n" + isDestroy = true + } + if field.GoName == "DeletedAt" { + update += "\t\tdao." + in.In.DaoName + ".Columns().DeletedAt: gtime.Now(),\n" + } + } + + if !isDestroy { + return "" + } + + update += "\t" + return fmt.Sprintf(LogicDeletedUpdate, update) +} + +func (l *gCurd) generateLogicStatusUpdate(ctx context.Context, in *CurdPreviewInput) string { + var update string + for _, field := range in.masterFields { + if field.GoName == "UpdatedBy" { + update += "\t\tdao." + in.In.DaoName + ".Columns().UpdatedBy: contexts.GetUserId(ctx),\n" + } + } + + update += "\t" + return fmt.Sprintf(LogicStatusUpdate, in.In.DaoName, update) +} + +func (l *gCurd) generateLogicSwitchUpdate(ctx context.Context, in *CurdPreviewInput) string { + var update string + for _, field := range in.masterFields { + if field.GoName == "UpdatedBy" { + update += "\t\tdao." + in.In.DaoName + ".Columns().UpdatedBy: contexts.GetUserId(ctx),\n" + } + } + + update += "\t" + return fmt.Sprintf(LogicSwitchUpdate, update) +} + +func (l *gCurd) generateLogicSwitchFields(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + if in.options.Step.HasSwitch { + for _, field := range in.masterFields { + if field.FormMode == "Switch" { + buffer.WriteString("\t\tdao." + in.In.DaoName + ".Columns()." + field.GoName + ",\n") + } + } + } + return buffer.String() +} + +func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.Map { + var ( + data = make(g.Map) + updateBuffer = bytes.NewBuffer(nil) + insertBuffer = bytes.NewBuffer(nil) + uniqueBuffer = bytes.NewBuffer(nil) + validationBuffer = bytes.NewBuffer(nil) + ) + + for _, field := range in.masterFields { + if field.GoName == "UpdatedBy" { + updateBuffer.WriteString("\t\tin.UpdatedBy = contexts.GetUserId(ctx)\n") + } + + if field.GoName == "CreatedBy" { + insertBuffer.WriteString("\tin.CreatedBy = contexts.GetUserId(ctx)\n") + } + + if field.Unique { + uniqueBuffer.WriteString(fmt.Sprintf(LogicEditUnique, field.GoName, in.In.DaoName, in.In.DaoName, field.GoName, field.GoName, field.Dc, in.pk.GoName)) + } + + // 添加 YAML 格式验证 + if field.IsEdit && field.FormRole == FormRoleYaml { + validationBuffer.WriteString(fmt.Sprintf(EditInpValidatorYaml, field.GoName, field.Dc)) + } + } + + notFilterAuth := "" + if gstr.InArray(in.options.ColumnOps, "notFilterAuth") { + notFilterAuth = ", &handler.Option{FilterAuth: false}" + } + + updateBuffer.WriteString(fmt.Sprintf(LogicEditUpdate, notFilterAuth, in.options.TemplateGroup, in.In.VarName, in.pk.GoName, in.In.TableComment)) + insertBuffer.WriteString(fmt.Sprintf(LogicEditInsert, in.options.TemplateGroup, in.In.VarName, in.In.TableComment)) + + data["update"] = updateBuffer.String() + data["insert"] = insertBuffer.String() + data["unique"] = uniqueBuffer.String() + data["validation"] = validationBuffer.String() + return data +} + +func (l *gCurd) generateLogicListOrder(ctx context.Context, in *CurdPreviewInput) string { + statement := "" + if hasEffectiveJoins(in.options.Join) { + statement = "dao." + in.In.DaoName + ".Table() + \".\" +" + } + buffer := bytes.NewBuffer(nil) + if in.options.Step.HasMaxSort { + buffer.WriteString("OrderAsc(" + statement + "dao." + in.In.DaoName + ".Columns().Sort).") + } + buffer.WriteString("OrderDesc(" + statement + "dao." + in.In.DaoName + ".Columns()." + in.pk.GoName + ")") + return buffer.String() +} + +func (l *gCurd) generateLogicListJoin(ctx context.Context, in *CurdPreviewInput) (link string) { + connector := `"="` + if hasEffectiveJoins(in.options.Join) { + linkBuffer := bytes.NewBuffer(nil) + for _, join := range in.options.Join { + if isEffectiveJoin(join) { + linkBuffer.WriteString("\tmod = mod." + consts.GenCodesJoinLinkMap[join.LinkMode] + "OnFields(dao." + join.DaoName + ".Table(), dao." + in.In.DaoName + ".Columns()." + gstr.CaseCamel(join.MasterField) + "," + connector + ", dao." + join.DaoName + ".Columns()." + gstr.CaseCamel(join.Field) + ")\n") + } + } + link = linkBuffer.String() + } + return +} + +func (l *gCurd) generateLogicListFields(ctx context.Context, in *CurdPreviewInput) (fields string) { + selectBuffer := bytes.NewBuffer(nil) + if hasEffectiveJoins(in.options.Join) { + selectBuffer.WriteString("mod = mod.FieldsPrefix(dao." + in.In.DaoName + ".Table(), " + in.options.TemplateGroup + "in." + in.In.VarName + "ListModel{})\n") + for _, join := range in.options.Join { + if isEffectiveJoin(join) { + selectBuffer.WriteString("mod = mod.Fields(hgorm.JoinFields(ctx, " + in.options.TemplateGroup + "in." + in.In.VarName + "ListModel{}, &dao." + join.DaoName + ", \"" + join.Alias + "\"))\n") + } + } + fields = selectBuffer.String() + } else { + fields = fmt.Sprintf("mod = mod.Fields(%sin.%sListModel{})", in.options.TemplateGroup, in.In.VarName) + } + return +} + +func (l *gCurd) generateLogicListWhere(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + + // 主表 + l.generateLogicListWhereEach(buffer, in, in.masterFields, in.In.DaoName, "") + + // 关联表 + if hasEffectiveJoins(in.options.Join) { + for _, v := range in.options.Join { + if isEffectiveJoin(v) { + l.generateLogicListWhereEach(buffer, in, v.Columns, v.DaoName, v.Alias) + } + } + } + return buffer.String() +} + +func (l *gCurd) generateLogicListWhereEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel, daoName string, alias string) { + isLink := false + if alias != "" { + alias = `"` + alias + `."+` + isLink = true + } + + tablePrefix := "" + wherePrefix := "Where" + if isLink { + wherePrefix = "WherePrefix" + tablePrefix = "dao." + daoName + ".Table(), " + } + + for _, field := range fields { + isQuery := false + // 树表查询上级 + if in.options.Step.IsTreeTable && IsPidName(field.Name) { + isQuery = true + field.QueryWhere = WhereModeEq + } + + if (!field.IsQuery && !isQuery) || field.QueryWhere == "" { + continue + } + + buffer.WriteString(fmt.Sprintf(LogicWhereComments, field.Dc)) + + var ( + linkMode string + whereTag string + columnName string + ) + + // 查询用户摘要 + if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) { + servicePackName := "service" + if in.options.Step.IsAddon { + servicePackName = "isc" + } + buffer.WriteString(fmt.Sprintf("if in.%v != \"\" {\n\t\t\t\tids, err := %v.AdminMember().GetIdsByKeyword(ctx, in.%v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, 0, err\n\t\t\t\t}\n\t\t\t\tmod = mod.WhereIn(dao.%v.Columns().%v, ids)\n\t\t\t}\n", field.GoName, servicePackName, field.GoName, in.In.DaoName, field.GoName)) + continue + } + + if IsNumberType(field.GoType) { + linkMode = `in.` + field.GoName + ` > 0` + } else if field.GoType == GoTypeBool { + linkMode = `true` // bool 类型始终可以查询 + } else if field.GoType == GoTypeGTime { + linkMode = `in.` + field.GoName + ` != nil` + } else if field.GoType == GoTypeJson { + linkMode = `!in.` + field.GoName + `.IsNil()` + } else { + linkMode = `in.` + field.GoName + ` != ""` + } + + if field.QueryWhere == WhereModeBetween || field.QueryWhere == WhereModeNotBetween { + linkMode = `len(in.` + field.GoName + `) == 2` + } + + // 如果是关联表重新转换字段 + columnName = field.GoName + if isLink { + columnName = gstr.CaseCamel(field.Name) + } + + switch field.QueryWhere { + case WhereModeEq: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeNeq: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Not(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeGt: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "GT(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeGte: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "GTE(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeLt: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "LT(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeLte: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "LTE(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeIn: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "In(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeNotIn: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotIn(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + ")\n\t}" + case WhereModeBetween: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Between(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" + case WhereModeNotBetween: + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotBetween(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", in." + field.GoName + "[0], in." + field.GoName + "[1])\n\t}" + case WhereModeLike: + fieldValue := "in." + field.GoName + if field.GoType != GoTypeString && field.GoType != GoTypeBytes { + fieldValue = "gconv.String(in." + field.GoName + ")" + } + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + fieldValue + ")\n\t}" + case WhereModeLikeAll: + val := `"%"+in.` + field.GoName + `+"%"` + if field.GoType != GoTypeString && field.GoType != GoTypeBytes { + val = `"%"+gconv.String(in.` + field.GoName + `)+"%"` + } + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "Like(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + val + ")\n\t}" + case WhereModeNotLike: + fieldValue := "in." + field.GoName + if field.GoType != GoTypeString && field.GoType != GoTypeBytes { + fieldValue = "gconv.String(in." + field.GoName + ")" + } + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "NotLike(" + tablePrefix + "dao." + daoName + ".Columns()." + columnName + ", " + fieldValue + ")\n\t}" + case WhereModeJsonContains: + val := tablePrefix + `"JSON_CONTAINS("+dao.` + daoName + `.Columns().` + columnName + `+",?)", in.` + field.GoName + whereTag = "\tif " + linkMode + " {\n\t\tmod = mod." + wherePrefix + "(" + val + ")\n\t}" + + default: + buffer.WriteString(fmt.Sprintf(LogicWhereNoSupport, field.QueryWhere)) + } + + buffer.WriteString(whereTag + "\n") + } +} diff --git a/server/internal/library/hggen/views/curd_generate_web_edit.go b/server/internal/library/hggen/views/curd_generate_web_edit.go new file mode 100644 index 0000000..25aeb10 --- /dev/null +++ b/server/internal/library/hggen/views/curd_generate_web_edit.go @@ -0,0 +1,232 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "bytes" + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/text/gstr" +) + +func (l *gCurd) webEditTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { + data = make(g.Map) + data["formItem"] = l.generateWebEditFormItem(ctx, in) + data["script"] = l.generateWebEditScript(ctx, in) + return +} + +func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + for _, field := range in.masterFields { + if !field.IsEdit { + continue + } + + if IsIndexPK(field.Index) { + continue + } + + var ( + defaultComponent = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.Dc, field.TsName) + component string + ) + + if in.options.Step.IsTreeTable && IsPidName(field.Name) { + field.FormMode = FormModePidTreeSelect + } + + switch field.FormMode { + case FormModeInput: + component = defaultComponent + + case FormModeInputNumber: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.Dc, field.TsName) + + case FormModeInputTextarea: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.Dc, field.TsName) + + case FormModeInputEditor: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, field.TsName) + + case FormModeInputYaml: + component = fmt.Sprintf(" \n handleYamlValidationChange(isValid, '%s')\"\n />\n ", field.Dc, field.TsName, field.TsName, field.TsName, field.Dc, field.TsName) + + case FormModeInputDynamic: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModeDate: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + // case FormModeDateRange: // 必须要有两个字段,后面优化下 + + case FormModeTime: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + // case FormModeTimeRange: // 必须要有两个字段,后面优化下 + + case FormModeRadio: + component = fmt.Sprintf(" \n \n \n \n ", field.Dc, field.TsName, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName], field.TsName, field.TsName, field.TsName) + + case FormModeCheckbox: + component = fmt.Sprintf(" \n \n \n \n \n \n ", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName]) + + case FormModeSelect: + if in.options.dictMap[field.TsName] != nil { + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName]) + } else { + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + } + + case FormModeSelectMultiple: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, in.options.dictMap[field.TsName]) + + case FormModeUploadImage: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModeUploadImages: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModeUploadFile: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModeUploadFiles: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModeSwitch: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModeRate: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName, field.GoName) + + case FormModeCitySelector: + component = fmt.Sprintf(" \n \n ", field.Dc, field.TsName, field.TsName) + + case FormModePidTreeSelect: + component = fmt.Sprintf(` + + `, field.Dc, in.pk.TsName, in.options.Tree.TitleField.TsName) + + case FormModeTreeSelect: + component = fmt.Sprintf(` + + `, field.Dc, field.TsName, field.Dc, field.TsName) + + case FormModeCascader: + component = fmt.Sprintf(` + + `, field.Dc, field.TsName, field.Dc, field.TsName) + + default: + component = defaultComponent + } + + buffer.WriteString(fmt.Sprintf("\n%v\n \n\n", field.FormGridSpan, component)) + } + return buffer.String() +} + +func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) g.Map { + var ( + data = make(g.Map) + importBuffer = bytes.NewBuffer(nil) + setupBuffer = bytes.NewBuffer(nil) + hasYamlField = false + ) + + // 导入字典 + if in.options.DictOps.Has { + importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\n") + } + + // 导入api + var importApiMethod = []string{"Edit", "View"} + if in.options.Step.HasMaxSort { + importApiMethod = append(importApiMethod, "MaxSort") + } + importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n") + + // 导入model + var importModelMethod = []string{"State", "newState"} + if in.options.Step.IsTreeTable { + importModelMethod = append(importModelMethod, []string{"treeOption", "loadTreeOption"}...) + } + + if in.options.Step.HasRules { + importModelMethod = append(importModelMethod, "rules") + } + importBuffer.WriteString(" import " + ImportWebMethod(importModelMethod) + " from './model';\n") + + for _, field := range in.masterFields { + if !field.IsEdit { + continue + } + switch field.FormMode { + case FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange: + if !gstr.Contains(importBuffer.String(), `import DatePicker`) { + importBuffer.WriteString(" import DatePicker from '@/components/DatePicker/datePicker.vue';\n") + } + case FormModeInputEditor: + if !gstr.Contains(importBuffer.String(), `import Editor`) { + importBuffer.WriteString(" import Editor from '@/components/Editor/editor.vue';\n") + } + case FormModeInputYaml: + if !gstr.Contains(importBuffer.String(), `import YamlEditor`) { + importBuffer.WriteString(" import YamlEditor from '@/components/YamlEditor/index.vue';\n") + } + hasYamlField = true + case FormModeUploadImage, FormModeUploadImages: + if !gstr.Contains(importBuffer.String(), `import UploadImage`) { + importBuffer.WriteString(" import UploadImage from '@/components/Upload/uploadImage.vue';\n") + } + case FormModeUploadFile, FormModeUploadFiles: + if !gstr.Contains(importBuffer.String(), `import UploadFile`) { + importBuffer.WriteString(" import UploadFile from '@/components/Upload/uploadFile.vue';\n") + } + case FormModeRate: + setupBuffer.WriteString(fmt.Sprintf(" function update%s(num) {\n formValue.value.%s = num;\n }\n", field.GoName, field.TsName)) + case FormModeCitySelector: + if !gstr.Contains(importBuffer.String(), `import CitySelector`) { + importBuffer.WriteString(" import CitySelector from '@/components/CitySelector/citySelector.vue';\n") + } + } + } + + // 根据是否有 YAML 字段添加相应的验证逻辑 + if hasYamlField { + setupBuffer.WriteString(" const yamlValidationStates = ref(new Map());\n const isFormValid = computed(() => {\n for (const [fieldName, isValid] of yamlValidationStates.value) {\n if (!isValid) return false;\n }\n return true;\n });\n const handleYamlError = (error) => {\n console.error('YAML 验证错误:', error);\n };\n const handleYamlValid = (content) => {\n console.log('YAML 验证通过:', content);\n };\n const handleYamlValidationChange = (isValid, fieldName) => {\n yamlValidationStates.value.set(fieldName, isValid);\n };\n") + } else { + setupBuffer.WriteString(" const isFormValid = ref(true);\n") + } + + data["import"] = importBuffer.String() + data["setup"] = setupBuffer.String() + return data +} diff --git a/server/internal/library/hggen/views/curd_generate_web_index.go b/server/internal/library/hggen/views/curd_generate_web_index.go new file mode 100644 index 0000000..2193b21 --- /dev/null +++ b/server/internal/library/hggen/views/curd_generate_web_index.go @@ -0,0 +1,167 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "bytes" + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Map, error) { + var ( + data = make(g.Map) + importBuffer = bytes.NewBuffer(nil) + importVueMethod = []string{"h", "reactive", "ref", "computed"} + importApiMethod = []string{"List"} + importModelMethod = []string{"columns", "schemas", "State"} + importUtilsMethod = []string{"adaTableScrollX"} + importIcons []string + actionWidth int64 = 72 + ) + + // 添加 + if in.options.Step.HasAdd { + importIcons = append(importIcons, "PlusOutlined") + } + + // 编辑 + if in.options.Step.HasEdit { + in.options.Step.ActionColumnWidth += actionWidth + if in.options.Step.IsTreeTable && !in.options.Step.IsOptionTreeTable { + in.options.Step.ActionColumnWidth += actionWidth + } + if in.options.Step.IsOptionTreeTable { + importIcons = append(importIcons, "EditOutlined") + } + } + + // 导出 + if in.options.Step.HasExport { + importIcons = append(importIcons, "ExportOutlined") + importApiMethod = append(importApiMethod, "Export") + } + + // 删除 + if in.options.Step.HasDel { + importApiMethod = append(importApiMethod, "Delete") + in.options.Step.ActionColumnWidth += actionWidth + } + + // 批量删除 + if in.options.Step.HasBatchDel { + importIcons = append(importIcons, "DeleteOutlined") + importApiMethod = append(importApiMethod, "Delete") + } + + // 修改状态 + if in.options.Step.HasStatus { + importApiMethod = append(importApiMethod, "Status") + in.options.Step.ActionColumnWidth += actionWidth + } + + // 更多 + // 查看详情 + if in.options.Step.HasView { + in.options.Step.ActionColumnWidth += actionWidth + } + + // 展开树 + if in.options.Step.IsTreeTable { + importIcons = append(importIcons, "AlignLeftOutlined") + } + + // 存在字典数据选项 + if in.options.DictOps.Has { + importVueMethod = append(importVueMethod, "onMounted") + importModelMethod = append(importModelMethod, "loadOptions") + } + + // 普通树表 + if in.options.Step.IsTreeTable && !in.options.Step.IsOptionTreeTable { + importUtilsMethod = append(importUtilsMethod, "convertListToTree") + } + + // 选项式树表 + if in.options.Step.IsOptionTreeTable { + importVueMethod = append(importVueMethod, []string{"onMounted", "unref"}...) + importIcons = append(importIcons, []string{"FormOutlined", "SearchOutlined"}...) + importApiMethod = append(importApiMethod, "TreeOption") + importUtilsMethod = append(importUtilsMethod, "getTreeKeys") + importModelMethod = append(importModelMethod, []string{"loadTreeOption", "treeOption", "State"}...) + } + + // 操作按钮宽度最小值 + if in.options.Step.ActionColumnWidth > 0 && in.options.Step.ActionColumnWidth < actionWidth*2 { + in.options.Step.ActionColumnWidth = 100 + } + + // 导入基础包 + importBuffer.WriteString(" import { useDialog, useMessage } from 'naive-ui';\n") + importBuffer.WriteString(" import { BasicTable, TableAction } from '@/components/Table';\n") + importBuffer.WriteString(" import { BasicForm, useForm } from '@/components/Form/index';\n") + importBuffer.WriteString(" import { usePermission } from '@/hooks/web/usePermission';\n") + + // 导入字典 + if in.options.DictOps.Has { + importBuffer.WriteString(" import { useDictStore } from '@/store/modules/dict';\n") + } + + // 导入api + importBuffer.WriteString(" import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n") + + // 导入icons + if len(importIcons) > 0 { + importBuffer.WriteString(" import " + ImportWebMethod(importIcons) + " from '@vicons/antd';\n") + } + + // 导入model + if in.options.Step.IsTreeTable { + importModelMethod = append(importModelMethod, "newState") + } + importBuffer.WriteString(" import " + ImportWebMethod(importModelMethod) + " from './model';\n") + + // 导入utils + if len(importUtilsMethod) > 0 { + importBuffer.WriteString(" import " + ImportWebMethod(importUtilsMethod) + " from '@/utils/hotgo';\n") + } + + // 导入edit组件 + if in.options.Step.HasEdit { + importBuffer.WriteString(" import Edit from './edit.vue';\n") + } + + // 导入view组件 + if in.options.Step.HasView { + importBuffer.WriteString(" import View from './view.vue';\n") + } + + // 没有需要查询的字段则隐藏搜索表单 + isSearchForm := false + for _, field := range in.masterFields { + if field.IsQuery { + isSearchForm = true + break + } + } + if !isSearchForm { + if len(in.options.Join) > 0 { + LoopOut: + for _, v := range in.options.Join { + for _, column := range v.Columns { + if column.IsQuery { + isSearchForm = true + break LoopOut + } + } + } + } + } + data["isSearchForm"] = isSearchForm + data["import"] = importBuffer.String() + return data, nil +} diff --git a/server/internal/library/hggen/views/curd_generate_web_model.go b/server/internal/library/hggen/views/curd_generate_web_model.go new file mode 100644 index 0000000..5ba4dd1 --- /dev/null +++ b/server/internal/library/hggen/views/curd_generate_web_model.go @@ -0,0 +1,493 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "bytes" + "context" + "errors" + "fmt" + "hotgo/internal/dao" + "hotgo/internal/library/dict" + "hotgo/internal/model/input/sysin" + "hotgo/utility/convert" + "strings" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +type StateItem struct { + Name string + DefaultValue interface{} + Dc string + DataType string // 新增字段:存储字段类型信息 +} + +func (l *gCurd) webModelTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { + data = make(g.Map) + data["stateItems"] = l.generateWebModelStateItems(ctx, in) + data["rules"] = l.generateWebModelRules(ctx, in) + data["formSchema"] = l.generateWebModelFormSchema(ctx, in) + if data["columns"], err = l.generateWebModelColumns(ctx, in); err != nil { + return nil, err + } + + // 根据表单生成情况,按需导包 + data["import"], data["const"] = l.generateWebModelImport(ctx, in) + return +} + +func (l *gCurd) generateWebModelImport(ctx context.Context, in *CurdPreviewInput) (string, string) { + importBuffer := bytes.NewBuffer(nil) + constBuffer := bytes.NewBuffer(nil) + + // 导入基础组件 + if len(in.options.Step.ImportModel.NaiveUI) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.NaiveUI) + " from 'naive-ui';\n") + } + + importBuffer.WriteString("import { cloneDeep } from 'lodash-es';\n") + + // 导入表单搜索 + if in.options.Step.HasSearchForm { + importBuffer.WriteString("import { FormSchema } from '@/components/Form';\n") + } + + // 导入工具类 + if len(in.options.Step.ImportModel.UtilsIs) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsIs) + " from '@/utils/is';\n") + } + + if len(in.options.Step.ImportModel.UtilsUrl) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsUrl) + " from '@/utils/urlUtils';\n") + } + + if len(in.options.Step.ImportModel.UtilsDate) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsDate) + " from '@/utils/dateUtil';\n") + } + + if in.options.Step.HasRulesValidator { + importBuffer.WriteString("import { validate } from '@/utils/validateUtil';\n") + } + + if len(in.options.Step.ImportModel.UtilsHotGo) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsHotGo) + " from '@/utils/hotgo';\n") + } + + if len(in.options.Step.ImportModel.UtilsIndex) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(in.options.Step.ImportModel.UtilsIndex) + " from '@/utils';\n") + } + + // 导入api + var importApiMethod []string + if in.options.Step.HasSwitch { + importApiMethod = append(importApiMethod, "Switch") + } + if in.options.Step.IsTreeTable { + importApiMethod = append(importApiMethod, "TreeOption") + } + if len(importApiMethod) > 0 { + importBuffer.WriteString("import " + ImportWebMethod(importApiMethod) + " from '" + in.options.ImportWebApi + "';\n") + } + + // 导入字典选项 + if in.options.DictOps.Has { + importBuffer.WriteString("import { useDictStore } from '@/store/modules/dict';\n") + constBuffer.WriteString("const dict = useDictStore();\n") + } + + if in.options.Step.HasSwitch { + importBuffer.WriteString("import { usePermission } from '@/hooks/web/usePermission';\n") + constBuffer.WriteString("const { hasPermission } = usePermission();\n") + constBuffer.WriteString("const $message = window['$message'];\n") + } + + return importBuffer.String(), constBuffer.String() +} + +func (l *gCurd) generateWebModelStateItems(ctx context.Context, in *CurdPreviewInput) (items []*StateItem) { + for _, field := range in.masterFields { + dataType := field.DataType + if dataType == "" && field.SqlType != "" { + if parts := strings.SplitN(field.SqlType, "(", 2); len(parts) > 0 { + dataType = parts[0] + } + } + + isStr := isStringType(field.TsType, dataType) + isArray := strings.HasPrefix(dataType, "_") || strings.Contains(field.SqlType, "[]") + + var value = field.DefaultValue + if value == nil { + if isArray { + value = "null" + } else if isStr { + value = "" + } else { + value = "null" + } + } + if value == "" { + if isArray || !isStr { + value = "null" + } + } else if valueStr, ok := value.(string); ok && isStr { + value = valueStr + } + + // 选项组件默认值调整 + if gconv.Int(value) == 0 && IsSelectFormMode(field.FormMode) { + value = "null" + } + + if field.Name == "status" { + value = 1 + } + if field.FormMode == FormModeSwitch { + value = 2 + } + if field.FormMode == FormModeInputDynamic { + value = "[]" + } + items = append(items, &StateItem{ + Name: field.TsName, + DefaultValue: value, + DataType: dataType, + Dc: field.Dc, + }) + + // 查询用户摘要 + if field.IsList && in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + items = append(items, &StateItem{ + Name: field.TsName + "Summa?: null | MemberSumma", + DefaultValue: "null", + DataType: dataType, + Dc: field.Dc + "摘要信息", + }) + } + } + return +} + +func (l *gCurd) generateWebModelDictOptions(ctx context.Context, in *CurdPreviewInput) error { + type DictType struct { + Id int64 `json:"id"` + Type string `json:"type"` + } + + var ( + dictTypeIds []int64 + dictTypeList []*DictType + builtinDictTypeIds []int64 + builtinDictTypeList []*DictType + ) + + for _, field := range in.masterFields { + if field.DictType > 0 { + dictTypeIds = append(dictTypeIds, field.DictType) + } + + if field.DictType < 0 { + builtinDictTypeIds = append(builtinDictTypeIds, field.DictType) + } + } + + dictTypeIds = convert.UniqueSlice(dictTypeIds) + builtinDictTypeIds = convert.UniqueSlice(builtinDictTypeIds) + + if len(dictTypeIds) == 0 && len(builtinDictTypeIds) == 0 { + return nil + } + + if len(dictTypeIds) > 0 { + err := dao.SysDictType.Ctx(ctx). + Fields(dao.SysDictType.Columns().Id, dao.SysDictType.Columns().Type). + WhereIn(dao.SysDictType.Columns().Id, dictTypeIds). + Scan(&dictTypeList) + if err != nil { + return err + } + } + + if len(builtinDictTypeIds) > 0 { + for _, id := range builtinDictTypeIds { + typ, err := dict.GetTypeById(ctx, id) + if err != nil && !errors.Is(err, dict.NotExistKeyError) { + return err + } + if len(typ) > 0 { + row := new(DictType) + row.Id = id + row.Type = typ + builtinDictTypeList = append(builtinDictTypeList, row) + } + } + } + + if len(dictTypeList) == 0 && len(builtinDictTypeList) == 0 { + return nil + } + + if len(builtinDictTypeList) > 0 { + dictTypeList = append(dictTypeList, builtinDictTypeList...) + } + + in.options.DictOps.Has = true + + for _, v := range dictTypeList { + // 字段映射字典 + for _, field := range in.masterFields { + if field.DictType != 0 && v.Id == field.DictType { + in.options.dictMap[field.TsName] = v.Type + in.options.DictOps.Schemas = append(in.options.DictOps.Schemas, &OptionsSchemasField{ + Field: field.TsName, + Type: v.Type, + }) + } + } + in.options.DictOps.Types = append(in.options.DictOps.Types, v.Type) + } + return nil +} + +func (l *gCurd) generateWebModelRules(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + buffer.WriteString("export const rules = {\n") + for _, field := range in.masterFields { + if !field.IsEdit || (!field.Required && (field.FormRole == "" || field.FormRole == FormRoleNone)) { + continue + } + + in.options.Step.HasRules = true + if field.FormRole == "" || field.FormRole == FormRoleNone || field.FormRole == "required" { + buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n type: '%s',\n message: '请输入%s',\n },\n", field.TsName, field.Required, field.TsType, field.Dc)) + } else { + in.options.Step.HasRulesValidator = true + buffer.WriteString(fmt.Sprintf(" %s: {\n required: %v,\n trigger: ['blur', 'input'],\n type: '%s',\n validator: validate.%v,\n },\n", field.TsName, field.Required, field.TsType, field.FormRole)) + } + } + buffer.WriteString("};\n") + return buffer.String() +} + +func (l *gCurd) generateWebModelFormSchema(ctx context.Context, in *CurdPreviewInput) string { + buffer := bytes.NewBuffer(nil) + buffer.WriteString("export const schemas = ref([\n") + + // 主表 + l.generateWebModelFormSchemaEach(buffer, in.masterFields, in) + + // 关联表 + if len(in.options.Join) > 0 { + for _, v := range in.options.Join { + if !isEffectiveJoin(v) { + continue + } + l.generateWebModelFormSchemaEach(buffer, v.Columns, in) + } + } + + buffer.WriteString("]);\n") + return buffer.String() +} + +// generateWebDictOption 生产字典选项 +func (l *gCurd) generateWebDictOption(typ any) string { + return fmt.Sprintf(`dict.getOption('%v')`, typ) +} + +func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*sysin.GenCodesColumnListModel, in *CurdPreviewInput) { + for _, field := range fields { + if !field.IsQuery { + continue + } + in.options.Step.HasSearchForm = true + + // 查询用户摘要 + if field.IsQuery && in.options.Step.HasQueryMemberSummary && IsMemberSummaryField(field.Name) { + buffer.WriteString(fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入ID|用户名|姓名|手机号',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInput", field.Dc)) + continue + } + + var ( + defaultComponent = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入%s',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInput", field.Dc, field.Dc) + component string + ) + + // 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况 + switch field.FormMode { + case FormModeInput, FormModeInputTextarea, FormModeInputEditor, FormModeInputYaml: + component = defaultComponent + + case FormModeInputNumber: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n placeholder: '请输入%s',\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NInputNumber", field.Dc, field.Dc) + + case FormModeDate: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "date", "defShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defShortcuts") + + case FormModeDateRange: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "daterange", "defRangeShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defRangeShortcuts") + + case FormModeTime: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "datetime", "defShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defShortcuts") + + case FormModeTimeRange: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n componentProps: {\n type: '%s',\n clearable: true,\n shortcuts: %s,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NDatePicker", field.Dc, "datetimerange", "defRangeShortcuts()") + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "defRangeShortcuts") + + case FormModeSwitch: + fallthrough + case FormModeRadio: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n giProps: {\n //span: 24,\n },\n componentProps: {\n options: %v,\n onUpdateChecked: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NRadioGroup", field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName])) + + case FormModeCheckbox: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n giProps: {\n span: 1,\n },\n componentProps: {\n placeholder: '请选择%s',\n options: %v,\n onUpdateChecked: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NCheckbox", field.Dc, field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName])) + + case FormModeSelect: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n defaultValue: null,\n componentProps: {\n placeholder: '请选择%s',\n options: %v,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NSelect", field.Dc, field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName])) + + case FormModeSelectMultiple: + component = fmt.Sprintf(" {\n field: '%s',\n component: '%s',\n label: '%s',\n defaultValue: null,\n componentProps: {\n multiple: true,\n placeholder: '请选择%s',\n options: %v,\n onUpdateValue: (e: any) => {\n console.log(e);\n },\n },\n },\n", field.TsName, "NSelect", field.Dc, field.Dc, l.generateWebDictOption(in.options.dictMap[field.TsName])) + + default: + component = defaultComponent + } + + buffer.WriteString(component) + } +} + +func (l *gCurd) generateWebModelColumns(ctx context.Context, in *CurdPreviewInput) (string, error) { + buffer := bytes.NewBuffer(nil) + buffer.WriteString("export const columns = [\n") + + // 主表 + if err := l.generateWebModelColumnsEach(buffer, in, in.masterFields); err != nil { + return "", err + } + + // 关联表 + if len(in.options.Join) > 0 { + for _, v := range in.options.Join { + if !isEffectiveJoin(v) { + continue + } + if err := l.generateWebModelColumnsEach(buffer, in, v.Columns); err != nil { + return "", err + } + } + } + + buffer.WriteString("];\n") + return buffer.String(), nil +} + +func (l *gCurd) generateWebModelColumnsEach(buffer *bytes.Buffer, in *CurdPreviewInput, fields []*sysin.GenCodesColumnListModel) (err error) { + for _, field := range fields { + if !field.IsList { + continue + } + var ( + defaultComponent = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n },\n", field.Dc, field.TsName, field.Align, field.Width) + component string + ) + + // 查询用户摘要 + if in.options.Step.HasHookMemberSummary && IsMemberSummaryField(field.Name) { + buffer.WriteString(fmt.Sprintf(" {\n title: '%v',\n key: '%v',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderPopoverMemberSumma(row.%vSumma);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName)) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, []string{"renderPopoverMemberSumma", "MemberSumma"}...) + continue + } + + // 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况 + switch field.FormMode { + case FormModeDate: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return formatToDate(row.%s);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName) + in.options.Step.ImportModel.UtilsDate = append(in.options.Step.ImportModel.UtilsDate, "formatToDate") + + case FormModeRadio: + fallthrough + case FormModeSelect: + if g.IsEmpty(in.options.dictMap[field.TsName]) { + err = gerror.Newf("设置单选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name) + return + } + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderOptionTag('%v', row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, in.options.dictMap[field.TsName], field.TsName) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderOptionTag") + + case FormModeSelectMultiple: + if g.IsEmpty(in.options.dictMap[field.TsName]) { + err = gerror.Newf("设置多选下拉框选项时,必须选择字典类型,字段名称:%v", field.Name) + return + } + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n if (isNullObject(row.%s) || !Array.isArray(row.%s)) {\n return ``;\n }\n return row.%s.map((tagKey) => {\n return renderOptionTag('%s', tagKey)\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName, field.TsName, field.TsName, in.options.dictMap[field.TsName]) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NTag") + in.options.Step.ImportModel.UtilsIs = append(in.options.Step.ImportModel.UtilsIs, "isNullObject") + + case FormModeUploadImage: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderImage(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderImage") + + case FormModeUploadImages: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderImageGroup(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderImageGroup") + + case FormModeUploadFile: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderFile(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderFile") + + case FormModeUploadFiles: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return renderFileGroup(row.%v);\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, field.TsName) + in.options.Step.ImportModel.UtilsIndex = append(in.options.Step.ImportModel.UtilsIndex, "renderFileGroup") + + case FormModeSwitch: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return h(%s, {\n value: row.%s === 1,\n checked: '开启',\n unchecked: '关闭',\n disabled: !hasPermission(['%s']),\n onUpdateValue: function (e) {\n console.log('onUpdateValue e:' + JSON.stringify(e));\n row.%s = e ? 1 : 2;\n Switch({ %s: row.%s, key: '%s', value: row.%s }).then((_res) => {\n $message.success('操作成功');\n });\n },\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, "NSwitch", field.TsName, "/"+in.options.ApiPrefix+"/switch", field.TsName, in.pk.TsName, in.pk.TsName, convert.CamelCaseToUnderline(field.TsName), field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NSwitch") + + case FormModeRate: + component = fmt.Sprintf(" {\n title: '%s',\n key: '%s',\n align: '%v',\n width: %v,\n render(row: State) {\n return h(%s, {\n allowHalf: true,\n readonly: true,\n defaultValue: row.%s,\n });\n },\n },\n", field.Dc, field.TsName, field.Align, field.Width, "NRate", field.TsName) + in.options.Step.ImportModel.NaiveUI = append(in.options.Step.ImportModel.NaiveUI, "NRate") + + default: + component = defaultComponent + } + + buffer.WriteString(component) + } + return +} + +// isStringType 判断字段是否为字符串类型 +func isStringType(tsType, dataType string) bool { + // 根据 TypeScript 类型判断 + if tsType == "string" { + return true + } + + // 根据数据库数据类型判断 + stringTypes := []string{ + // mysql + "varchar", "char", "text", "longtext", "mediumtext", "tinytext", + "nvarchar", "nchar", "ntext", + "string", "enum", "set", + // pgsql + "bpchar", "date", "time", "timetz", "timestamp", "timestamptz", "interval", + "uuid", "bytea", "inet", "cidr", "macaddr", "macaddr8", "bit", "varbit", "json", "jsonb", + "point", "line", "lseg", "box", "path", "polygon", "circle", "money", + } + + for _, t := range stringTypes { + if strings.EqualFold(dataType, t) { + return true + } + } + return false +} diff --git a/server/internal/library/hggen/views/curd_generate_web_view.go b/server/internal/library/hggen/views/curd_generate_web_view.go new file mode 100644 index 0000000..dc932fd --- /dev/null +++ b/server/internal/library/hggen/views/curd_generate_web_view.go @@ -0,0 +1,90 @@ +// Package views +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2023 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package views + +import ( + "bytes" + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" +) + +func (l *gCurd) webViewTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { + data = make(g.Map) + item, hasUpload := l.generateWebViewItem(ctx, in) + data["item"] = item + data["hasUploadFile"] = hasUpload + return +} + +func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) (string, bool) { + buffer := bytes.NewBuffer(nil) + hasUploadFile := false + + for _, field := range in.masterFields { + if !field.IsEdit { + continue + } + + // 检测是否使用了文件上传相关的组件 + if field.FormMode == FormModeUploadFile || field.FormMode == FormModeUploadFiles { + hasUploadFile = true + } + + var ( + defaultComponent = fmt.Sprintf("\n \n {{ formValue.%s }}\n ", field.Dc, field.TsName) + component string + ) + + switch field.FormMode { + case FormModeInputTextarea, FormModeInputEditor: + component = fmt.Sprintf("\n \n \n ", field.Dc, field.TsName) + + case FormModeInputYaml: + component = fmt.Sprintf("\n \n
{{ formValue.%s }}
\n
", field.Dc, field.TsName) + + case FormModeInputDynamic: + component = defaultComponent + + case FormModeDate: + component = defaultComponent + + case FormModeTime: + component = defaultComponent + + case FormModeRadio, FormModeSelect: + component = fmt.Sprintf("\n \n {{ dict.getLabel('%s', formValue.%s) }}\n \n ", field.Dc, in.options.dictMap[field.TsName], field.TsName, in.options.dictMap[field.TsName], field.TsName) + + case FormModeCheckbox, FormModeSelectMultiple: + component = fmt.Sprintf("\n \n ", field.Dc, field.TsName, in.options.dictMap[field.TsName], in.options.dictMap[field.TsName]) + + case FormModeUploadImage: + component = fmt.Sprintf("\n \n \n ", field.Dc, field.TsName) + + case FormModeUploadImages: + component = fmt.Sprintf("\n \n \n \n \n \n \n \n \n ", field.Dc, field.TsName) + + case FormModeUploadFile: + component = fmt.Sprintf("\n \n
\n
\n
\n
\n {{ getFileExt(formValue.%s) }}\n
\n
\n
\n
\n
", field.Dc, field.TsName, field.TsName, field.TsName) + + case FormModeUploadFiles: + component = fmt.Sprintf("\n \n
\n \n \n
\n
\n \n {{ getFileExt(item) }}\n \n
\n
\n
\n \n \n
", field.Dc, field.TsName) + + case FormModeSwitch: + component = fmt.Sprintf("\n \n ", field.Dc, field.TsName) + + case FormModeRate: + component = fmt.Sprintf("\n \n ", field.Dc, field.TsName) + + default: + component = defaultComponent + } + + buffer.WriteString(" " + component + "\n\n") + } + return buffer.String(), hasUploadFile +} diff --git a/server/internal/library/hggen/views/gohtml/consts.go b/server/internal/library/hggen/views/gohtml/consts.go new file mode 100644 index 0000000..625118c --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/consts.go @@ -0,0 +1,7 @@ +package gohtml + +const ( + defaultIndentString = " " + startIndent = 0 + defaultLastElement = "" +) diff --git a/server/internal/library/hggen/views/gohtml/doc.go b/server/internal/library/hggen/views/gohtml/doc.go new file mode 100644 index 0000000..f787ed3 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/doc.go @@ -0,0 +1,2 @@ +// Package gohtml provides an HTML formatting function. +package gohtml diff --git a/server/internal/library/hggen/views/gohtml/element.go b/server/internal/library/hggen/views/gohtml/element.go new file mode 100644 index 0000000..a41be68 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/element.go @@ -0,0 +1,7 @@ +package gohtml + +// An element represents an HTML element. +type element interface { + isInline() bool + write(*formattedBuffer, bool) bool +} diff --git a/server/internal/library/hggen/views/gohtml/formatter.go b/server/internal/library/hggen/views/gohtml/formatter.go new file mode 100644 index 0000000..0f1e1f6 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/formatter.go @@ -0,0 +1,36 @@ +package gohtml + +import ( + "bytes" + "strconv" + "strings" +) + +// Format parses the input HTML string, formats it and returns the result. +func Format(s string) string { + return parse(strings.NewReader(s)).html() +} + +// FormatBytes parses input HTML as bytes, formats it and returns the result. +func FormatBytes(b []byte) []byte { + return parse(bytes.NewReader(b)).bytes() +} + +// Format parses the input HTML string, formats it and returns the result with line no. +func FormatWithLineNo(s string) string { + return AddLineNo(Format(s)) +} + +func AddLineNo(s string) string { + lines := strings.Split(s, "\n") + maxLineNoStrLen := len(strconv.Itoa(len(lines))) + bf := &bytes.Buffer{} + for i, line := range lines { + lineNoStr := strconv.Itoa(i + 1) + if i > 0 { + bf.WriteString("\n") + } + bf.WriteString(strings.Repeat(" ", maxLineNoStrLen-len(lineNoStr)) + lineNoStr + " " + line) + } + return bf.String() +} diff --git a/server/internal/library/hggen/views/gohtml/html_document.go b/server/internal/library/hggen/views/gohtml/html_document.go new file mode 100644 index 0000000..3d9e6ea --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/html_document.go @@ -0,0 +1,54 @@ +package gohtml + +import ( + "bytes" + "regexp" +) + +// Column to wrap lines to (disabled by default) +var LineWrapColumn = 0 + +// Maxmimum characters a long word can extend past LineWrapColumn without wrapping +var LineWrapMaxSpillover = 5 + +// An htmlDocument represents an HTML document. +type htmlDocument struct { + elements []element +} + +// html generates an HTML source code and returns it. +func (htmlDoc *htmlDocument) html() string { + str := string(htmlDoc.bytes()) + str = replaceMultipleNewlinesWithSpaceAndTabs(str) + return str +} + +func replaceMultipleNewlinesWithSpaceAndTabs(input string) string { + re := regexp.MustCompile(`\n\s*\n+`) + formattedString := re.ReplaceAllString(input, "\n") + return formattedString +} + +// bytes reads from htmlDocument's internal array of elements and returns HTML source code +func (htmlDoc *htmlDocument) bytes() []byte { + bf := &formattedBuffer{ + buffer: &bytes.Buffer{}, + + lineWrapColumn: LineWrapColumn, + lineWrapMaxSpillover: LineWrapMaxSpillover, + + indentString: defaultIndentString, + indentLevel: startIndent, + } + + isPreviousNodeInline := true + for _, child := range htmlDoc.elements { + isPreviousNodeInline = child.write(bf, isPreviousNodeInline) + } + return bf.buffer.Bytes() +} + +// append appends an element to the htmlDocument. +func (htmlDoc *htmlDocument) append(e element) { + htmlDoc.elements = append(htmlDoc.elements, e) +} diff --git a/server/internal/library/hggen/views/gohtml/parser.go b/server/internal/library/hggen/views/gohtml/parser.go new file mode 100644 index 0000000..2ca7444 --- /dev/null +++ b/server/internal/library/hggen/views/gohtml/parser.go @@ -0,0 +1,128 @@ +package gohtml + +import ( + "io" + "regexp" + "strings" + + "golang.org/x/net/html" +) + +// parse parses a stirng and converts it into an html. +func parse(r io.Reader) *htmlDocument { + htmlDoc := &htmlDocument{} + tokenizer := html.NewTokenizer(r) + for { + if errorToken, _, _ := parseToken(tokenizer, htmlDoc, nil); errorToken { + break + } + } + return htmlDoc +} + +// Function that identifies which tags will be treated as containing preformatted +// content. Such tags will have the formatting of all its contents preserved +// unchanged. +// The opening tag html.Token is passed to this function. +// By default, only
 and