From 4d08921f92ea0f7f5e380112b474ae6765dae332 Mon Sep 17 00:00:00 2001 From: Echo <1711788888@qq.com> Date: Fri, 28 Mar 2025 17:14:57 +0800 Subject: [PATCH] :sparkles: init project --- .idea/.gitignore | 8 + Dockerfile | 31 + api/v1/enter.go | 13 + api/v1/example/enter.go | 15 + api/v1/example/exa_attachment_category.go | 82 + api/v1/example/exa_breakpoint_continue.go | 150 + api/v1/example/exa_customer.go | 176 + api/v1/example/exa_file_upload_download.go | 135 + api/v1/system/auto_code_history.go | 115 + api/v1/system/auto_code_package.go | 100 + api/v1/system/auto_code_plugin.go | 119 + api/v1/system/auto_code_template.go | 121 + api/v1/system/enter.go | 47 + api/v1/system/sys_api.go | 323 + api/v1/system/sys_authority.go | 202 + api/v1/system/sys_authority_btn.go | 80 + api/v1/system/sys_auto_code.go | 155 + api/v1/system/sys_captcha.go | 70 + api/v1/system/sys_casbin.go | 69 + api/v1/system/sys_dictionary.go | 129 + api/v1/system/sys_dictionary_detail.go | 148 + api/v1/system/sys_export_template.go | 428 + api/v1/system/sys_initdb.go | 59 + api/v1/system/sys_jwt_blacklist.go | 33 + api/v1/system/sys_menu.go | 265 + api/v1/system/sys_operation_record.go | 149 + api/v1/system/sys_params.go | 171 + api/v1/system/sys_system.go | 88 + api/v1/system/sys_user.go | 483 + config.docker.yaml | 220 + config.yaml | 277 + config/auto_code.go | 22 + config/captcha.go | 9 + config/config.go | 37 + config/cors.go | 14 + config/db_list.go | 52 + config/disk.go | 9 + config/email.go | 11 + config/excel.go | 5 + config/gorm_mssql.go | 10 + config/gorm_mysql.go | 9 + config/gorm_oracle.go | 10 + config/gorm_pgsql.go | 17 + config/gorm_sqlite.go | 13 + config/jwt.go | 8 + config/mongo.go | 41 + config/oss_aliyun.go | 10 + config/oss_aws.go | 13 + config/oss_cloudflare.go | 10 + config/oss_huawei.go | 9 + config/oss_local.go | 6 + config/oss_minio.go | 11 + config/oss_qiniu.go | 11 + config/oss_tencent.go | 10 + config/redis.go | 10 + config/system.go | 14 + config/zap.go | 71 + core/internal/constant.go | 9 + core/internal/cutter.go | 121 + core/internal/zap_core.go | 68 + core/server.go | 55 + core/server_other.go | 19 + core/server_win.go | 21 + core/viper.go | 71 + core/zap.go | 32 + docs/docs.go | 9311 +++++++++++++++++ docs/swagger.json | 9286 ++++++++++++++++ docs/swagger.yaml | 5677 ++++++++++ global/global.go | 66 + global/model.go | 14 + go.mod | 177 + go.sum | 593 ++ initialize/db_list.go | 36 + initialize/ensure_tables.go | 113 + initialize/gorm.go | 78 + initialize/gorm_biz.go | 14 + initialize/gorm_mssql.go | 60 + initialize/gorm_mysql.go | 55 + initialize/gorm_oracle.go | 52 + initialize/gorm_pgsql.go | 50 + initialize/gorm_sqlite.go | 42 + initialize/internal/gorm.go | 46 + initialize/internal/gorm_logger_writer.go | 41 + initialize/internal/mongo.go | 29 + initialize/mongo.go | 155 + initialize/other.go | 32 + initialize/plugin.go | 15 + initialize/plugin_biz_v1.go | 34 + initialize/plugin_biz_v2.go | 16 + initialize/redis.go | 59 + initialize/register_init.go | 10 + initialize/router.go | 111 + initialize/router_biz.go | 19 + initialize/timer.go | 37 + initialize/validator.go | 22 + main.go | 45 + middleware/casbin_rbac.go | 36 + middleware/cors.go | 73 + middleware/email.go | 60 + middleware/error.go | 61 + middleware/jwt.go | 80 + middleware/limit_ip.go | 92 + middleware/loadtls.go | 27 + middleware/logger.go | 89 + middleware/operation.go | 133 + model/common/basetypes.go | 43 + model/common/clearDB.go | 7 + model/common/request/common.go | 48 + model/common/response/common.go | 8 + model/common/response/response.go | 63 + model/example/exa_attachment_category.go | 16 + model/example/exa_breakpoint_continue.go | 24 + model/example/exa_customer.go | 15 + model/example/exa_file_upload_download.go | 18 + .../request/exa_file_upload_and_downloads.go | 10 + .../response/exa_breakpoint_continue.go | 11 + model/example/response/exa_customer.go | 7 + .../response/exa_file_upload_download.go | 7 + model/system/request/jwt.go | 21 + model/system/request/sys_api.go | 14 + model/system/request/sys_authority_btn.go | 7 + model/system/request/sys_auto_code.go | 286 + model/system/request/sys_auto_code_package.go | 31 + model/system/request/sys_auto_history.go | 57 + model/system/request/sys_casbin.go | 27 + model/system/request/sys_dictionary_detail.go | 11 + model/system/request/sys_export_template.go | 14 + model/system/request/sys_init.go | 124 + model/system/request/sys_menu.go | 27 + model/system/request/sys_operation_record.go | 11 + model/system/request/sys_params.go | 14 + model/system/request/sys_user.go | 65 + model/system/response/sys_api.go | 18 + model/system/response/sys_authority.go | 12 + model/system/response/sys_authority_btn.go | 5 + model/system/response/sys_auto_code.go | 17 + model/system/response/sys_captcha.go | 8 + model/system/response/sys_casbin.go | 9 + model/system/response/sys_menu.go | 15 + model/system/response/sys_system.go | 7 + model/system/response/sys_user.go | 15 + model/system/sys_api.go | 28 + model/system/sys_authority.go | 23 + model/system/sys_authority_btn.go | 8 + model/system/sys_authority_menu.go | 19 + model/system/sys_auto_code_history.go | 68 + model/system/sys_auto_code_package.go | 18 + model/system/sys_base_menu.go | 42 + model/system/sys_dictionary.go | 20 + model/system/sys_dictionary_detail.go | 21 + model/system/sys_export_template.go | 44 + model/system/sys_jwt_blacklist.go | 10 + model/system/sys_menu_btn.go | 10 + model/system/sys_operation_record.go | 24 + model/system/sys_params.go | 20 + model/system/sys_system.go | 10 + model/system/sys_user.go | 62 + model/system/sys_user_authority.go | 11 + plugin/announcement/api/enter.go | 10 + plugin/announcement/api/info.go | 183 + plugin/announcement/config/config.go | 4 + plugin/announcement/gen/gen.go | 17 + plugin/announcement/initialize/api.go | 49 + plugin/announcement/initialize/gorm.go | 20 + plugin/announcement/initialize/menu.go | 22 + plugin/announcement/initialize/router.go | 15 + plugin/announcement/initialize/viper.go | 17 + plugin/announcement/model/info.go | 20 + plugin/announcement/model/request/info.go | 12 + plugin/announcement/plugin.go | 26 + plugin/announcement/plugin/plugin.go | 5 + plugin/announcement/router/enter.go | 10 + plugin/announcement/router/info.go | 31 + plugin/announcement/service/enter.go | 5 + plugin/announcement/service/info.go | 78 + plugin/email/README.MD | 75 + plugin/email/api/enter.go | 7 + plugin/email/api/sys_email.go | 53 + plugin/email/config/email.go | 11 + plugin/email/global/gloabl.go | 5 + plugin/email/main.go | 28 + plugin/email/model/response/email.go | 7 + plugin/email/router/enter.go | 7 + plugin/email/router/sys_email.go | 19 + plugin/email/service/enter.go | 7 + plugin/email/service/sys_email.go | 32 + plugin/email/utils/email.go | 82 + plugin/plugin-tool/utils/check.go | 50 + resource/function/api.go.tpl | 44 + resource/function/api.js.tpl | 32 + resource/function/server.go.tpl | 25 + resource/package/readme.txt.tpl | 7 + resource/package/server/api/api.go.tpl | 260 + resource/package/server/api/enter.go.tpl | 4 + resource/package/server/model/model.go.tpl | 111 + .../server/model/request/request.go.tpl | 58 + resource/package/server/router/enter.go.tpl | 4 + resource/package/server/router/router.go.tpl | 42 + resource/package/server/service/enter.go.tpl | 4 + .../package/server/service/service.go.tpl | 251 + resource/package/web/api/api.js.tpl | 130 + resource/package/web/view/form.vue.tpl | 464 + resource/package/web/view/table.vue.tpl | 1363 +++ resource/plugin/server/api/api.go.template | 255 + resource/plugin/server/api/enter.go.template | 6 + .../plugin/server/config/config.go.template | 4 + resource/plugin/server/gen/gen.go.template | 18 + .../plugin/server/initialize/api.go.template | 12 + .../plugin/server/initialize/gorm.go.template | 17 + .../plugin/server/initialize/menu.go.template | 12 + .../server/initialize/router.go.template | 14 + .../server/initialize/viper.go.template | 17 + .../plugin/server/model/model.go.template | 112 + .../server/model/request/request.go.template | 57 + resource/plugin/server/plugin.go.template | 26 + .../plugin/server/plugin/plugin.go.template | 5 + .../plugin/server/router/enter.go.template | 6 + .../plugin/server/router/router.go.template | 46 + .../plugin/server/service/enter.go.template | 7 + .../plugin/server/service/service.go.template | 259 + resource/plugin/web/api/api.js.template | 127 + resource/plugin/web/form/form.vue.template | 464 + resource/plugin/web/view/view.vue.template | 1361 +++ router/enter.go | 13 + router/example/enter.go | 17 + router/example/exa_attachment_category.go | 16 + router/example/exa_customer.go | 22 + .../example/exa_file_upload_and_download.go | 22 + router/system/enter.go | 44 + router/system/sys_api.go | 33 + router/system/sys_authority.go | 23 + router/system/sys_authority_btn.go | 19 + router/system/sys_auto_code.go | 40 + router/system/sys_auto_code_history.go | 17 + router/system/sys_base.go | 16 + router/system/sys_casbin.go | 19 + router/system/sys_dictionary.go | 22 + router/system/sys_dictionary_detail.go | 22 + router/system/sys_export_template.go | 34 + router/system/sys_initdb.go | 15 + router/system/sys_jwt.go | 14 + router/system/sys_menu.go | 27 + router/system/sys_operation_record.go | 19 + router/system/sys_params.go | 25 + router/system/sys_system.go | 22 + router/system/sys_user.go | 28 + service/enter.go | 13 + service/example/enter.go | 7 + service/example/exa_attachment_category.go | 66 + service/example/exa_breakpoint_continue.go | 71 + service/example/exa_customer.go | 87 + service/example/exa_file_upload_download.go | 123 + service/system/auto_code_history.go | 217 + service/system/auto_code_package.go | 680 ++ service/system/auto_code_package_test.go | 105 + service/system/auto_code_plugin.go | 249 + service/system/auto_code_template.go | 452 + service/system/auto_code_template_test.go | 84 + service/system/enter.go | 24 + service/system/jwt_black_list.go | 84 + service/system/sys_api.go | 326 + service/system/sys_authority.go | 330 + service/system/sys_authority_btn.go | 60 + service/system/sys_auto_code_interface.go | 55 + service/system/sys_auto_code_mssql.go | 83 + service/system/sys_auto_code_mysql.go | 83 + service/system/sys_auto_code_oracle.go | 72 + service/system/sys_auto_code_pgsql.go | 135 + service/system/sys_auto_code_sqlite.go | 84 + service/system/sys_base_menu.go | 146 + service/system/sys_casbin.go | 221 + service/system/sys_dictionary.go | 112 + service/system/sys_dictionary_detail.go | 118 + service/system/sys_export_template.go | 424 + service/system/sys_initdb.go | 189 + service/system/sys_initdb_mssql.go | 92 + service/system/sys_initdb_mysql.go | 97 + service/system/sys_initdb_pgsql.go | 101 + service/system/sys_initdb_sqlite.go | 88 + service/system/sys_menu.go | 289 + service/system/sys_operation_record.go | 88 + service/system/sys_params.go | 82 + service/system/sys_system.go | 62 + service/system/sys_user.go | 317 + source/example/file_upload_download.go | 65 + source/system/api.go | 206 + source/system/api_ignore.go | 77 + source/system/authorities_menus.go | 84 + source/system/authority.go | 88 + source/system/casbin.go | 292 + source/system/dictionary.go | 71 + source/system/dictionary_detail.go | 121 + source/system/excel_template.go | 75 + source/system/menu.go | 102 + source/system/user.go | 106 + task/clearTable.go | 51 + utils/ast/ast.go | 231 + utils/ast/ast_auto_enter.go | 47 + utils/ast/ast_enter.go | 181 + utils/ast/ast_gorm.go | 166 + utils/ast/ast_init_test.go | 11 + utils/ast/ast_rollback.go | 173 + utils/ast/ast_router.go | 135 + utils/ast/ast_test.go | 32 + utils/ast/ast_type.go | 53 + utils/ast/import.go | 94 + utils/ast/interfaces.go | 17 + utils/ast/interfaces_base.go | 76 + utils/ast/package_enter.go | 85 + utils/ast/package_enter_test.go | 154 + utils/ast/package_initialize_gorm.go | 196 + utils/ast/package_initialize_gorm_test.go | 171 + utils/ast/package_initialize_router.go | 150 + utils/ast/package_initialize_router_test.go | 158 + utils/ast/package_module_enter.go | 180 + utils/ast/package_module_enter_test.go | 185 + utils/ast/plugin_enter.go | 167 + utils/ast/plugin_enter_test.go | 200 + utils/ast/plugin_gen.go | 189 + utils/ast/plugin_gen_test.go | 127 + utils/ast/plugin_initialize_gorm.go | 111 + utils/ast/plugin_initialize_gorm_test.go | 138 + utils/ast/plugin_initialize_router.go | 124 + utils/ast/plugin_initialize_router_test.go | 155 + utils/ast/plugin_initialize_v2.go | 52 + utils/ast/plugin_initialize_v2_test.go | 100 + utils/breakpoint_continue.go | 112 + utils/captcha/redis.go | 60 + utils/claims.go | 148 + utils/directory.go | 124 + utils/fmt_plus.go | 108 + utils/hash.go | 31 + utils/human_duration.go | 29 + utils/human_duration_test.go | 49 + utils/json.go | 34 + utils/json_test.go | 53 + utils/jwt.go | 87 + utils/plugin/plugin.go | 18 + utils/plugin/v2/plugin.go | 11 + utils/reload.go | 18 + utils/request/http.go | 62 + utils/server.go | 126 + utils/timer/timed_task.go | 229 + utils/timer/timed_task_test.go | 72 + utils/upload/aliyun_oss.go | 75 + utils/upload/aws_s3.go | 97 + utils/upload/cloudflare_r2.go | 85 + utils/upload/local.go | 109 + utils/upload/minio_oss.go | 99 + utils/upload/obs.go | 69 + utils/upload/qiniu.go | 96 + utils/upload/tencent_cos.go | 61 + utils/upload/upload.go | 46 + utils/validator.go | 294 + utils/validator_test.go | 37 + utils/verify.go | 19 + utils/zip.go | 53 + 357 files changed, 54458 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 Dockerfile create mode 100644 api/v1/enter.go create mode 100644 api/v1/example/enter.go create mode 100644 api/v1/example/exa_attachment_category.go create mode 100644 api/v1/example/exa_breakpoint_continue.go create mode 100644 api/v1/example/exa_customer.go create mode 100644 api/v1/example/exa_file_upload_download.go create mode 100644 api/v1/system/auto_code_history.go create mode 100644 api/v1/system/auto_code_package.go create mode 100644 api/v1/system/auto_code_plugin.go create mode 100644 api/v1/system/auto_code_template.go create mode 100644 api/v1/system/enter.go create mode 100644 api/v1/system/sys_api.go create mode 100644 api/v1/system/sys_authority.go create mode 100644 api/v1/system/sys_authority_btn.go create mode 100644 api/v1/system/sys_auto_code.go create mode 100644 api/v1/system/sys_captcha.go create mode 100644 api/v1/system/sys_casbin.go create mode 100644 api/v1/system/sys_dictionary.go create mode 100644 api/v1/system/sys_dictionary_detail.go create mode 100644 api/v1/system/sys_export_template.go create mode 100644 api/v1/system/sys_initdb.go create mode 100644 api/v1/system/sys_jwt_blacklist.go create mode 100644 api/v1/system/sys_menu.go create mode 100644 api/v1/system/sys_operation_record.go create mode 100644 api/v1/system/sys_params.go create mode 100644 api/v1/system/sys_system.go create mode 100644 api/v1/system/sys_user.go create mode 100644 config.docker.yaml create mode 100644 config.yaml create mode 100644 config/auto_code.go create mode 100644 config/captcha.go create mode 100644 config/config.go create mode 100644 config/cors.go create mode 100644 config/db_list.go create mode 100644 config/disk.go create mode 100644 config/email.go create mode 100644 config/excel.go create mode 100644 config/gorm_mssql.go create mode 100644 config/gorm_mysql.go create mode 100644 config/gorm_oracle.go create mode 100644 config/gorm_pgsql.go create mode 100644 config/gorm_sqlite.go create mode 100644 config/jwt.go create mode 100644 config/mongo.go create mode 100644 config/oss_aliyun.go create mode 100644 config/oss_aws.go create mode 100644 config/oss_cloudflare.go create mode 100644 config/oss_huawei.go create mode 100644 config/oss_local.go create mode 100644 config/oss_minio.go create mode 100644 config/oss_qiniu.go create mode 100644 config/oss_tencent.go create mode 100644 config/redis.go create mode 100644 config/system.go create mode 100644 config/zap.go create mode 100644 core/internal/constant.go create mode 100644 core/internal/cutter.go create mode 100644 core/internal/zap_core.go create mode 100644 core/server.go create mode 100644 core/server_other.go create mode 100644 core/server_win.go create mode 100644 core/viper.go create mode 100644 core/zap.go create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 global/global.go create mode 100644 global/model.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 initialize/db_list.go create mode 100644 initialize/ensure_tables.go create mode 100644 initialize/gorm.go create mode 100644 initialize/gorm_biz.go create mode 100644 initialize/gorm_mssql.go create mode 100644 initialize/gorm_mysql.go create mode 100644 initialize/gorm_oracle.go create mode 100644 initialize/gorm_pgsql.go create mode 100644 initialize/gorm_sqlite.go create mode 100644 initialize/internal/gorm.go create mode 100644 initialize/internal/gorm_logger_writer.go create mode 100644 initialize/internal/mongo.go create mode 100644 initialize/mongo.go create mode 100644 initialize/other.go create mode 100644 initialize/plugin.go create mode 100644 initialize/plugin_biz_v1.go create mode 100644 initialize/plugin_biz_v2.go create mode 100644 initialize/redis.go create mode 100644 initialize/register_init.go create mode 100644 initialize/router.go create mode 100644 initialize/router_biz.go create mode 100644 initialize/timer.go create mode 100644 initialize/validator.go create mode 100644 main.go create mode 100644 middleware/casbin_rbac.go create mode 100644 middleware/cors.go create mode 100644 middleware/email.go create mode 100644 middleware/error.go create mode 100644 middleware/jwt.go create mode 100644 middleware/limit_ip.go create mode 100644 middleware/loadtls.go create mode 100644 middleware/logger.go create mode 100644 middleware/operation.go create mode 100644 model/common/basetypes.go create mode 100644 model/common/clearDB.go create mode 100644 model/common/request/common.go create mode 100644 model/common/response/common.go create mode 100644 model/common/response/response.go create mode 100644 model/example/exa_attachment_category.go create mode 100644 model/example/exa_breakpoint_continue.go create mode 100644 model/example/exa_customer.go create mode 100644 model/example/exa_file_upload_download.go create mode 100644 model/example/request/exa_file_upload_and_downloads.go create mode 100644 model/example/response/exa_breakpoint_continue.go create mode 100644 model/example/response/exa_customer.go create mode 100644 model/example/response/exa_file_upload_download.go create mode 100644 model/system/request/jwt.go create mode 100644 model/system/request/sys_api.go create mode 100644 model/system/request/sys_authority_btn.go create mode 100644 model/system/request/sys_auto_code.go create mode 100644 model/system/request/sys_auto_code_package.go create mode 100644 model/system/request/sys_auto_history.go create mode 100644 model/system/request/sys_casbin.go create mode 100644 model/system/request/sys_dictionary_detail.go create mode 100644 model/system/request/sys_export_template.go create mode 100644 model/system/request/sys_init.go create mode 100644 model/system/request/sys_menu.go create mode 100644 model/system/request/sys_operation_record.go create mode 100644 model/system/request/sys_params.go create mode 100644 model/system/request/sys_user.go create mode 100644 model/system/response/sys_api.go create mode 100644 model/system/response/sys_authority.go create mode 100644 model/system/response/sys_authority_btn.go create mode 100644 model/system/response/sys_auto_code.go create mode 100644 model/system/response/sys_captcha.go create mode 100644 model/system/response/sys_casbin.go create mode 100644 model/system/response/sys_menu.go create mode 100644 model/system/response/sys_system.go create mode 100644 model/system/response/sys_user.go create mode 100644 model/system/sys_api.go create mode 100644 model/system/sys_authority.go create mode 100644 model/system/sys_authority_btn.go create mode 100644 model/system/sys_authority_menu.go create mode 100644 model/system/sys_auto_code_history.go create mode 100644 model/system/sys_auto_code_package.go create mode 100644 model/system/sys_base_menu.go create mode 100644 model/system/sys_dictionary.go create mode 100644 model/system/sys_dictionary_detail.go create mode 100644 model/system/sys_export_template.go create mode 100644 model/system/sys_jwt_blacklist.go create mode 100644 model/system/sys_menu_btn.go create mode 100644 model/system/sys_operation_record.go create mode 100644 model/system/sys_params.go create mode 100644 model/system/sys_system.go create mode 100644 model/system/sys_user.go create mode 100644 model/system/sys_user_authority.go create mode 100644 plugin/announcement/api/enter.go create mode 100644 plugin/announcement/api/info.go create mode 100644 plugin/announcement/config/config.go create mode 100644 plugin/announcement/gen/gen.go create mode 100644 plugin/announcement/initialize/api.go create mode 100644 plugin/announcement/initialize/gorm.go create mode 100644 plugin/announcement/initialize/menu.go create mode 100644 plugin/announcement/initialize/router.go create mode 100644 plugin/announcement/initialize/viper.go create mode 100644 plugin/announcement/model/info.go create mode 100644 plugin/announcement/model/request/info.go create mode 100644 plugin/announcement/plugin.go create mode 100644 plugin/announcement/plugin/plugin.go create mode 100644 plugin/announcement/router/enter.go create mode 100644 plugin/announcement/router/info.go create mode 100644 plugin/announcement/service/enter.go create mode 100644 plugin/announcement/service/info.go create mode 100644 plugin/email/README.MD create mode 100644 plugin/email/api/enter.go create mode 100644 plugin/email/api/sys_email.go create mode 100644 plugin/email/config/email.go create mode 100644 plugin/email/global/gloabl.go create mode 100644 plugin/email/main.go create mode 100644 plugin/email/model/response/email.go create mode 100644 plugin/email/router/enter.go create mode 100644 plugin/email/router/sys_email.go create mode 100644 plugin/email/service/enter.go create mode 100644 plugin/email/service/sys_email.go create mode 100644 plugin/email/utils/email.go create mode 100644 plugin/plugin-tool/utils/check.go create mode 100644 resource/function/api.go.tpl create mode 100644 resource/function/api.js.tpl create mode 100644 resource/function/server.go.tpl create mode 100644 resource/package/readme.txt.tpl create mode 100644 resource/package/server/api/api.go.tpl create mode 100644 resource/package/server/api/enter.go.tpl create mode 100644 resource/package/server/model/model.go.tpl create mode 100644 resource/package/server/model/request/request.go.tpl create mode 100644 resource/package/server/router/enter.go.tpl create mode 100644 resource/package/server/router/router.go.tpl create mode 100644 resource/package/server/service/enter.go.tpl create mode 100644 resource/package/server/service/service.go.tpl create mode 100644 resource/package/web/api/api.js.tpl create mode 100644 resource/package/web/view/form.vue.tpl create mode 100644 resource/package/web/view/table.vue.tpl create mode 100644 resource/plugin/server/api/api.go.template create mode 100644 resource/plugin/server/api/enter.go.template create mode 100644 resource/plugin/server/config/config.go.template create mode 100644 resource/plugin/server/gen/gen.go.template create mode 100644 resource/plugin/server/initialize/api.go.template create mode 100644 resource/plugin/server/initialize/gorm.go.template create mode 100644 resource/plugin/server/initialize/menu.go.template create mode 100644 resource/plugin/server/initialize/router.go.template create mode 100644 resource/plugin/server/initialize/viper.go.template create mode 100644 resource/plugin/server/model/model.go.template create mode 100644 resource/plugin/server/model/request/request.go.template create mode 100644 resource/plugin/server/plugin.go.template create mode 100644 resource/plugin/server/plugin/plugin.go.template create mode 100644 resource/plugin/server/router/enter.go.template create mode 100644 resource/plugin/server/router/router.go.template create mode 100644 resource/plugin/server/service/enter.go.template create mode 100644 resource/plugin/server/service/service.go.template create mode 100644 resource/plugin/web/api/api.js.template create mode 100644 resource/plugin/web/form/form.vue.template create mode 100644 resource/plugin/web/view/view.vue.template create mode 100644 router/enter.go create mode 100644 router/example/enter.go create mode 100644 router/example/exa_attachment_category.go create mode 100644 router/example/exa_customer.go create mode 100644 router/example/exa_file_upload_and_download.go create mode 100644 router/system/enter.go create mode 100644 router/system/sys_api.go create mode 100644 router/system/sys_authority.go create mode 100644 router/system/sys_authority_btn.go create mode 100644 router/system/sys_auto_code.go create mode 100644 router/system/sys_auto_code_history.go create mode 100644 router/system/sys_base.go create mode 100644 router/system/sys_casbin.go create mode 100644 router/system/sys_dictionary.go create mode 100644 router/system/sys_dictionary_detail.go create mode 100644 router/system/sys_export_template.go create mode 100644 router/system/sys_initdb.go create mode 100644 router/system/sys_jwt.go create mode 100644 router/system/sys_menu.go create mode 100644 router/system/sys_operation_record.go create mode 100644 router/system/sys_params.go create mode 100644 router/system/sys_system.go create mode 100644 router/system/sys_user.go create mode 100644 service/enter.go create mode 100644 service/example/enter.go create mode 100644 service/example/exa_attachment_category.go create mode 100644 service/example/exa_breakpoint_continue.go create mode 100644 service/example/exa_customer.go create mode 100644 service/example/exa_file_upload_download.go create mode 100644 service/system/auto_code_history.go create mode 100644 service/system/auto_code_package.go create mode 100644 service/system/auto_code_package_test.go create mode 100644 service/system/auto_code_plugin.go create mode 100644 service/system/auto_code_template.go create mode 100644 service/system/auto_code_template_test.go create mode 100644 service/system/enter.go create mode 100644 service/system/jwt_black_list.go create mode 100644 service/system/sys_api.go create mode 100644 service/system/sys_authority.go create mode 100644 service/system/sys_authority_btn.go create mode 100644 service/system/sys_auto_code_interface.go create mode 100644 service/system/sys_auto_code_mssql.go create mode 100644 service/system/sys_auto_code_mysql.go create mode 100644 service/system/sys_auto_code_oracle.go create mode 100644 service/system/sys_auto_code_pgsql.go create mode 100644 service/system/sys_auto_code_sqlite.go create mode 100644 service/system/sys_base_menu.go create mode 100644 service/system/sys_casbin.go create mode 100644 service/system/sys_dictionary.go create mode 100644 service/system/sys_dictionary_detail.go create mode 100644 service/system/sys_export_template.go create mode 100644 service/system/sys_initdb.go create mode 100644 service/system/sys_initdb_mssql.go create mode 100644 service/system/sys_initdb_mysql.go create mode 100644 service/system/sys_initdb_pgsql.go create mode 100644 service/system/sys_initdb_sqlite.go create mode 100644 service/system/sys_menu.go create mode 100644 service/system/sys_operation_record.go create mode 100644 service/system/sys_params.go create mode 100644 service/system/sys_system.go create mode 100644 service/system/sys_user.go create mode 100644 source/example/file_upload_download.go create mode 100644 source/system/api.go create mode 100644 source/system/api_ignore.go create mode 100644 source/system/authorities_menus.go create mode 100644 source/system/authority.go create mode 100644 source/system/casbin.go create mode 100644 source/system/dictionary.go create mode 100644 source/system/dictionary_detail.go create mode 100644 source/system/excel_template.go create mode 100644 source/system/menu.go create mode 100644 source/system/user.go create mode 100644 task/clearTable.go create mode 100644 utils/ast/ast.go create mode 100644 utils/ast/ast_auto_enter.go create mode 100644 utils/ast/ast_enter.go create mode 100644 utils/ast/ast_gorm.go create mode 100644 utils/ast/ast_init_test.go create mode 100644 utils/ast/ast_rollback.go create mode 100644 utils/ast/ast_router.go create mode 100644 utils/ast/ast_test.go create mode 100644 utils/ast/ast_type.go create mode 100644 utils/ast/import.go create mode 100644 utils/ast/interfaces.go create mode 100644 utils/ast/interfaces_base.go create mode 100644 utils/ast/package_enter.go create mode 100644 utils/ast/package_enter_test.go create mode 100644 utils/ast/package_initialize_gorm.go create mode 100644 utils/ast/package_initialize_gorm_test.go create mode 100644 utils/ast/package_initialize_router.go create mode 100644 utils/ast/package_initialize_router_test.go create mode 100644 utils/ast/package_module_enter.go create mode 100644 utils/ast/package_module_enter_test.go create mode 100644 utils/ast/plugin_enter.go create mode 100644 utils/ast/plugin_enter_test.go create mode 100644 utils/ast/plugin_gen.go create mode 100644 utils/ast/plugin_gen_test.go create mode 100644 utils/ast/plugin_initialize_gorm.go create mode 100644 utils/ast/plugin_initialize_gorm_test.go create mode 100644 utils/ast/plugin_initialize_router.go create mode 100644 utils/ast/plugin_initialize_router_test.go create mode 100644 utils/ast/plugin_initialize_v2.go create mode 100644 utils/ast/plugin_initialize_v2_test.go create mode 100644 utils/breakpoint_continue.go create mode 100644 utils/captcha/redis.go create mode 100644 utils/claims.go create mode 100644 utils/directory.go create mode 100644 utils/fmt_plus.go create mode 100644 utils/hash.go create mode 100644 utils/human_duration.go create mode 100644 utils/human_duration_test.go create mode 100644 utils/json.go create mode 100644 utils/json_test.go create mode 100644 utils/jwt.go create mode 100644 utils/plugin/plugin.go create mode 100644 utils/plugin/v2/plugin.go create mode 100644 utils/reload.go create mode 100644 utils/request/http.go create mode 100644 utils/server.go create mode 100644 utils/timer/timed_task.go create mode 100644 utils/timer/timed_task_test.go create mode 100644 utils/upload/aliyun_oss.go create mode 100644 utils/upload/aws_s3.go create mode 100644 utils/upload/cloudflare_r2.go create mode 100644 utils/upload/local.go create mode 100644 utils/upload/minio_oss.go create mode 100644 utils/upload/obs.go create mode 100644 utils/upload/qiniu.go create mode 100644 utils/upload/tencent_cos.go create mode 100644 utils/upload/upload.go create mode 100644 utils/validator.go create mode 100644 utils/validator_test.go create mode 100644 utils/verify.go create mode 100644 utils/zip.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..095a80d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM golang:alpine as builder + +WORKDIR /go/src/git.echol.cn/loser/xiecheng_server +COPY . . + +RUN go env -w GO111MODULE=on \ + && go env -w GOPROXY=https://goproxy.cn,direct \ + && go env -w CGO_ENABLED=0 \ + && go env \ + && go mod tidy \ + && go build -o server . + +FROM alpine:latest + +LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com" +# 设置时区 +ENV TZ=Asia/Shanghai +RUN apk update && apk add --no-cache tzdata openntpd \ + && ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +WORKDIR /go/src/git.echol.cn/loser/xiecheng_server + +COPY --from=0 /go/src/git.echol.cn/loser/xiecheng_server/server ./ +COPY --from=0 /go/src/git.echol.cn/loser/xiecheng_server/resource ./resource/ +COPY --from=0 /go/src/git.echol.cn/loser/xiecheng_server/config.docker.yaml ./ + +# 挂载目录:如果使用了sqlite数据库,容器命令示例:docker run -d -v /宿主机路径/gva.db:/go/src/git.echol.cn/loser/xiecheng_server/gva.db -p 8888:8888 --name gva-server-v1 gva-server:1.0 +# VOLUME ["/go/src/git.echol.cn/loser/xiecheng_server"] + +EXPOSE 8888 +ENTRYPOINT ./server -c config.docker.yaml diff --git a/api/v1/enter.go b/api/v1/enter.go new file mode 100644 index 0000000..416edae --- /dev/null +++ b/api/v1/enter.go @@ -0,0 +1,13 @@ +package v1 + +import ( + "git.echol.cn/loser/xiecheng_server/api/v1/example" + "git.echol.cn/loser/xiecheng_server/api/v1/system" +) + +var ApiGroupApp = new(ApiGroup) + +type ApiGroup struct { + SystemApiGroup system.ApiGroup + ExampleApiGroup example.ApiGroup +} diff --git a/api/v1/example/enter.go b/api/v1/example/enter.go new file mode 100644 index 0000000..2ecdc9d --- /dev/null +++ b/api/v1/example/enter.go @@ -0,0 +1,15 @@ +package example + +import "git.echol.cn/loser/xiecheng_server/service" + +type ApiGroup struct { + CustomerApi + FileUploadAndDownloadApi + AttachmentCategoryApi +} + +var ( + customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService + fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService + attachmentCategoryService = service.ServiceGroupApp.ExampleServiceGroup.AttachmentCategoryService +) diff --git a/api/v1/example/exa_attachment_category.go b/api/v1/example/exa_attachment_category.go new file mode 100644 index 0000000..552d86e --- /dev/null +++ b/api/v1/example/exa_attachment_category.go @@ -0,0 +1,82 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" + common "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/example" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AttachmentCategoryApi struct{} + +// GetCategoryList +// @Tags GetCategoryList +// @Summary 媒体库分类列表 +// @Security AttachmentCategory +// @Produce application/json +// @Success 200 {object} response.Response{data=example.ExaAttachmentCategory,msg=string} "媒体库分类列表" +// @Router /attachmentCategory/getCategoryList [get] +func (a *AttachmentCategoryApi) GetCategoryList(c *gin.Context) { + res, err := attachmentCategoryService.GetCategoryList() + if err != nil { + global.GVA_LOG.Error("获取分类列表失败!", zap.Error(err)) + response.FailWithMessage("获取分类列表失败", c) + return + } + response.OkWithData(res, c) +} + +// AddCategory +// @Tags AddCategory +// @Summary 添加媒体库分类 +// @Security AttachmentCategory +// @accept application/json +// @Produce application/json +// @Param data body example.ExaAttachmentCategory true "媒体库分类数据"// @Success 200 {object} response.Response{msg=string} "添加媒体库分类" +// @Router /attachmentCategory/addCategory [post] +func (a *AttachmentCategoryApi) AddCategory(c *gin.Context) { + var req example.ExaAttachmentCategory + if err := c.ShouldBindJSON(&req); err != nil { + global.GVA_LOG.Error("参数错误!", zap.Error(err)) + response.FailWithMessage("参数错误", c) + return + } + + if err := attachmentCategoryService.AddCategory(&req); err != nil { + global.GVA_LOG.Error("创建/更新失败!", zap.Error(err)) + response.FailWithMessage("创建/更新失败:"+err.Error(), c) + return + } + response.OkWithMessage("创建/更新成功", c) +} + +// DeleteCategory +// @Tags DeleteCategory +// @Summary 删除分类 +// @Security AttachmentCategory +// @accept application/json +// @Produce application/json +// @Param data body common.GetById true "分类id" +// @Success 200 {object} response.Response{msg=string} "删除分类" +// @Router /attachmentCategory/deleteCategory [post] +func (a *AttachmentCategoryApi) DeleteCategory(c *gin.Context) { + var req common.GetById + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("参数错误", c) + return + } + + if req.ID == 0 { + response.FailWithMessage("参数错误", c) + return + } + + if err := attachmentCategoryService.DeleteCategory(&req.ID); err != nil { + response.FailWithMessage("删除失败", c) + return + } + + response.OkWithMessage("删除成功", c) +} diff --git a/api/v1/example/exa_breakpoint_continue.go b/api/v1/example/exa_breakpoint_continue.go new file mode 100644 index 0000000..d2e81c8 --- /dev/null +++ b/api/v1/example/exa_breakpoint_continue.go @@ -0,0 +1,150 @@ +package example + +import ( + "fmt" + "io" + "mime/multipart" + "strconv" + + "git.echol.cn/loser/xiecheng_server/model/example" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + exampleRes "git.echol.cn/loser/xiecheng_server/model/example/response" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// BreakpointContinue +// @Tags ExaFileUploadAndDownload +// @Summary 断点续传到服务器 +// @Security ApiKeyAuth +// @accept multipart/form-data +// @Produce application/json +// @Param file formData file true "an example for breakpoint resume, 断点续传示例" +// @Success 200 {object} response.Response{msg=string} "断点续传到服务器" +// @Router /fileUploadAndDownload/breakpointContinue [post] +func (b *FileUploadAndDownloadApi) BreakpointContinue(c *gin.Context) { + fileMd5 := c.Request.FormValue("fileMd5") + fileName := c.Request.FormValue("fileName") + chunkMd5 := c.Request.FormValue("chunkMd5") + chunkNumber, _ := strconv.Atoi(c.Request.FormValue("chunkNumber")) + chunkTotal, _ := strconv.Atoi(c.Request.FormValue("chunkTotal")) + _, FileHeader, err := c.Request.FormFile("file") + if err != nil { + global.GVA_LOG.Error("接收文件失败!", zap.Error(err)) + response.FailWithMessage("接收文件失败", c) + return + } + f, err := FileHeader.Open() + if err != nil { + global.GVA_LOG.Error("文件读取失败!", zap.Error(err)) + response.FailWithMessage("文件读取失败", c) + return + } + defer func(f multipart.File) { + err := f.Close() + if err != nil { + fmt.Println(err) + } + }(f) + cen, _ := io.ReadAll(f) + if !utils.CheckMd5(cen, chunkMd5) { + global.GVA_LOG.Error("检查md5失败!", zap.Error(err)) + response.FailWithMessage("检查md5失败", c) + return + } + file, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal) + if err != nil { + global.GVA_LOG.Error("查找或创建记录失败!", zap.Error(err)) + response.FailWithMessage("查找或创建记录失败", c) + return + } + pathC, err := utils.BreakPointContinue(cen, fileName, chunkNumber, chunkTotal, fileMd5) + if err != nil { + global.GVA_LOG.Error("断点续传失败!", zap.Error(err)) + response.FailWithMessage("断点续传失败", c) + return + } + + if err = fileUploadAndDownloadService.CreateFileChunk(file.ID, pathC, chunkNumber); err != nil { + global.GVA_LOG.Error("创建文件记录失败!", zap.Error(err)) + response.FailWithMessage("创建文件记录失败", c) + return + } + response.OkWithMessage("切片创建成功", c) +} + +// FindFile +// @Tags ExaFileUploadAndDownload +// @Summary 查找文件 +// @Security ApiKeyAuth +// @accept multipart/form-data +// @Produce application/json +// @Param file formData file true "Find the file, 查找文件" +// @Success 200 {object} response.Response{data=exampleRes.FileResponse,msg=string} "查找文件,返回包括文件详情" +// @Router /fileUploadAndDownload/findFile [get] +func (b *FileUploadAndDownloadApi) FindFile(c *gin.Context) { + fileMd5 := c.Query("fileMd5") + fileName := c.Query("fileName") + chunkTotal, _ := strconv.Atoi(c.Query("chunkTotal")) + file, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal) + if err != nil { + global.GVA_LOG.Error("查找失败!", zap.Error(err)) + response.FailWithMessage("查找失败", c) + } else { + response.OkWithDetailed(exampleRes.FileResponse{File: file}, "查找成功", c) + } +} + +// BreakpointContinueFinish +// @Tags ExaFileUploadAndDownload +// @Summary 创建文件 +// @Security ApiKeyAuth +// @accept multipart/form-data +// @Produce application/json +// @Param file formData file true "上传文件完成" +// @Success 200 {object} response.Response{data=exampleRes.FilePathResponse,msg=string} "创建文件,返回包括文件路径" +// @Router /fileUploadAndDownload/findFile [post] +func (b *FileUploadAndDownloadApi) BreakpointContinueFinish(c *gin.Context) { + fileMd5 := c.Query("fileMd5") + fileName := c.Query("fileName") + filePath, err := utils.MakeFile(fileName, fileMd5) + if err != nil { + global.GVA_LOG.Error("文件创建失败!", zap.Error(err)) + response.FailWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, "文件创建失败", c) + } else { + response.OkWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, "文件创建成功", c) + } +} + +// RemoveChunk +// @Tags ExaFileUploadAndDownload +// @Summary 删除切片 +// @Security ApiKeyAuth +// @accept multipart/form-data +// @Produce application/json +// @Param file formData file true "删除缓存切片" +// @Success 200 {object} response.Response{msg=string} "删除切片" +// @Router /fileUploadAndDownload/removeChunk [post] +func (b *FileUploadAndDownloadApi) RemoveChunk(c *gin.Context) { + var file example.ExaFile + err := c.ShouldBindJSON(&file) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.RemoveChunk(file.FileMd5) + if err != nil { + global.GVA_LOG.Error("缓存切片删除失败!", zap.Error(err)) + return + } + err = fileUploadAndDownloadService.DeleteFileChunk(file.FileMd5, file.FilePath) + if err != nil { + global.GVA_LOG.Error(err.Error(), zap.Error(err)) + response.FailWithMessage(err.Error(), c) + return + } + response.OkWithMessage("缓存切片删除成功", c) +} diff --git a/api/v1/example/exa_customer.go b/api/v1/example/exa_customer.go new file mode 100644 index 0000000..6fa6777 --- /dev/null +++ b/api/v1/example/exa_customer.go @@ -0,0 +1,176 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/example" + exampleRes "git.echol.cn/loser/xiecheng_server/model/example/response" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type CustomerApi struct{} + +// CreateExaCustomer +// @Tags ExaCustomer +// @Summary 创建客户 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body example.ExaCustomer true "客户用户名, 客户手机号码" +// @Success 200 {object} response.Response{msg=string} "创建客户" +// @Router /customer/customer [post] +func (e *CustomerApi) CreateExaCustomer(c *gin.Context) { + var customer example.ExaCustomer + err := c.ShouldBindJSON(&customer) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(customer, utils.CustomerVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + customer.SysUserID = utils.GetUserID(c) + customer.SysUserAuthorityID = utils.GetUserAuthorityId(c) + err = customerService.CreateExaCustomer(customer) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteExaCustomer +// @Tags ExaCustomer +// @Summary 删除客户 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body example.ExaCustomer true "客户ID" +// @Success 200 {object} response.Response{msg=string} "删除客户" +// @Router /customer/customer [delete] +func (e *CustomerApi) DeleteExaCustomer(c *gin.Context) { + var customer example.ExaCustomer + err := c.ShouldBindJSON(&customer) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(customer.GVA_MODEL, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = customerService.DeleteExaCustomer(customer) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// UpdateExaCustomer +// @Tags ExaCustomer +// @Summary 更新客户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body example.ExaCustomer true "客户ID, 客户信息" +// @Success 200 {object} response.Response{msg=string} "更新客户信息" +// @Router /customer/customer [put] +func (e *CustomerApi) UpdateExaCustomer(c *gin.Context) { + var customer example.ExaCustomer + err := c.ShouldBindJSON(&customer) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(customer.GVA_MODEL, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(customer, utils.CustomerVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = customerService.UpdateExaCustomer(&customer) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// GetExaCustomer +// @Tags ExaCustomer +// @Summary 获取单一客户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query example.ExaCustomer true "客户ID" +// @Success 200 {object} response.Response{data=exampleRes.ExaCustomerResponse,msg=string} "获取单一客户信息,返回包括客户详情" +// @Router /customer/customer [get] +func (e *CustomerApi) GetExaCustomer(c *gin.Context) { + var customer example.ExaCustomer + err := c.ShouldBindQuery(&customer) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(customer.GVA_MODEL, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + data, err := customerService.GetExaCustomer(customer.ID) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(exampleRes.ExaCustomerResponse{Customer: data}, "获取成功", c) +} + +// GetExaCustomerList +// @Tags ExaCustomer +// @Summary 分页获取权限客户列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "页码, 每页大小" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取权限客户列表,返回包括列表,总数,页码,每页数量" +// @Router /customer/customerList [get] +func (e *CustomerApi) GetExaCustomerList(c *gin.Context) { + var pageInfo request.PageInfo + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(pageInfo, utils.PageInfoVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + customerList, total, err := customerService.GetCustomerInfoList(utils.GetUserAuthorityId(c), pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败"+err.Error(), c) + return + } + response.OkWithDetailed(response.PageResult{ + List: customerList, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} diff --git a/api/v1/example/exa_file_upload_download.go b/api/v1/example/exa_file_upload_download.go new file mode 100644 index 0000000..be20511 --- /dev/null +++ b/api/v1/example/exa_file_upload_download.go @@ -0,0 +1,135 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/example" + "git.echol.cn/loser/xiecheng_server/model/example/request" + exampleRes "git.echol.cn/loser/xiecheng_server/model/example/response" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "strconv" +) + +type FileUploadAndDownloadApi struct{} + +// UploadFile +// @Tags ExaFileUploadAndDownload +// @Summary 上传文件示例 +// @Security ApiKeyAuth +// @accept multipart/form-data +// @Produce application/json +// @Param file formData file true "上传文件示例" +// @Success 200 {object} response.Response{data=exampleRes.ExaFileResponse,msg=string} "上传文件示例,返回包括文件详情" +// @Router /fileUploadAndDownload/upload [post] +func (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) { + var file example.ExaFileUploadAndDownload + noSave := c.DefaultQuery("noSave", "0") + _, header, err := c.Request.FormFile("file") + classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0")) + if err != nil { + global.GVA_LOG.Error("接收文件失败!", zap.Error(err)) + response.FailWithMessage("接收文件失败", c) + return + } + file, err = fileUploadAndDownloadService.UploadFile(header, noSave, classId) // 文件上传后拿到文件路径 + if err != nil { + global.GVA_LOG.Error("上传文件失败!", zap.Error(err)) + response.FailWithMessage("上传文件失败", c) + return + } + response.OkWithDetailed(exampleRes.ExaFileResponse{File: file}, "上传成功", c) +} + +// EditFileName 编辑文件名或者备注 +func (b *FileUploadAndDownloadApi) EditFileName(c *gin.Context) { + var file example.ExaFileUploadAndDownload + err := c.ShouldBindJSON(&file) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = fileUploadAndDownloadService.EditFileName(file) + if err != nil { + global.GVA_LOG.Error("编辑失败!", zap.Error(err)) + response.FailWithMessage("编辑失败", c) + return + } + response.OkWithMessage("编辑成功", c) +} + +// DeleteFile +// @Tags ExaFileUploadAndDownload +// @Summary 删除文件 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可" +// @Success 200 {object} response.Response{msg=string} "删除文件" +// @Router /fileUploadAndDownload/deleteFile [post] +func (b *FileUploadAndDownloadApi) DeleteFile(c *gin.Context) { + var file example.ExaFileUploadAndDownload + err := c.ShouldBindJSON(&file) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := fileUploadAndDownloadService.DeleteFile(file); err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// GetFileList +// @Tags ExaFileUploadAndDownload +// @Summary 分页文件列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.ExaAttachmentCategorySearch true "页码, 每页大小, 分类id" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量" +// @Router /fileUploadAndDownload/getFileList [post] +func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) { + var pageInfo request.ExaAttachmentCategorySearch + err := c.ShouldBindJSON(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := fileUploadAndDownloadService.GetFileRecordInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} + +// ImportURL +// @Tags ExaFileUploadAndDownload +// @Summary 导入URL +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body example.ExaFileUploadAndDownload true "对象" +// @Success 200 {object} response.Response{msg=string} "导入URL" +// @Router /fileUploadAndDownload/importURL [post] +func (b *FileUploadAndDownloadApi) ImportURL(c *gin.Context) { + var file []example.ExaFileUploadAndDownload + err := c.ShouldBindJSON(&file) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := fileUploadAndDownloadService.ImportURL(&file); err != nil { + global.GVA_LOG.Error("导入URL失败!", zap.Error(err)) + response.FailWithMessage("导入URL失败", c) + return + } + response.OkWithMessage("导入URL成功", c) +} diff --git a/api/v1/system/auto_code_history.go b/api/v1/system/auto_code_history.go new file mode 100644 index 0000000..d62ce81 --- /dev/null +++ b/api/v1/system/auto_code_history.go @@ -0,0 +1,115 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + common "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + request "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AutoCodeHistoryApi struct{} + +// First +// @Tags AutoCode +// @Summary 获取meta信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetById true "请求参数" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取meta信息" +// @Router /autoCode/getMeta [post] +func (a *AutoCodeHistoryApi) First(c *gin.Context) { + var info common.GetById + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + data, err := autoCodeHistoryService.First(c.Request.Context(), info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + response.OkWithDetailed(gin.H{"meta": data}, "获取成功", c) +} + +// Delete +// @Tags AutoCode +// @Summary 删除回滚记录 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetById true "请求参数" +// @Success 200 {object} response.Response{msg=string} "删除回滚记录" +// @Router /autoCode/delSysHistory [post] +func (a *AutoCodeHistoryApi) Delete(c *gin.Context) { + var info common.GetById + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = autoCodeHistoryService.Delete(c.Request.Context(), info) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// RollBack +// @Tags AutoCode +// @Summary 回滚自动生成代码 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.SysAutoHistoryRollBack true "请求参数" +// @Success 200 {object} response.Response{msg=string} "回滚自动生成代码" +// @Router /autoCode/rollback [post] +func (a *AutoCodeHistoryApi) RollBack(c *gin.Context) { + var info request.SysAutoHistoryRollBack + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = autoCodeHistoryService.RollBack(c.Request.Context(), info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + response.OkWithMessage("回滚成功", c) +} + +// GetList +// @Tags AutoCode +// @Summary 查询回滚记录 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body common.PageInfo true "请求参数" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "查询回滚记录,返回包括列表,总数,页码,每页数量" +// @Router /autoCode/getSysHistory [post] +func (a *AutoCodeHistoryApi) GetList(c *gin.Context) { + var info common.PageInfo + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := autoCodeHistoryService.GetList(c.Request.Context(), info) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: info.Page, + PageSize: info.PageSize, + }, "获取成功", c) +} diff --git a/api/v1/system/auto_code_package.go b/api/v1/system/auto_code_package.go new file mode 100644 index 0000000..d46887a --- /dev/null +++ b/api/v1/system/auto_code_package.go @@ -0,0 +1,100 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + common "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "strings" +) + +type AutoCodePackageApi struct{} + +// Create +// @Tags AutoCodePackage +// @Summary 创建package +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.SysAutoCodePackageCreate true "创建package" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功" +// @Router /autoCode/createPackage [post] +func (a *AutoCodePackageApi) Create(c *gin.Context) { + var info request.SysAutoCodePackageCreate + _ = c.ShouldBindJSON(&info) + if err := utils.Verify(info, utils.AutoPackageVerify); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if strings.Contains(info.PackageName, "\\") || strings.Contains(info.PackageName, "/") || strings.Contains(info.PackageName, "..") { + response.FailWithMessage("包名不合法", c) + return + } // PackageName可能导致路径穿越的问题 / 和 \ 都要防止 + err := autoCodePackageService.Create(c.Request.Context(), &info) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// Delete +// @Tags AutoCode +// @Summary 删除package +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body common.GetById true "创建package" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "删除package成功" +// @Router /autoCode/delPackage [post] +func (a *AutoCodePackageApi) Delete(c *gin.Context) { + var info common.GetById + _ = c.ShouldBindJSON(&info) + err := autoCodePackageService.Delete(c.Request.Context(), info) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// All +// @Tags AutoCodePackage +// @Summary 获取package +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功" +// @Router /autoCode/getPackage [post] +func (a *AutoCodePackageApi) All(c *gin.Context) { + data, err := autoCodePackageService.All(c.Request.Context()) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(gin.H{"pkgs": data}, "获取成功", c) +} + +// Templates +// @Tags AutoCodePackage +// @Summary 获取package +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功" +// @Router /autoCode/getTemplates [get] +func (a *AutoCodePackageApi) Templates(c *gin.Context) { + data, err := autoCodePackageService.Templates(c.Request.Context()) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(data, "获取成功", c) +} diff --git a/api/v1/system/auto_code_plugin.go b/api/v1/system/auto_code_plugin.go new file mode 100644 index 0000000..6b8ee87 --- /dev/null +++ b/api/v1/system/auto_code_plugin.go @@ -0,0 +1,119 @@ +package system + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AutoCodePluginApi struct{} + +// Install +// @Tags AutoCodePlugin +// @Summary 安装插件 +// @Security ApiKeyAuth +// @accept multipart/form-data +// @Produce application/json +// @Param plug formData file true "this is a test file" +// @Success 200 {object} response.Response{data=[]interface{},msg=string} "安装插件成功" +// @Router /autoCode/installPlugin [post] +func (a *AutoCodePluginApi) Install(c *gin.Context) { + header, err := c.FormFile("plug") + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + web, server, err := autoCodePluginService.Install(header) + webStr := "web插件安装成功" + serverStr := "server插件安装成功" + if web == -1 { + webStr = "web端插件未成功安装,请按照文档自行解压安装,如果为纯后端插件请忽略此条提示" + } + if server == -1 { + serverStr = "server端插件未成功安装,请按照文档自行解压安装,如果为纯前端插件请忽略此条提示" + } + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + response.OkWithData([]interface{}{ + gin.H{ + "code": web, + "msg": webStr, + }, + gin.H{ + "code": server, + "msg": serverStr, + }}, c) +} + +// Packaged +// @Tags AutoCodePlugin +// @Summary 打包插件 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param plugName query string true "插件名称" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功" +// @Router /autoCode/pubPlug [post] +func (a *AutoCodePluginApi) Packaged(c *gin.Context) { + plugName := c.Query("plugName") + zipPath, err := autoCodePluginService.PubPlug(plugName) + if err != nil { + global.GVA_LOG.Error("打包失败!", zap.Error(err)) + response.FailWithMessage("打包失败"+err.Error(), c) + return + } + response.OkWithMessage(fmt.Sprintf("打包成功,文件路径为:%s", zipPath), c) +} + +// InitMenu +// @Tags AutoCodePlugin +// @Summary 打包插件 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功" +// @Router /autoCode/initMenu [post] +func (a *AutoCodePluginApi) InitMenu(c *gin.Context) { + var menuInfo request.InitMenu + err := c.ShouldBindJSON(&menuInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = autoCodePluginService.InitMenu(menuInfo) + if err != nil { + global.GVA_LOG.Error("创建初始化Menu失败!", zap.Error(err)) + response.FailWithMessage("创建初始化Menu失败"+err.Error(), c) + return + } + response.OkWithMessage("文件变更成功", c) +} + +// InitAPI +// @Tags AutoCodePlugin +// @Summary 打包插件 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功" +// @Router /autoCode/initAPI [post] +func (a *AutoCodePluginApi) InitAPI(c *gin.Context) { + var apiInfo request.InitApi + err := c.ShouldBindJSON(&apiInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = autoCodePluginService.InitAPI(apiInfo) + if err != nil { + global.GVA_LOG.Error("创建初始化API失败!", zap.Error(err)) + response.FailWithMessage("创建初始化API失败"+err.Error(), c) + return + } + response.OkWithMessage("文件变更成功", c) +} diff --git a/api/v1/system/auto_code_template.go b/api/v1/system/auto_code_template.go new file mode 100644 index 0000000..8b338c3 --- /dev/null +++ b/api/v1/system/auto_code_template.go @@ -0,0 +1,121 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AutoCodeTemplateApi struct{} + +// Preview +// @Tags AutoCodeTemplate +// @Summary 预览创建后的代码 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.AutoCode true "预览创建代码" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "预览创建后的代码" +// @Router /autoCode/preview [post] +func (a *AutoCodeTemplateApi) Preview(c *gin.Context) { + var info request.AutoCode + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(info, utils.AutoCodeVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = info.Pretreatment() + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + info.PackageT = utils.FirstUpper(info.Package) + autoCode, err := autoCodeTemplateService.Preview(c.Request.Context(), info) + if err != nil { + global.GVA_LOG.Error(err.Error(), zap.Error(err)) + response.FailWithMessage("预览失败:"+err.Error(), c) + } else { + response.OkWithDetailed(gin.H{"autoCode": autoCode}, "预览成功", c) + } +} + +// Create +// @Tags AutoCodeTemplate +// @Summary 自动代码模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.AutoCode true "创建自动代码" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /autoCode/createTemp [post] +func (a *AutoCodeTemplateApi) Create(c *gin.Context) { + var info request.AutoCode + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(info, utils.AutoCodeVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = info.Pretreatment() + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = autoCodeTemplateService.Create(c.Request.Context(), info) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage(err.Error(), c) + } else { + response.OkWithMessage("创建成功", c) + } +} + +// AddFunc +// @Tags AddFunc +// @Summary 增加方法 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.AutoCode true "增加方法" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /autoCode/addFunc [post] +func (a *AutoCodeTemplateApi) AddFunc(c *gin.Context) { + var info request.AutoFunc + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + var tempMap map[string]string + if info.IsPreview { + info.Router = "填充router" + info.FuncName = "填充funcName" + info.Method = "填充method" + info.Description = "填充description" + tempMap, err = autoCodeTemplateService.GetApiAndServer(info) + } else { + err = autoCodeTemplateService.AddFunc(info) + } + if err != nil { + global.GVA_LOG.Error("注入失败!", zap.Error(err)) + response.FailWithMessage("注入失败", c) + } else { + if info.IsPreview { + response.OkWithDetailed(tempMap, "注入成功", c) + return + } + response.OkWithMessage("注入成功", c) + } +} diff --git a/api/v1/system/enter.go b/api/v1/system/enter.go new file mode 100644 index 0000000..47d7e04 --- /dev/null +++ b/api/v1/system/enter.go @@ -0,0 +1,47 @@ +package system + +import "git.echol.cn/loser/xiecheng_server/service" + +type ApiGroup struct { + DBApi + JwtApi + BaseApi + SystemApi + CasbinApi + AutoCodeApi + SystemApiApi + AuthorityApi + DictionaryApi + AuthorityMenuApi + OperationRecordApi + DictionaryDetailApi + AuthorityBtnApi + SysExportTemplateApi + AutoCodePluginApi + AutoCodePackageApi + AutoCodeHistoryApi + AutoCodeTemplateApi + SysParamsApi +} + +var ( + apiService = service.ServiceGroupApp.SystemServiceGroup.ApiService + jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService + menuService = service.ServiceGroupApp.SystemServiceGroup.MenuService + userService = service.ServiceGroupApp.SystemServiceGroup.UserService + initDBService = service.ServiceGroupApp.SystemServiceGroup.InitDBService + casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService + baseMenuService = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService + authorityService = service.ServiceGroupApp.SystemServiceGroup.AuthorityService + dictionaryService = service.ServiceGroupApp.SystemServiceGroup.DictionaryService + authorityBtnService = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService + systemConfigService = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService + sysParamsService = service.ServiceGroupApp.SystemServiceGroup.SysParamsService + operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService + dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService + autoCodeService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService + autoCodePluginService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePlugin + autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage + autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory + autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate +) diff --git a/api/v1/system/sys_api.go b/api/v1/system/sys_api.go new file mode 100644 index 0000000..e67d2b6 --- /dev/null +++ b/api/v1/system/sys_api.go @@ -0,0 +1,323 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "git.echol.cn/loser/xiecheng_server/utils" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type SystemApiApi struct{} + +// CreateApi +// @Tags SysApi +// @Summary 创建基础api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysApi true "api路径, api中文描述, api组, 方法" +// @Success 200 {object} response.Response{msg=string} "创建基础api" +// @Router /api/createApi [post] +func (s *SystemApiApi) CreateApi(c *gin.Context) { + var api system.SysApi + err := c.ShouldBindJSON(&api) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(api, utils.ApiVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = apiService.CreateApi(api) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// SyncApi +// @Tags SysApi +// @Summary 同步API +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "同步API" +// @Router /api/syncApi [get] +func (s *SystemApiApi) SyncApi(c *gin.Context) { + newApis, deleteApis, ignoreApis, err := apiService.SyncApi() + if err != nil { + global.GVA_LOG.Error("同步失败!", zap.Error(err)) + response.FailWithMessage("同步失败", c) + return + } + response.OkWithData(gin.H{ + "newApis": newApis, + "deleteApis": deleteApis, + "ignoreApis": ignoreApis, + }, c) +} + +// GetApiGroups +// @Tags SysApi +// @Summary 获取API分组 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "获取API分组" +// @Router /api/getApiGroups [get] +func (s *SystemApiApi) GetApiGroups(c *gin.Context) { + groups, apiGroupMap, err := apiService.GetApiGroups() + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithData(gin.H{ + "groups": groups, + "apiGroupMap": apiGroupMap, + }, c) +} + +// IgnoreApi +// @Tags IgnoreApi +// @Summary 忽略API +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "同步API" +// @Router /api/ignoreApi [post] +func (s *SystemApiApi) IgnoreApi(c *gin.Context) { + var ignoreApi system.SysIgnoreApi + err := c.ShouldBindJSON(&ignoreApi) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = apiService.IgnoreApi(ignoreApi) + if err != nil { + global.GVA_LOG.Error("忽略失败!", zap.Error(err)) + response.FailWithMessage("忽略失败", c) + return + } + response.Ok(c) +} + +// EnterSyncApi +// @Tags SysApi +// @Summary 确认同步API +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "确认同步API" +// @Router /api/enterSyncApi [post] +func (s *SystemApiApi) EnterSyncApi(c *gin.Context) { + var syncApi systemRes.SysSyncApis + err := c.ShouldBindJSON(&syncApi) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = apiService.EnterSyncApi(syncApi) + if err != nil { + global.GVA_LOG.Error("忽略失败!", zap.Error(err)) + response.FailWithMessage("忽略失败", c) + return + } + response.Ok(c) +} + +// DeleteApi +// @Tags SysApi +// @Summary 删除api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysApi true "ID" +// @Success 200 {object} response.Response{msg=string} "删除api" +// @Router /api/deleteApi [post] +func (s *SystemApiApi) DeleteApi(c *gin.Context) { + var api system.SysApi + err := c.ShouldBindJSON(&api) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(api.GVA_MODEL, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = apiService.DeleteApi(api) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// GetApiList +// @Tags SysApi +// @Summary 分页获取API列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.SearchApiParams true "分页获取API列表" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取API列表,返回包括列表,总数,页码,每页数量" +// @Router /api/getApiList [post] +func (s *SystemApiApi) GetApiList(c *gin.Context) { + var pageInfo systemReq.SearchApiParams + err := c.ShouldBindJSON(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(pageInfo.PageInfo, utils.PageInfoVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := apiService.GetAPIInfoList(pageInfo.SysApi, pageInfo.PageInfo, pageInfo.OrderKey, pageInfo.Desc) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} + +// GetApiById +// @Tags SysApi +// @Summary 根据id获取api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetById true "根据id获取api" +// @Success 200 {object} response.Response{data=systemRes.SysAPIResponse} "根据id获取api,返回包括api详情" +// @Router /api/getApiById [post] +func (s *SystemApiApi) GetApiById(c *gin.Context) { + var idInfo request.GetById + err := c.ShouldBindJSON(&idInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(idInfo, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + api, err := apiService.GetApiById(idInfo.ID) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(systemRes.SysAPIResponse{Api: api}, "获取成功", c) +} + +// UpdateApi +// @Tags SysApi +// @Summary 修改基础api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysApi true "api路径, api中文描述, api组, 方法" +// @Success 200 {object} response.Response{msg=string} "修改基础api" +// @Router /api/updateApi [post] +func (s *SystemApiApi) UpdateApi(c *gin.Context) { + var api system.SysApi + err := c.ShouldBindJSON(&api) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(api, utils.ApiVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = apiService.UpdateApi(api) + if err != nil { + global.GVA_LOG.Error("修改失败!", zap.Error(err)) + response.FailWithMessage("修改失败", c) + return + } + response.OkWithMessage("修改成功", c) +} + +// GetAllApis +// @Tags SysApi +// @Summary 获取所有的Api 不分页 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=systemRes.SysAPIListResponse,msg=string} "获取所有的Api 不分页,返回包括api列表" +// @Router /api/getAllApis [post] +func (s *SystemApiApi) GetAllApis(c *gin.Context) { + authorityID := utils.GetUserAuthorityId(c) + apis, err := apiService.GetAllApis(authorityID) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(systemRes.SysAPIListResponse{Apis: apis}, "获取成功", c) +} + +// DeleteApisByIds +// @Tags SysApi +// @Summary 删除选中Api +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "ID" +// @Success 200 {object} response.Response{msg=string} "删除选中Api" +// @Router /api/deleteApisByIds [delete] +func (s *SystemApiApi) DeleteApisByIds(c *gin.Context) { + var ids request.IdsReq + err := c.ShouldBindJSON(&ids) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = apiService.DeleteApisByIds(ids) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// FreshCasbin +// @Tags SysApi +// @Summary 刷新casbin缓存 +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "刷新成功" +// @Router /api/freshCasbin [get] +func (s *SystemApiApi) FreshCasbin(c *gin.Context) { + err := casbinService.FreshCasbin() + if err != nil { + global.GVA_LOG.Error("刷新失败!", zap.Error(err)) + response.FailWithMessage("刷新失败", c) + return + } + response.OkWithMessage("刷新成功", c) +} diff --git a/api/v1/system/sys_authority.go b/api/v1/system/sys_authority.go new file mode 100644 index 0000000..a63917a --- /dev/null +++ b/api/v1/system/sys_authority.go @@ -0,0 +1,202 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "git.echol.cn/loser/xiecheng_server/utils" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AuthorityApi struct{} + +// CreateAuthority +// @Tags Authority +// @Summary 创建角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysAuthority true "权限id, 权限名, 父角色id" +// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "创建角色,返回包括系统角色详情" +// @Router /authority/createAuthority [post] +func (a *AuthorityApi) CreateAuthority(c *gin.Context) { + var authority, authBack system.SysAuthority + var err error + + if err = c.ShouldBindJSON(&authority); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err = utils.Verify(authority, utils.AuthorityVerify); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if *authority.ParentId == 0 && global.GVA_CONFIG.System.UseStrictAuth { + authority.ParentId = utils.Pointer(utils.GetUserAuthorityId(c)) + } + + if authBack, err = authorityService.CreateAuthority(authority); err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败"+err.Error(), c) + return + } + err = casbinService.FreshCasbin() + if err != nil { + global.GVA_LOG.Error("创建成功,权限刷新失败。", zap.Error(err)) + response.FailWithMessage("创建成功,权限刷新失败。"+err.Error(), c) + return + } + response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "创建成功", c) +} + +// CopyAuthority +// @Tags Authority +// @Summary 拷贝角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body response.SysAuthorityCopyResponse true "旧角色id, 新权限id, 新权限名, 新父角色id" +// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "拷贝角色,返回包括系统角色详情" +// @Router /authority/copyAuthority [post] +func (a *AuthorityApi) CopyAuthority(c *gin.Context) { + var copyInfo systemRes.SysAuthorityCopyResponse + err := c.ShouldBindJSON(©Info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(copyInfo, utils.OldAuthorityVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(copyInfo.Authority, utils.AuthorityVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + adminAuthorityID := utils.GetUserAuthorityId(c) + authBack, err := authorityService.CopyAuthority(adminAuthorityID, copyInfo) + if err != nil { + global.GVA_LOG.Error("拷贝失败!", zap.Error(err)) + response.FailWithMessage("拷贝失败"+err.Error(), c) + return + } + response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "拷贝成功", c) +} + +// DeleteAuthority +// @Tags Authority +// @Summary 删除角色 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysAuthority true "删除角色" +// @Success 200 {object} response.Response{msg=string} "删除角色" +// @Router /authority/deleteAuthority [post] +func (a *AuthorityApi) DeleteAuthority(c *gin.Context) { + var authority system.SysAuthority + var err error + if err = c.ShouldBindJSON(&authority); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err = utils.Verify(authority, utils.AuthorityIdVerify); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + // 删除角色之前需要判断是否有用户正在使用此角色 + if err = authorityService.DeleteAuthority(&authority); err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败"+err.Error(), c) + return + } + _ = casbinService.FreshCasbin() + response.OkWithMessage("删除成功", c) +} + +// UpdateAuthority +// @Tags Authority +// @Summary 更新角色信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysAuthority true "权限id, 权限名, 父角色id" +// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "更新角色信息,返回包括系统角色详情" +// @Router /authority/updateAuthority [put] +func (a *AuthorityApi) UpdateAuthority(c *gin.Context) { + var auth system.SysAuthority + err := c.ShouldBindJSON(&auth) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(auth, utils.AuthorityVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + authority, err := authorityService.UpdateAuthority(auth) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败"+err.Error(), c) + return + } + response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authority}, "更新成功", c) +} + +// GetAuthorityList +// @Tags Authority +// @Summary 分页获取角色列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.PageInfo true "页码, 每页大小" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取角色列表,返回包括列表,总数,页码,每页数量" +// @Router /authority/getAuthorityList [post] +func (a *AuthorityApi) GetAuthorityList(c *gin.Context) { + authorityID := utils.GetUserAuthorityId(c) + list, err := authorityService.GetAuthorityInfoList(authorityID) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败"+err.Error(), c) + return + } + response.OkWithDetailed(list, "获取成功", c) +} + +// SetDataAuthority +// @Tags Authority +// @Summary 设置角色资源权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysAuthority true "设置角色资源权限" +// @Success 200 {object} response.Response{msg=string} "设置角色资源权限" +// @Router /authority/setDataAuthority [post] +func (a *AuthorityApi) SetDataAuthority(c *gin.Context) { + var auth system.SysAuthority + err := c.ShouldBindJSON(&auth) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(auth, utils.AuthorityIdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + adminAuthorityID := utils.GetUserAuthorityId(c) + err = authorityService.SetDataAuthority(adminAuthorityID, auth) + if err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败"+err.Error(), c) + return + } + response.OkWithMessage("设置成功", c) +} diff --git a/api/v1/system/sys_authority_btn.go b/api/v1/system/sys_authority_btn.go new file mode 100644 index 0000000..e4e188d --- /dev/null +++ b/api/v1/system/sys_authority_btn.go @@ -0,0 +1,80 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AuthorityBtnApi struct{} + +// GetAuthorityBtn +// @Tags AuthorityBtn +// @Summary 获取权限按钮 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.SysAuthorityBtnReq true "菜单id, 角色id, 选中的按钮id" +// @Success 200 {object} response.Response{data=response.SysAuthorityBtnRes,msg=string} "返回列表成功" +// @Router /authorityBtn/getAuthorityBtn [post] +func (a *AuthorityBtnApi) GetAuthorityBtn(c *gin.Context) { + var req request.SysAuthorityBtnReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + res, err := authorityBtnService.GetAuthorityBtn(req) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithDetailed(res, "查询成功", c) +} + +// SetAuthorityBtn +// @Tags AuthorityBtn +// @Summary 设置权限按钮 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.SysAuthorityBtnReq true "菜单id, 角色id, 选中的按钮id" +// @Success 200 {object} response.Response{msg=string} "返回列表成功" +// @Router /authorityBtn/setAuthorityBtn [post] +func (a *AuthorityBtnApi) SetAuthorityBtn(c *gin.Context) { + var req request.SysAuthorityBtnReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = authorityBtnService.SetAuthorityBtn(req) + if err != nil { + global.GVA_LOG.Error("分配失败!", zap.Error(err)) + response.FailWithMessage("分配失败", c) + return + } + response.OkWithMessage("分配成功", c) +} + +// CanRemoveAuthorityBtn +// @Tags AuthorityBtn +// @Summary 设置权限按钮 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "删除成功" +// @Router /authorityBtn/canRemoveAuthorityBtn [post] +func (a *AuthorityBtnApi) CanRemoveAuthorityBtn(c *gin.Context) { + id := c.Query("id") + err := authorityBtnService.CanRemoveAuthorityBtn(id) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage(err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} diff --git a/api/v1/system/sys_auto_code.go b/api/v1/system/sys_auto_code.go new file mode 100644 index 0000000..bc64c39 --- /dev/null +++ b/api/v1/system/sys_auto_code.go @@ -0,0 +1,155 @@ +package system + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/model/common" + "github.com/goccy/go-json" + "io" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/utils/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AutoCodeApi struct{} + +// GetDB +// @Tags AutoCode +// @Summary 获取当前所有数据库 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前所有数据库" +// @Router /autoCode/getDB [get] +func (autoApi *AutoCodeApi) GetDB(c *gin.Context) { + businessDB := c.Query("businessDB") + dbs, err := autoCodeService.Database(businessDB).GetDB(businessDB) + var dbList []map[string]interface{} + for _, db := range global.GVA_CONFIG.DBList { + var item = make(map[string]interface{}) + item["aliasName"] = db.AliasName + item["dbName"] = db.Dbname + item["disable"] = db.Disable + item["dbtype"] = db.Type + dbList = append(dbList, item) + } + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + } else { + response.OkWithDetailed(gin.H{"dbs": dbs, "dbList": dbList}, "获取成功", c) + } +} + +// GetTables +// @Tags AutoCode +// @Summary 获取当前数据库所有表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前数据库所有表" +// @Router /autoCode/getTables [get] +func (autoApi *AutoCodeApi) GetTables(c *gin.Context) { + dbName := c.Query("dbName") + businessDB := c.Query("businessDB") + if dbName == "" { + dbName = *global.GVA_ACTIVE_DBNAME + if businessDB != "" { + for _, db := range global.GVA_CONFIG.DBList { + if db.AliasName == businessDB { + dbName = db.Dbname + } + } + } + } + + tables, err := autoCodeService.Database(businessDB).GetTables(businessDB, dbName) + if err != nil { + global.GVA_LOG.Error("查询table失败!", zap.Error(err)) + response.FailWithMessage("查询table失败", c) + } else { + response.OkWithDetailed(gin.H{"tables": tables}, "获取成功", c) + } +} + +// GetColumn +// @Tags AutoCode +// @Summary 获取当前表所有字段 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前表所有字段" +// @Router /autoCode/getColumn [get] +func (autoApi *AutoCodeApi) GetColumn(c *gin.Context) { + businessDB := c.Query("businessDB") + dbName := c.Query("dbName") + if dbName == "" { + dbName = *global.GVA_ACTIVE_DBNAME + if businessDB != "" { + for _, db := range global.GVA_CONFIG.DBList { + if db.AliasName == businessDB { + dbName = db.Dbname + } + } + } + } + tableName := c.Query("tableName") + columns, err := autoCodeService.Database(businessDB).GetColumn(businessDB, tableName, dbName) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + } else { + response.OkWithDetailed(gin.H{"columns": columns}, "获取成功", c) + } +} + +func (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) { + var llm common.JSONMap + err := c.ShouldBindJSON(&llm) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if global.GVA_CONFIG.AutoCode.AiPath == "" { + response.FailWithMessage("请先前往插件市场个人中心获取AiPath并填入config.yaml中", c) + return + } + + path := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", fmt.Sprintf("api/chat/%s", llm["mode"])) + res, err := request.HttpRequest( + path, + "POST", + nil, + nil, + llm, + ) + if err != nil { + global.GVA_LOG.Error("大模型生成失败!", zap.Error(err)) + response.FailWithMessage("大模型生成失败"+err.Error(), c) + return + } + var resStruct response.Response + b, err := io.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + global.GVA_LOG.Error("大模型生成失败!", zap.Error(err)) + response.FailWithMessage("大模型生成失败"+err.Error(), c) + return + } + err = json.Unmarshal(b, &resStruct) + if err != nil { + global.GVA_LOG.Error("大模型生成失败!", zap.Error(err)) + response.FailWithMessage("大模型生成失败"+err.Error(), c) + return + } + + if resStruct.Code == 7 { + global.GVA_LOG.Error("大模型生成失败!"+resStruct.Msg, zap.Error(err)) + response.FailWithMessage("大模型生成失败"+resStruct.Msg, c) + return + } + response.OkWithData(resStruct.Data, c) +} diff --git a/api/v1/system/sys_captcha.go b/api/v1/system/sys_captcha.go new file mode 100644 index 0000000..bb4231c --- /dev/null +++ b/api/v1/system/sys_captcha.go @@ -0,0 +1,70 @@ +package system + +import ( + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "github.com/gin-gonic/gin" + "github.com/mojocn/base64Captcha" + "go.uber.org/zap" +) + +// 当开启多服务器部署时,替换下面的配置,使用redis共享存储验证码 +// var store = captcha.NewDefaultRedisStore() +var store = base64Captcha.DefaultMemStore + +type BaseApi struct{} + +// Captcha +// @Tags Base +// @Summary 生成验证码 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=systemRes.SysCaptchaResponse,msg=string} "生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码" +// @Router /base/captcha [post] +func (b *BaseApi) Captcha(c *gin.Context) { + // 判断验证码是否开启 + openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数 + openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间 + key := c.ClientIP() + v, ok := global.BlackCache.Get(key) + if !ok { + global.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut)) + } + + var oc bool + if openCaptcha == 0 || openCaptcha < interfaceToInt(v) { + oc = true + } + // 字符,公式,验证码配置 + // 生成默认数字的driver + driver := base64Captcha.NewDriverDigit(global.GVA_CONFIG.Captcha.ImgHeight, global.GVA_CONFIG.Captcha.ImgWidth, global.GVA_CONFIG.Captcha.KeyLong, 0.7, 80) + // cp := base64Captcha.NewCaptcha(driver, store.UseWithCtx(c)) // v8下使用redis + cp := base64Captcha.NewCaptcha(driver, store) + id, b64s, _, err := cp.Generate() + if err != nil { + global.GVA_LOG.Error("验证码获取失败!", zap.Error(err)) + response.FailWithMessage("验证码获取失败", c) + return + } + response.OkWithDetailed(systemRes.SysCaptchaResponse{ + CaptchaId: id, + PicPath: b64s, + CaptchaLength: global.GVA_CONFIG.Captcha.KeyLong, + OpenCaptcha: oc, + }, "验证码获取成功", c) +} + +// 类型转换 +func interfaceToInt(v interface{}) (i int) { + switch v := v.(type) { + case int: + i = v + default: + i = 0 + } + return +} diff --git a/api/v1/system/sys_casbin.go b/api/v1/system/sys_casbin.go new file mode 100644 index 0000000..6136e40 --- /dev/null +++ b/api/v1/system/sys_casbin.go @@ -0,0 +1,69 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system/request" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type CasbinApi struct{} + +// UpdateCasbin +// @Tags Casbin +// @Summary 更新角色api权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.CasbinInReceive true "权限id, 权限模型列表" +// @Success 200 {object} response.Response{msg=string} "更新角色api权限" +// @Router /casbin/UpdateCasbin [post] +func (cas *CasbinApi) UpdateCasbin(c *gin.Context) { + var cmr request.CasbinInReceive + err := c.ShouldBindJSON(&cmr) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(cmr, utils.AuthorityIdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + adminAuthorityID := utils.GetUserAuthorityId(c) + err = casbinService.UpdateCasbin(adminAuthorityID, cmr.AuthorityId, cmr.CasbinInfos) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// GetPolicyPathByAuthorityId +// @Tags Casbin +// @Summary 获取权限列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.CasbinInReceive true "权限id, 权限模型列表" +// @Success 200 {object} response.Response{data=systemRes.PolicyPathResponse,msg=string} "获取权限列表,返回包括casbin详情列表" +// @Router /casbin/getPolicyPathByAuthorityId [post] +func (cas *CasbinApi) GetPolicyPathByAuthorityId(c *gin.Context) { + var casbin request.CasbinInReceive + err := c.ShouldBindJSON(&casbin) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(casbin, utils.AuthorityIdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + paths := casbinService.GetPolicyPathByAuthorityId(casbin.AuthorityId) + response.OkWithDetailed(systemRes.PolicyPathResponse{Paths: paths}, "获取成功", c) +} diff --git a/api/v1/system/sys_dictionary.go b/api/v1/system/sys_dictionary.go new file mode 100644 index 0000000..e979ae3 --- /dev/null +++ b/api/v1/system/sys_dictionary.go @@ -0,0 +1,129 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type DictionaryApi struct{} + +// CreateSysDictionary +// @Tags SysDictionary +// @Summary 创建SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysDictionary true "SysDictionary模型" +// @Success 200 {object} response.Response{msg=string} "创建SysDictionary" +// @Router /sysDictionary/createSysDictionary [post] +func (s *DictionaryApi) CreateSysDictionary(c *gin.Context) { + var dictionary system.SysDictionary + err := c.ShouldBindJSON(&dictionary) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryService.CreateSysDictionary(dictionary) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteSysDictionary +// @Tags SysDictionary +// @Summary 删除SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysDictionary true "SysDictionary模型" +// @Success 200 {object} response.Response{msg=string} "删除SysDictionary" +// @Router /sysDictionary/deleteSysDictionary [delete] +func (s *DictionaryApi) DeleteSysDictionary(c *gin.Context) { + var dictionary system.SysDictionary + err := c.ShouldBindJSON(&dictionary) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryService.DeleteSysDictionary(dictionary) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// UpdateSysDictionary +// @Tags SysDictionary +// @Summary 更新SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysDictionary true "SysDictionary模型" +// @Success 200 {object} response.Response{msg=string} "更新SysDictionary" +// @Router /sysDictionary/updateSysDictionary [put] +func (s *DictionaryApi) UpdateSysDictionary(c *gin.Context) { + var dictionary system.SysDictionary + err := c.ShouldBindJSON(&dictionary) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryService.UpdateSysDictionary(&dictionary) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// FindSysDictionary +// @Tags SysDictionary +// @Summary 用id查询SysDictionary +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query system.SysDictionary true "ID或字典英名" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "用id查询SysDictionary" +// @Router /sysDictionary/findSysDictionary [get] +func (s *DictionaryApi) FindSysDictionary(c *gin.Context) { + var dictionary system.SysDictionary + err := c.ShouldBindQuery(&dictionary) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + sysDictionary, err := dictionaryService.GetSysDictionary(dictionary.Type, dictionary.ID, dictionary.Status) + if err != nil { + global.GVA_LOG.Error("字典未创建或未开启!", zap.Error(err)) + response.FailWithMessage("字典未创建或未开启", c) + return + } + response.OkWithDetailed(gin.H{"resysDictionary": sysDictionary}, "查询成功", c) +} + +// GetSysDictionaryList +// @Tags SysDictionary +// @Summary 分页获取SysDictionary列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量" +// @Router /sysDictionary/getSysDictionaryList [get] +func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) { + list, err := dictionaryService.GetSysDictionaryInfoList() + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(list, "获取成功", c) +} diff --git a/api/v1/system/sys_dictionary_detail.go b/api/v1/system/sys_dictionary_detail.go new file mode 100644 index 0000000..4046725 --- /dev/null +++ b/api/v1/system/sys_dictionary_detail.go @@ -0,0 +1,148 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type DictionaryDetailApi struct{} + +// CreateSysDictionaryDetail +// @Tags SysDictionaryDetail +// @Summary 创建SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysDictionaryDetail true "SysDictionaryDetail模型" +// @Success 200 {object} response.Response{msg=string} "创建SysDictionaryDetail" +// @Router /sysDictionaryDetail/createSysDictionaryDetail [post] +func (s *DictionaryDetailApi) CreateSysDictionaryDetail(c *gin.Context) { + var detail system.SysDictionaryDetail + err := c.ShouldBindJSON(&detail) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryDetailService.CreateSysDictionaryDetail(detail) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteSysDictionaryDetail +// @Tags SysDictionaryDetail +// @Summary 删除SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysDictionaryDetail true "SysDictionaryDetail模型" +// @Success 200 {object} response.Response{msg=string} "删除SysDictionaryDetail" +// @Router /sysDictionaryDetail/deleteSysDictionaryDetail [delete] +func (s *DictionaryDetailApi) DeleteSysDictionaryDetail(c *gin.Context) { + var detail system.SysDictionaryDetail + err := c.ShouldBindJSON(&detail) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryDetailService.DeleteSysDictionaryDetail(detail) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// UpdateSysDictionaryDetail +// @Tags SysDictionaryDetail +// @Summary 更新SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysDictionaryDetail true "更新SysDictionaryDetail" +// @Success 200 {object} response.Response{msg=string} "更新SysDictionaryDetail" +// @Router /sysDictionaryDetail/updateSysDictionaryDetail [put] +func (s *DictionaryDetailApi) UpdateSysDictionaryDetail(c *gin.Context) { + var detail system.SysDictionaryDetail + err := c.ShouldBindJSON(&detail) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = dictionaryDetailService.UpdateSysDictionaryDetail(&detail) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// FindSysDictionaryDetail +// @Tags SysDictionaryDetail +// @Summary 用id查询SysDictionaryDetail +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query system.SysDictionaryDetail true "用id查询SysDictionaryDetail" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "用id查询SysDictionaryDetail" +// @Router /sysDictionaryDetail/findSysDictionaryDetail [get] +func (s *DictionaryDetailApi) FindSysDictionaryDetail(c *gin.Context) { + var detail system.SysDictionaryDetail + err := c.ShouldBindQuery(&detail) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(detail, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + reSysDictionaryDetail, err := dictionaryDetailService.GetSysDictionaryDetail(detail.ID) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithDetailed(gin.H{"reSysDictionaryDetail": reSysDictionaryDetail}, "查询成功", c) +} + +// GetSysDictionaryDetailList +// @Tags SysDictionaryDetail +// @Summary 分页获取SysDictionaryDetail列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.SysDictionaryDetailSearch true "页码, 每页大小, 搜索条件" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量" +// @Router /sysDictionaryDetail/getSysDictionaryDetailList [get] +func (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) { + var pageInfo request.SysDictionaryDetailSearch + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := dictionaryDetailService.GetSysDictionaryDetailInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} diff --git a/api/v1/system/sys_export_template.go b/api/v1/system/sys_export_template.go new file mode 100644 index 0000000..5154334 --- /dev/null +++ b/api/v1/system/sys_export_template.go @@ -0,0 +1,428 @@ +package system + +import ( + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/service" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// 用于token一次性存储 +var ( + exportTokenCache = make(map[string]interface{}) + exportTokenExpiration = make(map[string]time.Time) + tokenMutex sync.RWMutex +) + +// 五分钟检测窗口过期 +func cleanupExpiredTokens() { + for { + time.Sleep(5 * time.Minute) + tokenMutex.Lock() + now := time.Now() + for token, expiry := range exportTokenExpiration { + if now.After(expiry) { + delete(exportTokenCache, token) + delete(exportTokenExpiration, token) + } + } + tokenMutex.Unlock() + } +} + +func init() { + go cleanupExpiredTokens() +} + +type SysExportTemplateApi struct { +} + +var sysExportTemplateService = service.ServiceGroupApp.SystemServiceGroup.SysExportTemplateService + +// CreateSysExportTemplate 创建导出模板 +// @Tags SysExportTemplate +// @Summary 创建导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysExportTemplate true "创建导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /sysExportTemplate/createSysExportTemplate [post] +func (sysExportTemplateApi *SysExportTemplateApi) CreateSysExportTemplate(c *gin.Context) { + var sysExportTemplate system.SysExportTemplate + err := c.ShouldBindJSON(&sysExportTemplate) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + verify := utils.Rules{ + "Name": {utils.NotEmpty()}, + } + if err := utils.Verify(sysExportTemplate, verify); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := sysExportTemplateService.CreateSysExportTemplate(&sysExportTemplate); err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + } else { + response.OkWithMessage("创建成功", c) + } +} + +// DeleteSysExportTemplate 删除导出模板 +// @Tags SysExportTemplate +// @Summary 删除导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysExportTemplate true "删除导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /sysExportTemplate/deleteSysExportTemplate [delete] +func (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplate(c *gin.Context) { + var sysExportTemplate system.SysExportTemplate + err := c.ShouldBindJSON(&sysExportTemplate) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := sysExportTemplateService.DeleteSysExportTemplate(sysExportTemplate); err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + } else { + response.OkWithMessage("删除成功", c) + } +} + +// DeleteSysExportTemplateByIds 批量删除导出模板 +// @Tags SysExportTemplate +// @Summary 批量删除导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"批量删除成功"}" +// @Router /sysExportTemplate/deleteSysExportTemplateByIds [delete] +func (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplateByIds(c *gin.Context) { + var IDS request.IdsReq + err := c.ShouldBindJSON(&IDS) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := sysExportTemplateService.DeleteSysExportTemplateByIds(IDS); err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败", c) + } else { + response.OkWithMessage("批量删除成功", c) + } +} + +// UpdateSysExportTemplate 更新导出模板 +// @Tags SysExportTemplate +// @Summary 更新导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysExportTemplate true "更新导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /sysExportTemplate/updateSysExportTemplate [put] +func (sysExportTemplateApi *SysExportTemplateApi) UpdateSysExportTemplate(c *gin.Context) { + var sysExportTemplate system.SysExportTemplate + err := c.ShouldBindJSON(&sysExportTemplate) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + verify := utils.Rules{ + "Name": {utils.NotEmpty()}, + } + if err := utils.Verify(sysExportTemplate, verify); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := sysExportTemplateService.UpdateSysExportTemplate(sysExportTemplate); err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + } else { + response.OkWithMessage("更新成功", c) + } +} + +// FindSysExportTemplate 用id查询导出模板 +// @Tags SysExportTemplate +// @Summary 用id查询导出模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query system.SysExportTemplate true "用id查询导出模板" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /sysExportTemplate/findSysExportTemplate [get] +func (sysExportTemplateApi *SysExportTemplateApi) FindSysExportTemplate(c *gin.Context) { + var sysExportTemplate system.SysExportTemplate + err := c.ShouldBindQuery(&sysExportTemplate) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if resysExportTemplate, err := sysExportTemplateService.GetSysExportTemplate(sysExportTemplate.ID); err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + } else { + response.OkWithData(gin.H{"resysExportTemplate": resysExportTemplate}, c) + } +} + +// GetSysExportTemplateList 分页获取导出模板列表 +// @Tags SysExportTemplate +// @Summary 分页获取导出模板列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query systemReq.SysExportTemplateSearch true "分页获取导出模板列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /sysExportTemplate/getSysExportTemplateList [get] +func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gin.Context) { + var pageInfo systemReq.SysExportTemplateSearch + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if list, total, err := sysExportTemplateService.GetSysExportTemplateInfoList(pageInfo); err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + } else { + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) + } +} + +// ExportExcel 导出表格token +// @Tags SysExportTemplate +// @Summary 导出表格 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/exportExcel [get] +func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) { + templateID := c.Query("templateID") + if templateID == "" { + response.FailWithMessage("模板ID不能为空", c) + return + } + + queryParams := c.Request.URL.Query() + + //创造一次性token + token := utils.RandomString(32) // 随机32位 + + // 记录本次请求参数 + exportParams := map[string]interface{}{ + "templateID": templateID, + "queryParams": queryParams, + } + + // 参数保留记录完成鉴权 + tokenMutex.Lock() + exportTokenCache[token] = exportParams + exportTokenExpiration[token] = time.Now().Add(30 * time.Minute) + tokenMutex.Unlock() + + // 生成一次性链接 + exportUrl := fmt.Sprintf("/sysExportTemplate/exportExcelByToken?token=%s", token) + response.OkWithData(exportUrl, c) +} + +// ExportExcelByToken 导出表格 +// @Tags ExportExcelByToken +// @Summary 导出表格 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/exportExcelByToken [get] +func (sysExportTemplateApi *SysExportTemplateApi) ExportExcelByToken(c *gin.Context) { + token := c.Query("token") + if token == "" { + response.FailWithMessage("导出token不能为空", c) + return + } + + // 获取token并且从缓存中剔除 + tokenMutex.RLock() + exportParamsRaw, exists := exportTokenCache[token] + expiry, _ := exportTokenExpiration[token] + tokenMutex.RUnlock() + + if !exists || time.Now().After(expiry) { + global.GVA_LOG.Error("导出token无效或已过期!") + response.FailWithMessage("导出token无效或已过期", c) + return + } + + // 从token获取参数 + exportParams, ok := exportParamsRaw.(map[string]interface{}) + if !ok { + global.GVA_LOG.Error("解析导出参数失败!") + response.FailWithMessage("解析导出参数失败", c) + return + } + + // 获取导出参数 + templateID := exportParams["templateID"].(string) + queryParams := exportParams["queryParams"].(url.Values) + + // 清理一次性token + tokenMutex.Lock() + delete(exportTokenCache, token) + delete(exportTokenExpiration, token) + tokenMutex.Unlock() + + // 导出 + if file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + } else { + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx")) + c.Header("success", "true") + c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes()) + } +} + +// ExportTemplate 导出表格模板 +// @Tags SysExportTemplate +// @Summary 导出表格模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/exportTemplate [get] +func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplate(c *gin.Context) { + templateID := c.Query("templateID") + if templateID == "" { + response.FailWithMessage("模板ID不能为空", c) + return + } + + // 创造一次性token + token := utils.RandomString(32) // 随机32位 + + // 记录本次请求参数 + exportParams := map[string]interface{}{ + "templateID": templateID, + "isTemplate": true, + } + + // 参数保留记录完成鉴权 + tokenMutex.Lock() + exportTokenCache[token] = exportParams + exportTokenExpiration[token] = time.Now().Add(30 * time.Minute) + tokenMutex.Unlock() + + // 生成一次性链接 + exportUrl := fmt.Sprintf("/sysExportTemplate/exportTemplateByToken?token=%s", token) + response.OkWithData(exportUrl, c) +} + +// ExportTemplateByToken 通过token导出表格模板 +// @Tags ExportTemplateByToken +// @Summary 通过token导出表格模板 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/exportTemplateByToken [get] +func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplateByToken(c *gin.Context) { + token := c.Query("token") + if token == "" { + response.FailWithMessage("导出token不能为空", c) + return + } + + // 获取token并且从缓存中剔除 + tokenMutex.RLock() + exportParamsRaw, exists := exportTokenCache[token] + expiry, _ := exportTokenExpiration[token] + tokenMutex.RUnlock() + + if !exists || time.Now().After(expiry) { + global.GVA_LOG.Error("导出token无效或已过期!") + response.FailWithMessage("导出token无效或已过期", c) + return + } + + // 从token获取参数 + exportParams, ok := exportParamsRaw.(map[string]interface{}) + if !ok { + global.GVA_LOG.Error("解析导出参数失败!") + response.FailWithMessage("解析导出参数失败", c) + return + } + + // 检查是否为模板导出 + isTemplate, _ := exportParams["isTemplate"].(bool) + if !isTemplate { + global.GVA_LOG.Error("token类型错误!") + response.FailWithMessage("token类型错误", c) + return + } + + // 获取导出参数 + templateID := exportParams["templateID"].(string) + + // 清理一次性token + tokenMutex.Lock() + delete(exportTokenCache, token) + delete(exportTokenExpiration, token) + tokenMutex.Unlock() + + // 导出模板 + if file, name, err := sysExportTemplateService.ExportTemplate(templateID); err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + } else { + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx")) + c.Header("success", "true") + c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes()) + } +} + +// ImportExcel 导入表格 +// @Tags SysImportTemplate +// @Summary 导入表格 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Router /sysExportTemplate/importExcel [post] +func (sysExportTemplateApi *SysExportTemplateApi) ImportExcel(c *gin.Context) { + templateID := c.Query("templateID") + if templateID == "" { + response.FailWithMessage("模板ID不能为空", c) + return + } + file, err := c.FormFile("file") + if err != nil { + global.GVA_LOG.Error("文件获取失败!", zap.Error(err)) + response.FailWithMessage("文件获取失败", c) + return + } + if err := sysExportTemplateService.ImportExcel(templateID, file); err != nil { + global.GVA_LOG.Error(err.Error(), zap.Error(err)) + response.FailWithMessage(err.Error(), c) + } else { + response.OkWithMessage("导入成功", c) + } +} diff --git a/api/v1/system/sys_initdb.go b/api/v1/system/sys_initdb.go new file mode 100644 index 0000000..6847cc6 --- /dev/null +++ b/api/v1/system/sys_initdb.go @@ -0,0 +1,59 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "go.uber.org/zap" + + "github.com/gin-gonic/gin" +) + +type DBApi struct{} + +// InitDB +// @Tags InitDB +// @Summary 初始化用户数据库 +// @Produce application/json +// @Param data body request.InitDB true "初始化数据库参数" +// @Success 200 {object} response.Response{data=string} "初始化用户数据库" +// @Router /init/initdb [post] +func (i *DBApi) InitDB(c *gin.Context) { + if global.GVA_DB != nil { + global.GVA_LOG.Error("已存在数据库配置!") + response.FailWithMessage("已存在数据库配置", c) + return + } + var dbInfo request.InitDB + if err := c.ShouldBindJSON(&dbInfo); err != nil { + global.GVA_LOG.Error("参数校验不通过!", zap.Error(err)) + response.FailWithMessage("参数校验不通过", c) + return + } + if err := initDBService.InitDB(dbInfo); err != nil { + global.GVA_LOG.Error("自动创建数据库失败!", zap.Error(err)) + response.FailWithMessage("自动创建数据库失败,请查看后台日志,检查后在进行初始化", c) + return + } + response.OkWithMessage("自动创建数据库成功", c) +} + +// CheckDB +// @Tags CheckDB +// @Summary 初始化用户数据库 +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "初始化用户数据库" +// @Router /init/checkdb [post] +func (i *DBApi) CheckDB(c *gin.Context) { + var ( + message = "前往初始化数据库" + needInit = true + ) + + if global.GVA_DB != nil { + message = "数据库无需初始化" + needInit = false + } + global.GVA_LOG.Info(message) + response.OkWithDetailed(gin.H{"needInit": needInit}, message, c) +} diff --git a/api/v1/system/sys_jwt_blacklist.go b/api/v1/system/sys_jwt_blacklist.go new file mode 100644 index 0000000..154fa3f --- /dev/null +++ b/api/v1/system/sys_jwt_blacklist.go @@ -0,0 +1,33 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type JwtApi struct{} + +// JsonInBlacklist +// @Tags Jwt +// @Summary jwt加入黑名单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "jwt加入黑名单" +// @Router /jwt/jsonInBlacklist [post] +func (j *JwtApi) JsonInBlacklist(c *gin.Context) { + token := utils.GetToken(c) + jwt := system.JwtBlacklist{Jwt: token} + err := jwtService.JsonInBlacklist(jwt) + if err != nil { + global.GVA_LOG.Error("jwt作废失败!", zap.Error(err)) + response.FailWithMessage("jwt作废失败", c) + return + } + utils.ClearToken(c) + response.OkWithMessage("jwt作废成功", c) +} diff --git a/api/v1/system/sys_menu.go b/api/v1/system/sys_menu.go new file mode 100644 index 0000000..8bd7363 --- /dev/null +++ b/api/v1/system/sys_menu.go @@ -0,0 +1,265 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "git.echol.cn/loser/xiecheng_server/utils" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type AuthorityMenuApi struct{} + +// GetMenu +// @Tags AuthorityMenu +// @Summary 获取用户动态路由 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body request.Empty true "空" +// @Success 200 {object} response.Response{data=systemRes.SysMenusResponse,msg=string} "获取用户动态路由,返回包括系统菜单详情列表" +// @Router /menu/getMenu [post] +func (a *AuthorityMenuApi) GetMenu(c *gin.Context) { + menus, err := menuService.GetMenuTree(utils.GetUserAuthorityId(c)) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + if menus == nil { + menus = []system.SysMenu{} + } + response.OkWithDetailed(systemRes.SysMenusResponse{Menus: menus}, "获取成功", c) +} + +// GetBaseMenuTree +// @Tags AuthorityMenu +// @Summary 获取用户动态路由 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body request.Empty true "空" +// @Success 200 {object} response.Response{data=systemRes.SysBaseMenusResponse,msg=string} "获取用户动态路由,返回包括系统菜单列表" +// @Router /menu/getBaseMenuTree [post] +func (a *AuthorityMenuApi) GetBaseMenuTree(c *gin.Context) { + authority := utils.GetUserAuthorityId(c) + menus, err := menuService.GetBaseMenuTree(authority) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(systemRes.SysBaseMenusResponse{Menus: menus}, "获取成功", c) +} + +// AddMenuAuthority +// @Tags AuthorityMenu +// @Summary 增加menu和角色关联关系 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.AddMenuAuthorityInfo true "角色ID" +// @Success 200 {object} response.Response{msg=string} "增加menu和角色关联关系" +// @Router /menu/addMenuAuthority [post] +func (a *AuthorityMenuApi) AddMenuAuthority(c *gin.Context) { + var authorityMenu systemReq.AddMenuAuthorityInfo + err := c.ShouldBindJSON(&authorityMenu) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if err := utils.Verify(authorityMenu, utils.AuthorityIdVerify); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + adminAuthorityID := utils.GetUserAuthorityId(c) + if err := menuService.AddMenuAuthority(authorityMenu.Menus, adminAuthorityID, authorityMenu.AuthorityId); err != nil { + global.GVA_LOG.Error("添加失败!", zap.Error(err)) + response.FailWithMessage("添加失败", c) + } else { + response.OkWithMessage("添加成功", c) + } +} + +// GetMenuAuthority +// @Tags AuthorityMenu +// @Summary 获取指定角色menu +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetAuthorityId true "角色ID" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取指定角色menu" +// @Router /menu/getMenuAuthority [post] +func (a *AuthorityMenuApi) GetMenuAuthority(c *gin.Context) { + var param request.GetAuthorityId + err := c.ShouldBindJSON(¶m) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(param, utils.AuthorityIdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + menus, err := menuService.GetMenuAuthority(¶m) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithDetailed(systemRes.SysMenusResponse{Menus: menus}, "获取失败", c) + return + } + response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c) +} + +// AddBaseMenu +// @Tags Menu +// @Summary 新增菜单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysBaseMenu true "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记" +// @Success 200 {object} response.Response{msg=string} "新增菜单" +// @Router /menu/addBaseMenu [post] +func (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) { + var menu system.SysBaseMenu + err := c.ShouldBindJSON(&menu) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(menu, utils.MenuVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(menu.Meta, utils.MenuMetaVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = menuService.AddBaseMenu(menu) + if err != nil { + global.GVA_LOG.Error("添加失败!", zap.Error(err)) + response.FailWithMessage("添加失败", c) + return + } + response.OkWithMessage("添加成功", c) +} + +// DeleteBaseMenu +// @Tags Menu +// @Summary 删除菜单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetById true "菜单id" +// @Success 200 {object} response.Response{msg=string} "删除菜单" +// @Router /menu/deleteBaseMenu [post] +func (a *AuthorityMenuApi) DeleteBaseMenu(c *gin.Context) { + var menu request.GetById + err := c.ShouldBindJSON(&menu) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(menu, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = baseMenuService.DeleteBaseMenu(menu.ID) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败:"+err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} + +// UpdateBaseMenu +// @Tags Menu +// @Summary 更新菜单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysBaseMenu true "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记" +// @Success 200 {object} response.Response{msg=string} "更新菜单" +// @Router /menu/updateBaseMenu [post] +func (a *AuthorityMenuApi) UpdateBaseMenu(c *gin.Context) { + var menu system.SysBaseMenu + err := c.ShouldBindJSON(&menu) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(menu, utils.MenuVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(menu.Meta, utils.MenuMetaVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = baseMenuService.UpdateBaseMenu(menu) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// GetBaseMenuById +// @Tags Menu +// @Summary 根据id获取菜单 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetById true "菜单id" +// @Success 200 {object} response.Response{data=systemRes.SysBaseMenuResponse,msg=string} "根据id获取菜单,返回包括系统菜单列表" +// @Router /menu/getBaseMenuById [post] +func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) { + var idInfo request.GetById + err := c.ShouldBindJSON(&idInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(idInfo, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + menu, err := baseMenuService.GetBaseMenuById(idInfo.ID) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, "获取成功", c) +} + +// GetMenuList +// @Tags Menu +// @Summary 分页获取基础menu列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.PageInfo true "页码, 每页大小" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取基础menu列表,返回包括列表,总数,页码,每页数量" +// @Router /menu/getMenuList [post] +func (a *AuthorityMenuApi) GetMenuList(c *gin.Context) { + authorityID := utils.GetUserAuthorityId(c) + menuList, err := menuService.GetInfoList(authorityID) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(menuList, "获取成功", c) +} diff --git a/api/v1/system/sys_operation_record.go b/api/v1/system/sys_operation_record.go new file mode 100644 index 0000000..24545a7 --- /dev/null +++ b/api/v1/system/sys_operation_record.go @@ -0,0 +1,149 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type OperationRecordApi struct{} + +// CreateSysOperationRecord +// @Tags SysOperationRecord +// @Summary 创建SysOperationRecord +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysOperationRecord true "创建SysOperationRecord" +// @Success 200 {object} response.Response{msg=string} "创建SysOperationRecord" +// @Router /sysOperationRecord/createSysOperationRecord [post] +func (s *OperationRecordApi) CreateSysOperationRecord(c *gin.Context) { + var sysOperationRecord system.SysOperationRecord + err := c.ShouldBindJSON(&sysOperationRecord) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = operationRecordService.CreateSysOperationRecord(sysOperationRecord) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteSysOperationRecord +// @Tags SysOperationRecord +// @Summary 删除SysOperationRecord +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysOperationRecord true "SysOperationRecord模型" +// @Success 200 {object} response.Response{msg=string} "删除SysOperationRecord" +// @Router /sysOperationRecord/deleteSysOperationRecord [delete] +func (s *OperationRecordApi) DeleteSysOperationRecord(c *gin.Context) { + var sysOperationRecord system.SysOperationRecord + err := c.ShouldBindJSON(&sysOperationRecord) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = operationRecordService.DeleteSysOperationRecord(sysOperationRecord) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// DeleteSysOperationRecordByIds +// @Tags SysOperationRecord +// @Summary 批量删除SysOperationRecord +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除SysOperationRecord" +// @Success 200 {object} response.Response{msg=string} "批量删除SysOperationRecord" +// @Router /sysOperationRecord/deleteSysOperationRecordByIds [delete] +func (s *OperationRecordApi) DeleteSysOperationRecordByIds(c *gin.Context) { + var IDS request.IdsReq + err := c.ShouldBindJSON(&IDS) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = operationRecordService.DeleteSysOperationRecordByIds(IDS) + if err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败", c) + return + } + response.OkWithMessage("批量删除成功", c) +} + +// FindSysOperationRecord +// @Tags SysOperationRecord +// @Summary 用id查询SysOperationRecord +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query system.SysOperationRecord true "Id" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "用id查询SysOperationRecord" +// @Router /sysOperationRecord/findSysOperationRecord [get] +func (s *OperationRecordApi) FindSysOperationRecord(c *gin.Context) { + var sysOperationRecord system.SysOperationRecord + err := c.ShouldBindQuery(&sysOperationRecord) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(sysOperationRecord, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + reSysOperationRecord, err := operationRecordService.GetSysOperationRecord(sysOperationRecord.ID) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithDetailed(gin.H{"reSysOperationRecord": reSysOperationRecord}, "查询成功", c) +} + +// GetSysOperationRecordList +// @Tags SysOperationRecord +// @Summary 分页获取SysOperationRecord列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.SysOperationRecordSearch true "页码, 每页大小, 搜索条件" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量" +// @Router /sysOperationRecord/getSysOperationRecordList [get] +func (s *OperationRecordApi) GetSysOperationRecordList(c *gin.Context) { + var pageInfo systemReq.SysOperationRecordSearch + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := operationRecordService.GetSysOperationRecordInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} diff --git a/api/v1/system/sys_params.go b/api/v1/system/sys_params.go new file mode 100644 index 0000000..770c187 --- /dev/null +++ b/api/v1/system/sys_params.go @@ -0,0 +1,171 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type SysParamsApi struct{} + +// CreateSysParams 创建参数 +// @Tags SysParams +// @Summary 创建参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysParams true "创建参数" +// @Success 200 {object} response.Response{msg=string} "创建成功" +// @Router /sysParams/createSysParams [post] +func (sysParamsApi *SysParamsApi) CreateSysParams(c *gin.Context) { + var sysParams system.SysParams + err := c.ShouldBindJSON(&sysParams) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = sysParamsService.CreateSysParams(&sysParams) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败:"+err.Error(), c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteSysParams 删除参数 +// @Tags SysParams +// @Summary 删除参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysParams true "删除参数" +// @Success 200 {object} response.Response{msg=string} "删除成功" +// @Router /sysParams/deleteSysParams [delete] +func (sysParamsApi *SysParamsApi) DeleteSysParams(c *gin.Context) { + ID := c.Query("ID") + err := sysParamsService.DeleteSysParams(ID) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败:"+err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} + +// DeleteSysParamsByIds 批量删除参数 +// @Tags SysParams +// @Summary 批量删除参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "批量删除成功" +// @Router /sysParams/deleteSysParamsByIds [delete] +func (sysParamsApi *SysParamsApi) DeleteSysParamsByIds(c *gin.Context) { + IDs := c.QueryArray("IDs[]") + err := sysParamsService.DeleteSysParamsByIds(IDs) + if err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败:"+err.Error(), c) + return + } + response.OkWithMessage("批量删除成功", c) +} + +// UpdateSysParams 更新参数 +// @Tags SysParams +// @Summary 更新参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysParams true "更新参数" +// @Success 200 {object} response.Response{msg=string} "更新成功" +// @Router /sysParams/updateSysParams [put] +func (sysParamsApi *SysParamsApi) UpdateSysParams(c *gin.Context) { + var sysParams system.SysParams + err := c.ShouldBindJSON(&sysParams) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = sysParamsService.UpdateSysParams(sysParams) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败:"+err.Error(), c) + return + } + response.OkWithMessage("更新成功", c) +} + +// FindSysParams 用id查询参数 +// @Tags SysParams +// @Summary 用id查询参数 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query system.SysParams true "用id查询参数" +// @Success 200 {object} response.Response{data=system.SysParams,msg=string} "查询成功" +// @Router /sysParams/findSysParams [get] +func (sysParamsApi *SysParamsApi) FindSysParams(c *gin.Context) { + ID := c.Query("ID") + resysParams, err := sysParamsService.GetSysParams(ID) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败:"+err.Error(), c) + return + } + response.OkWithData(resysParams, c) +} + +// GetSysParamsList 分页获取参数列表 +// @Tags SysParams +// @Summary 分页获取参数列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query systemReq.SysParamsSearch true "分页获取参数列表" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /sysParams/getSysParamsList [get] +func (sysParamsApi *SysParamsApi) GetSysParamsList(c *gin.Context) { + var pageInfo systemReq.SysParamsSearch + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := sysParamsService.GetSysParamsInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败:"+err.Error(), c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} + +// GetSysParam 根据key获取参数value +// @Tags SysParams +// @Summary 根据key获取参数value +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param key query string true "key" +// @Success 200 {object} response.Response{data=system.SysParams,msg=string} "获取成功" +// @Router /sysParams/getSysParam [get] +func (sysParamsApi *SysParamsApi) GetSysParam(c *gin.Context) { + k := c.Query("key") + params, err := sysParamsService.GetSysParam(k) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败:"+err.Error(), c) + return + } + response.OkWithDetailed(params, "获取成功", c) +} diff --git a/api/v1/system/sys_system.go b/api/v1/system/sys_system.go new file mode 100644 index 0000000..21d5fbf --- /dev/null +++ b/api/v1/system/sys_system.go @@ -0,0 +1,88 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type SystemApi struct{} + +// GetSystemConfig +// @Tags System +// @Summary 获取配置文件内容 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {object} response.Response{data=systemRes.SysConfigResponse,msg=string} "获取配置文件内容,返回包括系统配置" +// @Router /system/getSystemConfig [post] +func (s *SystemApi) GetSystemConfig(c *gin.Context) { + config, err := systemConfigService.GetSystemConfig() + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(systemRes.SysConfigResponse{Config: config}, "获取成功", c) +} + +// SetSystemConfig +// @Tags System +// @Summary 设置配置文件内容 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body system.System true "设置配置文件内容" +// @Success 200 {object} response.Response{data=string} "设置配置文件内容" +// @Router /system/setSystemConfig [post] +func (s *SystemApi) SetSystemConfig(c *gin.Context) { + var sys system.System + err := c.ShouldBindJSON(&sys) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = systemConfigService.SetSystemConfig(sys) + if err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败", c) + return + } + response.OkWithMessage("设置成功", c) +} + +// ReloadSystem +// @Tags System +// @Summary 重启系统 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "重启系统" +// @Router /system/reloadSystem [post] +func (s *SystemApi) ReloadSystem(c *gin.Context) { + err := utils.Reload() + if err != nil { + global.GVA_LOG.Error("重启系统失败!", zap.Error(err)) + response.FailWithMessage("重启系统失败", c) + return + } + response.OkWithMessage("重启系统成功", c) +} + +// GetServerInfo +// @Tags System +// @Summary 获取服务器信息 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取服务器信息" +// @Router /system/getServerInfo [post] +func (s *SystemApi) GetServerInfo(c *gin.Context) { + server, err := systemConfigService.GetServerInfo() + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(gin.H{"server": server}, "获取成功", c) +} diff --git a/api/v1/system/sys_user.go b/api/v1/system/sys_user.go new file mode 100644 index 0000000..2b8bf08 --- /dev/null +++ b/api/v1/system/sys_user.go @@ -0,0 +1,483 @@ +package system + +import ( + "strconv" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" + "github.com/redis/go-redis/v9" + "go.uber.org/zap" +) + +// Login +// @Tags Base +// @Summary 用户登录 +// @Produce application/json +// @Param data body systemReq.Login true "用户名, 密码, 验证码" +// @Success 200 {object} response.Response{data=systemRes.LoginResponse,msg=string} "返回包括用户信息,token,过期时间" +// @Router /base/login [post] +func (b *BaseApi) Login(c *gin.Context) { + var l systemReq.Login + err := c.ShouldBindJSON(&l) + key := c.ClientIP() + + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(l, utils.LoginVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 判断验证码是否开启 + openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数 + openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间 + v, ok := global.BlackCache.Get(key) + if !ok { + global.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut)) + } + + var oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v) + + if !oc || (l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true)) { + u := &system.SysUser{Username: l.Username, Password: l.Password} + user, err := userService.Login(u) + if err != nil { + global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err)) + // 验证码次数+1 + global.BlackCache.Increment(key, 1) + response.FailWithMessage("用户名不存在或者密码错误", c) + return + } + if user.Enable != 1 { + global.GVA_LOG.Error("登陆失败! 用户被禁止登录!") + // 验证码次数+1 + global.BlackCache.Increment(key, 1) + response.FailWithMessage("用户被禁止登录", c) + return + } + b.TokenNext(c, *user) + return + } + // 验证码次数+1 + global.BlackCache.Increment(key, 1) + response.FailWithMessage("验证码错误", c) +} + +// TokenNext 登录以后签发jwt +func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) { + token, claims, err := utils.LoginToken(&user) + if err != nil { + global.GVA_LOG.Error("获取token失败!", zap.Error(err)) + response.FailWithMessage("获取token失败", c) + return + } + if !global.GVA_CONFIG.System.UseMultipoint { + utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix())) + response.OkWithDetailed(systemRes.LoginResponse{ + User: user, + Token: token, + ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000, + }, "登录成功", c) + return + } + + if jwtStr, err := jwtService.GetRedisJWT(user.Username); err == redis.Nil { + if err := jwtService.SetRedisJWT(token, user.Username); err != nil { + global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err)) + response.FailWithMessage("设置登录状态失败", c) + return + } + utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix())) + response.OkWithDetailed(systemRes.LoginResponse{ + User: user, + Token: token, + ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000, + }, "登录成功", c) + } else if err != nil { + global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err)) + response.FailWithMessage("设置登录状态失败", c) + } else { + var blackJWT system.JwtBlacklist + blackJWT.Jwt = jwtStr + if err := jwtService.JsonInBlacklist(blackJWT); err != nil { + response.FailWithMessage("jwt作废失败", c) + return + } + if err := jwtService.SetRedisJWT(token, user.GetUsername()); err != nil { + response.FailWithMessage("设置登录状态失败", c) + return + } + utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix())) + response.OkWithDetailed(systemRes.LoginResponse{ + User: user, + Token: token, + ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000, + }, "登录成功", c) + } +} + +// Register +// @Tags SysUser +// @Summary 用户注册账号 +// @Produce application/json +// @Param data body systemReq.Register true "用户名, 昵称, 密码, 角色ID" +// @Success 200 {object} response.Response{data=systemRes.SysUserResponse,msg=string} "用户注册账号,返回包括用户信息" +// @Router /user/admin_register [post] +func (b *BaseApi) Register(c *gin.Context) { + var r systemReq.Register + err := c.ShouldBindJSON(&r) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(r, utils.RegisterVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + var authorities []system.SysAuthority + for _, v := range r.AuthorityIds { + authorities = append(authorities, system.SysAuthority{ + AuthorityId: v, + }) + } + user := &system.SysUser{Username: r.Username, NickName: r.NickName, Password: r.Password, HeaderImg: r.HeaderImg, AuthorityId: r.AuthorityId, Authorities: authorities, Enable: r.Enable, Phone: r.Phone, Email: r.Email} + userReturn, err := userService.Register(*user) + if err != nil { + global.GVA_LOG.Error("注册失败!", zap.Error(err)) + response.FailWithDetailed(systemRes.SysUserResponse{User: userReturn}, "注册失败", c) + return + } + response.OkWithDetailed(systemRes.SysUserResponse{User: userReturn}, "注册成功", c) +} + +// ChangePassword +// @Tags SysUser +// @Summary 用户修改密码 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body systemReq.ChangePasswordReq true "用户名, 原密码, 新密码" +// @Success 200 {object} response.Response{msg=string} "用户修改密码" +// @Router /user/changePassword [post] +func (b *BaseApi) ChangePassword(c *gin.Context) { + var req systemReq.ChangePasswordReq + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(req, utils.ChangePasswordVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + uid := utils.GetUserID(c) + u := &system.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password} + _, err = userService.ChangePassword(u, req.NewPassword) + if err != nil { + global.GVA_LOG.Error("修改失败!", zap.Error(err)) + response.FailWithMessage("修改失败,原密码与当前账户不符", c) + return + } + response.OkWithMessage("修改成功", c) +} + +// GetUserList +// @Tags SysUser +// @Summary 分页获取用户列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.GetUserList true "页码, 每页大小" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取用户列表,返回包括列表,总数,页码,每页数量" +// @Router /user/getUserList [post] +func (b *BaseApi) GetUserList(c *gin.Context) { + var pageInfo systemReq.GetUserList + err := c.ShouldBindJSON(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(pageInfo, utils.PageInfoVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := userService.GetUserInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} + +// SetUserAuthority +// @Tags SysUser +// @Summary 更改用户权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.SetUserAuth true "用户UUID, 角色ID" +// @Success 200 {object} response.Response{msg=string} "设置用户权限" +// @Router /user/setUserAuthority [post] +func (b *BaseApi) SetUserAuthority(c *gin.Context) { + var sua systemReq.SetUserAuth + err := c.ShouldBindJSON(&sua) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if UserVerifyErr := utils.Verify(sua, utils.SetUserAuthorityVerify); UserVerifyErr != nil { + response.FailWithMessage(UserVerifyErr.Error(), c) + return + } + userID := utils.GetUserID(c) + err = userService.SetUserAuthority(userID, sua.AuthorityId) + if err != nil { + global.GVA_LOG.Error("修改失败!", zap.Error(err)) + response.FailWithMessage(err.Error(), c) + return + } + claims := utils.GetUserInfo(c) + claims.AuthorityId = sua.AuthorityId + token, err := utils.NewJWT().CreateToken(*claims) + if err != nil { + global.GVA_LOG.Error("修改失败!", zap.Error(err)) + response.FailWithMessage(err.Error(), c) + return + } + c.Header("new-token", token) + c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10)) + utils.SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60)) + response.OkWithMessage("修改成功", c) +} + +// SetUserAuthorities +// @Tags SysUser +// @Summary 设置用户权限 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.SetUserAuthorities true "用户UUID, 角色ID" +// @Success 200 {object} response.Response{msg=string} "设置用户权限" +// @Router /user/setUserAuthorities [post] +func (b *BaseApi) SetUserAuthorities(c *gin.Context) { + var sua systemReq.SetUserAuthorities + err := c.ShouldBindJSON(&sua) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + authorityID := utils.GetUserAuthorityId(c) + err = userService.SetUserAuthorities(authorityID, sua.ID, sua.AuthorityIds) + if err != nil { + global.GVA_LOG.Error("修改失败!", zap.Error(err)) + response.FailWithMessage("修改失败", c) + return + } + response.OkWithMessage("修改成功", c) +} + +// DeleteUser +// @Tags SysUser +// @Summary 删除用户 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body request.GetById true "用户ID" +// @Success 200 {object} response.Response{msg=string} "删除用户" +// @Router /user/deleteUser [delete] +func (b *BaseApi) DeleteUser(c *gin.Context) { + var reqId request.GetById + err := c.ShouldBindJSON(&reqId) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(reqId, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + jwtId := utils.GetUserID(c) + if jwtId == uint(reqId.ID) { + response.FailWithMessage("删除失败, 无法删除自己。", c) + return + } + err = userService.DeleteUser(reqId.ID) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// SetUserInfo +// @Tags SysUser +// @Summary 设置用户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysUser true "ID, 用户名, 昵称, 头像链接" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户信息" +// @Router /user/setUserInfo [put] +func (b *BaseApi) SetUserInfo(c *gin.Context) { + var user systemReq.ChangeUserInfo + err := c.ShouldBindJSON(&user) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = utils.Verify(user, utils.IdVerify) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if len(user.AuthorityIds) != 0 { + authorityID := utils.GetUserAuthorityId(c) + err = userService.SetUserAuthorities(authorityID, user.ID, user.AuthorityIds) + if err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败", c) + return + } + } + err = userService.SetUserInfo(system.SysUser{ + GVA_MODEL: global.GVA_MODEL{ + ID: user.ID, + }, + NickName: user.NickName, + HeaderImg: user.HeaderImg, + Phone: user.Phone, + Email: user.Email, + Enable: user.Enable, + }) + if err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败", c) + return + } + response.OkWithMessage("设置成功", c) +} + +// SetSelfInfo +// @Tags SysUser +// @Summary 设置用户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body system.SysUser true "ID, 用户名, 昵称, 头像链接" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户信息" +// @Router /user/SetSelfInfo [put] +func (b *BaseApi) SetSelfInfo(c *gin.Context) { + var user systemReq.ChangeUserInfo + err := c.ShouldBindJSON(&user) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + user.ID = utils.GetUserID(c) + err = userService.SetSelfInfo(system.SysUser{ + GVA_MODEL: global.GVA_MODEL{ + ID: user.ID, + }, + NickName: user.NickName, + HeaderImg: user.HeaderImg, + Phone: user.Phone, + Email: user.Email, + Enable: user.Enable, + }) + if err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败", c) + return + } + response.OkWithMessage("设置成功", c) +} + +// SetSelfSetting +// @Tags SysUser +// @Summary 设置用户配置 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body map[string]interface{} true "用户配置数据" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户配置" +// @Router /user/SetSelfSetting [put] +func (b *BaseApi) SetSelfSetting(c *gin.Context) { + var req common.JSONMap + err := c.ShouldBindJSON(&req) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + err = userService.SetSelfSetting(req, utils.GetUserID(c)) + if err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败", c) + return + } + response.OkWithMessage("设置成功", c) +} + +// GetUserInfo +// @Tags SysUser +// @Summary 获取用户信息 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取用户信息" +// @Router /user/getUserInfo [get] +func (b *BaseApi) GetUserInfo(c *gin.Context) { + uuid := utils.GetUserUuid(c) + ReqUser, err := userService.GetUserInfo(uuid) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(gin.H{"userInfo": ReqUser}, "获取成功", c) +} + +// ResetPassword +// @Tags SysUser +// @Summary 重置用户密码 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body system.SysUser true "ID" +// @Success 200 {object} response.Response{msg=string} "重置用户密码" +// @Router /user/resetPassword [post] +func (b *BaseApi) ResetPassword(c *gin.Context) { + var user system.SysUser + err := c.ShouldBindJSON(&user) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = userService.ResetPassword(user.ID) + if err != nil { + global.GVA_LOG.Error("重置失败!", zap.Error(err)) + response.FailWithMessage("重置失败"+err.Error(), c) + return + } + response.OkWithMessage("重置成功", c) +} diff --git a/config.docker.yaml b/config.docker.yaml new file mode 100644 index 0000000..a7f4e57 --- /dev/null +++ b/config.docker.yaml @@ -0,0 +1,220 @@ +# git.echol.cn/loser/xiecheng_server Global Configuration + +# jwt configuration +jwt: + signing-key: qmPlus + expires-time: 7d + buffer-time: 1d + issuer: qmPlus +# zap logger configuration +zap: + level: info + format: console + prefix: "[git.echol.cn/loser/xiecheng_server]" + director: log + show-line: true + encode-level: LowercaseColorLevelEncoder + stacktrace-key: stacktrace + log-in-console: true + +# redis configuration +redis: + db: 0 + addr: 177.7.0.14:6379 + password: "" + +# mongo configuration +mongo: + coll: '' + options: '' + database: '' + username: '' + password: '' + min-pool-size: 0 + max-pool-size: 100 + socket-timeout-ms: 0 + connect-timeout-ms: 0 + is-zap: false + hosts: + - host: '' + port: '' + +# email configuration +email: + to: xxx@qq.com + port: 465 + from: xxx@163.com + host: smtp.163.com + is-ssl: true + secret: xxx + nickname: test + +# system configuration +system: + env: public # Change to "develop" to skip authentication for development mode + addr: 8888 + db-type: mysql + oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置 + use-redis: false # 使用redis + use-mongo: false # 使用mongo + use-multipoint: false + # IP限制次数 一个小时15000次 + iplimit-count: 15000 + # IP限制一个小时 + iplimit-time: 3600 + +# captcha configuration +captcha: + key-long: 6 + img-width: 240 + img-height: 80 + open-captcha: 0 # 0代表一直开启,大于0代表限制次数 + open-captcha-timeout: 3600 # open-captcha大于0时才生效 + +# mysql connect configuration +# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master) +mysql: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false + +# pgsql connect configuration +# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master) +pgsql: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false + +db-list: + - disable: true # 是否禁用 + type: "" # 数据库的类型,目前支持mysql、pgsql + alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一 + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false + + +# local configuration +local: + path: uploads/file + store-path: uploads/file + +# autocode configuration +autocode: + transfer-restart: true + # root 自动适配项目根目录 + # 请不要手动配置,他会在项目加载的时候识别出根路径 + root: "" + server: /server + server-plug: /plugin/%s + server-api: /api/v1/%s + server-initialize: /initialize + server-model: /model/%s + server-request: /model/%s/request/ + server-router: /router/%s + server-service: /service/%s + web: /web/src + web-api: /api + web-form: /view + web-table: /view + +# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址) +qiniu: + zone: ZoneHuaDong + bucket: "" + img-path: "" + use-https: false + access-key: "" + secret-key: "" + use-cdn-domains: false + +# aliyun oss configuration +aliyun-oss: + endpoint: yourEndpoint + access-key-id: yourAccessKeyId + access-key-secret: yourAccessKeySecret + bucket-name: yourBucketName + bucket-url: yourBucketUrl + base-path: yourBasePath + +# tencent cos configuration +tencent-cos: + bucket: xxxxx-10005608 + region: ap-shanghai + secret-id: your-secret-id + secret-key: your-secret-key + base-url: https://gin.vue.admin + path-prefix: git.echol.cn/loser/xiecheng_server + +# aws s3 configuration (minio compatible) +aws-s3: + bucket: xxxxx-10005608 + region: ap-shanghai + endpoint: "" + s3-force-path-style: false + disable-ssl: false + secret-id: your-secret-id + secret-key: your-secret-key + base-url: https://gin.vue.admin + path-prefix: git.echol.cn/loser/xiecheng_server + +# huawei obs configuration +hua-wei-obs: + path: you-path + bucket: you-bucket + endpoint: you-endpoint + access-key: you-access-key + secret-key: you-secret-key + +# excel configuration +excel: + dir: ./resource/excel/ + +# timer task db clear table +Timer: + start: true + spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3 + detail: + - tableName: sys_operation_records + compareField: created_at + interval: 2160h + - tableName: jwt_blacklists + compareField: created_at + interval: 168h + +# 跨域配置 +# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用 +cors: + mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝 + whitelist: + - allow-origin: example1.com + allow-headers: content-type + allow-methods: GET, POST + expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type + allow-credentials: true # 布尔值 + - allow-origin: example2.com + allow-headers: content-type + allow-methods: GET, POST + expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type + allow-credentials: true # 布尔值 \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..6c204dd --- /dev/null +++ b/config.yaml @@ -0,0 +1,277 @@ +# git.echol.cn/loser/xiecheng_server Global Configuration + +# jwt configuration +jwt: + signing-key: qmPlus + expires-time: 7d + buffer-time: 1d + issuer: qmPlus +# zap logger configuration +zap: + level: info + format: console + prefix: "[git.echol.cn/loser/xiecheng_server]" + director: log + show-line: true + encode-level: LowercaseColorLevelEncoder + stacktrace-key: stacktrace + log-in-console: true + retention-day: -1 + +# redis configuration +redis: + #是否使用redis集群模式 + useCluster: false + #使用集群模式addr和db默认无效 + addr: 127.0.0.1:6379 + password: "" + db: 0 + clusterAddrs: + - "172.21.0.3:7000" + - "172.21.0.4:7001" + - "172.21.0.2:7002" + +# redis-list configuration +redis-list: + - name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一 + useCluster: false # 是否使用redis集群模式 + addr: 127.0.0.1:6379 # 使用集群模式addr和db默认无效 + password: "" + db: 0 + clusterAddrs: + - "172.21.0.3:7000" + - "172.21.0.4:7001" + - "172.21.0.2:7002" + +# mongo configuration +mongo: + coll: '' + options: '' + database: '' + username: '' + password: '' + auth-source: '' + min-pool-size: 0 + max-pool-size: 100 + socket-timeout-ms: 0 + connect-timeout-ms: 0 + is-zap: false + hosts: + - host: '' + port: '' + +# email configuration +email: + to: xxx@qq.com + port: 465 + from: xxx@163.com + host: smtp.163.com + is-ssl: true + secret: xxx + nickname: test + +# system configuration +system: + env: local # 修改为public可以关闭路由日志输出 + addr: 8888 + db-type: mysql + oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置 + use-redis: false # 使用redis + use-mongo: false # 使用mongo + use-multipoint: false + # IP限制次数 一个小时15000次 + iplimit-count: 15000 + # IP限制一个小时 + iplimit-time: 3600 + # 路由全局前缀 + router-prefix: "" + # 严格角色模式 打开后权限将会存在上下级关系 + use-strict-auth: false + +# captcha configuration +captcha: + key-long: 6 + img-width: 240 + img-height: 80 + open-captcha: 0 # 0代表一直开启,大于0代表限制次数 + open-captcha-timeout: 3600 # open-captcha大于0时才生效 + +# mysql connect configuration +# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master) +mysql: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false + +# pgsql connect configuration +# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master) +pgsql: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false +oracle: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false +mssql: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false +sqlite: + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false +db-list: + - disable: true # 是否禁用 + type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle + alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一 + path: "" + port: "" + config: "" + db-name: "" + username: "" + password: "" + max-idle-conns: 10 + max-open-conns: 100 + log-mode: "" + log-zap: false + +# local configuration +local: + path: uploads/file + store-path: uploads/file + +# autocode configuration +autocode: + web: web/src + root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径 + server: server + module: 'git.echol.cn/loser/xiecheng_server' + ai-path: "" # AI服务路径 + +# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址) +qiniu: + zone: ZoneHuaDong + bucket: "" + img-path: "" + use-https: false + access-key: "" + secret-key: "" + use-cdn-domains: false + +# minio oss configuration +minio: + endpoint: yourEndpoint + access-key-id: yourAccessKeyId + access-key-secret: yourAccessKeySecret + bucket-name: yourBucketName + use-ssl: false + base-path: "" + bucket-url: "http://host:9000/yourBucketName" + +# aliyun oss configuration +aliyun-oss: + endpoint: yourEndpoint + access-key-id: yourAccessKeyId + access-key-secret: yourAccessKeySecret + bucket-name: yourBucketName + bucket-url: yourBucketUrl + base-path: yourBasePath + +# tencent cos configuration +tencent-cos: + bucket: xxxxx-10005608 + region: ap-shanghai + secret-id: your-secret-id + secret-key: your-secret-key + base-url: https://gin.vue.admin + path-prefix: git.echol.cn/loser/xiecheng_server + +# aws s3 configuration (minio compatible) +aws-s3: + bucket: xxxxx-10005608 + region: ap-shanghai + endpoint: "" + s3-force-path-style: false + disable-ssl: false + secret-id: your-secret-id + secret-key: your-secret-key + base-url: https://gin.vue.admin + path-prefix: git.echol.cn/loser/xiecheng_server + +# cloudflare r2 configuration +cloudflare-r2: + bucket: xxxx0bucket + base-url: https://gin.vue.admin.com + path: uploads + account-id: xxx_account_id + access-key-id: xxx_key_id + secret-access-key: xxx_secret_key + +# huawei obs configuration +hua-wei-obs: + path: you-path + bucket: you-bucket + endpoint: you-endpoint + access-key: you-access-key + secret-key: you-secret-key + +# excel configuration +excel: + dir: ./resource/excel/ + +# disk usage configuration +disk-list: + - mount-point: "/" + +# 跨域配置 +# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用 +cors: + mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝 + whitelist: + - allow-origin: example1.com + allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id + allow-methods: POST, GET + expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type + + allow-credentials: true # 布尔值 + - allow-origin: example2.com + allow-headers: content-type + allow-methods: GET, POST + expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type + allow-credentials: true # 布尔值 \ No newline at end of file diff --git a/config/auto_code.go b/config/auto_code.go new file mode 100644 index 0000000..ade79a0 --- /dev/null +++ b/config/auto_code.go @@ -0,0 +1,22 @@ +package config + +import ( + "path/filepath" + "strings" +) + +type Autocode struct { + Web string `mapstructure:"web" json:"web" yaml:"web"` + Root string `mapstructure:"root" json:"root" yaml:"root"` + Server string `mapstructure:"server" json:"server" yaml:"server"` + Module string `mapstructure:"module" json:"module" yaml:"module"` + AiPath string `mapstructure:"ai-path" json:"ai-path" yaml:"ai-path"` +} + +func (a *Autocode) WebRoot() string { + webs := strings.Split(a.Web, "/") + if len(webs) == 0 { + webs = strings.Split(a.Web, "\\") + } + return filepath.Join(webs...) +} diff --git a/config/captcha.go b/config/captcha.go new file mode 100644 index 0000000..074a9bf --- /dev/null +++ b/config/captcha.go @@ -0,0 +1,9 @@ +package config + +type Captcha struct { + KeyLong int `mapstructure:"key-long" json:"key-long" yaml:"key-long"` // 验证码长度 + ImgWidth int `mapstructure:"img-width" json:"img-width" yaml:"img-width"` // 验证码宽度 + ImgHeight int `mapstructure:"img-height" json:"img-height" yaml:"img-height"` // 验证码高度 + OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码 + OpenCaptchaTimeOut int `mapstructure:"open-captcha-timeout" json:"open-captcha-timeout" yaml:"open-captcha-timeout"` // 防爆破验证码超时时间,单位:s(秒) +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..15da9c7 --- /dev/null +++ b/config/config.go @@ -0,0 +1,37 @@ +package config + +type Server struct { + JWT JWT `mapstructure:"jwt" json:"jwt" yaml:"jwt"` + Zap Zap `mapstructure:"zap" json:"zap" yaml:"zap"` + Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"` + RedisList []Redis `mapstructure:"redis-list" json:"redis-list" yaml:"redis-list"` + Mongo Mongo `mapstructure:"mongo" json:"mongo" yaml:"mongo"` + Email Email `mapstructure:"email" json:"email" yaml:"email"` + System System `mapstructure:"system" json:"system" yaml:"system"` + Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"` + // auto + AutoCode Autocode `mapstructure:"autocode" json:"autocode" yaml:"autocode"` + // gorm + Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"` + Mssql Mssql `mapstructure:"mssql" json:"mssql" yaml:"mssql"` + Pgsql Pgsql `mapstructure:"pgsql" json:"pgsql" yaml:"pgsql"` + Oracle Oracle `mapstructure:"oracle" json:"oracle" yaml:"oracle"` + Sqlite Sqlite `mapstructure:"sqlite" json:"sqlite" yaml:"sqlite"` + DBList []SpecializedDB `mapstructure:"db-list" json:"db-list" yaml:"db-list"` + // oss + Local Local `mapstructure:"local" json:"local" yaml:"local"` + Qiniu Qiniu `mapstructure:"qiniu" json:"qiniu" yaml:"qiniu"` + AliyunOSS AliyunOSS `mapstructure:"aliyun-oss" json:"aliyun-oss" yaml:"aliyun-oss"` + HuaWeiObs HuaWeiObs `mapstructure:"hua-wei-obs" json:"hua-wei-obs" yaml:"hua-wei-obs"` + TencentCOS TencentCOS `mapstructure:"tencent-cos" json:"tencent-cos" yaml:"tencent-cos"` + AwsS3 AwsS3 `mapstructure:"aws-s3" json:"aws-s3" yaml:"aws-s3"` + CloudflareR2 CloudflareR2 `mapstructure:"cloudflare-r2" json:"cloudflare-r2" yaml:"cloudflare-r2"` + Minio Minio `mapstructure:"minio" json:"minio" yaml:"minio"` + + Excel Excel `mapstructure:"excel" json:"excel" yaml:"excel"` + + DiskList []DiskList `mapstructure:"disk-list" json:"disk-list" yaml:"disk-list"` + + // 跨域配置 + Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"` +} diff --git a/config/cors.go b/config/cors.go new file mode 100644 index 0000000..7fba993 --- /dev/null +++ b/config/cors.go @@ -0,0 +1,14 @@ +package config + +type CORS struct { + Mode string `mapstructure:"mode" json:"mode" yaml:"mode"` + Whitelist []CORSWhitelist `mapstructure:"whitelist" json:"whitelist" yaml:"whitelist"` +} + +type CORSWhitelist struct { + AllowOrigin string `mapstructure:"allow-origin" json:"allow-origin" yaml:"allow-origin"` + AllowMethods string `mapstructure:"allow-methods" json:"allow-methods" yaml:"allow-methods"` + AllowHeaders string `mapstructure:"allow-headers" json:"allow-headers" yaml:"allow-headers"` + ExposeHeaders string `mapstructure:"expose-headers" json:"expose-headers" yaml:"expose-headers"` + AllowCredentials bool `mapstructure:"allow-credentials" json:"allow-credentials" yaml:"allow-credentials"` +} diff --git a/config/db_list.go b/config/db_list.go new file mode 100644 index 0000000..39767f5 --- /dev/null +++ b/config/db_list.go @@ -0,0 +1,52 @@ +package config + +import ( + "gorm.io/gorm/logger" + "strings" +) + +type DsnProvider interface { + Dsn() string +} + +// Embeded 结构体可以压平到上一层,从而保持 config 文件的结构和原来一样 +// 见 playground: https://go.dev/play/p/KIcuhqEoxmY + +// GeneralDB 也被 Pgsql 和 Mysql 原样使用 +type GeneralDB struct { + Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 数据库前缀 + Port string `mapstructure:"port" json:"port" yaml:"port"` // 数据库端口 + Config string `mapstructure:"config" json:"config" yaml:"config"` // 高级配置 + Dbname string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` // 数据库名 + Username string `mapstructure:"username" json:"username" yaml:"username"` // 数据库账号 + Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码 + Path string `mapstructure:"path" json:"path" yaml:"path"` // 数据库地址 + Engine string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"` // 数据库引擎,默认InnoDB + LogMode string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"` // 是否开启Gorm全局日志 + MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` // 空闲中的最大连接数 + MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` // 打开到数据库的最大连接数 + Singular bool `mapstructure:"singular" json:"singular" yaml:"singular"` // 是否开启全局禁用复数,true表示开启 + LogZap bool `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` // 是否通过zap写入日志文件 +} + +func (c GeneralDB) LogLevel() logger.LogLevel { + switch strings.ToLower(c.LogMode) { + case "silent", "Silent": + return logger.Silent + case "error", "Error": + return logger.Error + case "warn", "Warn": + return logger.Warn + case "info", "Info": + return logger.Info + default: + return logger.Info + } +} + +type SpecializedDB struct { + Type string `mapstructure:"type" json:"type" yaml:"type"` + AliasName string `mapstructure:"alias-name" json:"alias-name" yaml:"alias-name"` + GeneralDB `yaml:",inline" mapstructure:",squash"` + Disable bool `mapstructure:"disable" json:"disable" yaml:"disable"` +} diff --git a/config/disk.go b/config/disk.go new file mode 100644 index 0000000..59a6332 --- /dev/null +++ b/config/disk.go @@ -0,0 +1,9 @@ +package config + +type Disk struct { + MountPoint string `mapstructure:"mount-point" json:"mount-point" yaml:"mount-point"` +} + +type DiskList struct { + Disk `yaml:",inline" mapstructure:",squash"` +} diff --git a/config/email.go b/config/email.go new file mode 100644 index 0000000..0984616 --- /dev/null +++ b/config/email.go @@ -0,0 +1,11 @@ +package config + +type Email struct { + To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 + From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱 + Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议 + Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥 + Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱 + Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465 + IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL +} diff --git a/config/excel.go b/config/excel.go new file mode 100644 index 0000000..13caab7 --- /dev/null +++ b/config/excel.go @@ -0,0 +1,5 @@ +package config + +type Excel struct { + Dir string `mapstructure:"dir" json:"dir" yaml:"dir"` +} diff --git a/config/gorm_mssql.go b/config/gorm_mssql.go new file mode 100644 index 0000000..d187119 --- /dev/null +++ b/config/gorm_mssql.go @@ -0,0 +1,10 @@ +package config + +type Mssql struct { + GeneralDB `yaml:",inline" mapstructure:",squash"` +} + +// Dsn "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" +func (m *Mssql) Dsn() string { + return "sqlserver://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "?database=" + m.Dbname + "&encrypt=disable" +} diff --git a/config/gorm_mysql.go b/config/gorm_mysql.go new file mode 100644 index 0000000..77e0245 --- /dev/null +++ b/config/gorm_mysql.go @@ -0,0 +1,9 @@ +package config + +type Mysql struct { + GeneralDB `yaml:",inline" mapstructure:",squash"` +} + +func (m *Mysql) Dsn() string { + return m.Username + ":" + m.Password + "@tcp(" + m.Path + ":" + m.Port + ")/" + m.Dbname + "?" + m.Config +} diff --git a/config/gorm_oracle.go b/config/gorm_oracle.go new file mode 100644 index 0000000..1bbeb46 --- /dev/null +++ b/config/gorm_oracle.go @@ -0,0 +1,10 @@ +package config + +type Oracle struct { + GeneralDB `yaml:",inline" mapstructure:",squash"` +} + +func (m *Oracle) Dsn() string { + return "oracle://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "/" + m.Dbname + "?" + m.Config + +} diff --git a/config/gorm_pgsql.go b/config/gorm_pgsql.go new file mode 100644 index 0000000..29fe03f --- /dev/null +++ b/config/gorm_pgsql.go @@ -0,0 +1,17 @@ +package config + +type Pgsql struct { + GeneralDB `yaml:",inline" mapstructure:",squash"` +} + +// Dsn 基于配置文件获取 dsn +// Author [SliverHorn](https://github.com/SliverHorn) +func (p *Pgsql) Dsn() string { + return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + p.Dbname + " port=" + p.Port + " " + p.Config +} + +// LinkDsn 根据 dbname 生成 dsn +// Author [SliverHorn](https://github.com/SliverHorn) +func (p *Pgsql) LinkDsn(dbname string) string { + return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + dbname + " port=" + p.Port + " " + p.Config +} diff --git a/config/gorm_sqlite.go b/config/gorm_sqlite.go new file mode 100644 index 0000000..46f2e19 --- /dev/null +++ b/config/gorm_sqlite.go @@ -0,0 +1,13 @@ +package config + +import ( + "path/filepath" +) + +type Sqlite struct { + GeneralDB `yaml:",inline" mapstructure:",squash"` +} + +func (s *Sqlite) Dsn() string { + return filepath.Join(s.Path, s.Dbname+".db") +} diff --git a/config/jwt.go b/config/jwt.go new file mode 100644 index 0000000..c95d30d --- /dev/null +++ b/config/jwt.go @@ -0,0 +1,8 @@ +package config + +type JWT struct { + SigningKey string `mapstructure:"signing-key" json:"signing-key" yaml:"signing-key"` // jwt签名 + ExpiresTime string `mapstructure:"expires-time" json:"expires-time" yaml:"expires-time"` // 过期时间 + BufferTime string `mapstructure:"buffer-time" json:"buffer-time" yaml:"buffer-time"` // 缓冲时间 + Issuer string `mapstructure:"issuer" json:"issuer" yaml:"issuer"` // 签发者 +} diff --git a/config/mongo.go b/config/mongo.go new file mode 100644 index 0000000..2034a3f --- /dev/null +++ b/config/mongo.go @@ -0,0 +1,41 @@ +package config + +import ( + "fmt" + "strings" +) + +type Mongo struct { + Coll string `json:"coll" yaml:"coll" mapstructure:"coll"` // collection name + Options string `json:"options" yaml:"options" mapstructure:"options"` // mongodb options + Database string `json:"database" yaml:"database" mapstructure:"database"` // database name + Username string `json:"username" yaml:"username" mapstructure:"username"` // 用户名 + Password string `json:"password" yaml:"password" mapstructure:"password"` // 密码 + AuthSource string `json:"auth-source" yaml:"auth-source" mapstructure:"auth-source"` // 验证数据库 + MinPoolSize uint64 `json:"min-pool-size" yaml:"min-pool-size" mapstructure:"min-pool-size"` // 最小连接池 + MaxPoolSize uint64 `json:"max-pool-size" yaml:"max-pool-size" mapstructure:"max-pool-size"` // 最大连接池 + SocketTimeoutMs int64 `json:"socket-timeout-ms" yaml:"socket-timeout-ms" mapstructure:"socket-timeout-ms"` // socket超时时间 + ConnectTimeoutMs int64 `json:"connect-timeout-ms" yaml:"connect-timeout-ms" mapstructure:"connect-timeout-ms"` // 连接超时时间 + IsZap bool `json:"is-zap" yaml:"is-zap" mapstructure:"is-zap"` // 是否开启zap日志 + Hosts []*MongoHost `json:"hosts" yaml:"hosts" mapstructure:"hosts"` // 主机列表 +} + +type MongoHost struct { + Host string `json:"host" yaml:"host" mapstructure:"host"` // ip地址 + Port string `json:"port" yaml:"port" mapstructure:"port"` // 端口 +} + +// Uri . +func (x *Mongo) Uri() string { + length := len(x.Hosts) + hosts := make([]string, 0, length) + for i := 0; i < length; i++ { + if x.Hosts[i].Host != "" && x.Hosts[i].Port != "" { + hosts = append(hosts, x.Hosts[i].Host+":"+x.Hosts[i].Port) + } + } + if x.Options != "" { + return fmt.Sprintf("mongodb://%s/%s?%s", strings.Join(hosts, ","), x.Database, x.Options) + } + return fmt.Sprintf("mongodb://%s/%s", strings.Join(hosts, ","), x.Database) +} diff --git a/config/oss_aliyun.go b/config/oss_aliyun.go new file mode 100644 index 0000000..934bd78 --- /dev/null +++ b/config/oss_aliyun.go @@ -0,0 +1,10 @@ +package config + +type AliyunOSS struct { + Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"` + AccessKeyId string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"` + AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"` + BucketName string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"` + BucketUrl string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"` + BasePath string `mapstructure:"base-path" json:"base-path" yaml:"base-path"` +} diff --git a/config/oss_aws.go b/config/oss_aws.go new file mode 100644 index 0000000..7ec6acc --- /dev/null +++ b/config/oss_aws.go @@ -0,0 +1,13 @@ +package config + +type AwsS3 struct { + Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` + Region string `mapstructure:"region" json:"region" yaml:"region"` + Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"` + SecretID string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"` + SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"` + BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"` + PathPrefix string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"` + S3ForcePathStyle bool `mapstructure:"s3-force-path-style" json:"s3-force-path-style" yaml:"s3-force-path-style"` + DisableSSL bool `mapstructure:"disable-ssl" json:"disable-ssl" yaml:"disable-ssl"` +} diff --git a/config/oss_cloudflare.go b/config/oss_cloudflare.go new file mode 100644 index 0000000..ab7a393 --- /dev/null +++ b/config/oss_cloudflare.go @@ -0,0 +1,10 @@ +package config + +type CloudflareR2 struct { + Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` + BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"` + Path string `mapstructure:"path" json:"path" yaml:"path"` + AccountID string `mapstructure:"account-id" json:"account-id" yaml:"account-id"` + AccessKeyID string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"` + SecretAccessKey string `mapstructure:"secret-access-key" json:"secret-access-key" yaml:"secret-access-key"` +} diff --git a/config/oss_huawei.go b/config/oss_huawei.go new file mode 100644 index 0000000..45dfbcd --- /dev/null +++ b/config/oss_huawei.go @@ -0,0 +1,9 @@ +package config + +type HuaWeiObs struct { + Path string `mapstructure:"path" json:"path" yaml:"path"` + Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` + Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"` + AccessKey string `mapstructure:"access-key" json:"access-key" yaml:"access-key"` + SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"` +} diff --git a/config/oss_local.go b/config/oss_local.go new file mode 100644 index 0000000..7038d4a --- /dev/null +++ b/config/oss_local.go @@ -0,0 +1,6 @@ +package config + +type Local struct { + Path string `mapstructure:"path" json:"path" yaml:"path"` // 本地文件访问路径 + StorePath string `mapstructure:"store-path" json:"store-path" yaml:"store-path"` // 本地文件存储路径 +} diff --git a/config/oss_minio.go b/config/oss_minio.go new file mode 100644 index 0000000..a0faac7 --- /dev/null +++ b/config/oss_minio.go @@ -0,0 +1,11 @@ +package config + +type Minio struct { + Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"` + AccessKeyId string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"` + AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"` + BucketName string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"` + UseSSL bool `mapstructure:"use-ssl" json:"use-ssl" yaml:"use-ssl"` + BasePath string `mapstructure:"base-path" json:"base-path" yaml:"base-path"` + BucketUrl string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"` +} diff --git a/config/oss_qiniu.go b/config/oss_qiniu.go new file mode 100644 index 0000000..298fe2d --- /dev/null +++ b/config/oss_qiniu.go @@ -0,0 +1,11 @@ +package config + +type Qiniu struct { + Zone string `mapstructure:"zone" json:"zone" yaml:"zone"` // 存储区域 + Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` // 空间名称 + ImgPath string `mapstructure:"img-path" json:"img-path" yaml:"img-path"` // CDN加速域名 + AccessKey string `mapstructure:"access-key" json:"access-key" yaml:"access-key"` // 秘钥AK + SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"` // 秘钥SK + UseHTTPS bool `mapstructure:"use-https" json:"use-https" yaml:"use-https"` // 是否使用https + UseCdnDomains bool `mapstructure:"use-cdn-domains" json:"use-cdn-domains" yaml:"use-cdn-domains"` // 上传是否使用CDN上传加速 +} diff --git a/config/oss_tencent.go b/config/oss_tencent.go new file mode 100644 index 0000000..39a29d1 --- /dev/null +++ b/config/oss_tencent.go @@ -0,0 +1,10 @@ +package config + +type TencentCOS struct { + Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` + Region string `mapstructure:"region" json:"region" yaml:"region"` + SecretID string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"` + SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"` + BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"` + PathPrefix string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"` +} diff --git a/config/redis.go b/config/redis.go new file mode 100644 index 0000000..94b5bf6 --- /dev/null +++ b/config/redis.go @@ -0,0 +1,10 @@ +package config + +type Redis struct { + Name string `mapstructure:"name" json:"name" yaml:"name"` // 代表当前实例的名字 + Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口 + Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码 + DB int `mapstructure:"db" json:"db" yaml:"db"` // 单实例模式下redis的哪个数据库 + UseCluster bool `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"` // 是否使用集群模式 + ClusterAddrs []string `mapstructure:"clusterAddrs" json:"clusterAddrs" yaml:"clusterAddrs"` // 集群模式下的节点地址列表 +} diff --git a/config/system.go b/config/system.go new file mode 100644 index 0000000..b47dbf6 --- /dev/null +++ b/config/system.go @@ -0,0 +1,14 @@ +package config + +type System struct { + DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` // 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql + OssType string `mapstructure:"oss-type" json:"oss-type" yaml:"oss-type"` // Oss类型 + RouterPrefix string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"` + Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` // 端口值 + LimitCountIP int `mapstructure:"iplimit-count" json:"iplimit-count" yaml:"iplimit-count"` + LimitTimeIP int `mapstructure:"iplimit-time" json:"iplimit-time" yaml:"iplimit-time"` + UseMultipoint bool `mapstructure:"use-multipoint" json:"use-multipoint" yaml:"use-multipoint"` // 多点登录拦截 + UseRedis bool `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"` // 使用redis + UseMongo bool `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"` // 使用mongo + UseStrictAuth bool `mapstructure:"use-strict-auth" json:"use-strict-auth" yaml:"use-strict-auth"` // 使用树形角色分配模式 +} diff --git a/config/zap.go b/config/zap.go new file mode 100644 index 0000000..0e8ae2b --- /dev/null +++ b/config/zap.go @@ -0,0 +1,71 @@ +package config + +import ( + "go.uber.org/zap/zapcore" + "time" +) + +type Zap struct { + Level string `mapstructure:"level" json:"level" yaml:"level"` // 级别 + Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 日志前缀 + Format string `mapstructure:"format" json:"format" yaml:"format"` // 输出 + Director string `mapstructure:"director" json:"director" yaml:"director"` // 日志文件夹 + EncodeLevel string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"` // 编码级 + StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` // 栈名 + ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"` // 显示行 + LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` // 输出控制台 + RetentionDay int `mapstructure:"retention-day" json:"retention-day" yaml:"retention-day"` // 日志保留天数 +} + +// Levels 根据字符串转化为 zapcore.Levels +func (c *Zap) Levels() []zapcore.Level { + levels := make([]zapcore.Level, 0, 7) + level, err := zapcore.ParseLevel(c.Level) + if err != nil { + level = zapcore.DebugLevel + } + for ; level <= zapcore.FatalLevel; level++ { + levels = append(levels, level) + } + return levels +} + +func (c *Zap) Encoder() zapcore.Encoder { + config := zapcore.EncoderConfig{ + TimeKey: "time", + NameKey: "name", + LevelKey: "level", + CallerKey: "caller", + MessageKey: "message", + StacktraceKey: c.StacktraceKey, + LineEnding: zapcore.DefaultLineEnding, + EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) { + encoder.AppendString(c.Prefix + t.Format("2006-01-02 15:04:05.000")) + }, + EncodeLevel: c.LevelEncoder(), + EncodeCaller: zapcore.FullCallerEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + } + if c.Format == "json" { + return zapcore.NewJSONEncoder(config) + } + return zapcore.NewConsoleEncoder(config) + +} + +// LevelEncoder 根据 EncodeLevel 返回 zapcore.LevelEncoder +// Author [SliverHorn](https://github.com/SliverHorn) +func (c *Zap) LevelEncoder() zapcore.LevelEncoder { + switch { + case c.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认) + return zapcore.LowercaseLevelEncoder + case c.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色 + return zapcore.LowercaseColorLevelEncoder + case c.EncodeLevel == "CapitalLevelEncoder": // 大写编码器 + return zapcore.CapitalLevelEncoder + case c.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色 + return zapcore.CapitalColorLevelEncoder + default: + return zapcore.LowercaseLevelEncoder + } +} diff --git a/core/internal/constant.go b/core/internal/constant.go new file mode 100644 index 0000000..b22362c --- /dev/null +++ b/core/internal/constant.go @@ -0,0 +1,9 @@ +package internal + +const ( + ConfigEnv = "GVA_CONFIG" + ConfigDefaultFile = "config.yaml" + ConfigTestFile = "config.test.yaml" + ConfigDebugFile = "config.debug.yaml" + ConfigReleaseFile = "config.release.yaml" +) diff --git a/core/internal/cutter.go b/core/internal/cutter.go new file mode 100644 index 0000000..e053af6 --- /dev/null +++ b/core/internal/cutter.go @@ -0,0 +1,121 @@ +package internal + +import ( + "os" + "path/filepath" + "sync" + "time" +) + +// Cutter 实现 io.Writer 接口 +// 用于日志切割, strings.Join([]string{director,layout, formats..., level+".log"}, os.PathSeparator) +type Cutter struct { + level string // 日志级别(debug, info, warn, error, dpanic, panic, fatal) + layout string // 时间格式 2006-01-02 15:04:05 + formats []string // 自定义参数([]string{Director,"2006-01-02", "business"(此参数可不写), level+".log"} + director string // 日志文件夹 + retentionDay int //日志保留天数 + file *os.File // 文件句柄 + mutex *sync.RWMutex // 读写锁 +} + +type CutterOption func(*Cutter) + +// CutterWithLayout 时间格式 +func CutterWithLayout(layout string) CutterOption { + return func(c *Cutter) { + c.layout = layout + } +} + +// CutterWithFormats 格式化参数 +func CutterWithFormats(format ...string) CutterOption { + return func(c *Cutter) { + if len(format) > 0 { + c.formats = format + } + } +} + +func NewCutter(director string, level string, retentionDay int, options ...CutterOption) *Cutter { + rotate := &Cutter{ + level: level, + director: director, + retentionDay: retentionDay, + mutex: new(sync.RWMutex), + } + for i := 0; i < len(options); i++ { + options[i](rotate) + } + return rotate +} + +// Write satisfies the io.Writer interface. It writes to the +// appropriate file handle that is currently being used. +// If we have reached rotation time, the target file gets +// automatically rotated, and also purged if necessary. +func (c *Cutter) Write(bytes []byte) (n int, err error) { + c.mutex.Lock() + defer func() { + if c.file != nil { + _ = c.file.Close() + c.file = nil + } + c.mutex.Unlock() + }() + length := len(c.formats) + values := make([]string, 0, 3+length) + values = append(values, c.director) + if c.layout != "" { + values = append(values, time.Now().Format(c.layout)) + } + for i := 0; i < length; i++ { + values = append(values, c.formats[i]) + } + values = append(values, c.level+".log") + filename := filepath.Join(values...) + director := filepath.Dir(filename) + err = os.MkdirAll(director, os.ModePerm) + if err != nil { + return 0, err + } + err = removeNDaysFolders(c.director, c.retentionDay) + if err != nil { + return 0, err + } + c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return 0, err + } + return c.file.Write(bytes) +} + +func (c *Cutter) Sync() error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.file != nil { + return c.file.Sync() + } + return nil +} + +// 增加日志目录文件清理 小于等于零的值默认忽略不再处理 +func removeNDaysFolders(dir string, days int) error { + if days <= 0 { + return nil + } + cutoff := time.Now().AddDate(0, 0, -days) + return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() && info.ModTime().Before(cutoff) && path != dir { + err = os.RemoveAll(path) + if err != nil { + return err + } + } + return nil + }) +} diff --git a/core/internal/zap_core.go b/core/internal/zap_core.go new file mode 100644 index 0000000..282f751 --- /dev/null +++ b/core/internal/zap_core.go @@ -0,0 +1,68 @@ +package internal + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" + "time" +) + +type ZapCore struct { + level zapcore.Level + zapcore.Core +} + +func NewZapCore(level zapcore.Level) *ZapCore { + entity := &ZapCore{level: level} + syncer := entity.WriteSyncer() + levelEnabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool { + return l == level + }) + entity.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, levelEnabler) + return entity +} + +func (z *ZapCore) WriteSyncer(formats ...string) zapcore.WriteSyncer { + cutter := NewCutter( + global.GVA_CONFIG.Zap.Director, + z.level.String(), + global.GVA_CONFIG.Zap.RetentionDay, + CutterWithLayout(time.DateOnly), + CutterWithFormats(formats...), + ) + if global.GVA_CONFIG.Zap.LogInConsole { + multiSyncer := zapcore.NewMultiWriteSyncer(os.Stdout, cutter) + return zapcore.AddSync(multiSyncer) + } + return zapcore.AddSync(cutter) +} + +func (z *ZapCore) Enabled(level zapcore.Level) bool { + return z.level == level +} + +func (z *ZapCore) With(fields []zapcore.Field) zapcore.Core { + return z.Core.With(fields) +} + +func (z *ZapCore) Check(entry zapcore.Entry, check *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if z.Enabled(entry.Level) { + return check.AddCore(entry, z) + } + return check +} + +func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { + for i := 0; i < len(fields); i++ { + if fields[i].Key == "business" || fields[i].Key == "folder" || fields[i].Key == "directory" { + syncer := z.WriteSyncer(fields[i].String) + z.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level) + } + } + return z.Core.Write(entry, fields) +} + +func (z *ZapCore) Sync() error { + return z.Core.Sync() +} diff --git a/core/server.go b/core/server.go new file mode 100644 index 0000000..4633ca1 --- /dev/null +++ b/core/server.go @@ -0,0 +1,55 @@ +package core + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize" + "git.echol.cn/loser/xiecheng_server/service/system" + "go.uber.org/zap" +) + +type server interface { + ListenAndServe() error +} + +func RunWindowsServer() { + if global.GVA_CONFIG.System.UseMultipoint || global.GVA_CONFIG.System.UseRedis { + // 初始化redis服务 + initialize.Redis() + initialize.RedisList() + } + + if global.GVA_CONFIG.System.UseMongo { + err := initialize.Mongo.Initialization() + if err != nil { + zap.L().Error(fmt.Sprintf("%+v", err)) + } + } + // 从db加载jwt数据 + if global.GVA_DB != nil { + system.LoadAll() + } + + Router := initialize.Routers() + + address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr) + s := initServer(address, Router) + + global.GVA_LOG.Info("server run success on ", zap.String("address", address)) + + fmt.Printf(` + 欢迎使用 gin-vue-admin + 当前版本:v2.8.0 + 加群方式:微信号:shouzi_1994 QQ群:470239250 + 项目地址:https://github.com/flipped-aurora/gin-vue-admin + 插件市场:https://plugin.gin-vue-admin.com + GVA讨论社区:https://support.qq.com/products/371961 + 默认自动化文档地址:http://127.0.0.1%s/swagger/index.html + 默认前端文件运行地址:http://127.0.0.1:8080 + --------------------------------------版权声明-------------------------------------- + ** 版权所有方:flipped-aurora开源团队 ** + ** 版权持有公司:北京翻转极光科技有限责任公司 ** + ** 剔除授权标识需购买商用授权:https://gin-vue-admin.com/empower/index.html ** +`, address) + global.GVA_LOG.Error(s.ListenAndServe().Error()) +} diff --git a/core/server_other.go b/core/server_other.go new file mode 100644 index 0000000..83645fc --- /dev/null +++ b/core/server_other.go @@ -0,0 +1,19 @@ +//go:build !windows +// +build !windows + +package core + +import ( + "time" + + "github.com/fvbock/endless" + "github.com/gin-gonic/gin" +) + +func initServer(address string, router *gin.Engine) server { + s := endless.NewServer(address, router) + s.ReadHeaderTimeout = 10 * time.Minute + s.WriteTimeout = 10 * time.Minute + s.MaxHeaderBytes = 1 << 20 + return s +} diff --git a/core/server_win.go b/core/server_win.go new file mode 100644 index 0000000..20cf44b --- /dev/null +++ b/core/server_win.go @@ -0,0 +1,21 @@ +//go:build windows +// +build windows + +package core + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func initServer(address string, router *gin.Engine) server { + return &http.Server{ + Addr: address, + Handler: router, + ReadTimeout: 10 * time.Minute, + WriteTimeout: 10 * time.Minute, + MaxHeaderBytes: 1 << 20, + } +} diff --git a/core/viper.go b/core/viper.go new file mode 100644 index 0000000..525d824 --- /dev/null +++ b/core/viper.go @@ -0,0 +1,71 @@ +package core + +import ( + "flag" + "fmt" + "git.echol.cn/loser/xiecheng_server/core/internal" + "github.com/gin-gonic/gin" + "os" + "path/filepath" + + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + + "git.echol.cn/loser/xiecheng_server/global" +) + +// Viper // +// 优先级: 命令行 > 环境变量 > 默认值 +// Author [SliverHorn](https://github.com/SliverHorn) +func Viper(path ...string) *viper.Viper { + var config string + + if len(path) == 0 { + flag.StringVar(&config, "c", "", "choose config file.") + flag.Parse() + if config == "" { // 判断命令行参数是否为空 + if configEnv := os.Getenv(internal.ConfigEnv); configEnv == "" { // 判断 internal.ConfigEnv 常量存储的环境变量是否为空 + switch gin.Mode() { + case gin.DebugMode: + config = internal.ConfigDefaultFile + case gin.ReleaseMode: + config = internal.ConfigReleaseFile + case gin.TestMode: + config = internal.ConfigTestFile + } + fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), config) + } else { // internal.ConfigEnv 常量存储的环境变量不为空 将值赋值于config + config = configEnv + fmt.Printf("您正在使用%s环境变量,config的路径为%s\n", internal.ConfigEnv, config) + } + } else { // 命令行参数不为空 将值赋值于config + fmt.Printf("您正在使用命令行的-c参数传递的值,config的路径为%s\n", config) + } + } else { // 函数传递的可变参数的第一个值赋值于config + config = path[0] + fmt.Printf("您正在使用func Viper()传递的值,config的路径为%s\n", config) + } + + v := viper.New() + v.SetConfigFile(config) + v.SetConfigType("yaml") + err := v.ReadInConfig() + if err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + v.WatchConfig() + + v.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("config file changed:", e.Name) + if err = v.Unmarshal(&global.GVA_CONFIG); err != nil { + fmt.Println(err) + } + }) + if err = v.Unmarshal(&global.GVA_CONFIG); err != nil { + panic(err) + } + + // root 适配性 根据root位置去找到对应迁移位置,保证root路径有效 + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..") + return v +} diff --git a/core/zap.go b/core/zap.go new file mode 100644 index 0000000..0534bcc --- /dev/null +++ b/core/zap.go @@ -0,0 +1,32 @@ +package core + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/core/internal" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/utils" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" +) + +// Zap 获取 zap.Logger +// Author [SliverHorn](https://github.com/SliverHorn) +func Zap() (logger *zap.Logger) { + if ok, _ := utils.PathExists(global.GVA_CONFIG.Zap.Director); !ok { // 判断是否有Director文件夹 + fmt.Printf("create %v directory\n", global.GVA_CONFIG.Zap.Director) + _ = os.Mkdir(global.GVA_CONFIG.Zap.Director, os.ModePerm) + } + levels := global.GVA_CONFIG.Zap.Levels() + length := len(levels) + cores := make([]zapcore.Core, 0, length) + for i := 0; i < length; i++ { + core := internal.NewZapCore(levels[i]) + cores = append(cores, core) + } + logger = zap.New(zapcore.NewTee(cores...)) + if global.GVA_CONFIG.Zap.ShowLine { + logger = logger.WithOptions(zap.AddCaller()) + } + return logger +} diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..9edb572 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,9311 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/createApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "创建基础api", + "parameters": [ + { + "description": "api路径, api中文描述, api组, 方法", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysApi" + } + } + ], + "responses": { + "200": { + "description": "创建基础api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/deleteApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "删除api", + "parameters": [ + { + "description": "ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysApi" + } + } + ], + "responses": { + "200": { + "description": "删除api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/deleteApisByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "删除选中Api", + "parameters": [ + { + "description": "ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.IdsReq" + } + } + ], + "responses": { + "200": { + "description": "删除选中Api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/enterSyncApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "确认同步API", + "responses": { + "200": { + "description": "确认同步API", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/freshCasbin": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "刷新casbin缓存", + "responses": { + "200": { + "description": "刷新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/getAllApis": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "获取所有的Api 不分页", + "responses": { + "200": { + "description": "获取所有的Api 不分页,返回包括api列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAPIListResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/getApiById": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "根据id获取api", + "parameters": [ + { + "description": "根据id获取api", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "根据id获取api,返回包括api详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAPIResponse" + } + } + } + ] + } + } + } + } + }, + "/api/getApiGroups": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "获取API分组", + "responses": { + "200": { + "description": "获取API分组", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/getApiList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "分页获取API列表", + "parameters": [ + { + "description": "分页获取API列表", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SearchApiParams" + } + } + ], + "responses": { + "200": { + "description": "分页获取API列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/ignoreApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "IgnoreApi" + ], + "summary": "忽略API", + "responses": { + "200": { + "description": "同步API", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/syncApi": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "同步API", + "responses": { + "200": { + "description": "同步API", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/updateApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "修改基础api", + "parameters": [ + { + "description": "api路径, api中文描述, api组, 方法", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysApi" + } + } + ], + "responses": { + "200": { + "description": "修改基础api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/attachmentCategory/addCategory": { + "post": { + "security": [ + { + "AttachmentCategory": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AddCategory" + ], + "summary": "添加媒体库分类", + "parameters": [ + { + "description": "媒体库分类数据", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaAttachmentCategory" + } + } + ], + "responses": {} + } + }, + "/attachmentCategory/deleteCategory": { + "post": { + "security": [ + { + "AttachmentCategory": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DeleteCategory" + ], + "summary": "删除分类", + "parameters": [ + { + "description": "分类id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除分类", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/attachmentCategory/getCategoryList": { + "get": { + "security": [ + { + "AttachmentCategory": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "GetCategoryList" + ], + "summary": "媒体库分类列表", + "responses": { + "200": { + "description": "媒体库分类列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/example.ExaAttachmentCategory" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/copyAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "拷贝角色", + "parameters": [ + { + "description": "旧角色id, 新权限id, 新权限名, 新父角色id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/response.SysAuthorityCopyResponse" + } + } + ], + "responses": { + "200": { + "description": "拷贝角色,返回包括系统角色详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/createAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "创建角色", + "parameters": [ + { + "description": "权限id, 权限名, 父角色id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "创建角色,返回包括系统角色详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/deleteAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "删除角色", + "parameters": [ + { + "description": "删除角色", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "删除角色", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/getAuthorityList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "分页获取角色列表", + "parameters": [ + { + "description": "页码, 每页大小", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "分页获取角色列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/setDataAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "设置角色资源权限", + "parameters": [ + { + "description": "设置角色资源权限", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "设置角色资源权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/updateAuthority": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "更新角色信息", + "parameters": [ + { + "description": "权限id, 权限名, 父角色id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "更新角色信息,返回包括系统角色详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authorityBtn/canRemoveAuthorityBtn": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityBtn" + ], + "summary": "设置权限按钮", + "responses": { + "200": { + "description": "删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authorityBtn/getAuthorityBtn": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityBtn" + ], + "summary": "获取权限按钮", + "parameters": [ + { + "description": "菜单id, 角色id, 选中的按钮id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAuthorityBtnReq" + } + } + ], + "responses": { + "200": { + "description": "返回列表成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityBtnRes" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authorityBtn/setAuthorityBtn": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityBtn" + ], + "summary": "设置权限按钮", + "parameters": [ + { + "description": "菜单id, 角色id, 选中的按钮id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAuthorityBtnReq" + } + } + ], + "responses": { + "200": { + "description": "返回列表成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/addFunc": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AddFunc" + ], + "summary": "增加方法", + "parameters": [ + { + "description": "增加方法", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AutoCode" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/autoCode/createPackage": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePackage" + ], + "summary": "创建package", + "parameters": [ + { + "description": "创建package", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAutoCodePackageCreate" + } + } + ], + "responses": { + "200": { + "description": "创建package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/createTemp": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodeTemplate" + ], + "summary": "自动代码模板", + "parameters": [ + { + "description": "创建自动代码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AutoCode" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/autoCode/delPackage": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "删除package", + "parameters": [ + { + "description": "创建package", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/delSysHistory": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "删除回滚记录", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除回滚记录", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getColumn": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取当前表所有字段", + "responses": { + "200": { + "description": "获取当前表所有字段", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getDB": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取当前所有数据库", + "responses": { + "200": { + "description": "获取当前所有数据库", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getMeta": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取meta信息", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "获取meta信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getPackage": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePackage" + ], + "summary": "获取package", + "responses": { + "200": { + "description": "创建package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getSysHistory": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "查询回滚记录", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "查询回滚记录,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getTables": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取当前数据库所有表", + "responses": { + "200": { + "description": "获取当前数据库所有表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getTemplates": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePackage" + ], + "summary": "获取package", + "responses": { + "200": { + "description": "创建package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/initAPI": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "打包插件", + "responses": { + "200": { + "description": "打包插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/initMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "打包插件", + "responses": { + "200": { + "description": "打包插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/installPlugin": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "安装插件", + "parameters": [ + { + "type": "file", + "description": "this is a test file", + "name": "plug", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "安装插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/preview": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodeTemplate" + ], + "summary": "预览创建后的代码", + "parameters": [ + { + "description": "预览创建代码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AutoCode" + } + } + ], + "responses": { + "200": { + "description": "预览创建后的代码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/pubPlug": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "打包插件", + "parameters": [ + { + "type": "string", + "description": "插件名称", + "name": "plugName", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "打包插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/rollback": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "回滚自动生成代码", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAutoHistoryRollBack" + } + } + ], + "responses": { + "200": { + "description": "回滚自动生成代码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/base/captcha": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "生成验证码", + "responses": { + "200": { + "description": "生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysCaptchaResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/base/login": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "用户名, 密码, 验证码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Login" + } + } + ], + "responses": { + "200": { + "description": "返回包括用户信息,token,过期时间", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.LoginResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/casbin/UpdateCasbin": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Casbin" + ], + "summary": "更新角色api权限", + "parameters": [ + { + "description": "权限id, 权限模型列表", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CasbinInReceive" + } + } + ], + "responses": { + "200": { + "description": "更新角色api权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/casbin/getPolicyPathByAuthorityId": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Casbin" + ], + "summary": "获取权限列表", + "parameters": [ + { + "description": "权限id, 权限模型列表", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CasbinInReceive" + } + } + ], + "responses": { + "200": { + "description": "获取权限列表,返回包括casbin详情列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PolicyPathResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/customer/customer": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "获取单一客户信息", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "客户名", + "name": "customerName", + "in": "query" + }, + { + "type": "string", + "description": "客户手机号", + "name": "customerPhoneData", + "in": "query" + }, + { + "type": "integer", + "description": "管理角色ID", + "name": "sysUserAuthorityID", + "in": "query" + }, + { + "type": "integer", + "description": "管理ID", + "name": "sysUserId", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取单一客户信息,返回包括客户详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.ExaCustomerResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "更新客户信息", + "parameters": [ + { + "description": "客户ID, 客户信息", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + ], + "responses": { + "200": { + "description": "更新客户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "创建客户", + "parameters": [ + { + "description": "客户用户名, 客户手机号码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + ], + "responses": { + "200": { + "description": "创建客户", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "删除客户", + "parameters": [ + { + "description": "客户ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + ], + "responses": { + "200": { + "description": "删除客户", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/customer/customerList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "分页获取权限客户列表", + "parameters": [ + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + } + ], + "responses": { + "200": { + "description": "分页获取权限客户列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/email/emailTest": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "发送测试邮件", + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/email/sendEmail": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "发送邮件", + "parameters": [ + { + "description": "发送邮件必须的参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/response.Email" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/fileUploadAndDownload/breakpointContinue": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "断点续传到服务器", + "parameters": [ + { + "type": "file", + "description": "an example for breakpoint resume, 断点续传示例", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "断点续传到服务器", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/deleteFile": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "删除文件", + "parameters": [ + { + "description": "传入文件里面id即可", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaFileUploadAndDownload" + } + } + ], + "responses": { + "200": { + "description": "删除文件", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/findFile": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "查找文件", + "parameters": [ + { + "type": "file", + "description": "Find the file, 查找文件", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "查找文件,返回包括文件详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.FileResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "创建文件", + "parameters": [ + { + "type": "file", + "description": "上传文件完成", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "创建文件,返回包括文件路径", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.FilePathResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/getFileList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "分页文件列表", + "parameters": [ + { + "description": "页码, 每页大小, 分类id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ExaAttachmentCategorySearch" + } + } + ], + "responses": { + "200": { + "description": "分页文件列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/importURL": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "导入URL", + "parameters": [ + { + "description": "对象", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaFileUploadAndDownload" + } + } + ], + "responses": { + "200": { + "description": "导入URL", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/removeChunk": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "删除切片", + "parameters": [ + { + "type": "file", + "description": "删除缓存切片", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "删除切片", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/upload": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "上传文件示例", + "parameters": [ + { + "type": "file", + "description": "上传文件示例", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "上传文件示例,返回包括文件详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.ExaFileResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/createInfo": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "创建公告", + "parameters": [ + { + "description": "创建公告", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.Info" + } + } + ], + "responses": { + "200": { + "description": "创建成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/deleteInfo": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "删除公告", + "parameters": [ + { + "description": "删除公告", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.Info" + } + } + ], + "responses": { + "200": { + "description": "删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/deleteInfoByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "批量删除公告", + "responses": { + "200": { + "description": "批量删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/findInfo": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "用id查询公告", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "内容", + "name": "content", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "标题", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "integer", + "description": "作者", + "name": "userID", + "in": "query" + } + ], + "responses": { + "200": { + "description": "查询成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Info" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/getInfoDataSource": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "获取Info的数据源", + "responses": { + "200": { + "description": "查询成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/getInfoList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "分页获取公告列表", + "parameters": [ + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/getInfoPublic": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "不需要鉴权的公告接口", + "parameters": [ + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/updateInfo": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "更新公告", + "parameters": [ + { + "description": "更新公告", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.Info" + } + } + ], + "responses": { + "200": { + "description": "更新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/init/checkdb": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "CheckDB" + ], + "summary": "初始化用户数据库", + "responses": { + "200": { + "description": "初始化用户数据库", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/init/initdb": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "InitDB" + ], + "summary": "初始化用户数据库", + "parameters": [ + { + "description": "初始化数据库参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.InitDB" + } + } + ], + "responses": { + "200": { + "description": "初始化用户数据库", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/jwt/jsonInBlacklist": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jwt" + ], + "summary": "jwt加入黑名单", + "responses": { + "200": { + "description": "jwt加入黑名单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/addBaseMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "新增菜单", + "parameters": [ + { + "description": "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + ], + "responses": { + "200": { + "description": "新增菜单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/addMenuAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "增加menu和角色关联关系", + "parameters": [ + { + "description": "角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AddMenuAuthorityInfo" + } + } + ], + "responses": { + "200": { + "description": "增加menu和角色关联关系", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/deleteBaseMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "删除菜单", + "parameters": [ + { + "description": "菜单id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除菜单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getBaseMenuById": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "根据id获取菜单", + "parameters": [ + { + "description": "菜单id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "根据id获取菜单,返回包括系统菜单列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysBaseMenuResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getBaseMenuTree": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "获取用户动态路由", + "parameters": [ + { + "description": "空", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Empty" + } + } + ], + "responses": { + "200": { + "description": "获取用户动态路由,返回包括系统菜单列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysBaseMenusResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "获取用户动态路由", + "parameters": [ + { + "description": "空", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Empty" + } + } + ], + "responses": { + "200": { + "description": "获取用户动态路由,返回包括系统菜单详情列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysMenusResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getMenuAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "获取指定角色menu", + "parameters": [ + { + "description": "角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetAuthorityId" + } + } + ], + "responses": { + "200": { + "description": "获取指定角色menu", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getMenuList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "分页获取基础menu列表", + "parameters": [ + { + "description": "页码, 每页大小", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "分页获取基础menu列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/updateBaseMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "更新菜单", + "parameters": [ + { + "description": "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + ], + "responses": { + "200": { + "description": "更新菜单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/createSysDictionary": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "创建SysDictionary", + "parameters": [ + { + "description": "SysDictionary模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionary" + } + } + ], + "responses": { + "200": { + "description": "创建SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/deleteSysDictionary": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "删除SysDictionary", + "parameters": [ + { + "description": "SysDictionary模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionary" + } + } + ], + "responses": { + "200": { + "description": "删除SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/findSysDictionary": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "用id查询SysDictionary", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "描述", + "name": "desc", + "in": "query" + }, + { + "type": "string", + "description": "字典名(中)", + "name": "name", + "in": "query" + }, + { + "type": "boolean", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "字典名(英)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "用id查询SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/getSysDictionaryList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "分页获取SysDictionary列表", + "responses": { + "200": { + "description": "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/updateSysDictionary": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "更新SysDictionary", + "parameters": [ + { + "description": "SysDictionary模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionary" + } + } + ], + "responses": { + "200": { + "description": "更新SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/createSysDictionaryDetail": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "创建SysDictionaryDetail", + "parameters": [ + { + "description": "SysDictionaryDetail模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + } + ], + "responses": { + "200": { + "description": "创建SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/deleteSysDictionaryDetail": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "删除SysDictionaryDetail", + "parameters": [ + { + "description": "SysDictionaryDetail模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + } + ], + "responses": { + "200": { + "description": "删除SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/findSysDictionaryDetail": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "用id查询SysDictionaryDetail", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "扩展值", + "name": "extend", + "in": "query" + }, + { + "type": "string", + "description": "展示值", + "name": "label", + "in": "query" + }, + { + "type": "integer", + "description": "排序标记", + "name": "sort", + "in": "query" + }, + { + "type": "boolean", + "description": "启用状态", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "关联标记", + "name": "sysDictionaryID", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "string", + "description": "字典值", + "name": "value", + "in": "query" + } + ], + "responses": { + "200": { + "description": "用id查询SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/getSysDictionaryDetailList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "分页获取SysDictionaryDetail列表", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "扩展值", + "name": "extend", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "string", + "description": "展示值", + "name": "label", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "排序标记", + "name": "sort", + "in": "query" + }, + { + "type": "boolean", + "description": "启用状态", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "关联标记", + "name": "sysDictionaryID", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "string", + "description": "字典值", + "name": "value", + "in": "query" + } + ], + "responses": { + "200": { + "description": "分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/updateSysDictionaryDetail": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "更新SysDictionaryDetail", + "parameters": [ + { + "description": "更新SysDictionaryDetail", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + } + ], + "responses": { + "200": { + "description": "更新SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysExportTemplate/ExportTemplate": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "导出表格模板", + "responses": {} + } + }, + "/sysExportTemplate/createSysExportTemplate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "创建导出模板", + "parameters": [ + { + "description": "创建导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysExportTemplate" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/deleteSysExportTemplate": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "删除导出模板", + "parameters": [ + { + "description": "删除导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysExportTemplate" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/deleteSysExportTemplateByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "批量删除导出模板", + "parameters": [ + { + "description": "批量删除导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.IdsReq" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"批量删除成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/exportExcel": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "导出表格", + "responses": {} + } + }, + "/sysExportTemplate/findSysExportTemplate": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "用id查询导出模板", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "数据库名称", + "name": "dbName", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "模板名称", + "name": "name", + "in": "query" + }, + { + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "string", + "description": "表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "string", + "description": "模板标识", + "name": "templateID", + "in": "query" + }, + { + "type": "string", + "description": "模板信息", + "name": "templateInfo", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/getSysExportTemplateList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "分页获取导出模板列表", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "数据库名称", + "name": "dbName", + "in": "query" + }, + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "模板名称", + "name": "name", + "in": "query" + }, + { + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "string", + "description": "模板标识", + "name": "templateID", + "in": "query" + }, + { + "type": "string", + "description": "模板信息", + "name": "templateInfo", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/importExcel": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysImportTemplate" + ], + "summary": "导入表格", + "responses": {} + } + }, + "/sysExportTemplate/updateSysExportTemplate": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "更新导出模板", + "parameters": [ + { + "description": "更新导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysExportTemplate" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysOperationRecord/createSysOperationRecord": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "创建SysOperationRecord", + "parameters": [ + { + "description": "创建SysOperationRecord", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysOperationRecord" + } + } + ], + "responses": { + "200": { + "description": "创建SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/deleteSysOperationRecord": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "删除SysOperationRecord", + "parameters": [ + { + "description": "SysOperationRecord模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysOperationRecord" + } + } + ], + "responses": { + "200": { + "description": "删除SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/deleteSysOperationRecordByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "批量删除SysOperationRecord", + "parameters": [ + { + "description": "批量删除SysOperationRecord", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.IdsReq" + } + } + ], + "responses": { + "200": { + "description": "批量删除SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/findSysOperationRecord": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "用id查询SysOperationRecord", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "代理", + "name": "agent", + "in": "query" + }, + { + "type": "string", + "description": "请求Body", + "name": "body", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "错误信息", + "name": "error_message", + "in": "query" + }, + { + "type": "string", + "description": "请求ip", + "name": "ip", + "in": "query" + }, + { + "type": "string", + "description": "延迟", + "name": "latency", + "in": "query" + }, + { + "type": "string", + "description": "请求方法", + "name": "method", + "in": "query" + }, + { + "type": "string", + "description": "请求路径", + "name": "path", + "in": "query" + }, + { + "type": "string", + "description": "响应Body", + "name": "resp", + "in": "query" + }, + { + "type": "integer", + "description": "请求状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "用id查询SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/getSysOperationRecordList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "分页获取SysOperationRecord列表", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "代理", + "name": "agent", + "in": "query" + }, + { + "type": "string", + "description": "请求Body", + "name": "body", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "错误信息", + "name": "error_message", + "in": "query" + }, + { + "type": "string", + "description": "请求ip", + "name": "ip", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "string", + "description": "延迟", + "name": "latency", + "in": "query" + }, + { + "type": "string", + "description": "请求方法", + "name": "method", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "description": "请求路径", + "name": "path", + "in": "query" + }, + { + "type": "string", + "description": "响应Body", + "name": "resp", + "in": "query" + }, + { + "type": "integer", + "description": "请求状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/createSysParams": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "创建参数", + "parameters": [ + { + "description": "创建参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysParams" + } + } + ], + "responses": { + "200": { + "description": "创建成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/deleteSysParams": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "删除参数", + "parameters": [ + { + "description": "删除参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysParams" + } + } + ], + "responses": { + "200": { + "description": "删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/deleteSysParamsByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "批量删除参数", + "responses": { + "200": { + "description": "批量删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/findSysParams": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "用id查询参数", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "参数说明", + "name": "desc", + "in": "query" + }, + { + "type": "string", + "description": "参数键", + "name": "key", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "参数名称", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "string", + "description": "参数值", + "name": "value", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "查询成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/system.SysParams" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/getSysParam": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "根据key获取参数value", + "parameters": [ + { + "type": "string", + "description": "key", + "name": "key", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/system.SysParams" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/getSysParamsList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "分页获取参数列表", + "parameters": [ + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "name": "key", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/updateSysParams": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "更新参数", + "parameters": [ + { + "description": "更新参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysParams" + } + } + ], + "responses": { + "200": { + "description": "更新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/getServerInfo": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "获取服务器信息", + "responses": { + "200": { + "description": "获取服务器信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/getSystemConfig": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "获取配置文件内容", + "responses": { + "200": { + "description": "获取配置文件内容,返回包括系统配置", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysConfigResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/reloadSystem": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "重启系统", + "responses": { + "200": { + "description": "重启系统", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/setSystemConfig": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "设置配置文件内容", + "parameters": [ + { + "description": "设置配置文件内容", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.System" + } + } + ], + "responses": { + "200": { + "description": "设置配置文件内容", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/SetSelfInfo": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户信息", + "parameters": [ + { + "description": "ID, 用户名, 昵称, 头像链接", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysUser" + } + } + ], + "responses": { + "200": { + "description": "设置用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/SetSelfSetting": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户配置", + "parameters": [ + { + "description": "用户配置数据", + "name": "data", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "设置用户配置", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/admin_register": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "用户注册账号", + "parameters": [ + { + "description": "用户名, 昵称, 密码, 角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Register" + } + } + ], + "responses": { + "200": { + "description": "用户注册账号,返回包括用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysUserResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/changePassword": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "用户修改密码", + "parameters": [ + { + "description": "用户名, 原密码, 新密码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChangePasswordReq" + } + } + ], + "responses": { + "200": { + "description": "用户修改密码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/deleteUser": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "删除用户", + "parameters": [ + { + "description": "用户ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除用户", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/getUserInfo": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "获取用户信息", + "responses": { + "200": { + "description": "获取用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/getUserList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "分页获取用户列表", + "parameters": [ + { + "description": "页码, 每页大小", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetUserList" + } + } + ], + "responses": { + "200": { + "description": "分页获取用户列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/resetPassword": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "重置用户密码", + "parameters": [ + { + "description": "ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysUser" + } + } + ], + "responses": { + "200": { + "description": "重置用户密码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/setUserAuthorities": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户权限", + "parameters": [ + { + "description": "用户UUID, 角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SetUserAuthorities" + } + } + ], + "responses": { + "200": { + "description": "设置用户权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/setUserAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "更改用户权限", + "parameters": [ + { + "description": "用户UUID, 角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SetUserAuth" + } + } + ], + "responses": { + "200": { + "description": "设置用户权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/setUserInfo": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户信息", + "parameters": [ + { + "description": "ID, 用户名, 昵称, 头像链接", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysUser" + } + } + ], + "responses": { + "200": { + "description": "设置用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "common.JSONMap": { + "type": "object", + "additionalProperties": true + }, + "config.AliyunOSS": { + "type": "object", + "properties": { + "access-key-id": { + "type": "string" + }, + "access-key-secret": { + "type": "string" + }, + "base-path": { + "type": "string" + }, + "bucket-name": { + "type": "string" + }, + "bucket-url": { + "type": "string" + }, + "endpoint": { + "type": "string" + } + } + }, + "config.Autocode": { + "type": "object", + "properties": { + "ai-path": { + "type": "string" + }, + "module": { + "type": "string" + }, + "root": { + "type": "string" + }, + "server": { + "type": "string" + }, + "web": { + "type": "string" + } + } + }, + "config.AwsS3": { + "type": "object", + "properties": { + "base-url": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "disable-ssl": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "path-prefix": { + "type": "string" + }, + "region": { + "type": "string" + }, + "s3-force-path-style": { + "type": "boolean" + }, + "secret-id": { + "type": "string" + }, + "secret-key": { + "type": "string" + } + } + }, + "config.CORS": { + "type": "object", + "properties": { + "mode": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "$ref": "#/definitions/config.CORSWhitelist" + } + } + } + }, + "config.CORSWhitelist": { + "type": "object", + "properties": { + "allow-credentials": { + "type": "boolean" + }, + "allow-headers": { + "type": "string" + }, + "allow-methods": { + "type": "string" + }, + "allow-origin": { + "type": "string" + }, + "expose-headers": { + "type": "string" + } + } + }, + "config.Captcha": { + "type": "object", + "properties": { + "img-height": { + "description": "验证码高度", + "type": "integer" + }, + "img-width": { + "description": "验证码宽度", + "type": "integer" + }, + "key-long": { + "description": "验证码长度", + "type": "integer" + }, + "open-captcha": { + "description": "防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码", + "type": "integer" + }, + "open-captcha-timeout": { + "description": "防爆破验证码超时时间,单位:s(秒)", + "type": "integer" + } + } + }, + "config.CloudflareR2": { + "type": "object", + "properties": { + "access-key-id": { + "type": "string" + }, + "account-id": { + "type": "string" + }, + "base-url": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret-access-key": { + "type": "string" + } + } + }, + "config.DiskList": { + "type": "object", + "properties": { + "mount-point": { + "type": "string" + } + } + }, + "config.Excel": { + "type": "object", + "properties": { + "dir": { + "type": "string" + } + } + }, + "config.HuaWeiObs": { + "type": "object", + "properties": { + "access-key": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret-key": { + "type": "string" + } + } + }, + "config.JWT": { + "type": "object", + "properties": { + "buffer-time": { + "description": "缓冲时间", + "type": "string" + }, + "expires-time": { + "description": "过期时间", + "type": "string" + }, + "issuer": { + "description": "签发者", + "type": "string" + }, + "signing-key": { + "description": "jwt签名", + "type": "string" + } + } + }, + "config.Local": { + "type": "object", + "properties": { + "path": { + "description": "本地文件访问路径", + "type": "string" + }, + "store-path": { + "description": "本地文件存储路径", + "type": "string" + } + } + }, + "config.Minio": { + "type": "object", + "properties": { + "access-key-id": { + "type": "string" + }, + "access-key-secret": { + "type": "string" + }, + "base-path": { + "type": "string" + }, + "bucket-name": { + "type": "string" + }, + "bucket-url": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "use-ssl": { + "type": "boolean" + } + } + }, + "config.Mongo": { + "type": "object", + "properties": { + "auth-source": { + "description": "验证数据库", + "type": "string" + }, + "coll": { + "description": "collection name", + "type": "string" + }, + "connect-timeout-ms": { + "description": "连接超时时间", + "type": "integer" + }, + "database": { + "description": "database name", + "type": "string" + }, + "hosts": { + "description": "主机列表", + "type": "array", + "items": { + "$ref": "#/definitions/config.MongoHost" + } + }, + "is-zap": { + "description": "是否开启zap日志", + "type": "boolean" + }, + "max-pool-size": { + "description": "最大连接池", + "type": "integer" + }, + "min-pool-size": { + "description": "最小连接池", + "type": "integer" + }, + "options": { + "description": "mongodb options", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "socket-timeout-ms": { + "description": "socket超时时间", + "type": "integer" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "config.MongoHost": { + "type": "object", + "properties": { + "host": { + "description": "ip地址", + "type": "string" + }, + "port": { + "description": "端口", + "type": "string" + } + } + }, + "config.Mssql": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Mysql": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Oracle": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Pgsql": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Qiniu": { + "type": "object", + "properties": { + "access-key": { + "description": "秘钥AK", + "type": "string" + }, + "bucket": { + "description": "空间名称", + "type": "string" + }, + "img-path": { + "description": "CDN加速域名", + "type": "string" + }, + "secret-key": { + "description": "秘钥SK", + "type": "string" + }, + "use-cdn-domains": { + "description": "上传是否使用CDN上传加速", + "type": "boolean" + }, + "use-https": { + "description": "是否使用https", + "type": "boolean" + }, + "zone": { + "description": "存储区域", + "type": "string" + } + } + }, + "config.Redis": { + "type": "object", + "properties": { + "addr": { + "description": "服务器地址:端口", + "type": "string" + }, + "clusterAddrs": { + "description": "集群模式下的节点地址列表", + "type": "array", + "items": { + "type": "string" + } + }, + "db": { + "description": "单实例模式下redis的哪个数据库", + "type": "integer" + }, + "name": { + "description": "代表当前实例的名字", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "useCluster": { + "description": "是否使用集群模式", + "type": "boolean" + } + } + }, + "config.Server": { + "type": "object", + "properties": { + "aliyun-oss": { + "$ref": "#/definitions/config.AliyunOSS" + }, + "autocode": { + "description": "auto", + "allOf": [ + { + "$ref": "#/definitions/config.Autocode" + } + ] + }, + "aws-s3": { + "$ref": "#/definitions/config.AwsS3" + }, + "captcha": { + "$ref": "#/definitions/config.Captcha" + }, + "cloudflare-r2": { + "$ref": "#/definitions/config.CloudflareR2" + }, + "cors": { + "description": "跨域配置", + "allOf": [ + { + "$ref": "#/definitions/config.CORS" + } + ] + }, + "db-list": { + "type": "array", + "items": { + "$ref": "#/definitions/config.SpecializedDB" + } + }, + "disk-list": { + "type": "array", + "items": { + "$ref": "#/definitions/config.DiskList" + } + }, + "email": { + "$ref": "#/definitions/github_com_flipped-aurora_gin-vue-admin_server_config.Email" + }, + "excel": { + "$ref": "#/definitions/config.Excel" + }, + "hua-wei-obs": { + "$ref": "#/definitions/config.HuaWeiObs" + }, + "jwt": { + "$ref": "#/definitions/config.JWT" + }, + "local": { + "description": "oss", + "allOf": [ + { + "$ref": "#/definitions/config.Local" + } + ] + }, + "minio": { + "$ref": "#/definitions/config.Minio" + }, + "mongo": { + "$ref": "#/definitions/config.Mongo" + }, + "mssql": { + "$ref": "#/definitions/config.Mssql" + }, + "mysql": { + "description": "gorm", + "allOf": [ + { + "$ref": "#/definitions/config.Mysql" + } + ] + }, + "oracle": { + "$ref": "#/definitions/config.Oracle" + }, + "pgsql": { + "$ref": "#/definitions/config.Pgsql" + }, + "qiniu": { + "$ref": "#/definitions/config.Qiniu" + }, + "redis": { + "$ref": "#/definitions/config.Redis" + }, + "redis-list": { + "type": "array", + "items": { + "$ref": "#/definitions/config.Redis" + } + }, + "sqlite": { + "$ref": "#/definitions/config.Sqlite" + }, + "system": { + "$ref": "#/definitions/config.System" + }, + "tencent-cos": { + "$ref": "#/definitions/config.TencentCOS" + }, + "zap": { + "$ref": "#/definitions/config.Zap" + } + } + }, + "config.SpecializedDB": { + "type": "object", + "properties": { + "alias-name": { + "type": "string" + }, + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "disable": { + "type": "boolean" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "type": { + "type": "string" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Sqlite": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.System": { + "type": "object", + "properties": { + "addr": { + "description": "端口值", + "type": "integer" + }, + "db-type": { + "description": "数据库类型:mysql(默认)|sqlite|sqlserver|postgresql", + "type": "string" + }, + "iplimit-count": { + "type": "integer" + }, + "iplimit-time": { + "type": "integer" + }, + "oss-type": { + "description": "Oss类型", + "type": "string" + }, + "router-prefix": { + "type": "string" + }, + "use-mongo": { + "description": "使用mongo", + "type": "boolean" + }, + "use-multipoint": { + "description": "多点登录拦截", + "type": "boolean" + }, + "use-redis": { + "description": "使用redis", + "type": "boolean" + }, + "use-strict-auth": { + "description": "使用树形角色分配模式", + "type": "boolean" + } + } + }, + "config.TencentCOS": { + "type": "object", + "properties": { + "base-url": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "path-prefix": { + "type": "string" + }, + "region": { + "type": "string" + }, + "secret-id": { + "type": "string" + }, + "secret-key": { + "type": "string" + } + } + }, + "config.Zap": { + "type": "object", + "properties": { + "director": { + "description": "日志文件夹", + "type": "string" + }, + "encode-level": { + "description": "编码级", + "type": "string" + }, + "format": { + "description": "输出", + "type": "string" + }, + "level": { + "description": "级别", + "type": "string" + }, + "log-in-console": { + "description": "输出控制台", + "type": "boolean" + }, + "prefix": { + "description": "日志前缀", + "type": "string" + }, + "retention-day": { + "description": "日志保留天数", + "type": "integer" + }, + "show-line": { + "description": "显示行", + "type": "boolean" + }, + "stacktrace-key": { + "description": "栈名", + "type": "string" + } + } + }, + "example.ExaAttachmentCategory": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/example.ExaAttachmentCategory" + } + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "name": { + "type": "string" + }, + "pid": { + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaCustomer": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "customerName": { + "description": "客户名", + "type": "string" + }, + "customerPhoneData": { + "description": "客户手机号", + "type": "string" + }, + "sysUser": { + "description": "管理详情", + "allOf": [ + { + "$ref": "#/definitions/system.SysUser" + } + ] + }, + "sysUserAuthorityID": { + "description": "管理角色ID", + "type": "integer" + }, + "sysUserId": { + "description": "管理ID", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaFile": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "chunkTotal": { + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "exaFileChunk": { + "type": "array", + "items": { + "$ref": "#/definitions/example.ExaFileChunk" + } + }, + "fileMd5": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "isFinish": { + "type": "boolean" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaFileChunk": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "exaFileID": { + "type": "integer" + }, + "fileChunkNumber": { + "type": "integer" + }, + "fileChunkPath": { + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaFileUploadAndDownload": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "classId": { + "description": "分类id", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "key": { + "description": "编号", + "type": "string" + }, + "name": { + "description": "文件名", + "type": "string" + }, + "tag": { + "description": "文件标签", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "url": { + "description": "文件地址", + "type": "string" + } + } + }, + "github_com_flipped-aurora_gin-vue-admin_server_config.Email": { + "type": "object", + "properties": { + "from": { + "description": "发件人 你自己要发邮件的邮箱", + "type": "string" + }, + "host": { + "description": "服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议", + "type": "string" + }, + "is-ssl": { + "description": "是否SSL 是否开启SSL", + "type": "boolean" + }, + "nickname": { + "description": "昵称 发件人昵称 通常为自己的邮箱", + "type": "string" + }, + "port": { + "description": "端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465", + "type": "integer" + }, + "secret": { + "description": "密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥", + "type": "string" + }, + "to": { + "description": "收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用", + "type": "string" + } + } + }, + "model.Info": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "attachments": { + "description": "附件", + "type": "array", + "items": { + "type": "object" + } + }, + "content": { + "description": "内容", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "userID": { + "description": "作者", + "type": "integer" + } + } + }, + "request.AddMenuAuthorityInfo": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + }, + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + } + }, + "request.AutoCode": { + "type": "object", + "properties": { + "abbreviation": { + "description": "Struct简称", + "type": "string", + "example": "Struct简称" + }, + "autoCreateApiToSql": { + "description": "是否自动创建api", + "type": "boolean", + "example": false + }, + "autoCreateBtnAuth": { + "description": "是否自动创建按钮权限", + "type": "boolean", + "example": false + }, + "autoCreateMenuToSql": { + "description": "是否自动创建menu", + "type": "boolean", + "example": false + }, + "autoCreateResource": { + "description": "是否自动创建资源标识", + "type": "boolean", + "example": false + }, + "autoMigrate": { + "description": "是否自动迁移表结构", + "type": "boolean", + "example": false + }, + "businessDB": { + "description": "业务数据库", + "type": "string", + "example": "业务数据库" + }, + "description": { + "description": "Struct中文名称", + "type": "string", + "example": "Struct中文名称" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/request.AutoCodeField" + } + }, + "generateServer": { + "description": "是否生成server", + "type": "boolean", + "example": true + }, + "generateWeb": { + "description": "是否生成web", + "type": "boolean", + "example": true + }, + "gvaModel": { + "description": "是否使用gva默认Model", + "type": "boolean", + "example": false + }, + "humpPackageName": { + "description": "go文件名称", + "type": "string", + "example": "go文件名称" + }, + "isAdd": { + "description": "是否新增", + "type": "boolean", + "example": false + }, + "isTree": { + "description": "是否树形结构", + "type": "boolean", + "example": false + }, + "onlyTemplate": { + "description": "是否只生成模板", + "type": "boolean", + "example": false + }, + "package": { + "type": "string" + }, + "packageName": { + "description": "文件名称", + "type": "string", + "example": "文件名称" + }, + "primaryField": { + "$ref": "#/definitions/request.AutoCodeField" + }, + "structName": { + "description": "Struct名称", + "type": "string", + "example": "Struct名称" + }, + "tableName": { + "description": "表名", + "type": "string", + "example": "表名" + }, + "treeJson": { + "description": "展示的树json字段", + "type": "string", + "example": "展示的树json字段" + } + } + }, + "request.AutoCodeField": { + "type": "object", + "properties": { + "checkDataSource": { + "description": "是否检查数据源", + "type": "boolean" + }, + "clearable": { + "description": "是否可清空", + "type": "boolean" + }, + "columnName": { + "description": "数据库字段", + "type": "string" + }, + "comment": { + "description": "数据库字段描述", + "type": "string" + }, + "dataSource": { + "description": "数据源", + "allOf": [ + { + "$ref": "#/definitions/request.DataSource" + } + ] + }, + "dataTypeLong": { + "description": "数据库字段长度", + "type": "string" + }, + "defaultValue": { + "description": "是否必填", + "type": "string" + }, + "desc": { + "description": "是否前端详情", + "type": "boolean" + }, + "dictType": { + "description": "字典", + "type": "string" + }, + "errorText": { + "description": "校验失败文字", + "type": "string" + }, + "excel": { + "description": "是否导入/导出", + "type": "boolean" + }, + "fieldDesc": { + "description": "中文名", + "type": "string" + }, + "fieldIndexType": { + "description": "索引类型", + "type": "string" + }, + "fieldJson": { + "description": "FieldJson", + "type": "string" + }, + "fieldName": { + "description": "Field名", + "type": "string" + }, + "fieldSearchHide": { + "description": "是否隐藏查询条件", + "type": "boolean" + }, + "fieldSearchType": { + "description": "搜索条件", + "type": "string" + }, + "fieldType": { + "description": "Field数据类型", + "type": "string" + }, + "form": { + "description": "Front bool ` + "`" + `json:\"front\"` + "`" + ` // 是否前端可见", + "type": "boolean" + }, + "primaryKey": { + "description": "是否主键", + "type": "boolean" + }, + "require": { + "description": "是否必填", + "type": "boolean" + }, + "sort": { + "description": "是否增加排序", + "type": "boolean" + }, + "table": { + "description": "是否前端表格列", + "type": "boolean" + } + } + }, + "request.CasbinInReceive": { + "type": "object", + "properties": { + "authorityId": { + "description": "权限id", + "type": "integer" + }, + "casbinInfos": { + "type": "array", + "items": { + "$ref": "#/definitions/request.CasbinInfo" + } + } + } + }, + "request.CasbinInfo": { + "type": "object", + "properties": { + "method": { + "description": "方法", + "type": "string" + }, + "path": { + "description": "路径", + "type": "string" + } + } + }, + "request.ChangePasswordReq": { + "type": "object", + "properties": { + "newPassword": { + "description": "新密码", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + } + } + }, + "request.DataSource": { + "type": "object", + "properties": { + "association": { + "description": "关联关系 1 一对一 2 一对多", + "type": "integer" + }, + "dbName": { + "type": "string" + }, + "hasDeletedAt": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "table": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "request.Empty": { + "type": "object" + }, + "request.ExaAttachmentCategorySearch": { + "type": "object", + "properties": { + "classId": { + "type": "integer" + }, + "keyword": { + "description": "关键字", + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + } + } + }, + "request.GetAuthorityId": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + } + } + }, + "request.GetById": { + "type": "object", + "properties": { + "id": { + "description": "主键ID", + "type": "integer" + } + } + }, + "request.GetUserList": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "keyword": { + "description": "关键字", + "type": "string" + }, + "nickName": { + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + }, + "phone": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.IdsReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.InitDB": { + "type": "object", + "required": [ + "adminPassword", + "dbName" + ], + "properties": { + "adminPassword": { + "type": "string" + }, + "dbName": { + "description": "数据库名", + "type": "string" + }, + "dbPath": { + "description": "sqlite数据库文件路径", + "type": "string" + }, + "dbType": { + "description": "数据库类型", + "type": "string" + }, + "host": { + "description": "服务器地址", + "type": "string" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "port": { + "description": "数据库连接端口", + "type": "string" + }, + "template": { + "description": "postgresql指定template", + "type": "string" + }, + "userName": { + "description": "数据库用户名", + "type": "string" + } + } + }, + "request.Login": { + "type": "object", + "properties": { + "captcha": { + "description": "验证码", + "type": "string" + }, + "captchaId": { + "description": "验证码ID", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "request.PageInfo": { + "type": "object", + "properties": { + "keyword": { + "description": "关键字", + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + } + } + }, + "request.Register": { + "type": "object", + "properties": { + "authorityId": { + "type": "string", + "example": "int 角色id" + }, + "authorityIds": { + "type": "string", + "example": "[]uint 角色id" + }, + "email": { + "type": "string", + "example": "电子邮箱" + }, + "enable": { + "type": "string", + "example": "int 是否启用" + }, + "headerImg": { + "type": "string", + "example": "头像链接" + }, + "nickName": { + "type": "string", + "example": "昵称" + }, + "passWord": { + "type": "string", + "example": "密码" + }, + "phone": { + "type": "string", + "example": "电话号码" + }, + "userName": { + "type": "string", + "example": "用户名" + } + } + }, + "request.SearchApiParams": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "apiGroup": { + "description": "api组", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "description": "排序方式:升序false(默认)|降序true", + "type": "boolean" + }, + "description": { + "description": "api中文描述", + "type": "string" + }, + "keyword": { + "description": "关键字", + "type": "string" + }, + "method": { + "description": "方法:创建POST(默认)|查看GET|更新PUT|删除DELETE", + "type": "string" + }, + "orderKey": { + "description": "排序", + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + }, + "path": { + "description": "api路径", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "request.SetUserAuth": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + } + } + }, + "request.SetUserAuthorities": { + "type": "object", + "properties": { + "authorityIds": { + "description": "角色ID", + "type": "array", + "items": { + "type": "integer" + } + }, + "id": { + "type": "integer" + } + } + }, + "request.SysAuthorityBtnReq": { + "type": "object", + "properties": { + "authorityId": { + "type": "integer" + }, + "menuID": { + "type": "integer" + }, + "selected": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.SysAutoCodePackageCreate": { + "type": "object", + "properties": { + "desc": { + "type": "string", + "example": "描述" + }, + "label": { + "type": "string", + "example": "展示名" + }, + "packageName": { + "type": "string", + "example": "包名" + }, + "template": { + "type": "string", + "example": "模版" + } + } + }, + "request.SysAutoHistoryRollBack": { + "type": "object", + "properties": { + "deleteApi": { + "description": "是否删除接口", + "type": "boolean" + }, + "deleteMenu": { + "description": "是否删除菜单", + "type": "boolean" + }, + "deleteTable": { + "description": "是否删除表", + "type": "boolean" + }, + "id": { + "description": "主键ID", + "type": "integer" + } + } + }, + "response.Email": { + "type": "object", + "properties": { + "body": { + "description": "邮件内容", + "type": "string" + }, + "subject": { + "description": "邮件标题", + "type": "string" + }, + "to": { + "description": "邮件发送给谁", + "type": "string" + } + } + }, + "response.ExaCustomerResponse": { + "type": "object", + "properties": { + "customer": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + }, + "response.ExaFileResponse": { + "type": "object", + "properties": { + "file": { + "$ref": "#/definitions/example.ExaFileUploadAndDownload" + } + } + }, + "response.FilePathResponse": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + } + } + }, + "response.FileResponse": { + "type": "object", + "properties": { + "file": { + "$ref": "#/definitions/example.ExaFile" + } + } + }, + "response.LoginResponse": { + "type": "object", + "properties": { + "expiresAt": { + "type": "integer" + }, + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/system.SysUser" + } + } + }, + "response.PageResult": { + "type": "object", + "properties": { + "list": {}, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "response.PolicyPathResponse": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "$ref": "#/definitions/request.CasbinInfo" + } + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "msg": { + "type": "string" + } + } + }, + "response.SysAPIListResponse": { + "type": "object", + "properties": { + "apis": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysApi" + } + } + } + }, + "response.SysAPIResponse": { + "type": "object", + "properties": { + "api": { + "$ref": "#/definitions/system.SysApi" + } + } + }, + "response.SysAuthorityBtnRes": { + "type": "object", + "properties": { + "selected": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "response.SysAuthorityCopyResponse": { + "type": "object", + "properties": { + "authority": { + "$ref": "#/definitions/system.SysAuthority" + }, + "oldAuthorityId": { + "description": "旧角色ID", + "type": "integer" + } + } + }, + "response.SysAuthorityResponse": { + "type": "object", + "properties": { + "authority": { + "$ref": "#/definitions/system.SysAuthority" + } + } + }, + "response.SysBaseMenuResponse": { + "type": "object", + "properties": { + "menu": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + }, + "response.SysBaseMenusResponse": { + "type": "object", + "properties": { + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + } + }, + "response.SysCaptchaResponse": { + "type": "object", + "properties": { + "captchaId": { + "type": "string" + }, + "captchaLength": { + "type": "integer" + }, + "openCaptcha": { + "type": "boolean" + }, + "picPath": { + "type": "string" + } + } + }, + "response.SysConfigResponse": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/config.Server" + } + } + }, + "response.SysMenusResponse": { + "type": "object", + "properties": { + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysMenu" + } + } + } + }, + "response.SysUserResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/system.SysUser" + } + } + }, + "system.Condition": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "column": { + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "from": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "templateID": { + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.JoinTemplate": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "joins": { + "type": "string" + }, + "on": { + "type": "string" + }, + "table": { + "type": "string" + }, + "templateID": { + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.Meta": { + "type": "object", + "properties": { + "activeName": { + "type": "string" + }, + "closeTab": { + "description": "自动关闭tab", + "type": "boolean" + }, + "defaultMenu": { + "description": "是否是基础路由(开发中)", + "type": "boolean" + }, + "icon": { + "description": "菜单图标", + "type": "string" + }, + "keepAlive": { + "description": "是否缓存", + "type": "boolean" + }, + "title": { + "description": "菜单名", + "type": "string" + } + } + }, + "system.SysApi": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "apiGroup": { + "description": "api组", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "description": { + "description": "api中文描述", + "type": "string" + }, + "method": { + "description": "方法:创建POST(默认)|查看GET|更新PUT|删除DELETE", + "type": "string" + }, + "path": { + "description": "api路径", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysAuthority": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + }, + "authorityName": { + "description": "角色名", + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "dataAuthorityId": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "defaultRouter": { + "description": "默认菜单(默认dashboard)", + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + }, + "parentId": { + "description": "父角色ID", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysBaseMenu": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "authoritys": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + }, + "component": { + "description": "对应前端文件路径", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "hidden": { + "description": "是否在列表隐藏", + "type": "boolean" + }, + "menuBtn": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuBtn" + } + }, + "meta": { + "description": "附加属性", + "allOf": [ + { + "$ref": "#/definitions/system.Meta" + } + ] + }, + "name": { + "description": "路由name", + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuParameter" + } + }, + "parentId": { + "description": "父菜单ID", + "type": "integer" + }, + "path": { + "description": "路由path", + "type": "string" + }, + "sort": { + "description": "排序标记", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysBaseMenuBtn": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sysBaseMenuID": { + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysBaseMenuParameter": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "key": { + "description": "地址栏携带参数的key", + "type": "string" + }, + "sysBaseMenuID": { + "type": "integer" + }, + "type": { + "description": "地址栏携带参数为params还是query", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "value": { + "description": "地址栏携带参数的值", + "type": "string" + } + } + }, + "system.SysDictionary": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "description": "描述", + "type": "string" + }, + "name": { + "description": "字典名(中)", + "type": "string" + }, + "status": { + "description": "状态", + "type": "boolean" + }, + "sysDictionaryDetails": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + }, + "type": { + "description": "字典名(英)", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysDictionaryDetail": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "extend": { + "description": "扩展值", + "type": "string" + }, + "label": { + "description": "展示值", + "type": "string" + }, + "sort": { + "description": "排序标记", + "type": "integer" + }, + "status": { + "description": "启用状态", + "type": "boolean" + }, + "sysDictionaryID": { + "description": "关联标记", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "value": { + "description": "字典值", + "type": "string" + } + } + }, + "system.SysExportTemplate": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/definitions/system.Condition" + } + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "dbName": { + "description": "数据库名称", + "type": "string" + }, + "joinTemplate": { + "type": "array", + "items": { + "$ref": "#/definitions/system.JoinTemplate" + } + }, + "limit": { + "type": "integer" + }, + "name": { + "description": "模板名称", + "type": "string" + }, + "order": { + "type": "string" + }, + "tableName": { + "description": "表名称", + "type": "string" + }, + "templateID": { + "description": "模板标识", + "type": "string" + }, + "templateInfo": { + "description": "模板信息", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysMenu": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "authoritys": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "btns": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysMenu" + } + }, + "component": { + "description": "对应前端文件路径", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "hidden": { + "description": "是否在列表隐藏", + "type": "boolean" + }, + "menuBtn": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuBtn" + } + }, + "menuId": { + "type": "integer" + }, + "meta": { + "description": "附加属性", + "allOf": [ + { + "$ref": "#/definitions/system.Meta" + } + ] + }, + "name": { + "description": "路由name", + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuParameter" + } + }, + "parentId": { + "description": "父菜单ID", + "type": "integer" + }, + "path": { + "description": "路由path", + "type": "string" + }, + "sort": { + "description": "排序标记", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysOperationRecord": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "agent": { + "description": "代理", + "type": "string" + }, + "body": { + "description": "请求Body", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "error_message": { + "description": "错误信息", + "type": "string" + }, + "ip": { + "description": "请求ip", + "type": "string" + }, + "latency": { + "description": "延迟", + "type": "string" + }, + "method": { + "description": "请求方法", + "type": "string" + }, + "path": { + "description": "请求路径", + "type": "string" + }, + "resp": { + "description": "响应Body", + "type": "string" + }, + "status": { + "description": "请求状态", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "user": { + "$ref": "#/definitions/system.SysUser" + }, + "user_id": { + "description": "用户id", + "type": "integer" + } + } + }, + "system.SysParams": { + "type": "object", + "required": [ + "key", + "name", + "value" + ], + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "description": "参数说明", + "type": "string" + }, + "key": { + "description": "参数键", + "type": "string" + }, + "name": { + "description": "参数名称", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "value": { + "description": "参数值", + "type": "string" + } + } + }, + "system.SysUser": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "authorities": { + "description": "多用户角色", + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "authority": { + "description": "用户角色", + "allOf": [ + { + "$ref": "#/definitions/system.SysAuthority" + } + ] + }, + "authorityId": { + "description": "用户角色ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "email": { + "description": "用户邮箱", + "type": "string" + }, + "enable": { + "description": "用户是否被冻结 1正常 2冻结", + "type": "integer" + }, + "headerImg": { + "description": "用户头像", + "type": "string" + }, + "nickName": { + "description": "用户昵称", + "type": "string" + }, + "originSetting": { + "description": "配置", + "allOf": [ + { + "$ref": "#/definitions/common.JSONMap" + } + ] + }, + "phone": { + "description": "用户手机号", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "userName": { + "description": "用户登录名", + "type": "string" + }, + "uuid": { + "description": "用户UUID", + "type": "string" + } + } + }, + "system.System": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/config.Server" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "x-token", + "in": "header" + } + }, + "tags": [ + { + "name": "Base" + }, + { + "description": "用户", + "name": "SysUser" + } + ] +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "v2.8.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Gin-Vue-Admin Swagger API接口文档", + Description: "使用gin+vue进行极速开发的全栈开发基础平台", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..9eb3da4 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,9286 @@ +{ + "swagger": "2.0", + "info": { + "description": "使用gin+vue进行极速开发的全栈开发基础平台", + "title": "Gin-Vue-Admin Swagger API接口文档", + "contact": {}, + "version": "v2.7.9-beta" + }, + "paths": { + "/api/createApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "创建基础api", + "parameters": [ + { + "description": "api路径, api中文描述, api组, 方法", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysApi" + } + } + ], + "responses": { + "200": { + "description": "创建基础api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/deleteApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "删除api", + "parameters": [ + { + "description": "ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysApi" + } + } + ], + "responses": { + "200": { + "description": "删除api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/deleteApisByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "删除选中Api", + "parameters": [ + { + "description": "ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.IdsReq" + } + } + ], + "responses": { + "200": { + "description": "删除选中Api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/enterSyncApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "确认同步API", + "responses": { + "200": { + "description": "确认同步API", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/freshCasbin": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "刷新casbin缓存", + "responses": { + "200": { + "description": "刷新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/getAllApis": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "获取所有的Api 不分页", + "responses": { + "200": { + "description": "获取所有的Api 不分页,返回包括api列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAPIListResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/getApiById": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "根据id获取api", + "parameters": [ + { + "description": "根据id获取api", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "根据id获取api,返回包括api详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAPIResponse" + } + } + } + ] + } + } + } + } + }, + "/api/getApiGroups": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "获取API分组", + "responses": { + "200": { + "description": "获取API分组", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/getApiList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "分页获取API列表", + "parameters": [ + { + "description": "分页获取API列表", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SearchApiParams" + } + } + ], + "responses": { + "200": { + "description": "分页获取API列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/ignoreApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "IgnoreApi" + ], + "summary": "忽略API", + "responses": { + "200": { + "description": "同步API", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/syncApi": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "同步API", + "responses": { + "200": { + "description": "同步API", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/updateApi": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysApi" + ], + "summary": "修改基础api", + "parameters": [ + { + "description": "api路径, api中文描述, api组, 方法", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysApi" + } + } + ], + "responses": { + "200": { + "description": "修改基础api", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/attachmentCategory/addCategory": { + "post": { + "security": [ + { + "AttachmentCategory": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AddCategory" + ], + "summary": "添加媒体库分类", + "parameters": [ + { + "description": "媒体库分类数据", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaAttachmentCategory" + } + } + ], + "responses": {} + } + }, + "/attachmentCategory/deleteCategory": { + "post": { + "security": [ + { + "AttachmentCategory": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DeleteCategory" + ], + "summary": "删除分类", + "parameters": [ + { + "description": "分类id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除分类", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/attachmentCategory/getCategoryList": { + "get": { + "security": [ + { + "AttachmentCategory": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "GetCategoryList" + ], + "summary": "媒体库分类列表", + "responses": { + "200": { + "description": "媒体库分类列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/example.ExaAttachmentCategory" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/copyAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "拷贝角色", + "parameters": [ + { + "description": "旧角色id, 新权限id, 新权限名, 新父角色id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/response.SysAuthorityCopyResponse" + } + } + ], + "responses": { + "200": { + "description": "拷贝角色,返回包括系统角色详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/createAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "创建角色", + "parameters": [ + { + "description": "权限id, 权限名, 父角色id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "创建角色,返回包括系统角色详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/deleteAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "删除角色", + "parameters": [ + { + "description": "删除角色", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "删除角色", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/getAuthorityList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "分页获取角色列表", + "parameters": [ + { + "description": "页码, 每页大小", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "分页获取角色列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/setDataAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "设置角色资源权限", + "parameters": [ + { + "description": "设置角色资源权限", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "设置角色资源权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authority/updateAuthority": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Authority" + ], + "summary": "更新角色信息", + "parameters": [ + { + "description": "权限id, 权限名, 父角色id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysAuthority" + } + } + ], + "responses": { + "200": { + "description": "更新角色信息,返回包括系统角色详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authorityBtn/canRemoveAuthorityBtn": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityBtn" + ], + "summary": "设置权限按钮", + "responses": { + "200": { + "description": "删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authorityBtn/getAuthorityBtn": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityBtn" + ], + "summary": "获取权限按钮", + "parameters": [ + { + "description": "菜单id, 角色id, 选中的按钮id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAuthorityBtnReq" + } + } + ], + "responses": { + "200": { + "description": "返回列表成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysAuthorityBtnRes" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/authorityBtn/setAuthorityBtn": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityBtn" + ], + "summary": "设置权限按钮", + "parameters": [ + { + "description": "菜单id, 角色id, 选中的按钮id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAuthorityBtnReq" + } + } + ], + "responses": { + "200": { + "description": "返回列表成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/addFunc": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AddFunc" + ], + "summary": "增加方法", + "parameters": [ + { + "description": "增加方法", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AutoCode" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/autoCode/createPackage": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePackage" + ], + "summary": "创建package", + "parameters": [ + { + "description": "创建package", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAutoCodePackageCreate" + } + } + ], + "responses": { + "200": { + "description": "创建package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/createTemp": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodeTemplate" + ], + "summary": "自动代码模板", + "parameters": [ + { + "description": "创建自动代码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AutoCode" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/autoCode/delPackage": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "删除package", + "parameters": [ + { + "description": "创建package", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/delSysHistory": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "删除回滚记录", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除回滚记录", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getColumn": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取当前表所有字段", + "responses": { + "200": { + "description": "获取当前表所有字段", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getDB": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取当前所有数据库", + "responses": { + "200": { + "description": "获取当前所有数据库", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getMeta": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取meta信息", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "获取meta信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getPackage": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePackage" + ], + "summary": "获取package", + "responses": { + "200": { + "description": "创建package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getSysHistory": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "查询回滚记录", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "查询回滚记录,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getTables": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "获取当前数据库所有表", + "responses": { + "200": { + "description": "获取当前数据库所有表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/getTemplates": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePackage" + ], + "summary": "获取package", + "responses": { + "200": { + "description": "创建package成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/initAPI": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "打包插件", + "responses": { + "200": { + "description": "打包插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/initMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "打包插件", + "responses": { + "200": { + "description": "打包插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/installPlugin": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "安装插件", + "parameters": [ + { + "type": "file", + "description": "this is a test file", + "name": "plug", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "安装插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/preview": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodeTemplate" + ], + "summary": "预览创建后的代码", + "parameters": [ + { + "description": "预览创建代码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AutoCode" + } + } + ], + "responses": { + "200": { + "description": "预览创建后的代码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/pubPlug": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCodePlugin" + ], + "summary": "打包插件", + "parameters": [ + { + "type": "string", + "description": "插件名称", + "name": "plugName", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "打包插件成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/autoCode/rollback": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AutoCode" + ], + "summary": "回滚自动生成代码", + "parameters": [ + { + "description": "请求参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SysAutoHistoryRollBack" + } + } + ], + "responses": { + "200": { + "description": "回滚自动生成代码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/base/captcha": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "生成验证码", + "responses": { + "200": { + "description": "生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysCaptchaResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/base/login": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "Base" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "用户名, 密码, 验证码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Login" + } + } + ], + "responses": { + "200": { + "description": "返回包括用户信息,token,过期时间", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.LoginResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/casbin/UpdateCasbin": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Casbin" + ], + "summary": "更新角色api权限", + "parameters": [ + { + "description": "权限id, 权限模型列表", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CasbinInReceive" + } + } + ], + "responses": { + "200": { + "description": "更新角色api权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/casbin/getPolicyPathByAuthorityId": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Casbin" + ], + "summary": "获取权限列表", + "parameters": [ + { + "description": "权限id, 权限模型列表", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CasbinInReceive" + } + } + ], + "responses": { + "200": { + "description": "获取权限列表,返回包括casbin详情列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PolicyPathResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/customer/customer": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "获取单一客户信息", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "客户名", + "name": "customerName", + "in": "query" + }, + { + "type": "string", + "description": "客户手机号", + "name": "customerPhoneData", + "in": "query" + }, + { + "type": "integer", + "description": "管理角色ID", + "name": "sysUserAuthorityID", + "in": "query" + }, + { + "type": "integer", + "description": "管理ID", + "name": "sysUserId", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取单一客户信息,返回包括客户详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.ExaCustomerResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "更新客户信息", + "parameters": [ + { + "description": "客户ID, 客户信息", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + ], + "responses": { + "200": { + "description": "更新客户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "创建客户", + "parameters": [ + { + "description": "客户用户名, 客户手机号码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + ], + "responses": { + "200": { + "description": "创建客户", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "删除客户", + "parameters": [ + { + "description": "客户ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + ], + "responses": { + "200": { + "description": "删除客户", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/customer/customerList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaCustomer" + ], + "summary": "分页获取权限客户列表", + "parameters": [ + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + } + ], + "responses": { + "200": { + "description": "分页获取权限客户列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/email/emailTest": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "发送测试邮件", + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/email/sendEmail": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "发送邮件", + "parameters": [ + { + "description": "发送邮件必须的参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/response.Email" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/fileUploadAndDownload/breakpointContinue": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "断点续传到服务器", + "parameters": [ + { + "type": "file", + "description": "an example for breakpoint resume, 断点续传示例", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "断点续传到服务器", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/deleteFile": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "删除文件", + "parameters": [ + { + "description": "传入文件里面id即可", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaFileUploadAndDownload" + } + } + ], + "responses": { + "200": { + "description": "删除文件", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/findFile": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "查找文件", + "parameters": [ + { + "type": "file", + "description": "Find the file, 查找文件", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "查找文件,返回包括文件详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.FileResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "创建文件", + "parameters": [ + { + "type": "file", + "description": "上传文件完成", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "创建文件,返回包括文件路径", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.FilePathResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/getFileList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "分页文件列表", + "parameters": [ + { + "description": "页码, 每页大小, 分类id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ExaAttachmentCategorySearch" + } + } + ], + "responses": { + "200": { + "description": "分页文件列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/importURL": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "导入URL", + "parameters": [ + { + "description": "对象", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/example.ExaFileUploadAndDownload" + } + } + ], + "responses": { + "200": { + "description": "导入URL", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/removeChunk": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "删除切片", + "parameters": [ + { + "type": "file", + "description": "删除缓存切片", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "删除切片", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/fileUploadAndDownload/upload": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ExaFileUploadAndDownload" + ], + "summary": "上传文件示例", + "parameters": [ + { + "type": "file", + "description": "上传文件示例", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "上传文件示例,返回包括文件详情", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.ExaFileResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/createInfo": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "创建公告", + "parameters": [ + { + "description": "创建公告", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.Info" + } + } + ], + "responses": { + "200": { + "description": "创建成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/deleteInfo": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "删除公告", + "parameters": [ + { + "description": "删除公告", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.Info" + } + } + ], + "responses": { + "200": { + "description": "删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/deleteInfoByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "批量删除公告", + "responses": { + "200": { + "description": "批量删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/findInfo": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "用id查询公告", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "内容", + "name": "content", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "标题", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "integer", + "description": "作者", + "name": "userID", + "in": "query" + } + ], + "responses": { + "200": { + "description": "查询成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/model.Info" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/getInfoDataSource": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "获取Info的数据源", + "responses": { + "200": { + "description": "查询成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/getInfoList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "分页获取公告列表", + "parameters": [ + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/getInfoPublic": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "不需要鉴权的公告接口", + "parameters": [ + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/info/updateInfo": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "更新公告", + "parameters": [ + { + "description": "更新公告", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/model.Info" + } + } + ], + "responses": { + "200": { + "description": "更新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/init/checkdb": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "CheckDB" + ], + "summary": "初始化用户数据库", + "responses": { + "200": { + "description": "初始化用户数据库", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/init/initdb": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "InitDB" + ], + "summary": "初始化用户数据库", + "parameters": [ + { + "description": "初始化数据库参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.InitDB" + } + } + ], + "responses": { + "200": { + "description": "初始化用户数据库", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/jwt/jsonInBlacklist": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Jwt" + ], + "summary": "jwt加入黑名单", + "responses": { + "200": { + "description": "jwt加入黑名单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/addBaseMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "新增菜单", + "parameters": [ + { + "description": "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + ], + "responses": { + "200": { + "description": "新增菜单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/addMenuAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "增加menu和角色关联关系", + "parameters": [ + { + "description": "角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AddMenuAuthorityInfo" + } + } + ], + "responses": { + "200": { + "description": "增加menu和角色关联关系", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/deleteBaseMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "删除菜单", + "parameters": [ + { + "description": "菜单id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除菜单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getBaseMenuById": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "根据id获取菜单", + "parameters": [ + { + "description": "菜单id", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "根据id获取菜单,返回包括系统菜单列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysBaseMenuResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getBaseMenuTree": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "获取用户动态路由", + "parameters": [ + { + "description": "空", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Empty" + } + } + ], + "responses": { + "200": { + "description": "获取用户动态路由,返回包括系统菜单列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysBaseMenusResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "获取用户动态路由", + "parameters": [ + { + "description": "空", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Empty" + } + } + ], + "responses": { + "200": { + "description": "获取用户动态路由,返回包括系统菜单详情列表", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysMenusResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getMenuAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AuthorityMenu" + ], + "summary": "获取指定角色menu", + "parameters": [ + { + "description": "角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetAuthorityId" + } + } + ], + "responses": { + "200": { + "description": "获取指定角色menu", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/getMenuList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "分页获取基础menu列表", + "parameters": [ + { + "description": "页码, 每页大小", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "分页获取基础menu列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/menu/updateBaseMenu": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Menu" + ], + "summary": "更新菜单", + "parameters": [ + { + "description": "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + ], + "responses": { + "200": { + "description": "更新菜单", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/createSysDictionary": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "创建SysDictionary", + "parameters": [ + { + "description": "SysDictionary模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionary" + } + } + ], + "responses": { + "200": { + "description": "创建SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/deleteSysDictionary": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "删除SysDictionary", + "parameters": [ + { + "description": "SysDictionary模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionary" + } + } + ], + "responses": { + "200": { + "description": "删除SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/findSysDictionary": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "用id查询SysDictionary", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "描述", + "name": "desc", + "in": "query" + }, + { + "type": "string", + "description": "字典名(中)", + "name": "name", + "in": "query" + }, + { + "type": "boolean", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "字典名(英)", + "name": "type", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "用id查询SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/getSysDictionaryList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "分页获取SysDictionary列表", + "responses": { + "200": { + "description": "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionary/updateSysDictionary": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionary" + ], + "summary": "更新SysDictionary", + "parameters": [ + { + "description": "SysDictionary模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionary" + } + } + ], + "responses": { + "200": { + "description": "更新SysDictionary", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/createSysDictionaryDetail": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "创建SysDictionaryDetail", + "parameters": [ + { + "description": "SysDictionaryDetail模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + } + ], + "responses": { + "200": { + "description": "创建SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/deleteSysDictionaryDetail": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "删除SysDictionaryDetail", + "parameters": [ + { + "description": "SysDictionaryDetail模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + } + ], + "responses": { + "200": { + "description": "删除SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/findSysDictionaryDetail": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "用id查询SysDictionaryDetail", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "扩展值", + "name": "extend", + "in": "query" + }, + { + "type": "string", + "description": "展示值", + "name": "label", + "in": "query" + }, + { + "type": "integer", + "description": "排序标记", + "name": "sort", + "in": "query" + }, + { + "type": "boolean", + "description": "启用状态", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "关联标记", + "name": "sysDictionaryID", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "string", + "description": "字典值", + "name": "value", + "in": "query" + } + ], + "responses": { + "200": { + "description": "用id查询SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/getSysDictionaryDetailList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "分页获取SysDictionaryDetail列表", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "扩展值", + "name": "extend", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "string", + "description": "展示值", + "name": "label", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "排序标记", + "name": "sort", + "in": "query" + }, + { + "type": "boolean", + "description": "启用状态", + "name": "status", + "in": "query" + }, + { + "type": "integer", + "description": "关联标记", + "name": "sysDictionaryID", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "string", + "description": "字典值", + "name": "value", + "in": "query" + } + ], + "responses": { + "200": { + "description": "分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysDictionaryDetail/updateSysDictionaryDetail": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysDictionaryDetail" + ], + "summary": "更新SysDictionaryDetail", + "parameters": [ + { + "description": "更新SysDictionaryDetail", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + } + ], + "responses": { + "200": { + "description": "更新SysDictionaryDetail", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysExportTemplate/ExportTemplate": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "导出表格模板", + "responses": {} + } + }, + "/sysExportTemplate/createSysExportTemplate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "创建导出模板", + "parameters": [ + { + "description": "创建导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysExportTemplate" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/deleteSysExportTemplate": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "删除导出模板", + "parameters": [ + { + "description": "删除导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysExportTemplate" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/deleteSysExportTemplateByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "批量删除导出模板", + "parameters": [ + { + "description": "批量删除导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.IdsReq" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"批量删除成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/exportExcel": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "导出表格", + "responses": {} + } + }, + "/sysExportTemplate/findSysExportTemplate": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "用id查询导出模板", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "数据库名称", + "name": "dbName", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "模板名称", + "name": "name", + "in": "query" + }, + { + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "string", + "description": "表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "string", + "description": "模板标识", + "name": "templateID", + "in": "query" + }, + { + "type": "string", + "description": "模板信息", + "name": "templateInfo", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/getSysExportTemplateList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "分页获取导出模板列表", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "数据库名称", + "name": "dbName", + "in": "query" + }, + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "模板名称", + "name": "name", + "in": "query" + }, + { + "type": "string", + "name": "order", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + }, + { + "type": "string", + "description": "表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "string", + "description": "模板标识", + "name": "templateID", + "in": "query" + }, + { + "type": "string", + "description": "模板信息", + "name": "templateInfo", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysExportTemplate/importExcel": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysImportTemplate" + ], + "summary": "导入表格", + "responses": {} + } + }, + "/sysExportTemplate/updateSysExportTemplate": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysExportTemplate" + ], + "summary": "更新导出模板", + "parameters": [ + { + "description": "更新导出模板", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysExportTemplate" + } + } + ], + "responses": { + "200": { + "description": "{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/sysOperationRecord/createSysOperationRecord": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "创建SysOperationRecord", + "parameters": [ + { + "description": "创建SysOperationRecord", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysOperationRecord" + } + } + ], + "responses": { + "200": { + "description": "创建SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/deleteSysOperationRecord": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "删除SysOperationRecord", + "parameters": [ + { + "description": "SysOperationRecord模型", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysOperationRecord" + } + } + ], + "responses": { + "200": { + "description": "删除SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/deleteSysOperationRecordByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "批量删除SysOperationRecord", + "parameters": [ + { + "description": "批量删除SysOperationRecord", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.IdsReq" + } + } + ], + "responses": { + "200": { + "description": "批量删除SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/findSysOperationRecord": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "用id查询SysOperationRecord", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "代理", + "name": "agent", + "in": "query" + }, + { + "type": "string", + "description": "请求Body", + "name": "body", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "错误信息", + "name": "error_message", + "in": "query" + }, + { + "type": "string", + "description": "请求ip", + "name": "ip", + "in": "query" + }, + { + "type": "string", + "description": "延迟", + "name": "latency", + "in": "query" + }, + { + "type": "string", + "description": "请求方法", + "name": "method", + "in": "query" + }, + { + "type": "string", + "description": "请求路径", + "name": "path", + "in": "query" + }, + { + "type": "string", + "description": "响应Body", + "name": "resp", + "in": "query" + }, + { + "type": "integer", + "description": "请求状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "用id查询SysOperationRecord", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysOperationRecord/getSysOperationRecordList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysOperationRecord" + ], + "summary": "分页获取SysOperationRecord列表", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "代理", + "name": "agent", + "in": "query" + }, + { + "type": "string", + "description": "请求Body", + "name": "body", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "错误信息", + "name": "error_message", + "in": "query" + }, + { + "type": "string", + "description": "请求ip", + "name": "ip", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "string", + "description": "延迟", + "name": "latency", + "in": "query" + }, + { + "type": "string", + "description": "请求方法", + "name": "method", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "description": "请求路径", + "name": "path", + "in": "query" + }, + { + "type": "string", + "description": "响应Body", + "name": "resp", + "in": "query" + }, + { + "type": "integer", + "description": "请求状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "integer", + "description": "用户id", + "name": "user_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/createSysParams": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "创建参数", + "parameters": [ + { + "description": "创建参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysParams" + } + } + ], + "responses": { + "200": { + "description": "创建成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/deleteSysParams": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "删除参数", + "parameters": [ + { + "description": "删除参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysParams" + } + } + ], + "responses": { + "200": { + "description": "删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/deleteSysParamsByIds": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "批量删除参数", + "responses": { + "200": { + "description": "批量删除成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/findSysParams": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "用id查询参数", + "parameters": [ + { + "type": "integer", + "description": "主键ID", + "name": "ID", + "in": "query" + }, + { + "type": "string", + "description": "创建时间", + "name": "createdAt", + "in": "query" + }, + { + "type": "string", + "description": "参数说明", + "name": "desc", + "in": "query" + }, + { + "type": "string", + "description": "参数键", + "name": "key", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "参数名称", + "name": "name", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "更新时间", + "name": "updatedAt", + "in": "query" + }, + { + "type": "string", + "description": "参数值", + "name": "value", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "查询成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/system.SysParams" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/getSysParam": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "根据key获取参数value", + "parameters": [ + { + "type": "string", + "description": "key", + "name": "key", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/system.SysParams" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/getSysParamsList": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "分页获取参数列表", + "parameters": [ + { + "type": "string", + "name": "endCreatedAt", + "in": "query" + }, + { + "type": "string", + "name": "key", + "in": "query" + }, + { + "type": "string", + "description": "关键字", + "name": "keyword", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "每页大小", + "name": "pageSize", + "in": "query" + }, + { + "type": "string", + "name": "startCreatedAt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/sysParams/updateSysParams": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysParams" + ], + "summary": "更新参数", + "parameters": [ + { + "description": "更新参数", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysParams" + } + } + ], + "responses": { + "200": { + "description": "更新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/getServerInfo": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "获取服务器信息", + "responses": { + "200": { + "description": "获取服务器信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/getSystemConfig": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "获取配置文件内容", + "responses": { + "200": { + "description": "获取配置文件内容,返回包括系统配置", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysConfigResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/reloadSystem": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "重启系统", + "responses": { + "200": { + "description": "重启系统", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/system/setSystemConfig": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "System" + ], + "summary": "设置配置文件内容", + "parameters": [ + { + "description": "设置配置文件内容", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.System" + } + } + ], + "responses": { + "200": { + "description": "设置配置文件内容", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/SetSelfInfo": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户信息", + "parameters": [ + { + "description": "ID, 用户名, 昵称, 头像链接", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysUser" + } + } + ], + "responses": { + "200": { + "description": "设置用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/SetSelfSetting": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户配置", + "parameters": [ + { + "description": "用户配置数据", + "name": "data", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "设置用户配置", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/admin_register": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "用户注册账号", + "parameters": [ + { + "description": "用户名, 昵称, 密码, 角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.Register" + } + } + ], + "responses": { + "200": { + "description": "用户注册账号,返回包括用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.SysUserResponse" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/changePassword": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "用户修改密码", + "parameters": [ + { + "description": "用户名, 原密码, 新密码", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChangePasswordReq" + } + } + ], + "responses": { + "200": { + "description": "用户修改密码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/deleteUser": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "删除用户", + "parameters": [ + { + "description": "用户ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetById" + } + } + ], + "responses": { + "200": { + "description": "删除用户", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/getUserInfo": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "获取用户信息", + "responses": { + "200": { + "description": "获取用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/getUserList": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "分页获取用户列表", + "parameters": [ + { + "description": "页码, 每页大小", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.GetUserList" + } + } + ], + "responses": { + "200": { + "description": "分页获取用户列表,返回包括列表,总数,页码,每页数量", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/response.PageResult" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/resetPassword": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "重置用户密码", + "parameters": [ + { + "description": "ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysUser" + } + } + ], + "responses": { + "200": { + "description": "重置用户密码", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/setUserAuthorities": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户权限", + "parameters": [ + { + "description": "用户UUID, 角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SetUserAuthorities" + } + } + ], + "responses": { + "200": { + "description": "设置用户权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/setUserAuthority": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "更改用户权限", + "parameters": [ + { + "description": "用户UUID, 角色ID", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SetUserAuth" + } + } + ], + "responses": { + "200": { + "description": "设置用户权限", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/user/setUserInfo": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SysUser" + ], + "summary": "设置用户信息", + "parameters": [ + { + "description": "ID, 用户名, 昵称, 头像链接", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/system.SysUser" + } + } + ], + "responses": { + "200": { + "description": "设置用户信息", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + } + }, + "definitions": { + "common.JSONMap": { + "type": "object", + "additionalProperties": true + }, + "config.AliyunOSS": { + "type": "object", + "properties": { + "access-key-id": { + "type": "string" + }, + "access-key-secret": { + "type": "string" + }, + "base-path": { + "type": "string" + }, + "bucket-name": { + "type": "string" + }, + "bucket-url": { + "type": "string" + }, + "endpoint": { + "type": "string" + } + } + }, + "config.Autocode": { + "type": "object", + "properties": { + "ai-path": { + "type": "string" + }, + "module": { + "type": "string" + }, + "root": { + "type": "string" + }, + "server": { + "type": "string" + }, + "web": { + "type": "string" + } + } + }, + "config.AwsS3": { + "type": "object", + "properties": { + "base-url": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "disable-ssl": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "path-prefix": { + "type": "string" + }, + "region": { + "type": "string" + }, + "s3-force-path-style": { + "type": "boolean" + }, + "secret-id": { + "type": "string" + }, + "secret-key": { + "type": "string" + } + } + }, + "config.CORS": { + "type": "object", + "properties": { + "mode": { + "type": "string" + }, + "whitelist": { + "type": "array", + "items": { + "$ref": "#/definitions/config.CORSWhitelist" + } + } + } + }, + "config.CORSWhitelist": { + "type": "object", + "properties": { + "allow-credentials": { + "type": "boolean" + }, + "allow-headers": { + "type": "string" + }, + "allow-methods": { + "type": "string" + }, + "allow-origin": { + "type": "string" + }, + "expose-headers": { + "type": "string" + } + } + }, + "config.Captcha": { + "type": "object", + "properties": { + "img-height": { + "description": "验证码高度", + "type": "integer" + }, + "img-width": { + "description": "验证码宽度", + "type": "integer" + }, + "key-long": { + "description": "验证码长度", + "type": "integer" + }, + "open-captcha": { + "description": "防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码", + "type": "integer" + }, + "open-captcha-timeout": { + "description": "防爆破验证码超时时间,单位:s(秒)", + "type": "integer" + } + } + }, + "config.CloudflareR2": { + "type": "object", + "properties": { + "access-key-id": { + "type": "string" + }, + "account-id": { + "type": "string" + }, + "base-url": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret-access-key": { + "type": "string" + } + } + }, + "config.DiskList": { + "type": "object", + "properties": { + "mount-point": { + "type": "string" + } + } + }, + "config.Excel": { + "type": "object", + "properties": { + "dir": { + "type": "string" + } + } + }, + "config.HuaWeiObs": { + "type": "object", + "properties": { + "access-key": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret-key": { + "type": "string" + } + } + }, + "config.JWT": { + "type": "object", + "properties": { + "buffer-time": { + "description": "缓冲时间", + "type": "string" + }, + "expires-time": { + "description": "过期时间", + "type": "string" + }, + "issuer": { + "description": "签发者", + "type": "string" + }, + "signing-key": { + "description": "jwt签名", + "type": "string" + } + } + }, + "config.Local": { + "type": "object", + "properties": { + "path": { + "description": "本地文件访问路径", + "type": "string" + }, + "store-path": { + "description": "本地文件存储路径", + "type": "string" + } + } + }, + "config.Minio": { + "type": "object", + "properties": { + "access-key-id": { + "type": "string" + }, + "access-key-secret": { + "type": "string" + }, + "base-path": { + "type": "string" + }, + "bucket-name": { + "type": "string" + }, + "bucket-url": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "use-ssl": { + "type": "boolean" + } + } + }, + "config.Mongo": { + "type": "object", + "properties": { + "auth-source": { + "description": "验证数据库", + "type": "string" + }, + "coll": { + "description": "collection name", + "type": "string" + }, + "connect-timeout-ms": { + "description": "连接超时时间", + "type": "integer" + }, + "database": { + "description": "database name", + "type": "string" + }, + "hosts": { + "description": "主机列表", + "type": "array", + "items": { + "$ref": "#/definitions/config.MongoHost" + } + }, + "is-zap": { + "description": "是否开启zap日志", + "type": "boolean" + }, + "max-pool-size": { + "description": "最大连接池", + "type": "integer" + }, + "min-pool-size": { + "description": "最小连接池", + "type": "integer" + }, + "options": { + "description": "mongodb options", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "socket-timeout-ms": { + "description": "socket超时时间", + "type": "integer" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "config.MongoHost": { + "type": "object", + "properties": { + "host": { + "description": "ip地址", + "type": "string" + }, + "port": { + "description": "端口", + "type": "string" + } + } + }, + "config.Mssql": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Mysql": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Oracle": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Pgsql": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Qiniu": { + "type": "object", + "properties": { + "access-key": { + "description": "秘钥AK", + "type": "string" + }, + "bucket": { + "description": "空间名称", + "type": "string" + }, + "img-path": { + "description": "CDN加速域名", + "type": "string" + }, + "secret-key": { + "description": "秘钥SK", + "type": "string" + }, + "use-cdn-domains": { + "description": "上传是否使用CDN上传加速", + "type": "boolean" + }, + "use-https": { + "description": "是否使用https", + "type": "boolean" + }, + "zone": { + "description": "存储区域", + "type": "string" + } + } + }, + "config.Redis": { + "type": "object", + "properties": { + "addr": { + "description": "服务器地址:端口", + "type": "string" + }, + "clusterAddrs": { + "description": "集群模式下的节点地址列表", + "type": "array", + "items": { + "type": "string" + } + }, + "db": { + "description": "单实例模式下redis的哪个数据库", + "type": "integer" + }, + "name": { + "description": "代表当前实例的名字", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "useCluster": { + "description": "是否使用集群模式", + "type": "boolean" + } + } + }, + "config.Server": { + "type": "object", + "properties": { + "aliyun-oss": { + "$ref": "#/definitions/config.AliyunOSS" + }, + "autocode": { + "description": "auto", + "allOf": [ + { + "$ref": "#/definitions/config.Autocode" + } + ] + }, + "aws-s3": { + "$ref": "#/definitions/config.AwsS3" + }, + "captcha": { + "$ref": "#/definitions/config.Captcha" + }, + "cloudflare-r2": { + "$ref": "#/definitions/config.CloudflareR2" + }, + "cors": { + "description": "跨域配置", + "allOf": [ + { + "$ref": "#/definitions/config.CORS" + } + ] + }, + "db-list": { + "type": "array", + "items": { + "$ref": "#/definitions/config.SpecializedDB" + } + }, + "disk-list": { + "type": "array", + "items": { + "$ref": "#/definitions/config.DiskList" + } + }, + "email": { + "$ref": "#/definitions/github_com_flipped-aurora_gin-vue-admin_server_config.Email" + }, + "excel": { + "$ref": "#/definitions/config.Excel" + }, + "hua-wei-obs": { + "$ref": "#/definitions/config.HuaWeiObs" + }, + "jwt": { + "$ref": "#/definitions/config.JWT" + }, + "local": { + "description": "oss", + "allOf": [ + { + "$ref": "#/definitions/config.Local" + } + ] + }, + "minio": { + "$ref": "#/definitions/config.Minio" + }, + "mongo": { + "$ref": "#/definitions/config.Mongo" + }, + "mssql": { + "$ref": "#/definitions/config.Mssql" + }, + "mysql": { + "description": "gorm", + "allOf": [ + { + "$ref": "#/definitions/config.Mysql" + } + ] + }, + "oracle": { + "$ref": "#/definitions/config.Oracle" + }, + "pgsql": { + "$ref": "#/definitions/config.Pgsql" + }, + "qiniu": { + "$ref": "#/definitions/config.Qiniu" + }, + "redis": { + "$ref": "#/definitions/config.Redis" + }, + "redis-list": { + "type": "array", + "items": { + "$ref": "#/definitions/config.Redis" + } + }, + "sqlite": { + "$ref": "#/definitions/config.Sqlite" + }, + "system": { + "$ref": "#/definitions/config.System" + }, + "tencent-cos": { + "$ref": "#/definitions/config.TencentCOS" + }, + "zap": { + "$ref": "#/definitions/config.Zap" + } + } + }, + "config.SpecializedDB": { + "type": "object", + "properties": { + "alias-name": { + "type": "string" + }, + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "disable": { + "type": "boolean" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "type": { + "type": "string" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.Sqlite": { + "type": "object", + "properties": { + "config": { + "description": "高级配置", + "type": "string" + }, + "db-name": { + "description": "数据库名", + "type": "string" + }, + "engine": { + "description": "数据库引擎,默认InnoDB", + "type": "string", + "default": "InnoDB" + }, + "log-mode": { + "description": "是否开启Gorm全局日志", + "type": "string" + }, + "log-zap": { + "description": "是否通过zap写入日志文件", + "type": "boolean" + }, + "max-idle-conns": { + "description": "空闲中的最大连接数", + "type": "integer" + }, + "max-open-conns": { + "description": "打开到数据库的最大连接数", + "type": "integer" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "path": { + "description": "数据库地址", + "type": "string" + }, + "port": { + "description": "数据库端口", + "type": "string" + }, + "prefix": { + "description": "数据库前缀", + "type": "string" + }, + "singular": { + "description": "是否开启全局禁用复数,true表示开启", + "type": "boolean" + }, + "username": { + "description": "数据库账号", + "type": "string" + } + } + }, + "config.System": { + "type": "object", + "properties": { + "addr": { + "description": "端口值", + "type": "integer" + }, + "db-type": { + "description": "数据库类型:mysql(默认)|sqlite|sqlserver|postgresql", + "type": "string" + }, + "iplimit-count": { + "type": "integer" + }, + "iplimit-time": { + "type": "integer" + }, + "oss-type": { + "description": "Oss类型", + "type": "string" + }, + "router-prefix": { + "type": "string" + }, + "use-mongo": { + "description": "使用mongo", + "type": "boolean" + }, + "use-multipoint": { + "description": "多点登录拦截", + "type": "boolean" + }, + "use-redis": { + "description": "使用redis", + "type": "boolean" + }, + "use-strict-auth": { + "description": "使用树形角色分配模式", + "type": "boolean" + } + } + }, + "config.TencentCOS": { + "type": "object", + "properties": { + "base-url": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "path-prefix": { + "type": "string" + }, + "region": { + "type": "string" + }, + "secret-id": { + "type": "string" + }, + "secret-key": { + "type": "string" + } + } + }, + "config.Zap": { + "type": "object", + "properties": { + "director": { + "description": "日志文件夹", + "type": "string" + }, + "encode-level": { + "description": "编码级", + "type": "string" + }, + "format": { + "description": "输出", + "type": "string" + }, + "level": { + "description": "级别", + "type": "string" + }, + "log-in-console": { + "description": "输出控制台", + "type": "boolean" + }, + "prefix": { + "description": "日志前缀", + "type": "string" + }, + "retention-day": { + "description": "日志保留天数", + "type": "integer" + }, + "show-line": { + "description": "显示行", + "type": "boolean" + }, + "stacktrace-key": { + "description": "栈名", + "type": "string" + } + } + }, + "example.ExaAttachmentCategory": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/example.ExaAttachmentCategory" + } + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "name": { + "type": "string" + }, + "pid": { + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaCustomer": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "customerName": { + "description": "客户名", + "type": "string" + }, + "customerPhoneData": { + "description": "客户手机号", + "type": "string" + }, + "sysUser": { + "description": "管理详情", + "allOf": [ + { + "$ref": "#/definitions/system.SysUser" + } + ] + }, + "sysUserAuthorityID": { + "description": "管理角色ID", + "type": "integer" + }, + "sysUserId": { + "description": "管理ID", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaFile": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "chunkTotal": { + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "exaFileChunk": { + "type": "array", + "items": { + "$ref": "#/definitions/example.ExaFileChunk" + } + }, + "fileMd5": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "isFinish": { + "type": "boolean" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaFileChunk": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "exaFileID": { + "type": "integer" + }, + "fileChunkNumber": { + "type": "integer" + }, + "fileChunkPath": { + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "example.ExaFileUploadAndDownload": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "classId": { + "description": "分类id", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "key": { + "description": "编号", + "type": "string" + }, + "name": { + "description": "文件名", + "type": "string" + }, + "tag": { + "description": "文件标签", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "url": { + "description": "文件地址", + "type": "string" + } + } + }, + "github_com_flipped-aurora_gin-vue-admin_server_config.Email": { + "type": "object", + "properties": { + "from": { + "description": "发件人 你自己要发邮件的邮箱", + "type": "string" + }, + "host": { + "description": "服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议", + "type": "string" + }, + "is-ssl": { + "description": "是否SSL 是否开启SSL", + "type": "boolean" + }, + "nickname": { + "description": "昵称 发件人昵称 通常为自己的邮箱", + "type": "string" + }, + "port": { + "description": "端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465", + "type": "integer" + }, + "secret": { + "description": "密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥", + "type": "string" + }, + "to": { + "description": "收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用", + "type": "string" + } + } + }, + "model.Info": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "attachments": { + "description": "附件", + "type": "array", + "items": { + "type": "object" + } + }, + "content": { + "description": "内容", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "userID": { + "description": "作者", + "type": "integer" + } + } + }, + "request.AddMenuAuthorityInfo": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + }, + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + } + }, + "request.AutoCode": { + "type": "object", + "properties": { + "abbreviation": { + "description": "Struct简称", + "type": "string", + "example": "Struct简称" + }, + "autoCreateApiToSql": { + "description": "是否自动创建api", + "type": "boolean", + "example": false + }, + "autoCreateBtnAuth": { + "description": "是否自动创建按钮权限", + "type": "boolean", + "example": false + }, + "autoCreateMenuToSql": { + "description": "是否自动创建menu", + "type": "boolean", + "example": false + }, + "autoCreateResource": { + "description": "是否自动创建资源标识", + "type": "boolean", + "example": false + }, + "autoMigrate": { + "description": "是否自动迁移表结构", + "type": "boolean", + "example": false + }, + "businessDB": { + "description": "业务数据库", + "type": "string", + "example": "业务数据库" + }, + "description": { + "description": "Struct中文名称", + "type": "string", + "example": "Struct中文名称" + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/request.AutoCodeField" + } + }, + "generateServer": { + "description": "是否生成server", + "type": "boolean", + "example": true + }, + "generateWeb": { + "description": "是否生成web", + "type": "boolean", + "example": true + }, + "gvaModel": { + "description": "是否使用gva默认Model", + "type": "boolean", + "example": false + }, + "humpPackageName": { + "description": "go文件名称", + "type": "string", + "example": "go文件名称" + }, + "isAdd": { + "description": "是否新增", + "type": "boolean", + "example": false + }, + "isTree": { + "description": "是否树形结构", + "type": "boolean", + "example": false + }, + "onlyTemplate": { + "description": "是否只生成模板", + "type": "boolean", + "example": false + }, + "package": { + "type": "string" + }, + "packageName": { + "description": "文件名称", + "type": "string", + "example": "文件名称" + }, + "primaryField": { + "$ref": "#/definitions/request.AutoCodeField" + }, + "structName": { + "description": "Struct名称", + "type": "string", + "example": "Struct名称" + }, + "tableName": { + "description": "表名", + "type": "string", + "example": "表名" + }, + "treeJson": { + "description": "展示的树json字段", + "type": "string", + "example": "展示的树json字段" + } + } + }, + "request.AutoCodeField": { + "type": "object", + "properties": { + "checkDataSource": { + "description": "是否检查数据源", + "type": "boolean" + }, + "clearable": { + "description": "是否可清空", + "type": "boolean" + }, + "columnName": { + "description": "数据库字段", + "type": "string" + }, + "comment": { + "description": "数据库字段描述", + "type": "string" + }, + "dataSource": { + "description": "数据源", + "allOf": [ + { + "$ref": "#/definitions/request.DataSource" + } + ] + }, + "dataTypeLong": { + "description": "数据库字段长度", + "type": "string" + }, + "defaultValue": { + "description": "是否必填", + "type": "string" + }, + "desc": { + "description": "是否前端详情", + "type": "boolean" + }, + "dictType": { + "description": "字典", + "type": "string" + }, + "errorText": { + "description": "校验失败文字", + "type": "string" + }, + "excel": { + "description": "是否导入/导出", + "type": "boolean" + }, + "fieldDesc": { + "description": "中文名", + "type": "string" + }, + "fieldIndexType": { + "description": "索引类型", + "type": "string" + }, + "fieldJson": { + "description": "FieldJson", + "type": "string" + }, + "fieldName": { + "description": "Field名", + "type": "string" + }, + "fieldSearchHide": { + "description": "是否隐藏查询条件", + "type": "boolean" + }, + "fieldSearchType": { + "description": "搜索条件", + "type": "string" + }, + "fieldType": { + "description": "Field数据类型", + "type": "string" + }, + "form": { + "description": "Front bool `json:\"front\"` // 是否前端可见", + "type": "boolean" + }, + "primaryKey": { + "description": "是否主键", + "type": "boolean" + }, + "require": { + "description": "是否必填", + "type": "boolean" + }, + "sort": { + "description": "是否增加排序", + "type": "boolean" + }, + "table": { + "description": "是否前端表格列", + "type": "boolean" + } + } + }, + "request.CasbinInReceive": { + "type": "object", + "properties": { + "authorityId": { + "description": "权限id", + "type": "integer" + }, + "casbinInfos": { + "type": "array", + "items": { + "$ref": "#/definitions/request.CasbinInfo" + } + } + } + }, + "request.CasbinInfo": { + "type": "object", + "properties": { + "method": { + "description": "方法", + "type": "string" + }, + "path": { + "description": "路径", + "type": "string" + } + } + }, + "request.ChangePasswordReq": { + "type": "object", + "properties": { + "newPassword": { + "description": "新密码", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + } + } + }, + "request.DataSource": { + "type": "object", + "properties": { + "association": { + "description": "关联关系 1 一对一 2 一对多", + "type": "integer" + }, + "dbName": { + "type": "string" + }, + "hasDeletedAt": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "table": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "request.Empty": { + "type": "object" + }, + "request.ExaAttachmentCategorySearch": { + "type": "object", + "properties": { + "classId": { + "type": "integer" + }, + "keyword": { + "description": "关键字", + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + } + } + }, + "request.GetAuthorityId": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + } + } + }, + "request.GetById": { + "type": "object", + "properties": { + "id": { + "description": "主键ID", + "type": "integer" + } + } + }, + "request.GetUserList": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "keyword": { + "description": "关键字", + "type": "string" + }, + "nickName": { + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + }, + "phone": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.IdsReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.InitDB": { + "type": "object", + "required": [ + "adminPassword", + "dbName" + ], + "properties": { + "adminPassword": { + "type": "string" + }, + "dbName": { + "description": "数据库名", + "type": "string" + }, + "dbPath": { + "description": "sqlite数据库文件路径", + "type": "string" + }, + "dbType": { + "description": "数据库类型", + "type": "string" + }, + "host": { + "description": "服务器地址", + "type": "string" + }, + "password": { + "description": "数据库密码", + "type": "string" + }, + "port": { + "description": "数据库连接端口", + "type": "string" + }, + "template": { + "description": "postgresql指定template", + "type": "string" + }, + "userName": { + "description": "数据库用户名", + "type": "string" + } + } + }, + "request.Login": { + "type": "object", + "properties": { + "captcha": { + "description": "验证码", + "type": "string" + }, + "captchaId": { + "description": "验证码ID", + "type": "string" + }, + "password": { + "description": "密码", + "type": "string" + }, + "username": { + "description": "用户名", + "type": "string" + } + } + }, + "request.PageInfo": { + "type": "object", + "properties": { + "keyword": { + "description": "关键字", + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + } + } + }, + "request.Register": { + "type": "object", + "properties": { + "authorityId": { + "type": "string", + "example": "int 角色id" + }, + "authorityIds": { + "type": "string", + "example": "[]uint 角色id" + }, + "email": { + "type": "string", + "example": "电子邮箱" + }, + "enable": { + "type": "string", + "example": "int 是否启用" + }, + "headerImg": { + "type": "string", + "example": "头像链接" + }, + "nickName": { + "type": "string", + "example": "昵称" + }, + "passWord": { + "type": "string", + "example": "密码" + }, + "phone": { + "type": "string", + "example": "电话号码" + }, + "userName": { + "type": "string", + "example": "用户名" + } + } + }, + "request.SearchApiParams": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "apiGroup": { + "description": "api组", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "description": "排序方式:升序false(默认)|降序true", + "type": "boolean" + }, + "description": { + "description": "api中文描述", + "type": "string" + }, + "keyword": { + "description": "关键字", + "type": "string" + }, + "method": { + "description": "方法:创建POST(默认)|查看GET|更新PUT|删除DELETE", + "type": "string" + }, + "orderKey": { + "description": "排序", + "type": "string" + }, + "page": { + "description": "页码", + "type": "integer" + }, + "pageSize": { + "description": "每页大小", + "type": "integer" + }, + "path": { + "description": "api路径", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "request.SetUserAuth": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + } + } + }, + "request.SetUserAuthorities": { + "type": "object", + "properties": { + "authorityIds": { + "description": "角色ID", + "type": "array", + "items": { + "type": "integer" + } + }, + "id": { + "type": "integer" + } + } + }, + "request.SysAuthorityBtnReq": { + "type": "object", + "properties": { + "authorityId": { + "type": "integer" + }, + "menuID": { + "type": "integer" + }, + "selected": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.SysAutoCodePackageCreate": { + "type": "object", + "properties": { + "desc": { + "type": "string", + "example": "描述" + }, + "label": { + "type": "string", + "example": "展示名" + }, + "packageName": { + "type": "string", + "example": "包名" + }, + "template": { + "type": "string", + "example": "模版" + } + } + }, + "request.SysAutoHistoryRollBack": { + "type": "object", + "properties": { + "deleteApi": { + "description": "是否删除接口", + "type": "boolean" + }, + "deleteMenu": { + "description": "是否删除菜单", + "type": "boolean" + }, + "deleteTable": { + "description": "是否删除表", + "type": "boolean" + }, + "id": { + "description": "主键ID", + "type": "integer" + } + } + }, + "response.Email": { + "type": "object", + "properties": { + "body": { + "description": "邮件内容", + "type": "string" + }, + "subject": { + "description": "邮件标题", + "type": "string" + }, + "to": { + "description": "邮件发送给谁", + "type": "string" + } + } + }, + "response.ExaCustomerResponse": { + "type": "object", + "properties": { + "customer": { + "$ref": "#/definitions/example.ExaCustomer" + } + } + }, + "response.ExaFileResponse": { + "type": "object", + "properties": { + "file": { + "$ref": "#/definitions/example.ExaFileUploadAndDownload" + } + } + }, + "response.FilePathResponse": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + } + } + }, + "response.FileResponse": { + "type": "object", + "properties": { + "file": { + "$ref": "#/definitions/example.ExaFile" + } + } + }, + "response.LoginResponse": { + "type": "object", + "properties": { + "expiresAt": { + "type": "integer" + }, + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/system.SysUser" + } + } + }, + "response.PageResult": { + "type": "object", + "properties": { + "list": {}, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "response.PolicyPathResponse": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "$ref": "#/definitions/request.CasbinInfo" + } + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "msg": { + "type": "string" + } + } + }, + "response.SysAPIListResponse": { + "type": "object", + "properties": { + "apis": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysApi" + } + } + } + }, + "response.SysAPIResponse": { + "type": "object", + "properties": { + "api": { + "$ref": "#/definitions/system.SysApi" + } + } + }, + "response.SysAuthorityBtnRes": { + "type": "object", + "properties": { + "selected": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "response.SysAuthorityCopyResponse": { + "type": "object", + "properties": { + "authority": { + "$ref": "#/definitions/system.SysAuthority" + }, + "oldAuthorityId": { + "description": "旧角色ID", + "type": "integer" + } + } + }, + "response.SysAuthorityResponse": { + "type": "object", + "properties": { + "authority": { + "$ref": "#/definitions/system.SysAuthority" + } + } + }, + "response.SysBaseMenuResponse": { + "type": "object", + "properties": { + "menu": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + }, + "response.SysBaseMenusResponse": { + "type": "object", + "properties": { + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + } + } + }, + "response.SysCaptchaResponse": { + "type": "object", + "properties": { + "captchaId": { + "type": "string" + }, + "captchaLength": { + "type": "integer" + }, + "openCaptcha": { + "type": "boolean" + }, + "picPath": { + "type": "string" + } + } + }, + "response.SysConfigResponse": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/config.Server" + } + } + }, + "response.SysMenusResponse": { + "type": "object", + "properties": { + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysMenu" + } + } + } + }, + "response.SysUserResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/system.SysUser" + } + } + }, + "system.Condition": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "column": { + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "from": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "templateID": { + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.JoinTemplate": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "joins": { + "type": "string" + }, + "on": { + "type": "string" + }, + "table": { + "type": "string" + }, + "templateID": { + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.Meta": { + "type": "object", + "properties": { + "activeName": { + "type": "string" + }, + "closeTab": { + "description": "自动关闭tab", + "type": "boolean" + }, + "defaultMenu": { + "description": "是否是基础路由(开发中)", + "type": "boolean" + }, + "icon": { + "description": "菜单图标", + "type": "string" + }, + "keepAlive": { + "description": "是否缓存", + "type": "boolean" + }, + "title": { + "description": "菜单名", + "type": "string" + } + } + }, + "system.SysApi": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "apiGroup": { + "description": "api组", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "description": { + "description": "api中文描述", + "type": "string" + }, + "method": { + "description": "方法:创建POST(默认)|查看GET|更新PUT|删除DELETE", + "type": "string" + }, + "path": { + "description": "api路径", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysAuthority": { + "type": "object", + "properties": { + "authorityId": { + "description": "角色ID", + "type": "integer" + }, + "authorityName": { + "description": "角色名", + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "dataAuthorityId": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "defaultRouter": { + "description": "默认菜单(默认dashboard)", + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "menus": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + }, + "parentId": { + "description": "父角色ID", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysBaseMenu": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "authoritys": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenu" + } + }, + "component": { + "description": "对应前端文件路径", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "hidden": { + "description": "是否在列表隐藏", + "type": "boolean" + }, + "menuBtn": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuBtn" + } + }, + "meta": { + "description": "附加属性", + "allOf": [ + { + "$ref": "#/definitions/system.Meta" + } + ] + }, + "name": { + "description": "路由name", + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuParameter" + } + }, + "parentId": { + "description": "父菜单ID", + "type": "integer" + }, + "path": { + "description": "路由path", + "type": "string" + }, + "sort": { + "description": "排序标记", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysBaseMenuBtn": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sysBaseMenuID": { + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysBaseMenuParameter": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "key": { + "description": "地址栏携带参数的key", + "type": "string" + }, + "sysBaseMenuID": { + "type": "integer" + }, + "type": { + "description": "地址栏携带参数为params还是query", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "value": { + "description": "地址栏携带参数的值", + "type": "string" + } + } + }, + "system.SysDictionary": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "description": "描述", + "type": "string" + }, + "name": { + "description": "字典名(中)", + "type": "string" + }, + "status": { + "description": "状态", + "type": "boolean" + }, + "sysDictionaryDetails": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysDictionaryDetail" + } + }, + "type": { + "description": "字典名(英)", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysDictionaryDetail": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "extend": { + "description": "扩展值", + "type": "string" + }, + "label": { + "description": "展示值", + "type": "string" + }, + "sort": { + "description": "排序标记", + "type": "integer" + }, + "status": { + "description": "启用状态", + "type": "boolean" + }, + "sysDictionaryID": { + "description": "关联标记", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "value": { + "description": "字典值", + "type": "string" + } + } + }, + "system.SysExportTemplate": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/definitions/system.Condition" + } + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "dbName": { + "description": "数据库名称", + "type": "string" + }, + "joinTemplate": { + "type": "array", + "items": { + "$ref": "#/definitions/system.JoinTemplate" + } + }, + "limit": { + "type": "integer" + }, + "name": { + "description": "模板名称", + "type": "string" + }, + "order": { + "type": "string" + }, + "tableName": { + "description": "表名称", + "type": "string" + }, + "templateID": { + "description": "模板标识", + "type": "string" + }, + "templateInfo": { + "description": "模板信息", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysMenu": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "authoritys": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "btns": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysMenu" + } + }, + "component": { + "description": "对应前端文件路径", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "hidden": { + "description": "是否在列表隐藏", + "type": "boolean" + }, + "menuBtn": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuBtn" + } + }, + "menuId": { + "type": "integer" + }, + "meta": { + "description": "附加属性", + "allOf": [ + { + "$ref": "#/definitions/system.Meta" + } + ] + }, + "name": { + "description": "路由name", + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/definitions/system.SysBaseMenuParameter" + } + }, + "parentId": { + "description": "父菜单ID", + "type": "integer" + }, + "path": { + "description": "路由path", + "type": "string" + }, + "sort": { + "description": "排序标记", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + } + } + }, + "system.SysOperationRecord": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "agent": { + "description": "代理", + "type": "string" + }, + "body": { + "description": "请求Body", + "type": "string" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "error_message": { + "description": "错误信息", + "type": "string" + }, + "ip": { + "description": "请求ip", + "type": "string" + }, + "latency": { + "description": "延迟", + "type": "string" + }, + "method": { + "description": "请求方法", + "type": "string" + }, + "path": { + "description": "请求路径", + "type": "string" + }, + "resp": { + "description": "响应Body", + "type": "string" + }, + "status": { + "description": "请求状态", + "type": "integer" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "user": { + "$ref": "#/definitions/system.SysUser" + }, + "user_id": { + "description": "用户id", + "type": "integer" + } + } + }, + "system.SysParams": { + "type": "object", + "required": [ + "key", + "name", + "value" + ], + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "desc": { + "description": "参数说明", + "type": "string" + }, + "key": { + "description": "参数键", + "type": "string" + }, + "name": { + "description": "参数名称", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "value": { + "description": "参数值", + "type": "string" + } + } + }, + "system.SysUser": { + "type": "object", + "properties": { + "ID": { + "description": "主键ID", + "type": "integer" + }, + "authorities": { + "description": "多用户角色", + "type": "array", + "items": { + "$ref": "#/definitions/system.SysAuthority" + } + }, + "authority": { + "description": "用户角色", + "allOf": [ + { + "$ref": "#/definitions/system.SysAuthority" + } + ] + }, + "authorityId": { + "description": "用户角色ID", + "type": "integer" + }, + "createdAt": { + "description": "创建时间", + "type": "string" + }, + "email": { + "description": "用户邮箱", + "type": "string" + }, + "enable": { + "description": "用户是否被冻结 1正常 2冻结", + "type": "integer" + }, + "headerImg": { + "description": "用户头像", + "type": "string" + }, + "nickName": { + "description": "用户昵称", + "type": "string" + }, + "originSetting": { + "description": "配置", + "allOf": [ + { + "$ref": "#/definitions/common.JSONMap" + } + ] + }, + "phone": { + "description": "用户手机号", + "type": "string" + }, + "updatedAt": { + "description": "更新时间", + "type": "string" + }, + "userName": { + "description": "用户登录名", + "type": "string" + }, + "uuid": { + "description": "用户UUID", + "type": "string" + } + } + }, + "system.System": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/config.Server" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "x-token", + "in": "header" + } + }, + "tags": [ + { + "name": "Base" + }, + { + "description": "用户", + "name": "SysUser" + } + ] +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..1a1f8cc --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,5677 @@ +definitions: + common.JSONMap: + additionalProperties: true + type: object + config.AliyunOSS: + properties: + access-key-id: + type: string + access-key-secret: + type: string + base-path: + type: string + bucket-name: + type: string + bucket-url: + type: string + endpoint: + type: string + type: object + config.Autocode: + properties: + ai-path: + type: string + module: + type: string + root: + type: string + server: + type: string + web: + type: string + type: object + config.AwsS3: + properties: + base-url: + type: string + bucket: + type: string + disable-ssl: + type: boolean + endpoint: + type: string + path-prefix: + type: string + region: + type: string + s3-force-path-style: + type: boolean + secret-id: + type: string + secret-key: + type: string + type: object + config.CORS: + properties: + mode: + type: string + whitelist: + items: + $ref: '#/definitions/config.CORSWhitelist' + type: array + type: object + config.CORSWhitelist: + properties: + allow-credentials: + type: boolean + allow-headers: + type: string + allow-methods: + type: string + allow-origin: + type: string + expose-headers: + type: string + type: object + config.Captcha: + properties: + img-height: + description: 验证码高度 + type: integer + img-width: + description: 验证码宽度 + type: integer + key-long: + description: 验证码长度 + type: integer + open-captcha: + description: 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码 + type: integer + open-captcha-timeout: + description: 防爆破验证码超时时间,单位:s(秒) + type: integer + type: object + config.CloudflareR2: + properties: + access-key-id: + type: string + account-id: + type: string + base-url: + type: string + bucket: + type: string + path: + type: string + secret-access-key: + type: string + type: object + config.DiskList: + properties: + mount-point: + type: string + type: object + config.Excel: + properties: + dir: + type: string + type: object + config.HuaWeiObs: + properties: + access-key: + type: string + bucket: + type: string + endpoint: + type: string + path: + type: string + secret-key: + type: string + type: object + config.JWT: + properties: + buffer-time: + description: 缓冲时间 + type: string + expires-time: + description: 过期时间 + type: string + issuer: + description: 签发者 + type: string + signing-key: + description: jwt签名 + type: string + type: object + config.Local: + properties: + path: + description: 本地文件访问路径 + type: string + store-path: + description: 本地文件存储路径 + type: string + type: object + config.Minio: + properties: + access-key-id: + type: string + access-key-secret: + type: string + base-path: + type: string + bucket-name: + type: string + bucket-url: + type: string + endpoint: + type: string + use-ssl: + type: boolean + type: object + config.Mongo: + properties: + auth-source: + description: 验证数据库 + type: string + coll: + description: collection name + type: string + connect-timeout-ms: + description: 连接超时时间 + type: integer + database: + description: database name + type: string + hosts: + description: 主机列表 + items: + $ref: '#/definitions/config.MongoHost' + type: array + is-zap: + description: 是否开启zap日志 + type: boolean + max-pool-size: + description: 最大连接池 + type: integer + min-pool-size: + description: 最小连接池 + type: integer + options: + description: mongodb options + type: string + password: + description: 密码 + type: string + socket-timeout-ms: + description: socket超时时间 + type: integer + username: + description: 用户名 + type: string + type: object + config.MongoHost: + properties: + host: + description: ip地址 + type: string + port: + description: 端口 + type: string + type: object + config.Mssql: + properties: + config: + description: 高级配置 + type: string + db-name: + description: 数据库名 + type: string + engine: + default: InnoDB + description: 数据库引擎,默认InnoDB + type: string + log-mode: + description: 是否开启Gorm全局日志 + type: string + log-zap: + description: 是否通过zap写入日志文件 + type: boolean + max-idle-conns: + description: 空闲中的最大连接数 + type: integer + max-open-conns: + description: 打开到数据库的最大连接数 + type: integer + password: + description: 数据库密码 + type: string + path: + description: 数据库地址 + type: string + port: + description: 数据库端口 + type: string + prefix: + description: 数据库前缀 + type: string + singular: + description: 是否开启全局禁用复数,true表示开启 + type: boolean + username: + description: 数据库账号 + type: string + type: object + config.Mysql: + properties: + config: + description: 高级配置 + type: string + db-name: + description: 数据库名 + type: string + engine: + default: InnoDB + description: 数据库引擎,默认InnoDB + type: string + log-mode: + description: 是否开启Gorm全局日志 + type: string + log-zap: + description: 是否通过zap写入日志文件 + type: boolean + max-idle-conns: + description: 空闲中的最大连接数 + type: integer + max-open-conns: + description: 打开到数据库的最大连接数 + type: integer + password: + description: 数据库密码 + type: string + path: + description: 数据库地址 + type: string + port: + description: 数据库端口 + type: string + prefix: + description: 数据库前缀 + type: string + singular: + description: 是否开启全局禁用复数,true表示开启 + type: boolean + username: + description: 数据库账号 + type: string + type: object + config.Oracle: + properties: + config: + description: 高级配置 + type: string + db-name: + description: 数据库名 + type: string + engine: + default: InnoDB + description: 数据库引擎,默认InnoDB + type: string + log-mode: + description: 是否开启Gorm全局日志 + type: string + log-zap: + description: 是否通过zap写入日志文件 + type: boolean + max-idle-conns: + description: 空闲中的最大连接数 + type: integer + max-open-conns: + description: 打开到数据库的最大连接数 + type: integer + password: + description: 数据库密码 + type: string + path: + description: 数据库地址 + type: string + port: + description: 数据库端口 + type: string + prefix: + description: 数据库前缀 + type: string + singular: + description: 是否开启全局禁用复数,true表示开启 + type: boolean + username: + description: 数据库账号 + type: string + type: object + config.Pgsql: + properties: + config: + description: 高级配置 + type: string + db-name: + description: 数据库名 + type: string + engine: + default: InnoDB + description: 数据库引擎,默认InnoDB + type: string + log-mode: + description: 是否开启Gorm全局日志 + type: string + log-zap: + description: 是否通过zap写入日志文件 + type: boolean + max-idle-conns: + description: 空闲中的最大连接数 + type: integer + max-open-conns: + description: 打开到数据库的最大连接数 + type: integer + password: + description: 数据库密码 + type: string + path: + description: 数据库地址 + type: string + port: + description: 数据库端口 + type: string + prefix: + description: 数据库前缀 + type: string + singular: + description: 是否开启全局禁用复数,true表示开启 + type: boolean + username: + description: 数据库账号 + type: string + type: object + config.Qiniu: + properties: + access-key: + description: 秘钥AK + type: string + bucket: + description: 空间名称 + type: string + img-path: + description: CDN加速域名 + type: string + secret-key: + description: 秘钥SK + type: string + use-cdn-domains: + description: 上传是否使用CDN上传加速 + type: boolean + use-https: + description: 是否使用https + type: boolean + zone: + description: 存储区域 + type: string + type: object + config.Redis: + properties: + addr: + description: 服务器地址:端口 + type: string + clusterAddrs: + description: 集群模式下的节点地址列表 + items: + type: string + type: array + db: + description: 单实例模式下redis的哪个数据库 + type: integer + name: + description: 代表当前实例的名字 + type: string + password: + description: 密码 + type: string + useCluster: + description: 是否使用集群模式 + type: boolean + type: object + config.Server: + properties: + aliyun-oss: + $ref: '#/definitions/config.AliyunOSS' + autocode: + allOf: + - $ref: '#/definitions/config.Autocode' + description: auto + aws-s3: + $ref: '#/definitions/config.AwsS3' + captcha: + $ref: '#/definitions/config.Captcha' + cloudflare-r2: + $ref: '#/definitions/config.CloudflareR2' + cors: + allOf: + - $ref: '#/definitions/config.CORS' + description: 跨域配置 + db-list: + items: + $ref: '#/definitions/config.SpecializedDB' + type: array + disk-list: + items: + $ref: '#/definitions/config.DiskList' + type: array + email: + $ref: '#/definitions/github_com_flipped-aurora_gin-vue-admin_server_config.Email' + excel: + $ref: '#/definitions/config.Excel' + hua-wei-obs: + $ref: '#/definitions/config.HuaWeiObs' + jwt: + $ref: '#/definitions/config.JWT' + local: + allOf: + - $ref: '#/definitions/config.Local' + description: oss + minio: + $ref: '#/definitions/config.Minio' + mongo: + $ref: '#/definitions/config.Mongo' + mssql: + $ref: '#/definitions/config.Mssql' + mysql: + allOf: + - $ref: '#/definitions/config.Mysql' + description: gorm + oracle: + $ref: '#/definitions/config.Oracle' + pgsql: + $ref: '#/definitions/config.Pgsql' + qiniu: + $ref: '#/definitions/config.Qiniu' + redis: + $ref: '#/definitions/config.Redis' + redis-list: + items: + $ref: '#/definitions/config.Redis' + type: array + sqlite: + $ref: '#/definitions/config.Sqlite' + system: + $ref: '#/definitions/config.System' + tencent-cos: + $ref: '#/definitions/config.TencentCOS' + zap: + $ref: '#/definitions/config.Zap' + type: object + config.SpecializedDB: + properties: + alias-name: + type: string + config: + description: 高级配置 + type: string + db-name: + description: 数据库名 + type: string + disable: + type: boolean + engine: + default: InnoDB + description: 数据库引擎,默认InnoDB + type: string + log-mode: + description: 是否开启Gorm全局日志 + type: string + log-zap: + description: 是否通过zap写入日志文件 + type: boolean + max-idle-conns: + description: 空闲中的最大连接数 + type: integer + max-open-conns: + description: 打开到数据库的最大连接数 + type: integer + password: + description: 数据库密码 + type: string + path: + description: 数据库地址 + type: string + port: + description: 数据库端口 + type: string + prefix: + description: 数据库前缀 + type: string + singular: + description: 是否开启全局禁用复数,true表示开启 + type: boolean + type: + type: string + username: + description: 数据库账号 + type: string + type: object + config.Sqlite: + properties: + config: + description: 高级配置 + type: string + db-name: + description: 数据库名 + type: string + engine: + default: InnoDB + description: 数据库引擎,默认InnoDB + type: string + log-mode: + description: 是否开启Gorm全局日志 + type: string + log-zap: + description: 是否通过zap写入日志文件 + type: boolean + max-idle-conns: + description: 空闲中的最大连接数 + type: integer + max-open-conns: + description: 打开到数据库的最大连接数 + type: integer + password: + description: 数据库密码 + type: string + path: + description: 数据库地址 + type: string + port: + description: 数据库端口 + type: string + prefix: + description: 数据库前缀 + type: string + singular: + description: 是否开启全局禁用复数,true表示开启 + type: boolean + username: + description: 数据库账号 + type: string + type: object + config.System: + properties: + addr: + description: 端口值 + type: integer + db-type: + description: 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql + type: string + iplimit-count: + type: integer + iplimit-time: + type: integer + oss-type: + description: Oss类型 + type: string + router-prefix: + type: string + use-mongo: + description: 使用mongo + type: boolean + use-multipoint: + description: 多点登录拦截 + type: boolean + use-redis: + description: 使用redis + type: boolean + use-strict-auth: + description: 使用树形角色分配模式 + type: boolean + type: object + config.TencentCOS: + properties: + base-url: + type: string + bucket: + type: string + path-prefix: + type: string + region: + type: string + secret-id: + type: string + secret-key: + type: string + type: object + config.Zap: + properties: + director: + description: 日志文件夹 + type: string + encode-level: + description: 编码级 + type: string + format: + description: 输出 + type: string + level: + description: 级别 + type: string + log-in-console: + description: 输出控制台 + type: boolean + prefix: + description: 日志前缀 + type: string + retention-day: + description: 日志保留天数 + type: integer + show-line: + description: 显示行 + type: boolean + stacktrace-key: + description: 栈名 + type: string + type: object + example.ExaAttachmentCategory: + properties: + ID: + description: 主键ID + type: integer + children: + items: + $ref: '#/definitions/example.ExaAttachmentCategory' + type: array + createdAt: + description: 创建时间 + type: string + name: + type: string + pid: + type: integer + updatedAt: + description: 更新时间 + type: string + type: object + example.ExaCustomer: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + customerName: + description: 客户名 + type: string + customerPhoneData: + description: 客户手机号 + type: string + sysUser: + allOf: + - $ref: '#/definitions/system.SysUser' + description: 管理详情 + sysUserAuthorityID: + description: 管理角色ID + type: integer + sysUserId: + description: 管理ID + type: integer + updatedAt: + description: 更新时间 + type: string + type: object + example.ExaFile: + properties: + ID: + description: 主键ID + type: integer + chunkTotal: + type: integer + createdAt: + description: 创建时间 + type: string + exaFileChunk: + items: + $ref: '#/definitions/example.ExaFileChunk' + type: array + fileMd5: + type: string + fileName: + type: string + filePath: + type: string + isFinish: + type: boolean + updatedAt: + description: 更新时间 + type: string + type: object + example.ExaFileChunk: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + exaFileID: + type: integer + fileChunkNumber: + type: integer + fileChunkPath: + type: string + updatedAt: + description: 更新时间 + type: string + type: object + example.ExaFileUploadAndDownload: + properties: + ID: + description: 主键ID + type: integer + classId: + description: 分类id + type: integer + createdAt: + description: 创建时间 + type: string + key: + description: 编号 + type: string + name: + description: 文件名 + type: string + tag: + description: 文件标签 + type: string + updatedAt: + description: 更新时间 + type: string + url: + description: 文件地址 + type: string + type: object + github_com_flipped-aurora_gin-vue-admin_server_config.Email: + properties: + from: + description: 发件人 你自己要发邮件的邮箱 + type: string + host: + description: 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议 + type: string + is-ssl: + description: 是否SSL 是否开启SSL + type: boolean + nickname: + description: 昵称 发件人昵称 通常为自己的邮箱 + type: string + port: + description: 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465 + type: integer + secret: + description: 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥 + type: string + to: + description: 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 + type: string + type: object + model.Info: + properties: + ID: + description: 主键ID + type: integer + attachments: + description: 附件 + items: + type: object + type: array + content: + description: 内容 + type: string + createdAt: + description: 创建时间 + type: string + title: + description: 标题 + type: string + updatedAt: + description: 更新时间 + type: string + userID: + description: 作者 + type: integer + type: object + request.AddMenuAuthorityInfo: + properties: + authorityId: + description: 角色ID + type: integer + menus: + items: + $ref: '#/definitions/system.SysBaseMenu' + type: array + type: object + request.AutoCode: + properties: + abbreviation: + description: Struct简称 + example: Struct简称 + type: string + autoCreateApiToSql: + description: 是否自动创建api + example: false + type: boolean + autoCreateBtnAuth: + description: 是否自动创建按钮权限 + example: false + type: boolean + autoCreateMenuToSql: + description: 是否自动创建menu + example: false + type: boolean + autoCreateResource: + description: 是否自动创建资源标识 + example: false + type: boolean + autoMigrate: + description: 是否自动迁移表结构 + example: false + type: boolean + businessDB: + description: 业务数据库 + example: 业务数据库 + type: string + description: + description: Struct中文名称 + example: Struct中文名称 + type: string + fields: + items: + $ref: '#/definitions/request.AutoCodeField' + type: array + generateServer: + description: 是否生成server + example: true + type: boolean + generateWeb: + description: 是否生成web + example: true + type: boolean + gvaModel: + description: 是否使用gva默认Model + example: false + type: boolean + humpPackageName: + description: go文件名称 + example: go文件名称 + type: string + isAdd: + description: 是否新增 + example: false + type: boolean + isTree: + description: 是否树形结构 + example: false + type: boolean + onlyTemplate: + description: 是否只生成模板 + example: false + type: boolean + package: + type: string + packageName: + description: 文件名称 + example: 文件名称 + type: string + primaryField: + $ref: '#/definitions/request.AutoCodeField' + structName: + description: Struct名称 + example: Struct名称 + type: string + tableName: + description: 表名 + example: 表名 + type: string + treeJson: + description: 展示的树json字段 + example: 展示的树json字段 + type: string + type: object + request.AutoCodeField: + properties: + checkDataSource: + description: 是否检查数据源 + type: boolean + clearable: + description: 是否可清空 + type: boolean + columnName: + description: 数据库字段 + type: string + comment: + description: 数据库字段描述 + type: string + dataSource: + allOf: + - $ref: '#/definitions/request.DataSource' + description: 数据源 + dataTypeLong: + description: 数据库字段长度 + type: string + defaultValue: + description: 是否必填 + type: string + desc: + description: 是否前端详情 + type: boolean + dictType: + description: 字典 + type: string + errorText: + description: 校验失败文字 + type: string + excel: + description: 是否导入/导出 + type: boolean + fieldDesc: + description: 中文名 + type: string + fieldIndexType: + description: 索引类型 + type: string + fieldJson: + description: FieldJson + type: string + fieldName: + description: Field名 + type: string + fieldSearchHide: + description: 是否隐藏查询条件 + type: boolean + fieldSearchType: + description: 搜索条件 + type: string + fieldType: + description: Field数据类型 + type: string + form: + description: Front bool `json:"front"` // 是否前端可见 + type: boolean + primaryKey: + description: 是否主键 + type: boolean + require: + description: 是否必填 + type: boolean + sort: + description: 是否增加排序 + type: boolean + table: + description: 是否前端表格列 + type: boolean + type: object + request.CasbinInReceive: + properties: + authorityId: + description: 权限id + type: integer + casbinInfos: + items: + $ref: '#/definitions/request.CasbinInfo' + type: array + type: object + request.CasbinInfo: + properties: + method: + description: 方法 + type: string + path: + description: 路径 + type: string + type: object + request.ChangePasswordReq: + properties: + newPassword: + description: 新密码 + type: string + password: + description: 密码 + type: string + type: object + request.DataSource: + properties: + association: + description: 关联关系 1 一对一 2 一对多 + type: integer + dbName: + type: string + hasDeletedAt: + type: boolean + label: + type: string + table: + type: string + value: + type: string + type: object + request.Empty: + type: object + request.ExaAttachmentCategorySearch: + properties: + classId: + type: integer + keyword: + description: 关键字 + type: string + page: + description: 页码 + type: integer + pageSize: + description: 每页大小 + type: integer + type: object + request.GetAuthorityId: + properties: + authorityId: + description: 角色ID + type: integer + type: object + request.GetById: + properties: + id: + description: 主键ID + type: integer + type: object + request.GetUserList: + properties: + email: + type: string + keyword: + description: 关键字 + type: string + nickName: + type: string + page: + description: 页码 + type: integer + pageSize: + description: 每页大小 + type: integer + phone: + type: string + username: + type: string + type: object + request.IdsReq: + properties: + ids: + items: + type: integer + type: array + type: object + request.InitDB: + properties: + adminPassword: + type: string + dbName: + description: 数据库名 + type: string + dbPath: + description: sqlite数据库文件路径 + type: string + dbType: + description: 数据库类型 + type: string + host: + description: 服务器地址 + type: string + password: + description: 数据库密码 + type: string + port: + description: 数据库连接端口 + type: string + template: + description: postgresql指定template + type: string + userName: + description: 数据库用户名 + type: string + required: + - adminPassword + - dbName + type: object + request.Login: + properties: + captcha: + description: 验证码 + type: string + captchaId: + description: 验证码ID + type: string + password: + description: 密码 + type: string + username: + description: 用户名 + type: string + type: object + request.PageInfo: + properties: + keyword: + description: 关键字 + type: string + page: + description: 页码 + type: integer + pageSize: + description: 每页大小 + type: integer + type: object + request.Register: + properties: + authorityId: + example: int 角色id + type: string + authorityIds: + example: '[]uint 角色id' + type: string + email: + example: 电子邮箱 + type: string + enable: + example: int 是否启用 + type: string + headerImg: + example: 头像链接 + type: string + nickName: + example: 昵称 + type: string + passWord: + example: 密码 + type: string + phone: + example: 电话号码 + type: string + userName: + example: 用户名 + type: string + type: object + request.SearchApiParams: + properties: + ID: + description: 主键ID + type: integer + apiGroup: + description: api组 + type: string + createdAt: + description: 创建时间 + type: string + desc: + description: 排序方式:升序false(默认)|降序true + type: boolean + description: + description: api中文描述 + type: string + keyword: + description: 关键字 + type: string + method: + description: 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE + type: string + orderKey: + description: 排序 + type: string + page: + description: 页码 + type: integer + pageSize: + description: 每页大小 + type: integer + path: + description: api路径 + type: string + updatedAt: + description: 更新时间 + type: string + type: object + request.SetUserAuth: + properties: + authorityId: + description: 角色ID + type: integer + type: object + request.SetUserAuthorities: + properties: + authorityIds: + description: 角色ID + items: + type: integer + type: array + id: + type: integer + type: object + request.SysAuthorityBtnReq: + properties: + authorityId: + type: integer + menuID: + type: integer + selected: + items: + type: integer + type: array + type: object + request.SysAutoCodePackageCreate: + properties: + desc: + example: 描述 + type: string + label: + example: 展示名 + type: string + packageName: + example: 包名 + type: string + template: + example: 模版 + type: string + type: object + request.SysAutoHistoryRollBack: + properties: + deleteApi: + description: 是否删除接口 + type: boolean + deleteMenu: + description: 是否删除菜单 + type: boolean + deleteTable: + description: 是否删除表 + type: boolean + id: + description: 主键ID + type: integer + type: object + response.Email: + properties: + body: + description: 邮件内容 + type: string + subject: + description: 邮件标题 + type: string + to: + description: 邮件发送给谁 + type: string + type: object + response.ExaCustomerResponse: + properties: + customer: + $ref: '#/definitions/example.ExaCustomer' + type: object + response.ExaFileResponse: + properties: + file: + $ref: '#/definitions/example.ExaFileUploadAndDownload' + type: object + response.FilePathResponse: + properties: + filePath: + type: string + type: object + response.FileResponse: + properties: + file: + $ref: '#/definitions/example.ExaFile' + type: object + response.LoginResponse: + properties: + expiresAt: + type: integer + token: + type: string + user: + $ref: '#/definitions/system.SysUser' + type: object + response.PageResult: + properties: + list: {} + page: + type: integer + pageSize: + type: integer + total: + type: integer + type: object + response.PolicyPathResponse: + properties: + paths: + items: + $ref: '#/definitions/request.CasbinInfo' + type: array + type: object + response.Response: + properties: + code: + type: integer + data: {} + msg: + type: string + type: object + response.SysAPIListResponse: + properties: + apis: + items: + $ref: '#/definitions/system.SysApi' + type: array + type: object + response.SysAPIResponse: + properties: + api: + $ref: '#/definitions/system.SysApi' + type: object + response.SysAuthorityBtnRes: + properties: + selected: + items: + type: integer + type: array + type: object + response.SysAuthorityCopyResponse: + properties: + authority: + $ref: '#/definitions/system.SysAuthority' + oldAuthorityId: + description: 旧角色ID + type: integer + type: object + response.SysAuthorityResponse: + properties: + authority: + $ref: '#/definitions/system.SysAuthority' + type: object + response.SysBaseMenuResponse: + properties: + menu: + $ref: '#/definitions/system.SysBaseMenu' + type: object + response.SysBaseMenusResponse: + properties: + menus: + items: + $ref: '#/definitions/system.SysBaseMenu' + type: array + type: object + response.SysCaptchaResponse: + properties: + captchaId: + type: string + captchaLength: + type: integer + openCaptcha: + type: boolean + picPath: + type: string + type: object + response.SysConfigResponse: + properties: + config: + $ref: '#/definitions/config.Server' + type: object + response.SysMenusResponse: + properties: + menus: + items: + $ref: '#/definitions/system.SysMenu' + type: array + type: object + response.SysUserResponse: + properties: + user: + $ref: '#/definitions/system.SysUser' + type: object + system.Condition: + properties: + ID: + description: 主键ID + type: integer + column: + type: string + createdAt: + description: 创建时间 + type: string + from: + type: string + operator: + type: string + templateID: + type: string + updatedAt: + description: 更新时间 + type: string + type: object + system.JoinTemplate: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + joins: + type: string + "on": + type: string + table: + type: string + templateID: + type: string + updatedAt: + description: 更新时间 + type: string + type: object + system.Meta: + properties: + activeName: + type: string + closeTab: + description: 自动关闭tab + type: boolean + defaultMenu: + description: 是否是基础路由(开发中) + type: boolean + icon: + description: 菜单图标 + type: string + keepAlive: + description: 是否缓存 + type: boolean + title: + description: 菜单名 + type: string + type: object + system.SysApi: + properties: + ID: + description: 主键ID + type: integer + apiGroup: + description: api组 + type: string + createdAt: + description: 创建时间 + type: string + description: + description: api中文描述 + type: string + method: + description: 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE + type: string + path: + description: api路径 + type: string + updatedAt: + description: 更新时间 + type: string + type: object + system.SysAuthority: + properties: + authorityId: + description: 角色ID + type: integer + authorityName: + description: 角色名 + type: string + children: + items: + $ref: '#/definitions/system.SysAuthority' + type: array + createdAt: + description: 创建时间 + type: string + dataAuthorityId: + items: + $ref: '#/definitions/system.SysAuthority' + type: array + defaultRouter: + description: 默认菜单(默认dashboard) + type: string + deletedAt: + type: string + menus: + items: + $ref: '#/definitions/system.SysBaseMenu' + type: array + parentId: + description: 父角色ID + type: integer + updatedAt: + description: 更新时间 + type: string + type: object + system.SysBaseMenu: + properties: + ID: + description: 主键ID + type: integer + authoritys: + items: + $ref: '#/definitions/system.SysAuthority' + type: array + children: + items: + $ref: '#/definitions/system.SysBaseMenu' + type: array + component: + description: 对应前端文件路径 + type: string + createdAt: + description: 创建时间 + type: string + hidden: + description: 是否在列表隐藏 + type: boolean + menuBtn: + items: + $ref: '#/definitions/system.SysBaseMenuBtn' + type: array + meta: + allOf: + - $ref: '#/definitions/system.Meta' + description: 附加属性 + name: + description: 路由name + type: string + parameters: + items: + $ref: '#/definitions/system.SysBaseMenuParameter' + type: array + parentId: + description: 父菜单ID + type: integer + path: + description: 路由path + type: string + sort: + description: 排序标记 + type: integer + updatedAt: + description: 更新时间 + type: string + type: object + system.SysBaseMenuBtn: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + desc: + type: string + name: + type: string + sysBaseMenuID: + type: integer + updatedAt: + description: 更新时间 + type: string + type: object + system.SysBaseMenuParameter: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + key: + description: 地址栏携带参数的key + type: string + sysBaseMenuID: + type: integer + type: + description: 地址栏携带参数为params还是query + type: string + updatedAt: + description: 更新时间 + type: string + value: + description: 地址栏携带参数的值 + type: string + type: object + system.SysDictionary: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + desc: + description: 描述 + type: string + name: + description: 字典名(中) + type: string + status: + description: 状态 + type: boolean + sysDictionaryDetails: + items: + $ref: '#/definitions/system.SysDictionaryDetail' + type: array + type: + description: 字典名(英) + type: string + updatedAt: + description: 更新时间 + type: string + type: object + system.SysDictionaryDetail: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + extend: + description: 扩展值 + type: string + label: + description: 展示值 + type: string + sort: + description: 排序标记 + type: integer + status: + description: 启用状态 + type: boolean + sysDictionaryID: + description: 关联标记 + type: integer + updatedAt: + description: 更新时间 + type: string + value: + description: 字典值 + type: string + type: object + system.SysExportTemplate: + properties: + ID: + description: 主键ID + type: integer + conditions: + items: + $ref: '#/definitions/system.Condition' + type: array + createdAt: + description: 创建时间 + type: string + dbName: + description: 数据库名称 + type: string + joinTemplate: + items: + $ref: '#/definitions/system.JoinTemplate' + type: array + limit: + type: integer + name: + description: 模板名称 + type: string + order: + type: string + tableName: + description: 表名称 + type: string + templateID: + description: 模板标识 + type: string + templateInfo: + description: 模板信息 + type: string + updatedAt: + description: 更新时间 + type: string + type: object + system.SysMenu: + properties: + ID: + description: 主键ID + type: integer + authoritys: + items: + $ref: '#/definitions/system.SysAuthority' + type: array + btns: + additionalProperties: + type: integer + type: object + children: + items: + $ref: '#/definitions/system.SysMenu' + type: array + component: + description: 对应前端文件路径 + type: string + createdAt: + description: 创建时间 + type: string + hidden: + description: 是否在列表隐藏 + type: boolean + menuBtn: + items: + $ref: '#/definitions/system.SysBaseMenuBtn' + type: array + menuId: + type: integer + meta: + allOf: + - $ref: '#/definitions/system.Meta' + description: 附加属性 + name: + description: 路由name + type: string + parameters: + items: + $ref: '#/definitions/system.SysBaseMenuParameter' + type: array + parentId: + description: 父菜单ID + type: integer + path: + description: 路由path + type: string + sort: + description: 排序标记 + type: integer + updatedAt: + description: 更新时间 + type: string + type: object + system.SysOperationRecord: + properties: + ID: + description: 主键ID + type: integer + agent: + description: 代理 + type: string + body: + description: 请求Body + type: string + createdAt: + description: 创建时间 + type: string + error_message: + description: 错误信息 + type: string + ip: + description: 请求ip + type: string + latency: + description: 延迟 + type: string + method: + description: 请求方法 + type: string + path: + description: 请求路径 + type: string + resp: + description: 响应Body + type: string + status: + description: 请求状态 + type: integer + updatedAt: + description: 更新时间 + type: string + user: + $ref: '#/definitions/system.SysUser' + user_id: + description: 用户id + type: integer + type: object + system.SysParams: + properties: + ID: + description: 主键ID + type: integer + createdAt: + description: 创建时间 + type: string + desc: + description: 参数说明 + type: string + key: + description: 参数键 + type: string + name: + description: 参数名称 + type: string + updatedAt: + description: 更新时间 + type: string + value: + description: 参数值 + type: string + required: + - key + - name + - value + type: object + system.SysUser: + properties: + ID: + description: 主键ID + type: integer + authorities: + description: 多用户角色 + items: + $ref: '#/definitions/system.SysAuthority' + type: array + authority: + allOf: + - $ref: '#/definitions/system.SysAuthority' + description: 用户角色 + authorityId: + description: 用户角色ID + type: integer + createdAt: + description: 创建时间 + type: string + email: + description: 用户邮箱 + type: string + enable: + description: 用户是否被冻结 1正常 2冻结 + type: integer + headerImg: + description: 用户头像 + type: string + nickName: + description: 用户昵称 + type: string + originSetting: + allOf: + - $ref: '#/definitions/common.JSONMap' + description: 配置 + phone: + description: 用户手机号 + type: string + updatedAt: + description: 更新时间 + type: string + userName: + description: 用户登录名 + type: string + uuid: + description: 用户UUID + type: string + type: object + system.System: + properties: + config: + $ref: '#/definitions/config.Server' + type: object +info: + contact: {} + description: 使用gin+vue进行极速开发的全栈开发基础平台 + title: Gin-Vue-Admin Swagger API接口文档 + version: v2.7.9-beta +paths: + /api/createApi: + post: + consumes: + - application/json + parameters: + - description: api路径, api中文描述, api组, 方法 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysApi' + produces: + - application/json + responses: + "200": + description: 创建基础api + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建基础api + tags: + - SysApi + /api/deleteApi: + post: + consumes: + - application/json + parameters: + - description: ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysApi' + produces: + - application/json + responses: + "200": + description: 删除api + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除api + tags: + - SysApi + /api/deleteApisByIds: + delete: + consumes: + - application/json + parameters: + - description: ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.IdsReq' + produces: + - application/json + responses: + "200": + description: 删除选中Api + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除选中Api + tags: + - SysApi + /api/enterSyncApi: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 确认同步API + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 确认同步API + tags: + - SysApi + /api/freshCasbin: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 刷新成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + summary: 刷新casbin缓存 + tags: + - SysApi + /api/getAllApis: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 获取所有的Api 不分页,返回包括api列表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysAPIListResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取所有的Api 不分页 + tags: + - SysApi + /api/getApiById: + post: + consumes: + - application/json + parameters: + - description: 根据id获取api + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 根据id获取api,返回包括api详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysAPIResponse' + type: object + security: + - ApiKeyAuth: [] + summary: 根据id获取api + tags: + - SysApi + /api/getApiGroups: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 获取API分组 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取API分组 + tags: + - SysApi + /api/getApiList: + post: + consumes: + - application/json + parameters: + - description: 分页获取API列表 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SearchApiParams' + produces: + - application/json + responses: + "200": + description: 分页获取API列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取API列表 + tags: + - SysApi + /api/ignoreApi: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 同步API + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 忽略API + tags: + - IgnoreApi + /api/syncApi: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 同步API + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 同步API + tags: + - SysApi + /api/updateApi: + post: + consumes: + - application/json + parameters: + - description: api路径, api中文描述, api组, 方法 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysApi' + produces: + - application/json + responses: + "200": + description: 修改基础api + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 修改基础api + tags: + - SysApi + /attachmentCategory/addCategory: + post: + consumes: + - application/json + parameters: + - description: 媒体库分类数据 + in: body + name: data + required: true + schema: + $ref: '#/definitions/example.ExaAttachmentCategory' + produces: + - application/json + responses: {} + security: + - AttachmentCategory: [] + summary: 添加媒体库分类 + tags: + - AddCategory + /attachmentCategory/deleteCategory: + post: + consumes: + - application/json + parameters: + - description: 分类id + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 删除分类 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - AttachmentCategory: [] + summary: 删除分类 + tags: + - DeleteCategory + /attachmentCategory/getCategoryList: + get: + produces: + - application/json + responses: + "200": + description: 媒体库分类列表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/example.ExaAttachmentCategory' + msg: + type: string + type: object + security: + - AttachmentCategory: [] + summary: 媒体库分类列表 + tags: + - GetCategoryList + /authority/copyAuthority: + post: + consumes: + - application/json + parameters: + - description: 旧角色id, 新权限id, 新权限名, 新父角色id + in: body + name: data + required: true + schema: + $ref: '#/definitions/response.SysAuthorityCopyResponse' + produces: + - application/json + responses: + "200": + description: 拷贝角色,返回包括系统角色详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysAuthorityResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 拷贝角色 + tags: + - Authority + /authority/createAuthority: + post: + consumes: + - application/json + parameters: + - description: 权限id, 权限名, 父角色id + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysAuthority' + produces: + - application/json + responses: + "200": + description: 创建角色,返回包括系统角色详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysAuthorityResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建角色 + tags: + - Authority + /authority/deleteAuthority: + post: + consumes: + - application/json + parameters: + - description: 删除角色 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysAuthority' + produces: + - application/json + responses: + "200": + description: 删除角色 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除角色 + tags: + - Authority + /authority/getAuthorityList: + post: + consumes: + - application/json + parameters: + - description: 页码, 每页大小 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.PageInfo' + produces: + - application/json + responses: + "200": + description: 分页获取角色列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取角色列表 + tags: + - Authority + /authority/setDataAuthority: + post: + consumes: + - application/json + parameters: + - description: 设置角色资源权限 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysAuthority' + produces: + - application/json + responses: + "200": + description: 设置角色资源权限 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置角色资源权限 + tags: + - Authority + /authority/updateAuthority: + put: + consumes: + - application/json + parameters: + - description: 权限id, 权限名, 父角色id + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysAuthority' + produces: + - application/json + responses: + "200": + description: 更新角色信息,返回包括系统角色详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysAuthorityResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新角色信息 + tags: + - Authority + /authorityBtn/canRemoveAuthorityBtn: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 删除成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置权限按钮 + tags: + - AuthorityBtn + /authorityBtn/getAuthorityBtn: + post: + consumes: + - application/json + parameters: + - description: 菜单id, 角色id, 选中的按钮id + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SysAuthorityBtnReq' + produces: + - application/json + responses: + "200": + description: 返回列表成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysAuthorityBtnRes' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取权限按钮 + tags: + - AuthorityBtn + /authorityBtn/setAuthorityBtn: + post: + consumes: + - application/json + parameters: + - description: 菜单id, 角色id, 选中的按钮id + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SysAuthorityBtnReq' + produces: + - application/json + responses: + "200": + description: 返回列表成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置权限按钮 + tags: + - AuthorityBtn + /autoCode/addFunc: + post: + consumes: + - application/json + parameters: + - description: 增加方法 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.AutoCode' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"创建成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 增加方法 + tags: + - AddFunc + /autoCode/createPackage: + post: + consumes: + - application/json + parameters: + - description: 创建package + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SysAutoCodePackageCreate' + produces: + - application/json + responses: + "200": + description: 创建package成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建package + tags: + - AutoCodePackage + /autoCode/createTemp: + post: + consumes: + - application/json + parameters: + - description: 创建自动代码 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.AutoCode' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"创建成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 自动代码模板 + tags: + - AutoCodeTemplate + /autoCode/delPackage: + post: + consumes: + - application/json + parameters: + - description: 创建package + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 删除package成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除package + tags: + - AutoCode + /autoCode/delSysHistory: + post: + consumes: + - application/json + parameters: + - description: 请求参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 删除回滚记录 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除回滚记录 + tags: + - AutoCode + /autoCode/getColumn: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 获取当前表所有字段 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取当前表所有字段 + tags: + - AutoCode + /autoCode/getDB: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 获取当前所有数据库 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取当前所有数据库 + tags: + - AutoCode + /autoCode/getMeta: + post: + consumes: + - application/json + parameters: + - description: 请求参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 获取meta信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取meta信息 + tags: + - AutoCode + /autoCode/getPackage: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 创建package成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取package + tags: + - AutoCodePackage + /autoCode/getSysHistory: + post: + consumes: + - application/json + parameters: + - description: 请求参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.PageInfo' + produces: + - application/json + responses: + "200": + description: 查询回滚记录,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 查询回滚记录 + tags: + - AutoCode + /autoCode/getTables: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 获取当前数据库所有表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取当前数据库所有表 + tags: + - AutoCode + /autoCode/getTemplates: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 创建package成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取package + tags: + - AutoCodePackage + /autoCode/initAPI: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 打包插件成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 打包插件 + tags: + - AutoCodePlugin + /autoCode/initMenu: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 打包插件成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 打包插件 + tags: + - AutoCodePlugin + /autoCode/installPlugin: + post: + consumes: + - multipart/form-data + parameters: + - description: this is a test file + in: formData + name: plug + required: true + type: file + produces: + - application/json + responses: + "200": + description: 安装插件成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + items: + type: object + type: array + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 安装插件 + tags: + - AutoCodePlugin + /autoCode/preview: + post: + consumes: + - application/json + parameters: + - description: 预览创建代码 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.AutoCode' + produces: + - application/json + responses: + "200": + description: 预览创建后的代码 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 预览创建后的代码 + tags: + - AutoCodeTemplate + /autoCode/pubPlug: + post: + consumes: + - application/json + parameters: + - description: 插件名称 + in: query + name: plugName + required: true + type: string + produces: + - application/json + responses: + "200": + description: 打包插件成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 打包插件 + tags: + - AutoCodePlugin + /autoCode/rollback: + post: + consumes: + - application/json + parameters: + - description: 请求参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SysAutoHistoryRollBack' + produces: + - application/json + responses: + "200": + description: 回滚自动生成代码 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 回滚自动生成代码 + tags: + - AutoCode + /base/captcha: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysCaptchaResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 生成验证码 + tags: + - Base + /base/login: + post: + parameters: + - description: 用户名, 密码, 验证码 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.Login' + produces: + - application/json + responses: + "200": + description: 返回包括用户信息,token,过期时间 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.LoginResponse' + msg: + type: string + type: object + summary: 用户登录 + tags: + - Base + /casbin/UpdateCasbin: + post: + consumes: + - application/json + parameters: + - description: 权限id, 权限模型列表 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.CasbinInReceive' + produces: + - application/json + responses: + "200": + description: 更新角色api权限 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新角色api权限 + tags: + - Casbin + /casbin/getPolicyPathByAuthorityId: + post: + consumes: + - application/json + parameters: + - description: 权限id, 权限模型列表 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.CasbinInReceive' + produces: + - application/json + responses: + "200": + description: 获取权限列表,返回包括casbin详情列表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PolicyPathResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取权限列表 + tags: + - Casbin + /customer/customer: + delete: + consumes: + - application/json + parameters: + - description: 客户ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/example.ExaCustomer' + produces: + - application/json + responses: + "200": + description: 删除客户 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除客户 + tags: + - ExaCustomer + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 客户名 + in: query + name: customerName + type: string + - description: 客户手机号 + in: query + name: customerPhoneData + type: string + - description: 管理角色ID + in: query + name: sysUserAuthorityID + type: integer + - description: 管理ID + in: query + name: sysUserId + type: integer + - description: 更新时间 + in: query + name: updatedAt + type: string + produces: + - application/json + responses: + "200": + description: 获取单一客户信息,返回包括客户详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.ExaCustomerResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取单一客户信息 + tags: + - ExaCustomer + post: + consumes: + - application/json + parameters: + - description: 客户用户名, 客户手机号码 + in: body + name: data + required: true + schema: + $ref: '#/definitions/example.ExaCustomer' + produces: + - application/json + responses: + "200": + description: 创建客户 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建客户 + tags: + - ExaCustomer + put: + consumes: + - application/json + parameters: + - description: 客户ID, 客户信息 + in: body + name: data + required: true + schema: + $ref: '#/definitions/example.ExaCustomer' + produces: + - application/json + responses: + "200": + description: 更新客户信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新客户信息 + tags: + - ExaCustomer + /customer/customerList: + get: + consumes: + - application/json + parameters: + - description: 关键字 + in: query + name: keyword + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + produces: + - application/json + responses: + "200": + description: 分页获取权限客户列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取权限客户列表 + tags: + - ExaCustomer + /email/emailTest: + post: + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"发送成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 发送测试邮件 + tags: + - System + /email/sendEmail: + post: + parameters: + - description: 发送邮件必须的参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/response.Email' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"发送成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 发送邮件 + tags: + - System + /fileUploadAndDownload/breakpointContinue: + post: + consumes: + - multipart/form-data + parameters: + - description: an example for breakpoint resume, 断点续传示例 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: 断点续传到服务器 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 断点续传到服务器 + tags: + - ExaFileUploadAndDownload + /fileUploadAndDownload/deleteFile: + post: + parameters: + - description: 传入文件里面id即可 + in: body + name: data + required: true + schema: + $ref: '#/definitions/example.ExaFileUploadAndDownload' + produces: + - application/json + responses: + "200": + description: 删除文件 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除文件 + tags: + - ExaFileUploadAndDownload + /fileUploadAndDownload/findFile: + get: + consumes: + - multipart/form-data + parameters: + - description: Find the file, 查找文件 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: 查找文件,返回包括文件详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.FileResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 查找文件 + tags: + - ExaFileUploadAndDownload + post: + consumes: + - multipart/form-data + parameters: + - description: 上传文件完成 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: 创建文件,返回包括文件路径 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.FilePathResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建文件 + tags: + - ExaFileUploadAndDownload + /fileUploadAndDownload/getFileList: + post: + consumes: + - application/json + parameters: + - description: 页码, 每页大小, 分类id + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.ExaAttachmentCategorySearch' + produces: + - application/json + responses: + "200": + description: 分页文件列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页文件列表 + tags: + - ExaFileUploadAndDownload + /fileUploadAndDownload/importURL: + post: + parameters: + - description: 对象 + in: body + name: data + required: true + schema: + $ref: '#/definitions/example.ExaFileUploadAndDownload' + produces: + - application/json + responses: + "200": + description: 导入URL + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 导入URL + tags: + - ExaFileUploadAndDownload + /fileUploadAndDownload/removeChunk: + post: + consumes: + - multipart/form-data + parameters: + - description: 删除缓存切片 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: 删除切片 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除切片 + tags: + - ExaFileUploadAndDownload + /fileUploadAndDownload/upload: + post: + consumes: + - multipart/form-data + parameters: + - description: 上传文件示例 + in: formData + name: file + required: true + type: file + produces: + - application/json + responses: + "200": + description: 上传文件示例,返回包括文件详情 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.ExaFileResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 上传文件示例 + tags: + - ExaFileUploadAndDownload + /info/createInfo: + post: + consumes: + - application/json + parameters: + - description: 创建公告 + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.Info' + produces: + - application/json + responses: + "200": + description: 创建成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建公告 + tags: + - Info + /info/deleteInfo: + delete: + consumes: + - application/json + parameters: + - description: 删除公告 + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.Info' + produces: + - application/json + responses: + "200": + description: 删除成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除公告 + tags: + - Info + /info/deleteInfoByIds: + delete: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 批量删除成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 批量删除公告 + tags: + - Info + /info/findInfo: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 内容 + in: query + name: content + type: string + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 标题 + in: query + name: title + type: string + - description: 更新时间 + in: query + name: updatedAt + type: string + - description: 作者 + in: query + name: userID + type: integer + produces: + - application/json + responses: + "200": + description: 查询成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/model.Info' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 用id查询公告 + tags: + - Info + /info/getInfoDataSource: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 查询成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + type: object + msg: + type: string + type: object + summary: 获取Info的数据源 + tags: + - Info + /info/getInfoList: + get: + consumes: + - application/json + parameters: + - in: query + name: endCreatedAt + type: string + - description: 关键字 + in: query + name: keyword + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + - in: query + name: startCreatedAt + type: string + produces: + - application/json + responses: + "200": + description: 获取成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取公告列表 + tags: + - Info + /info/getInfoPublic: + get: + consumes: + - application/json + parameters: + - in: query + name: endCreatedAt + type: string + - description: 关键字 + in: query + name: keyword + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + - in: query + name: startCreatedAt + type: string + produces: + - application/json + responses: + "200": + description: 获取成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + type: object + msg: + type: string + type: object + summary: 不需要鉴权的公告接口 + tags: + - Info + /info/updateInfo: + put: + consumes: + - application/json + parameters: + - description: 更新公告 + in: body + name: data + required: true + schema: + $ref: '#/definitions/model.Info' + produces: + - application/json + responses: + "200": + description: 更新成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新公告 + tags: + - Info + /init/checkdb: + post: + produces: + - application/json + responses: + "200": + description: 初始化用户数据库 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + summary: 初始化用户数据库 + tags: + - CheckDB + /init/initdb: + post: + parameters: + - description: 初始化数据库参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.InitDB' + produces: + - application/json + responses: + "200": + description: 初始化用户数据库 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + type: string + type: object + summary: 初始化用户数据库 + tags: + - InitDB + /jwt/jsonInBlacklist: + post: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: jwt加入黑名单 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: jwt加入黑名单 + tags: + - Jwt + /menu/addBaseMenu: + post: + consumes: + - application/json + parameters: + - description: 路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysBaseMenu' + produces: + - application/json + responses: + "200": + description: 新增菜单 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 新增菜单 + tags: + - Menu + /menu/addMenuAuthority: + post: + consumes: + - application/json + parameters: + - description: 角色ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.AddMenuAuthorityInfo' + produces: + - application/json + responses: + "200": + description: 增加menu和角色关联关系 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 增加menu和角色关联关系 + tags: + - AuthorityMenu + /menu/deleteBaseMenu: + post: + consumes: + - application/json + parameters: + - description: 菜单id + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 删除菜单 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除菜单 + tags: + - Menu + /menu/getBaseMenuById: + post: + consumes: + - application/json + parameters: + - description: 菜单id + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 根据id获取菜单,返回包括系统菜单列表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysBaseMenuResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 根据id获取菜单 + tags: + - Menu + /menu/getBaseMenuTree: + post: + parameters: + - description: 空 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.Empty' + produces: + - application/json + responses: + "200": + description: 获取用户动态路由,返回包括系统菜单列表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysBaseMenusResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取用户动态路由 + tags: + - AuthorityMenu + /menu/getMenu: + post: + parameters: + - description: 空 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.Empty' + produces: + - application/json + responses: + "200": + description: 获取用户动态路由,返回包括系统菜单详情列表 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysMenusResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取用户动态路由 + tags: + - AuthorityMenu + /menu/getMenuAuthority: + post: + consumes: + - application/json + parameters: + - description: 角色ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetAuthorityId' + produces: + - application/json + responses: + "200": + description: 获取指定角色menu + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取指定角色menu + tags: + - AuthorityMenu + /menu/getMenuList: + post: + consumes: + - application/json + parameters: + - description: 页码, 每页大小 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.PageInfo' + produces: + - application/json + responses: + "200": + description: 分页获取基础menu列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取基础menu列表 + tags: + - Menu + /menu/updateBaseMenu: + post: + consumes: + - application/json + parameters: + - description: 路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysBaseMenu' + produces: + - application/json + responses: + "200": + description: 更新菜单 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新菜单 + tags: + - Menu + /sysDictionary/createSysDictionary: + post: + consumes: + - application/json + parameters: + - description: SysDictionary模型 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysDictionary' + produces: + - application/json + responses: + "200": + description: 创建SysDictionary + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建SysDictionary + tags: + - SysDictionary + /sysDictionary/deleteSysDictionary: + delete: + consumes: + - application/json + parameters: + - description: SysDictionary模型 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysDictionary' + produces: + - application/json + responses: + "200": + description: 删除SysDictionary + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除SysDictionary + tags: + - SysDictionary + /sysDictionary/findSysDictionary: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 描述 + in: query + name: desc + type: string + - description: 字典名(中) + in: query + name: name + type: string + - description: 状态 + in: query + name: status + type: boolean + - description: 字典名(英) + in: query + name: type + type: string + - description: 更新时间 + in: query + name: updatedAt + type: string + produces: + - application/json + responses: + "200": + description: 用id查询SysDictionary + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 用id查询SysDictionary + tags: + - SysDictionary + /sysDictionary/getSysDictionaryList: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取SysDictionary列表 + tags: + - SysDictionary + /sysDictionary/updateSysDictionary: + put: + consumes: + - application/json + parameters: + - description: SysDictionary模型 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysDictionary' + produces: + - application/json + responses: + "200": + description: 更新SysDictionary + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新SysDictionary + tags: + - SysDictionary + /sysDictionaryDetail/createSysDictionaryDetail: + post: + consumes: + - application/json + parameters: + - description: SysDictionaryDetail模型 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysDictionaryDetail' + produces: + - application/json + responses: + "200": + description: 创建SysDictionaryDetail + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建SysDictionaryDetail + tags: + - SysDictionaryDetail + /sysDictionaryDetail/deleteSysDictionaryDetail: + delete: + consumes: + - application/json + parameters: + - description: SysDictionaryDetail模型 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysDictionaryDetail' + produces: + - application/json + responses: + "200": + description: 删除SysDictionaryDetail + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除SysDictionaryDetail + tags: + - SysDictionaryDetail + /sysDictionaryDetail/findSysDictionaryDetail: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 扩展值 + in: query + name: extend + type: string + - description: 展示值 + in: query + name: label + type: string + - description: 排序标记 + in: query + name: sort + type: integer + - description: 启用状态 + in: query + name: status + type: boolean + - description: 关联标记 + in: query + name: sysDictionaryID + type: integer + - description: 更新时间 + in: query + name: updatedAt + type: string + - description: 字典值 + in: query + name: value + type: string + produces: + - application/json + responses: + "200": + description: 用id查询SysDictionaryDetail + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 用id查询SysDictionaryDetail + tags: + - SysDictionaryDetail + /sysDictionaryDetail/getSysDictionaryDetailList: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 扩展值 + in: query + name: extend + type: string + - description: 关键字 + in: query + name: keyword + type: string + - description: 展示值 + in: query + name: label + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + - description: 排序标记 + in: query + name: sort + type: integer + - description: 启用状态 + in: query + name: status + type: boolean + - description: 关联标记 + in: query + name: sysDictionaryID + type: integer + - description: 更新时间 + in: query + name: updatedAt + type: string + - description: 字典值 + in: query + name: value + type: string + produces: + - application/json + responses: + "200": + description: 分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取SysDictionaryDetail列表 + tags: + - SysDictionaryDetail + /sysDictionaryDetail/updateSysDictionaryDetail: + put: + consumes: + - application/json + parameters: + - description: 更新SysDictionaryDetail + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysDictionaryDetail' + produces: + - application/json + responses: + "200": + description: 更新SysDictionaryDetail + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新SysDictionaryDetail + tags: + - SysDictionaryDetail + /sysExportTemplate/ExportTemplate: + get: + consumes: + - application/json + produces: + - application/json + responses: {} + security: + - ApiKeyAuth: [] + summary: 导出表格模板 + tags: + - SysExportTemplate + /sysExportTemplate/createSysExportTemplate: + post: + consumes: + - application/json + parameters: + - description: 创建导出模板 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysExportTemplate' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"创建成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 创建导出模板 + tags: + - SysExportTemplate + /sysExportTemplate/deleteSysExportTemplate: + delete: + consumes: + - application/json + parameters: + - description: 删除导出模板 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysExportTemplate' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"删除成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 删除导出模板 + tags: + - SysExportTemplate + /sysExportTemplate/deleteSysExportTemplateByIds: + delete: + consumes: + - application/json + parameters: + - description: 批量删除导出模板 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.IdsReq' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"批量删除成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 批量删除导出模板 + tags: + - SysExportTemplate + /sysExportTemplate/exportExcel: + get: + consumes: + - application/json + produces: + - application/json + responses: {} + security: + - ApiKeyAuth: [] + summary: 导出表格 + tags: + - SysExportTemplate + /sysExportTemplate/findSysExportTemplate: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 数据库名称 + in: query + name: dbName + type: string + - in: query + name: limit + type: integer + - description: 模板名称 + in: query + name: name + type: string + - in: query + name: order + type: string + - description: 表名称 + in: query + name: tableName + type: string + - description: 模板标识 + in: query + name: templateID + type: string + - description: 模板信息 + in: query + name: templateInfo + type: string + - description: 更新时间 + in: query + name: updatedAt + type: string + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"查询成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 用id查询导出模板 + tags: + - SysExportTemplate + /sysExportTemplate/getSysExportTemplateList: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 数据库名称 + in: query + name: dbName + type: string + - in: query + name: endCreatedAt + type: string + - description: 关键字 + in: query + name: keyword + type: string + - in: query + name: limit + type: integer + - description: 模板名称 + in: query + name: name + type: string + - in: query + name: order + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + - in: query + name: startCreatedAt + type: string + - description: 表名称 + in: query + name: tableName + type: string + - description: 模板标识 + in: query + name: templateID + type: string + - description: 模板信息 + in: query + name: templateInfo + type: string + - description: 更新时间 + in: query + name: updatedAt + type: string + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"获取成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 分页获取导出模板列表 + tags: + - SysExportTemplate + /sysExportTemplate/importExcel: + post: + consumes: + - application/json + produces: + - application/json + responses: {} + security: + - ApiKeyAuth: [] + summary: 导入表格 + tags: + - SysImportTemplate + /sysExportTemplate/updateSysExportTemplate: + put: + consumes: + - application/json + parameters: + - description: 更新导出模板 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysExportTemplate' + produces: + - application/json + responses: + "200": + description: '{"success":true,"data":{},"msg":"更新成功"}' + schema: + type: string + security: + - ApiKeyAuth: [] + summary: 更新导出模板 + tags: + - SysExportTemplate + /sysOperationRecord/createSysOperationRecord: + post: + consumes: + - application/json + parameters: + - description: 创建SysOperationRecord + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysOperationRecord' + produces: + - application/json + responses: + "200": + description: 创建SysOperationRecord + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建SysOperationRecord + tags: + - SysOperationRecord + /sysOperationRecord/deleteSysOperationRecord: + delete: + consumes: + - application/json + parameters: + - description: SysOperationRecord模型 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysOperationRecord' + produces: + - application/json + responses: + "200": + description: 删除SysOperationRecord + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除SysOperationRecord + tags: + - SysOperationRecord + /sysOperationRecord/deleteSysOperationRecordByIds: + delete: + consumes: + - application/json + parameters: + - description: 批量删除SysOperationRecord + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.IdsReq' + produces: + - application/json + responses: + "200": + description: 批量删除SysOperationRecord + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 批量删除SysOperationRecord + tags: + - SysOperationRecord + /sysOperationRecord/findSysOperationRecord: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 代理 + in: query + name: agent + type: string + - description: 请求Body + in: query + name: body + type: string + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 错误信息 + in: query + name: error_message + type: string + - description: 请求ip + in: query + name: ip + type: string + - description: 延迟 + in: query + name: latency + type: string + - description: 请求方法 + in: query + name: method + type: string + - description: 请求路径 + in: query + name: path + type: string + - description: 响应Body + in: query + name: resp + type: string + - description: 请求状态 + in: query + name: status + type: integer + - description: 更新时间 + in: query + name: updatedAt + type: string + - description: 用户id + in: query + name: user_id + type: integer + produces: + - application/json + responses: + "200": + description: 用id查询SysOperationRecord + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 用id查询SysOperationRecord + tags: + - SysOperationRecord + /sysOperationRecord/getSysOperationRecordList: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 代理 + in: query + name: agent + type: string + - description: 请求Body + in: query + name: body + type: string + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 错误信息 + in: query + name: error_message + type: string + - description: 请求ip + in: query + name: ip + type: string + - description: 关键字 + in: query + name: keyword + type: string + - description: 延迟 + in: query + name: latency + type: string + - description: 请求方法 + in: query + name: method + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + - description: 请求路径 + in: query + name: path + type: string + - description: 响应Body + in: query + name: resp + type: string + - description: 请求状态 + in: query + name: status + type: integer + - description: 更新时间 + in: query + name: updatedAt + type: string + - description: 用户id + in: query + name: user_id + type: integer + produces: + - application/json + responses: + "200": + description: 分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取SysOperationRecord列表 + tags: + - SysOperationRecord + /sysParams/createSysParams: + post: + consumes: + - application/json + parameters: + - description: 创建参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysParams' + produces: + - application/json + responses: + "200": + description: 创建成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 创建参数 + tags: + - SysParams + /sysParams/deleteSysParams: + delete: + consumes: + - application/json + parameters: + - description: 删除参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysParams' + produces: + - application/json + responses: + "200": + description: 删除成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除参数 + tags: + - SysParams + /sysParams/deleteSysParamsByIds: + delete: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 批量删除成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 批量删除参数 + tags: + - SysParams + /sysParams/findSysParams: + get: + consumes: + - application/json + parameters: + - description: 主键ID + in: query + name: ID + type: integer + - description: 创建时间 + in: query + name: createdAt + type: string + - description: 参数说明 + in: query + name: desc + type: string + - description: 参数键 + in: query + name: key + required: true + type: string + - description: 参数名称 + in: query + name: name + required: true + type: string + - description: 更新时间 + in: query + name: updatedAt + type: string + - description: 参数值 + in: query + name: value + required: true + type: string + produces: + - application/json + responses: + "200": + description: 查询成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/system.SysParams' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 用id查询参数 + tags: + - SysParams + /sysParams/getSysParam: + get: + consumes: + - application/json + parameters: + - description: key + in: query + name: key + required: true + type: string + produces: + - application/json + responses: + "200": + description: 获取成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/system.SysParams' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 根据key获取参数value + tags: + - SysParams + /sysParams/getSysParamsList: + get: + consumes: + - application/json + parameters: + - in: query + name: endCreatedAt + type: string + - in: query + name: key + type: string + - description: 关键字 + in: query + name: keyword + type: string + - in: query + name: name + type: string + - description: 页码 + in: query + name: page + type: integer + - description: 每页大小 + in: query + name: pageSize + type: integer + - in: query + name: startCreatedAt + type: string + produces: + - application/json + responses: + "200": + description: 获取成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取参数列表 + tags: + - SysParams + /sysParams/updateSysParams: + put: + consumes: + - application/json + parameters: + - description: 更新参数 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysParams' + produces: + - application/json + responses: + "200": + description: 更新成功 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更新参数 + tags: + - SysParams + /system/getServerInfo: + post: + produces: + - application/json + responses: + "200": + description: 获取服务器信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取服务器信息 + tags: + - System + /system/getSystemConfig: + post: + produces: + - application/json + responses: + "200": + description: 获取配置文件内容,返回包括系统配置 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysConfigResponse' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取配置文件内容 + tags: + - System + /system/reloadSystem: + post: + produces: + - application/json + responses: + "200": + description: 重启系统 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 重启系统 + tags: + - System + /system/setSystemConfig: + post: + parameters: + - description: 设置配置文件内容 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.System' + produces: + - application/json + responses: + "200": + description: 设置配置文件内容 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置配置文件内容 + tags: + - System + /user/SetSelfInfo: + put: + consumes: + - application/json + parameters: + - description: ID, 用户名, 昵称, 头像链接 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysUser' + produces: + - application/json + responses: + "200": + description: 设置用户信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置用户信息 + tags: + - SysUser + /user/SetSelfSetting: + put: + consumes: + - application/json + parameters: + - description: 用户配置数据 + in: body + name: data + required: true + schema: + additionalProperties: true + type: object + produces: + - application/json + responses: + "200": + description: 设置用户配置 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置用户配置 + tags: + - SysUser + /user/admin_register: + post: + parameters: + - description: 用户名, 昵称, 密码, 角色ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.Register' + produces: + - application/json + responses: + "200": + description: 用户注册账号,返回包括用户信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.SysUserResponse' + msg: + type: string + type: object + summary: 用户注册账号 + tags: + - SysUser + /user/changePassword: + post: + parameters: + - description: 用户名, 原密码, 新密码 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.ChangePasswordReq' + produces: + - application/json + responses: + "200": + description: 用户修改密码 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 用户修改密码 + tags: + - SysUser + /user/deleteUser: + delete: + consumes: + - application/json + parameters: + - description: 用户ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetById' + produces: + - application/json + responses: + "200": + description: 删除用户 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 删除用户 + tags: + - SysUser + /user/getUserInfo: + get: + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: 获取用户信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 获取用户信息 + tags: + - SysUser + /user/getUserList: + post: + consumes: + - application/json + parameters: + - description: 页码, 每页大小 + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.GetUserList' + produces: + - application/json + responses: + "200": + description: 分页获取用户列表,返回包括列表,总数,页码,每页数量 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/response.PageResult' + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 分页获取用户列表 + tags: + - SysUser + /user/resetPassword: + post: + parameters: + - description: ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysUser' + produces: + - application/json + responses: + "200": + description: 重置用户密码 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 重置用户密码 + tags: + - SysUser + /user/setUserAuthorities: + post: + consumes: + - application/json + parameters: + - description: 用户UUID, 角色ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SetUserAuthorities' + produces: + - application/json + responses: + "200": + description: 设置用户权限 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置用户权限 + tags: + - SysUser + /user/setUserAuthority: + post: + consumes: + - application/json + parameters: + - description: 用户UUID, 角色ID + in: body + name: data + required: true + schema: + $ref: '#/definitions/request.SetUserAuth' + produces: + - application/json + responses: + "200": + description: 设置用户权限 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 更改用户权限 + tags: + - SysUser + /user/setUserInfo: + put: + consumes: + - application/json + parameters: + - description: ID, 用户名, 昵称, 头像链接 + in: body + name: data + required: true + schema: + $ref: '#/definitions/system.SysUser' + produces: + - application/json + responses: + "200": + description: 设置用户信息 + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + msg: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: 设置用户信息 + tags: + - SysUser +securityDefinitions: + ApiKeyAuth: + in: header + name: x-token + type: apiKey +swagger: "2.0" +tags: +- name: Base +- description: 用户 + name: SysUser diff --git a/global/global.go b/global/global.go new file mode 100644 index 0000000..28f8c89 --- /dev/null +++ b/global/global.go @@ -0,0 +1,66 @@ +package global + +import ( + "fmt" + "sync" + + "github.com/gin-gonic/gin" + "github.com/qiniu/qmgo" + + "git.echol.cn/loser/xiecheng_server/utils/timer" + "github.com/songzhibin97/gkit/cache/local_cache" + + "golang.org/x/sync/singleflight" + + "go.uber.org/zap" + + "git.echol.cn/loser/xiecheng_server/config" + + "github.com/redis/go-redis/v9" + "github.com/spf13/viper" + "gorm.io/gorm" +) + +var ( + GVA_DB *gorm.DB + GVA_DBList map[string]*gorm.DB + GVA_REDIS redis.UniversalClient + GVA_REDISList map[string]redis.UniversalClient + GVA_MONGO *qmgo.QmgoClient + GVA_CONFIG config.Server + GVA_VP *viper.Viper + // GVA_LOG *oplogging.Logger + GVA_LOG *zap.Logger + GVA_Timer timer.Timer = timer.NewTimerTask() + GVA_Concurrency_Control = &singleflight.Group{} + GVA_ROUTERS gin.RoutesInfo + GVA_ACTIVE_DBNAME *string + BlackCache local_cache.Cache + lock sync.RWMutex +) + +// GetGlobalDBByDBName 通过名称获取db list中的db +func GetGlobalDBByDBName(dbname string) *gorm.DB { + lock.RLock() + defer lock.RUnlock() + return GVA_DBList[dbname] +} + +// MustGetGlobalDBByDBName 通过名称获取db 如果不存在则panic +func MustGetGlobalDBByDBName(dbname string) *gorm.DB { + lock.RLock() + defer lock.RUnlock() + db, ok := GVA_DBList[dbname] + if !ok || db == nil { + panic("db no init") + } + return db +} + +func GetRedis(name string) redis.UniversalClient { + redis, ok := GVA_REDISList[name] + if !ok || redis == nil { + panic(fmt.Sprintf("redis `%s` no init", name)) + } + return redis +} diff --git a/global/model.go b/global/model.go new file mode 100644 index 0000000..9772eb3 --- /dev/null +++ b/global/model.go @@ -0,0 +1,14 @@ +package global + +import ( + "time" + + "gorm.io/gorm" +) + +type GVA_MODEL struct { + ID uint `gorm:"primarykey" json:"ID"` // 主键ID + CreatedAt time.Time // 创建时间 + UpdatedAt time.Time // 更新时间 + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 删除时间 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5611e7d --- /dev/null +++ b/go.mod @@ -0,0 +1,177 @@ +module git.echol.cn/loser/xiecheng_server + +go 1.22.2 + +require ( + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/aws/aws-sdk-go v1.55.6 + github.com/casbin/casbin/v2 v2.103.0 + github.com/casbin/gorm-adapter/v3 v3.32.0 + github.com/fsnotify/fsnotify v1.8.0 + github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 + github.com/gin-gonic/gin v1.10.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-sql-driver/mysql v1.8.1 + github.com/goccy/go-json v0.10.4 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 + github.com/gookit/color v1.5.4 + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible + github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible + github.com/mholt/archiver/v4 v4.0.0-alpha.9 + github.com/minio/minio-go/v7 v7.0.84 + github.com/mojocn/base64Captcha v1.3.8 + github.com/otiai10/copy v1.14.1 + github.com/pkg/errors v0.9.1 + github.com/qiniu/go-sdk/v7 v7.25.2 + github.com/qiniu/qmgo v1.1.9 + github.com/redis/go-redis/v9 v9.7.0 + github.com/robfig/cron/v3 v3.0.1 + github.com/shirou/gopsutil/v3 v3.24.5 + github.com/songzhibin97/gkit v1.2.13 + github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.10.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.4 + github.com/tencentyun/cos-go-sdk-v5 v0.7.60 + github.com/unrolled/secure v1.17.0 + github.com/xuri/excelize/v2 v2.9.0 + go.mongodb.org/mongo-driver v1.17.2 + go.uber.org/automaxprocs v1.6.0 + go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.32.0 + golang.org/x/sync v0.10.0 + golang.org/x/text v0.21.0 + gorm.io/datatypes v1.2.5 + gorm.io/driver/mysql v1.5.7 + gorm.io/driver/postgres v1.5.11 + gorm.io/driver/sqlserver v1.5.4 + gorm.io/gen v0.3.26 + gorm.io/gorm v1.25.12 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/STARRY-S/zip v0.1.0 // indirect + github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect + github.com/bodgit/plumbing v1.3.0 // indirect + github.com/bodgit/sevenzip v1.6.0 // indirect + github.com/bodgit/windows v1.0.1 // indirect + github.com/bytedance/sonic v1.12.7 // indirect + github.com/bytedance/sonic/loader v0.2.3 // indirect + github.com/casbin/govaluate v1.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clbanning/mxj v1.8.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gammazero/toposort v0.1.1 // indirect + github.com/gin-contrib/sse v1.0.0 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.24.0 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/microsoft/go-mssqldb v1.8.0 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/mozillazg/go-httpheader v0.4.0 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/nwaples/rardecode/v2 v2.0.1 // indirect + github.com/otiai10/mint v1.6.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sorairolake/lzip-go v0.3.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/therootcompany/xz v1.0.1 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.9.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 // indirect + github.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + golang.org/x/arch v0.13.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/image v0.23.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/hints v1.1.2 // indirect + gorm.io/plugin/dbresolver v1.5.3 // indirect + modernc.org/fileutil v1.3.0 // indirect + modernc.org/libc v1.61.9 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.8.2 // indirect + modernc.org/sqlite v1.34.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f9d4e89 --- /dev/null +++ b/go.sum @@ -0,0 +1,593 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/STARRY-S/zip v0.1.0/go.mod h1:qj/mTZkvb3AvfGQ2e775/3AODRvB4peSw8KNMvrM8/I= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= +github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= +github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw= +github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= +github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mholt/archiver/v4 v4.0.0-alpha.9/go.mod h1:5D3uct315OMkMRXKwEuMB+wQi/2m5NQngKDmApqwVlo= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nwaples/rardecode/v2 v2.0.1/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= +github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= +github.com/qiniu/go-sdk/v7 v7.25.2/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o= +github.com/qiniu/qmgo v1.1.9/go.mod h1:aba4tNSlMWrwUhe7RdILfwBRIgvBujt1y10X+T1YZSI= +github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/songzhibin97/gkit v1.2.13/go.mod h1:38CreNR27eTGaG1UMGihrXqI4xc3nGfYxLVKKVx6Ngg= +github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= +github.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0= +github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= +github.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= +golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= +gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= +gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/initialize/db_list.go b/initialize/db_list.go new file mode 100644 index 0000000..8b9c69d --- /dev/null +++ b/initialize/db_list.go @@ -0,0 +1,36 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "gorm.io/gorm" +) + +const sys = "system" + +func DBList() { + dbMap := make(map[string]*gorm.DB) + for _, info := range global.GVA_CONFIG.DBList { + if info.Disable { + continue + } + switch info.Type { + case "mysql": + dbMap[info.AliasName] = GormMysqlByConfig(config.Mysql{GeneralDB: info.GeneralDB}) + case "mssql": + dbMap[info.AliasName] = GormMssqlByConfig(config.Mssql{GeneralDB: info.GeneralDB}) + case "pgsql": + dbMap[info.AliasName] = GormPgSqlByConfig(config.Pgsql{GeneralDB: info.GeneralDB}) + case "oracle": + dbMap[info.AliasName] = GormOracleByConfig(config.Oracle{GeneralDB: info.GeneralDB}) + default: + continue + } + } + // 做特殊判断,是否有迁移 + // 适配低版本迁移多数据库版本 + if sysDB, ok := dbMap[sys]; ok { + global.GVA_DB = sysDB + } + global.GVA_DBList = dbMap +} diff --git a/initialize/ensure_tables.go b/initialize/ensure_tables.go new file mode 100644 index 0000000..8d7a721 --- /dev/null +++ b/initialize/ensure_tables.go @@ -0,0 +1,113 @@ +package initialize + +import ( + "context" + "git.echol.cn/loser/xiecheng_server/model/example" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model" + "git.echol.cn/loser/xiecheng_server/service/system" + adapter "github.com/casbin/gorm-adapter/v3" + "gorm.io/gorm" +) + +const initOrderEnsureTables = system.InitOrderExternal - 1 + +type ensureTables struct{} + +// auto run +func init() { + system.RegisterInit(initOrderEnsureTables, &ensureTables{}) +} + +func (e *ensureTables) InitializerName() string { + return "ensure_tables_created" +} +func (e *ensureTables) InitializeData(ctx context.Context) (next context.Context, err error) { + return ctx, nil +} + +func (e *ensureTables) DataInserted(ctx context.Context) bool { + return true +} + +func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + tables := []interface{}{ + sysModel.SysApi{}, + sysModel.SysUser{}, + sysModel.SysBaseMenu{}, + sysModel.SysAuthority{}, + sysModel.JwtBlacklist{}, + sysModel.SysDictionary{}, + sysModel.SysAutoCodeHistory{}, + sysModel.SysOperationRecord{}, + sysModel.SysDictionaryDetail{}, + sysModel.SysBaseMenuParameter{}, + sysModel.SysBaseMenuBtn{}, + sysModel.SysAuthorityBtn{}, + sysModel.SysAutoCodePackage{}, + sysModel.SysExportTemplate{}, + sysModel.Condition{}, + sysModel.JoinTemplate{}, + sysModel.SysParams{}, + + adapter.CasbinRule{}, + + example.ExaFile{}, + example.ExaCustomer{}, + example.ExaFileChunk{}, + example.ExaFileUploadAndDownload{}, + example.ExaAttachmentCategory{}, + + model.Info{}, + } + for _, t := range tables { + _ = db.AutoMigrate(&t) + // 视图 authority_menu 会被当成表来创建,引发冲突错误(更新版本的gorm似乎不会) + // 由于 AutoMigrate() 基本无需考虑错误,因此显式忽略 + } + return ctx, nil +} + +func (e *ensureTables) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + tables := []interface{}{ + sysModel.SysApi{}, + sysModel.SysUser{}, + sysModel.SysBaseMenu{}, + sysModel.SysAuthority{}, + sysModel.JwtBlacklist{}, + sysModel.SysDictionary{}, + sysModel.SysAutoCodeHistory{}, + sysModel.SysOperationRecord{}, + sysModel.SysDictionaryDetail{}, + sysModel.SysBaseMenuParameter{}, + sysModel.SysBaseMenuBtn{}, + sysModel.SysAuthorityBtn{}, + sysModel.SysAutoCodePackage{}, + sysModel.SysExportTemplate{}, + sysModel.Condition{}, + sysModel.JoinTemplate{}, + + adapter.CasbinRule{}, + + example.ExaFile{}, + example.ExaCustomer{}, + example.ExaFileChunk{}, + example.ExaFileUploadAndDownload{}, + example.ExaAttachmentCategory{}, + + model.Info{}, + } + yes := true + for _, t := range tables { + yes = yes && db.Migrator().HasTable(t) + } + return yes +} diff --git a/initialize/gorm.go b/initialize/gorm.go new file mode 100644 index 0000000..74de3dd --- /dev/null +++ b/initialize/gorm.go @@ -0,0 +1,78 @@ +package initialize + +import ( + "os" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/example" + "git.echol.cn/loser/xiecheng_server/model/system" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +func Gorm() *gorm.DB { + switch global.GVA_CONFIG.System.DbType { + case "mysql": + global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mysql.Dbname + return GormMysql() + case "pgsql": + global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Pgsql.Dbname + return GormPgSql() + case "oracle": + global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Oracle.Dbname + return GormOracle() + case "mssql": + global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mssql.Dbname + return GormMssql() + case "sqlite": + global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Sqlite.Dbname + return GormSqlite() + default: + global.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mysql.Dbname + return GormMysql() + } +} + +func RegisterTables() { + db := global.GVA_DB + err := db.AutoMigrate( + + system.SysApi{}, + system.SysIgnoreApi{}, + system.SysUser{}, + system.SysBaseMenu{}, + system.JwtBlacklist{}, + system.SysAuthority{}, + system.SysDictionary{}, + system.SysOperationRecord{}, + system.SysAutoCodeHistory{}, + system.SysDictionaryDetail{}, + system.SysBaseMenuParameter{}, + system.SysBaseMenuBtn{}, + system.SysAuthorityBtn{}, + system.SysAutoCodePackage{}, + system.SysExportTemplate{}, + system.Condition{}, + system.JoinTemplate{}, + system.SysParams{}, + + example.ExaFile{}, + example.ExaCustomer{}, + example.ExaFileChunk{}, + example.ExaFileUploadAndDownload{}, + example.ExaAttachmentCategory{}, + ) + if err != nil { + global.GVA_LOG.Error("register table failed", zap.Error(err)) + os.Exit(0) + } + + err = bizModel() + + if err != nil { + global.GVA_LOG.Error("register biz_table failed", zap.Error(err)) + os.Exit(0) + } + global.GVA_LOG.Info("register table success") +} diff --git a/initialize/gorm_biz.go b/initialize/gorm_biz.go new file mode 100644 index 0000000..b31b8e4 --- /dev/null +++ b/initialize/gorm_biz.go @@ -0,0 +1,14 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +func bizModel() error { + db := global.GVA_DB + err := db.AutoMigrate() + if err != nil { + return err + } + return nil +} diff --git a/initialize/gorm_mssql.go b/initialize/gorm_mssql.go new file mode 100644 index 0000000..2b98747 --- /dev/null +++ b/initialize/gorm_mssql.go @@ -0,0 +1,60 @@ +package initialize + +/* + * @Author: 逆光飞翔 191180776@qq.com + * @Date: 2022-12-08 17:25:49 + * @LastEditors: 逆光飞翔 191180776@qq.com + * @LastEditTime: 2022-12-08 18:00:00 + * @FilePath: \server\initialize\gorm_mssql.go + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize/internal" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" +) + +// GormMssql 初始化Mssql数据库 +// Author [LouisZhang](191180776@qq.com) +func GormMssql() *gorm.DB { + m := global.GVA_CONFIG.Mssql + if m.Dbname == "" { + return nil + } + mssqlConfig := sqlserver.Config{ + DSN: m.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + } + if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil { + return nil + } else { + db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine) + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(m.MaxIdleConns) + sqlDB.SetMaxOpenConns(m.MaxOpenConns) + return db + } +} + +// GormMssqlByConfig 初始化Mysql数据库用过传入配置 +func GormMssqlByConfig(m config.Mssql) *gorm.DB { + if m.Dbname == "" { + return nil + } + mssqlConfig := sqlserver.Config{ + DSN: m.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + } + if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil { + panic(err) + } else { + db.InstanceSet("gorm:table_options", "ENGINE=InnoDB") + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(m.MaxIdleConns) + sqlDB.SetMaxOpenConns(m.MaxOpenConns) + return db + } +} diff --git a/initialize/gorm_mysql.go b/initialize/gorm_mysql.go new file mode 100644 index 0000000..41587bd --- /dev/null +++ b/initialize/gorm_mysql.go @@ -0,0 +1,55 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize/internal" + _ "github.com/go-sql-driver/mysql" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// GormMysql 初始化Mysql数据库 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func GormMysql() *gorm.DB { + m := global.GVA_CONFIG.Mysql + if m.Dbname == "" { + return nil + } + mysqlConfig := mysql.Config{ + DSN: m.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + SkipInitializeWithVersion: false, // 根据版本自动配置 + } + if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil { + return nil + } else { + db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine) + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(m.MaxIdleConns) + sqlDB.SetMaxOpenConns(m.MaxOpenConns) + return db + } +} + +// GormMysqlByConfig 初始化Mysql数据库用过传入配置 +func GormMysqlByConfig(m config.Mysql) *gorm.DB { + if m.Dbname == "" { + return nil + } + mysqlConfig := mysql.Config{ + DSN: m.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + SkipInitializeWithVersion: false, // 根据版本自动配置 + } + if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil { + panic(err) + } else { + db.InstanceSet("gorm:table_options", "ENGINE=InnoDB") + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(m.MaxIdleConns) + sqlDB.SetMaxOpenConns(m.MaxOpenConns) + return db + } +} diff --git a/initialize/gorm_oracle.go b/initialize/gorm_oracle.go new file mode 100644 index 0000000..86184d6 --- /dev/null +++ b/initialize/gorm_oracle.go @@ -0,0 +1,52 @@ +package initialize + +import ( + //"github.com/dzwvip/oracle" + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize/internal" + + //_ "github.com/godror/godror" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +// GormOracle 初始化oracle数据库 +// 如果需要Oracle库 放开import里的注释 把下方 mysql.Config 改为 oracle.Config ; mysql.New 改为 oracle.New +func GormOracle() *gorm.DB { + m := global.GVA_CONFIG.Oracle + if m.Dbname == "" { + return nil + } + oracleConfig := mysql.Config{ + DSN: m.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + } + if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil { + panic(err) + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(m.MaxIdleConns) + sqlDB.SetMaxOpenConns(m.MaxOpenConns) + return db + } +} + +// GormOracleByConfig 初始化Oracle数据库用过传入配置 +func GormOracleByConfig(m config.Oracle) *gorm.DB { + if m.Dbname == "" { + return nil + } + oracleConfig := mysql.Config{ + DSN: m.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + } + if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil { + panic(err) + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(m.MaxIdleConns) + sqlDB.SetMaxOpenConns(m.MaxOpenConns) + return db + } +} diff --git a/initialize/gorm_pgsql.go b/initialize/gorm_pgsql.go new file mode 100644 index 0000000..5f573dc --- /dev/null +++ b/initialize/gorm_pgsql.go @@ -0,0 +1,50 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize/internal" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +// GormPgSql 初始化 Postgresql 数据库 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func GormPgSql() *gorm.DB { + p := global.GVA_CONFIG.Pgsql + if p.Dbname == "" { + return nil + } + pgsqlConfig := postgres.Config{ + DSN: p.Dsn(), // DSN data source name + PreferSimpleProtocol: false, + } + if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil { + return nil + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(p.MaxIdleConns) + sqlDB.SetMaxOpenConns(p.MaxOpenConns) + return db + } +} + +// GormPgSqlByConfig 初始化 Postgresql 数据库 通过参数 +func GormPgSqlByConfig(p config.Pgsql) *gorm.DB { + if p.Dbname == "" { + return nil + } + pgsqlConfig := postgres.Config{ + DSN: p.Dsn(), // DSN data source name + PreferSimpleProtocol: false, + } + if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil { + panic(err) + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(p.MaxIdleConns) + sqlDB.SetMaxOpenConns(p.MaxOpenConns) + return db + } +} diff --git a/initialize/gorm_sqlite.go b/initialize/gorm_sqlite.go new file mode 100644 index 0000000..5feecd4 --- /dev/null +++ b/initialize/gorm_sqlite.go @@ -0,0 +1,42 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize/internal" + "github.com/glebarez/sqlite" + "gorm.io/gorm" +) + +// GormSqlite 初始化Sqlite数据库 +func GormSqlite() *gorm.DB { + s := global.GVA_CONFIG.Sqlite + if s.Dbname == "" { + return nil + } + + if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil { + panic(err) + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(s.MaxIdleConns) + sqlDB.SetMaxOpenConns(s.MaxOpenConns) + return db + } +} + +// GormSqliteByConfig 初始化Sqlite数据库用过传入配置 +func GormSqliteByConfig(s config.Sqlite) *gorm.DB { + if s.Dbname == "" { + return nil + } + + if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil { + panic(err) + } else { + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(s.MaxIdleConns) + sqlDB.SetMaxOpenConns(s.MaxOpenConns) + return db + } +} diff --git a/initialize/internal/gorm.go b/initialize/internal/gorm.go new file mode 100644 index 0000000..2d377b9 --- /dev/null +++ b/initialize/internal/gorm.go @@ -0,0 +1,46 @@ +package internal + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" + "time" +) + +var Gorm = new(_gorm) + +type _gorm struct{} + +// Config gorm 自定义配置 +// Author [SliverHorn](https://github.com/SliverHorn) +func (g *_gorm) Config(prefix string, singular bool) *gorm.Config { + var general config.GeneralDB + switch global.GVA_CONFIG.System.DbType { + case "mysql": + general = global.GVA_CONFIG.Mysql.GeneralDB + case "pgsql": + general = global.GVA_CONFIG.Pgsql.GeneralDB + case "oracle": + general = global.GVA_CONFIG.Oracle.GeneralDB + case "sqlite": + general = global.GVA_CONFIG.Sqlite.GeneralDB + case "mssql": + general = global.GVA_CONFIG.Mssql.GeneralDB + default: + general = global.GVA_CONFIG.Mysql.GeneralDB + } + return &gorm.Config{ + Logger: logger.New(NewWriter(general), logger.Config{ + SlowThreshold: 200 * time.Millisecond, + LogLevel: general.LogLevel(), + Colorful: true, + }), + NamingStrategy: schema.NamingStrategy{ + TablePrefix: prefix, + SingularTable: singular, + }, + DisableForeignKeyConstraintWhenMigrating: true, + } +} diff --git a/initialize/internal/gorm_logger_writer.go b/initialize/internal/gorm_logger_writer.go new file mode 100644 index 0000000..3d42a68 --- /dev/null +++ b/initialize/internal/gorm_logger_writer.go @@ -0,0 +1,41 @@ +package internal + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "gorm.io/gorm/logger" +) + +type Writer struct { + config config.GeneralDB + writer logger.Writer +} + +func NewWriter(config config.GeneralDB) *Writer { + return &Writer{config: config} +} + +// Printf 格式化打印日志 +func (c *Writer) Printf(message string, data ...any) { + + // 当有日志时候均需要输出到控制台 + fmt.Printf(message, data...) + + // 当开启了zap的情况,会打印到日志记录 + if c.config.LogZap { + switch c.config.LogLevel() { + case logger.Silent: + global.GVA_LOG.Debug(fmt.Sprintf(message, data...)) + case logger.Error: + global.GVA_LOG.Error(fmt.Sprintf(message, data...)) + case logger.Warn: + global.GVA_LOG.Warn(fmt.Sprintf(message, data...)) + case logger.Info: + global.GVA_LOG.Info(fmt.Sprintf(message, data...)) + default: + global.GVA_LOG.Info(fmt.Sprintf(message, data...)) + } + return + } +} diff --git a/initialize/internal/mongo.go b/initialize/internal/mongo.go new file mode 100644 index 0000000..c4992d7 --- /dev/null +++ b/initialize/internal/mongo.go @@ -0,0 +1,29 @@ +package internal + +import ( + "context" + "fmt" + "github.com/qiniu/qmgo/options" + "go.mongodb.org/mongo-driver/event" + opt "go.mongodb.org/mongo-driver/mongo/options" + "go.uber.org/zap" +) + +var Mongo = new(mongo) + +type mongo struct{} + +func (m *mongo) GetClientOptions() []options.ClientOptions { + cmdMonitor := &event.CommandMonitor{ + Started: func(ctx context.Context, event *event.CommandStartedEvent) { + zap.L().Info(fmt.Sprintf("[MongoDB][RequestID:%d][database:%s] %s\n", event.RequestID, event.DatabaseName, event.Command), zap.String("business", "mongo")) + }, + Succeeded: func(ctx context.Context, event *event.CommandSucceededEvent) { + zap.L().Info(fmt.Sprintf("[MongoDB][RequestID:%d] [%s] %s\n", event.RequestID, event.Duration.String(), event.Reply), zap.String("business", "mongo")) + }, + Failed: func(ctx context.Context, event *event.CommandFailedEvent) { + zap.L().Error(fmt.Sprintf("[MongoDB][RequestID:%d] [%s] %s\n", event.RequestID, event.Duration.String(), event.Failure), zap.String("business", "mongo")) + }, + } + return []options.ClientOptions{{ClientOptions: &opt.ClientOptions{Monitor: cmdMonitor}}} +} diff --git a/initialize/mongo.go b/initialize/mongo.go new file mode 100644 index 0000000..6d40eab --- /dev/null +++ b/initialize/mongo.go @@ -0,0 +1,155 @@ +package initialize + +import ( + "context" + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize/internal" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/pkg/errors" + "github.com/qiniu/qmgo" + "github.com/qiniu/qmgo/options" + "go.mongodb.org/mongo-driver/bson" + option "go.mongodb.org/mongo-driver/mongo/options" + "sort" + "strings" +) + +var Mongo = new(mongo) + +type ( + mongo struct{} + Index struct { + V any `bson:"v"` + Ns any `bson:"ns"` + Key []bson.E `bson:"key"` + Name string `bson:"name"` + } +) + +func (m *mongo) Indexes(ctx context.Context) error { + // 表名:索引列表 列: "表名": [][]string{{"index1", "index2"}} + indexMap := map[string][][]string{} + for collection, indexes := range indexMap { + err := m.CreateIndexes(ctx, collection, indexes) + if err != nil { + return err + } + } + return nil +} + +func (m *mongo) Initialization() error { + var opts []options.ClientOptions + if global.GVA_CONFIG.Mongo.IsZap { + opts = internal.Mongo.GetClientOptions() + } + ctx := context.Background() + config := &qmgo.Config{ + Uri: global.GVA_CONFIG.Mongo.Uri(), + Coll: global.GVA_CONFIG.Mongo.Coll, + Database: global.GVA_CONFIG.Mongo.Database, + MinPoolSize: &global.GVA_CONFIG.Mongo.MinPoolSize, + MaxPoolSize: &global.GVA_CONFIG.Mongo.MaxPoolSize, + SocketTimeoutMS: &global.GVA_CONFIG.Mongo.SocketTimeoutMs, + ConnectTimeoutMS: &global.GVA_CONFIG.Mongo.ConnectTimeoutMs, + } + if global.GVA_CONFIG.Mongo.Username != "" && global.GVA_CONFIG.Mongo.Password != "" { + config.Auth = &qmgo.Credential{ + Username: global.GVA_CONFIG.Mongo.Username, + Password: global.GVA_CONFIG.Mongo.Password, + AuthSource: global.GVA_CONFIG.Mongo.AuthSource, + } + } + client, err := qmgo.Open(ctx, config, opts...) + + if err != nil { + return errors.Wrap(err, "链接mongodb数据库失败!") + } + global.GVA_MONGO = client + err = m.Indexes(ctx) + if err != nil { + return err + } + return nil +} + +func (m *mongo) CreateIndexes(ctx context.Context, name string, indexes [][]string) error { + collection, err := global.GVA_MONGO.Database.Collection(name).CloneCollection() + if err != nil { + return errors.Wrapf(err, "获取[%s]的表对象失败!", name) + } + list, err := collection.Indexes().List(ctx) + if err != nil { + return errors.Wrapf(err, "获取[%s]的索引对象失败!", name) + } + var entities []Index + err = list.All(ctx, &entities) + if err != nil { + return errors.Wrapf(err, "获取[%s]的索引列表失败!", name) + } + length := len(indexes) + indexMap1 := make(map[string][]string, length) + for i := 0; i < length; i++ { + sort.Strings(indexes[i]) // 对索引key进行排序, 在使用bson.M搜索时, bson会自动按照key的字母顺序进行排序 + length1 := len(indexes[i]) + keys := make([]string, 0, length1) + for j := 0; j < length1; j++ { + if indexes[i][i][0] == '-' { + keys = append(keys, indexes[i][j], "-1") + continue + } + keys = append(keys, indexes[i][j], "1") + } + key := strings.Join(keys, "_") + _, o1 := indexMap1[key] + if o1 { + return errors.Errorf("索引[%s]重复!", key) + } + indexMap1[key] = indexes[i] + } + length = len(entities) + indexMap2 := make(map[string]map[string]string, length) + for i := 0; i < length; i++ { + v1, o1 := indexMap2[entities[i].Name] + if !o1 { + keyLength := len(entities[i].Key) + v1 = make(map[string]string, keyLength) + for j := 0; j < keyLength; j++ { + v2, o2 := v1[entities[i].Key[j].Key] + if !o2 { + v1 = make(map[string]string) + } + v2 = entities[i].Key[j].Key + v1[entities[i].Key[j].Key] = v2 + indexMap2[entities[i].Name] = v1 + } + } + } + for k1, v1 := range indexMap1 { + _, o2 := indexMap2[k1] + if o2 { + continue + } // 索引存在 + if len(fmt.Sprintf("%s.%s.$%s", collection.Name(), name, v1)) > 127 { + err = global.GVA_MONGO.Database.Collection(name).CreateOneIndex(ctx, options.IndexModel{ + Key: v1, + IndexOptions: option.Index().SetName(utils.MD5V([]byte(k1))), + // IndexOptions: option.Index().SetName(utils.MD5V([]byte(k1))).SetExpireAfterSeconds(86400), // SetExpireAfterSeconds(86400) 设置索引过期时间, 86400 = 1天 + }) + if err != nil { + return errors.Wrapf(err, "创建索引[%s]失败!", k1) + } + return nil + } + err = global.GVA_MONGO.Database.Collection(name).CreateOneIndex(ctx, options.IndexModel{ + Key: v1, + IndexOptions: option.Index().SetExpireAfterSeconds(86400), + // IndexOptions: option.Index().SetName(utils.MD5V([]byte(k1))).SetExpireAfterSeconds(86400), // SetExpireAfterSeconds(86400) 设置索引过期时间(秒), 86400 = 1天 + }) + if err != nil { + return errors.Wrapf(err, "创建索引[%s]失败!", k1) + } + } + return nil +} diff --git a/initialize/other.go b/initialize/other.go new file mode 100644 index 0000000..176afbf --- /dev/null +++ b/initialize/other.go @@ -0,0 +1,32 @@ +package initialize + +import ( + "bufio" + "github.com/songzhibin97/gkit/cache/local_cache" + "os" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/utils" +) + +func OtherInit() { + dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + if err != nil { + panic(err) + } + _, err = utils.ParseDuration(global.GVA_CONFIG.JWT.BufferTime) + if err != nil { + panic(err) + } + + global.BlackCache = local_cache.NewCache( + local_cache.SetDefaultExpire(dr), + ) + file, err := os.Open("go.mod") + if err == nil && global.GVA_CONFIG.AutoCode.Module == "" { + scanner := bufio.NewScanner(file) + scanner.Scan() + global.GVA_CONFIG.AutoCode.Module = strings.TrimPrefix(scanner.Text(), "module ") + } +} diff --git a/initialize/plugin.go b/initialize/plugin.go new file mode 100644 index 0000000..a5b724e --- /dev/null +++ b/initialize/plugin.go @@ -0,0 +1,15 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "github.com/gin-gonic/gin" +) + +func InstallPlugin(PrivateGroup *gin.RouterGroup, PublicRouter *gin.RouterGroup, engine *gin.Engine) { + if global.GVA_DB == nil { + global.GVA_LOG.Info("项目暂未初始化,无法安装插件,初始化后重启项目即可完成插件安装") + return + } + bizPluginV1(PrivateGroup, PublicRouter) + bizPluginV2(engine) +} diff --git a/initialize/plugin_biz_v1.go b/initialize/plugin_biz_v1.go new file mode 100644 index 0000000..f93f091 --- /dev/null +++ b/initialize/plugin_biz_v1.go @@ -0,0 +1,34 @@ +package initialize + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/plugin/email" + "git.echol.cn/loser/xiecheng_server/utils/plugin" + "github.com/gin-gonic/gin" +) + +func PluginInit(group *gin.RouterGroup, Plugin ...plugin.Plugin) { + for i := range Plugin { + fmt.Println(Plugin[i].RouterPath(), "注册开始!") + PluginGroup := group.Group(Plugin[i].RouterPath()) + Plugin[i].Register(PluginGroup) + fmt.Println(Plugin[i].RouterPath(), "注册成功!") + } +} + +func bizPluginV1(group ...*gin.RouterGroup) { + private := group[0] + public := group[1] + // 添加跟角色挂钩权限的插件 示例 本地示例模式于在线仓库模式注意上方的import 可以自行切换 效果相同 + PluginInit(private, email.CreateEmailPlug( + global.GVA_CONFIG.Email.To, + global.GVA_CONFIG.Email.From, + global.GVA_CONFIG.Email.Host, + global.GVA_CONFIG.Email.Secret, + global.GVA_CONFIG.Email.Nickname, + global.GVA_CONFIG.Email.Port, + global.GVA_CONFIG.Email.IsSSL, + )) + holder(public, private) +} diff --git a/initialize/plugin_biz_v2.go b/initialize/plugin_biz_v2.go new file mode 100644 index 0000000..bd780b4 --- /dev/null +++ b/initialize/plugin_biz_v2.go @@ -0,0 +1,16 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/plugin/announcement" + "git.echol.cn/loser/xiecheng_server/utils/plugin/v2" + "github.com/gin-gonic/gin" +) + +func PluginInitV2(group *gin.Engine, plugins ...plugin.Plugin) { + for i := 0; i < len(plugins); i++ { + plugins[i].Register(group) + } +} +func bizPluginV2(engine *gin.Engine) { + PluginInitV2(engine, announcement.Plugin) +} diff --git a/initialize/redis.go b/initialize/redis.go new file mode 100644 index 0000000..07358d2 --- /dev/null +++ b/initialize/redis.go @@ -0,0 +1,59 @@ +package initialize + +import ( + "context" + + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + + "github.com/redis/go-redis/v9" + "go.uber.org/zap" +) + +func initRedisClient(redisCfg config.Redis) (redis.UniversalClient, error) { + var client redis.UniversalClient + // 使用集群模式 + if redisCfg.UseCluster { + client = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: redisCfg.ClusterAddrs, + Password: redisCfg.Password, + }) + } else { + // 使用单例模式 + client = redis.NewClient(&redis.Options{ + Addr: redisCfg.Addr, + Password: redisCfg.Password, + DB: redisCfg.DB, + }) + } + pong, err := client.Ping(context.Background()).Result() + if err != nil { + global.GVA_LOG.Error("redis connect ping failed, err:", zap.String("name", redisCfg.Name), zap.Error(err)) + return nil, err + } + + global.GVA_LOG.Info("redis connect ping response:", zap.String("name", redisCfg.Name), zap.String("pong", pong)) + return client, nil +} + +func Redis() { + redisClient, err := initRedisClient(global.GVA_CONFIG.Redis) + if err != nil { + panic(err) + } + global.GVA_REDIS = redisClient +} + +func RedisList() { + redisMap := make(map[string]redis.UniversalClient) + + for _, redisCfg := range global.GVA_CONFIG.RedisList { + client, err := initRedisClient(redisCfg) + if err != nil { + panic(err) + } + redisMap[redisCfg.Name] = client + } + + global.GVA_REDISList = redisMap +} diff --git a/initialize/register_init.go b/initialize/register_init.go new file mode 100644 index 0000000..939b381 --- /dev/null +++ b/initialize/register_init.go @@ -0,0 +1,10 @@ +package initialize + +import ( + _ "git.echol.cn/loser/xiecheng_server/source/example" + _ "git.echol.cn/loser/xiecheng_server/source/system" +) + +func init() { + // do nothing,only import source package so that inits can be registered +} diff --git a/initialize/router.go b/initialize/router.go new file mode 100644 index 0000000..dc515af --- /dev/null +++ b/initialize/router.go @@ -0,0 +1,111 @@ +package initialize + +import ( + "net/http" + "os" + + "git.echol.cn/loser/xiecheng_server/docs" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/middleware" + "git.echol.cn/loser/xiecheng_server/router" + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +type justFilesFilesystem struct { + fs http.FileSystem +} + +func (fs justFilesFilesystem) Open(name string) (http.File, error) { + f, err := fs.fs.Open(name) + if err != nil { + return nil, err + } + + stat, err := f.Stat() + if stat.IsDir() { + return nil, os.ErrPermission + } + + return f, nil +} + +// 初始化总路由 + +func Routers() *gin.Engine { + Router := gin.New() + Router.Use(gin.Recovery()) + if gin.Mode() == gin.DebugMode { + Router.Use(gin.Logger()) + } + + systemRouter := router.RouterGroupApp.System + exampleRouter := router.RouterGroupApp.Example + // 如果想要不使用nginx代理前端网页,可以修改 web/.env.production 下的 + // VUE_APP_BASE_API = / + // VUE_APP_BASE_PATH = http://localhost + // 然后执行打包命令 npm run build。在打开下面3行注释 + // Router.Static("/favicon.ico", "./dist/favicon.ico") + // Router.Static("/assets", "./dist/assets") // dist里面的静态资源 + // Router.StaticFile("/", "./dist/index.html") // 前端网页入口页面 + + Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件") + // 跨域,如需跨域可以打开下面的注释 + // Router.Use(middleware.Cors()) // 直接放行全部跨域请求 + // Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求 + // global.GVA_LOG.Info("use middleware cors") + docs.SwaggerInfo.BasePath = global.GVA_CONFIG.System.RouterPrefix + Router.GET(global.GVA_CONFIG.System.RouterPrefix+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + global.GVA_LOG.Info("register swagger handler") + // 方便统一添加路由组前缀 多服务器上线使用 + + PublicGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix) + PrivateGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix) + + PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()) + + { + // 健康监测 + PublicGroup.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + } + { + systemRouter.InitBaseRouter(PublicGroup) // 注册基础功能路由 不做鉴权 + systemRouter.InitInitRouter(PublicGroup) // 自动初始化相关 + } + + { + systemRouter.InitApiRouter(PrivateGroup, PublicGroup) // 注册功能api路由 + systemRouter.InitJwtRouter(PrivateGroup) // jwt相关路由 + systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由 + systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由 + systemRouter.InitSystemRouter(PrivateGroup) // system相关路由 + systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由 + systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码 + systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由 + systemRouter.InitSysDictionaryRouter(PrivateGroup) // 字典管理 + systemRouter.InitAutoCodeHistoryRouter(PrivateGroup) // 自动化代码历史 + systemRouter.InitSysOperationRecordRouter(PrivateGroup) // 操作记录 + systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理 + systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup) // 按钮权限管理 + systemRouter.InitSysExportTemplateRouter(PrivateGroup, PublicGroup) // 导出模板 + systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理 + exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由 + exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由 + exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类 + + } + + //插件路由安装 + InstallPlugin(PrivateGroup, PublicGroup, Router) + + // 注册业务路由 + initBizRouter(PrivateGroup, PublicGroup) + + global.GVA_ROUTERS = Router.Routes() + + global.GVA_LOG.Info("router register success") + return Router +} diff --git a/initialize/router_biz.go b/initialize/router_biz.go new file mode 100644 index 0000000..0acb1ca --- /dev/null +++ b/initialize/router_biz.go @@ -0,0 +1,19 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/router" + "github.com/gin-gonic/gin" +) + +// 占位方法,保证文件可以正确加载,避免go空变量检测报错,请勿删除。 +func holder(routers ...*gin.RouterGroup) { + _ = routers + _ = router.RouterGroupApp +} + +func initBizRouter(routers ...*gin.RouterGroup) { + privateGroup := routers[0] + publicGroup := routers[1] + + holder(publicGroup, privateGroup) +} diff --git a/initialize/timer.go b/initialize/timer.go new file mode 100644 index 0000000..f403372 --- /dev/null +++ b/initialize/timer.go @@ -0,0 +1,37 @@ +package initialize + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/task" + + "github.com/robfig/cron/v3" + + "git.echol.cn/loser/xiecheng_server/global" +) + +func Timer() { + go func() { + var option []cron.Option + option = append(option, cron.WithSeconds()) + // 清理DB定时任务 + _, err := global.GVA_Timer.AddTaskByFunc("ClearDB", "@daily", func() { + err := task.ClearTable(global.GVA_DB) // 定时任务方法定在task文件包中 + if err != nil { + fmt.Println("timer error:", err) + } + }, "定时清理数据库【日志,黑名单】内容", option...) + if err != nil { + fmt.Println("add timer error:", err) + } + + // 其他定时任务定在这里 参考上方使用方法 + + //_, err := global.GVA_Timer.AddTaskByFunc("定时任务标识", "corn表达式", func() { + // 具体执行内容... + // ...... + //}, option...) + //if err != nil { + // fmt.Println("add timer error:", err) + //} + }() +} diff --git a/initialize/validator.go b/initialize/validator.go new file mode 100644 index 0000000..6f9ff6a --- /dev/null +++ b/initialize/validator.go @@ -0,0 +1,22 @@ +package initialize + +import "git.echol.cn/loser/xiecheng_server/utils" + +func init() { + _ = utils.RegisterRule("PageVerify", + utils.Rules{ + "Page": {utils.NotEmpty()}, + "PageSize": {utils.NotEmpty()}, + }, + ) + _ = utils.RegisterRule("IdVerify", + utils.Rules{ + "Id": {utils.NotEmpty()}, + }, + ) + _ = utils.RegisterRule("AuthorityIdVerify", + utils.Rules{ + "AuthorityId": {utils.NotEmpty()}, + }, + ) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d887ffb --- /dev/null +++ b/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "git.echol.cn/loser/xiecheng_server/core" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/initialize" + _ "go.uber.org/automaxprocs" + "go.uber.org/zap" +) + +//go:generate go env -w GO111MODULE=on +//go:generate go env -w GOPROXY=https://goproxy.cn,direct +//go:generate go mod tidy +//go:generate go mod download + +// 这部分 @Tag 设置用于排序, 需要排序的接口请按照下面的格式添加 +// swag init 对 @Tag 只会从入口文件解析, 默认 main.go +// 也可通过 --generalInfo flag 指定其他文件 +// @Tag.Name Base +// @Tag.Name SysUser +// @Tag.Description 用户 + +// @title Gin-Vue-Admin Swagger API接口文档 +// @version v2.8.0 +// @description 使用gin+vue进行极速开发的全栈开发基础平台 +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name x-token +// @BasePath / +func main() { + global.GVA_VP = core.Viper() // 初始化Viper + initialize.OtherInit() + global.GVA_LOG = core.Zap() // 初始化zap日志库 + zap.ReplaceGlobals(global.GVA_LOG) + global.GVA_DB = initialize.Gorm() // gorm连接数据库 + initialize.Timer() + initialize.DBList() + if global.GVA_DB != nil { + initialize.RegisterTables() // 初始化表 + // 程序结束前关闭数据库链接 + db, _ := global.GVA_DB.DB() + defer db.Close() + } + core.RunWindowsServer() +} diff --git a/middleware/casbin_rbac.go b/middleware/casbin_rbac.go new file mode 100644 index 0000000..a8fddaa --- /dev/null +++ b/middleware/casbin_rbac.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "strconv" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/service" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/gin-gonic/gin" +) + +var casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService + +// CasbinHandler 拦截器 +func CasbinHandler() gin.HandlerFunc { + return func(c *gin.Context) { + waitUse, _ := utils.GetClaims(c) + //获取请求的PATH + path := c.Request.URL.Path + obj := strings.TrimPrefix(path, global.GVA_CONFIG.System.RouterPrefix) + // 获取请求方法 + act := c.Request.Method + // 获取用户的角色 + sub := strconv.Itoa(int(waitUse.AuthorityId)) + e := casbinService.Casbin() // 判断策略中是否存在 + success, _ := e.Enforce(sub, obj, act) + if !success { + response.FailWithDetailed(gin.H{}, "权限不足", c) + c.Abort() + return + } + c.Next() + } +} diff --git a/middleware/cors.go b/middleware/cors.go new file mode 100644 index 0000000..f1ccd3b --- /dev/null +++ b/middleware/cors.go @@ -0,0 +1,73 @@ +package middleware + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "github.com/gin-gonic/gin" + "net/http" +) + +// Cors 直接放行所有跨域请求并放行所有 OPTIONS 方法 +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + origin := c.Request.Header.Get("Origin") + c.Header("Access-Control-Allow-Origin", origin) + c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id") + c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT") + c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, New-Token, New-Expires-At") + c.Header("Access-Control-Allow-Credentials", "true") + + // 放行所有OPTIONS方法 + if method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + // 处理请求 + c.Next() + } +} + +// CorsByRules 按照配置处理跨域请求 +func CorsByRules() gin.HandlerFunc { + // 放行全部 + if global.GVA_CONFIG.Cors.Mode == "allow-all" { + return Cors() + } + return func(c *gin.Context) { + whitelist := checkCors(c.GetHeader("origin")) + + // 通过检查, 添加请求头 + if whitelist != nil { + c.Header("Access-Control-Allow-Origin", whitelist.AllowOrigin) + c.Header("Access-Control-Allow-Headers", whitelist.AllowHeaders) + c.Header("Access-Control-Allow-Methods", whitelist.AllowMethods) + c.Header("Access-Control-Expose-Headers", whitelist.ExposeHeaders) + if whitelist.AllowCredentials { + c.Header("Access-Control-Allow-Credentials", "true") + } + } + + // 严格白名单模式且未通过检查,直接拒绝处理请求 + if whitelist == nil && global.GVA_CONFIG.Cors.Mode == "strict-whitelist" && !(c.Request.Method == "GET" && c.Request.URL.Path == "/health") { + c.AbortWithStatus(http.StatusForbidden) + } else { + // 非严格白名单模式,无论是否通过检查均放行所有 OPTIONS 方法 + if c.Request.Method == http.MethodOptions { + c.AbortWithStatus(http.StatusNoContent) + } + } + + // 处理请求 + c.Next() + } +} + +func checkCors(currentOrigin string) *config.CORSWhitelist { + for _, whitelist := range global.GVA_CONFIG.Cors.Whitelist { + // 遍历配置中的跨域头,寻找匹配项 + if currentOrigin == whitelist.AllowOrigin { + return &whitelist + } + } + return nil +} diff --git a/middleware/email.go b/middleware/email.go new file mode 100644 index 0000000..b274493 --- /dev/null +++ b/middleware/email.go @@ -0,0 +1,60 @@ +package middleware + +import ( + "bytes" + "io" + "strconv" + "time" + + "git.echol.cn/loser/xiecheng_server/plugin/email/utils" + utils2 "git.echol.cn/loser/xiecheng_server/utils" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var userService = service.ServiceGroupApp.SystemServiceGroup.UserService + +func ErrorToEmail() gin.HandlerFunc { + return func(c *gin.Context) { + var username string + claims, _ := utils2.GetClaims(c) + if claims.Username != "" { + username = claims.Username + } else { + id, _ := strconv.Atoi(c.Request.Header.Get("x-user-id")) + user, err := userService.FindUserById(id) + if err != nil { + username = "Unknown" + } + username = user.Username + } + body, _ := io.ReadAll(c.Request.Body) + // 再重新写回请求体body中,ioutil.ReadAll会清空c.Request.Body中的数据 + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + record := system.SysOperationRecord{ + Ip: c.ClientIP(), + Method: c.Request.Method, + Path: c.Request.URL.Path, + Agent: c.Request.UserAgent(), + Body: string(body), + } + now := time.Now() + + c.Next() + + latency := time.Since(now) + status := c.Writer.Status() + record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String() + str := "接收到的请求为" + record.Body + "\n" + "请求方式为" + record.Method + "\n" + "报错信息如下" + record.ErrorMessage + "\n" + "耗时" + latency.String() + "\n" + if status != 200 { + subject := username + "" + record.Ip + "调用了" + record.Path + "报错了" + if err := utils.ErrorToEmail(subject, str); err != nil { + global.GVA_LOG.Error("ErrorToEmail Failed, err:", zap.Error(err)) + } + } + } +} diff --git a/middleware/error.go b/middleware/error.go new file mode 100644 index 0000000..94e5bec --- /dev/null +++ b/middleware/error.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 +func GinRecovery(stack bool) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + // Check for a broken connection, as it is not really a + // condition that warrants a panic stack trace. + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + if brokenPipe { + global.GVA_LOG.Error(c.Request.URL.Path, + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + // If the connection is dead, we can't write a status to it. + _ = c.Error(err.(error)) // nolint: errcheck + c.Abort() + return + } + + if stack { + global.GVA_LOG.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + zap.String("stack", string(debug.Stack())), + ) + } else { + global.GVA_LOG.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + c.AbortWithStatus(http.StatusInternalServerError) + } + }() + c.Next() + } +} diff --git a/middleware/jwt.go b/middleware/jwt.go new file mode 100644 index 0000000..41f74e0 --- /dev/null +++ b/middleware/jwt.go @@ -0,0 +1,80 @@ +package middleware + +import ( + "errors" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/golang-jwt/jwt/v5" + "strconv" + "time" + + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/service" + "github.com/gin-gonic/gin" +) + +var jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService + +func JWTAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录 + token := utils.GetToken(c) + if token == "" { + response.NoAuth("未登录或非法访问", c) + c.Abort() + return + } + if jwtService.IsBlacklist(token) { + response.NoAuth("您的帐户异地登陆或令牌失效", c) + utils.ClearToken(c) + c.Abort() + return + } + j := utils.NewJWT() + // parseToken 解析token包含的信息 + claims, err := j.ParseToken(token) + if err != nil { + if errors.Is(err, utils.TokenExpired) { + response.NoAuth("授权已过期", c) + utils.ClearToken(c) + c.Abort() + return + } + response.NoAuth(err.Error(), c) + utils.ClearToken(c) + c.Abort() + return + } + + // 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开 + // 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开 + + //if user, err := userService.FindUserByUuid(claims.UUID.String()); err != nil || user.Enable == 2 { + // _ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token}) + // response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c) + // c.Abort() + //} + c.Set("claims", claims) + if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime { + dr, _ := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(dr)) + newToken, _ := j.CreateTokenByOldToken(token, *claims) + newClaims, _ := j.ParseToken(newToken) + c.Header("new-token", newToken) + c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) + utils.SetToken(c, newToken, int(dr.Seconds())) + if global.GVA_CONFIG.System.UseMultipoint { + // 记录新的活跃jwt + _ = jwtService.SetRedisJWT(newToken, newClaims.Username) + } + } + c.Next() + + if newToken, exists := c.Get("new-token"); exists { + c.Header("new-token", newToken.(string)) + } + if newExpiresAt, exists := c.Get("new-expires-at"); exists { + c.Header("new-expires-at", newExpiresAt.(string)) + } + } +} diff --git a/middleware/limit_ip.go b/middleware/limit_ip.go new file mode 100644 index 0000000..6f59c26 --- /dev/null +++ b/middleware/limit_ip.go @@ -0,0 +1,92 @@ +package middleware + +import ( + "context" + "errors" + "net/http" + "time" + + "go.uber.org/zap" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "github.com/gin-gonic/gin" +) + +type LimitConfig struct { + // GenerationKey 根据业务生成key 下面CheckOrMark查询生成 + GenerationKey func(c *gin.Context) string + // 检查函数,用户可修改具体逻辑,更加灵活 + CheckOrMark func(key string, expire int, limit int) error + // Expire key 过期时间 + Expire int + // Limit 周期时间 + Limit int +} + +func (l LimitConfig) LimitWithTime() gin.HandlerFunc { + return func(c *gin.Context) { + if err := l.CheckOrMark(l.GenerationKey(c), l.Expire, l.Limit); err != nil { + c.JSON(http.StatusOK, gin.H{"code": response.ERROR, "msg": err.Error()}) + c.Abort() + return + } else { + c.Next() + } + } +} + +// DefaultGenerationKey 默认生成key +func DefaultGenerationKey(c *gin.Context) string { + return "GVA_Limit" + c.ClientIP() +} + +func DefaultCheckOrMark(key string, expire int, limit int) (err error) { + // 判断是否开启redis + if global.GVA_REDIS == nil { + return err + } + if err = SetLimitWithTime(key, limit, time.Duration(expire)*time.Second); err != nil { + global.GVA_LOG.Error("limit", zap.Error(err)) + } + return err +} + +func DefaultLimit() gin.HandlerFunc { + return LimitConfig{ + GenerationKey: DefaultGenerationKey, + CheckOrMark: DefaultCheckOrMark, + Expire: global.GVA_CONFIG.System.LimitTimeIP, + Limit: global.GVA_CONFIG.System.LimitCountIP, + }.LimitWithTime() +} + +// SetLimitWithTime 设置访问次数 +func SetLimitWithTime(key string, limit int, expiration time.Duration) error { + count, err := global.GVA_REDIS.Exists(context.Background(), key).Result() + if err != nil { + return err + } + if count == 0 { + pipe := global.GVA_REDIS.TxPipeline() + pipe.Incr(context.Background(), key) + pipe.Expire(context.Background(), key, expiration) + _, err = pipe.Exec(context.Background()) + return err + } else { + // 次数 + if times, err := global.GVA_REDIS.Get(context.Background(), key).Int(); err != nil { + return err + } else { + if times >= limit { + if t, err := global.GVA_REDIS.PTTL(context.Background(), key).Result(); err != nil { + return errors.New("请求太过频繁,请稍后再试") + } else { + return errors.New("请求太过频繁, 请 " + t.String() + " 秒后尝试") + } + } else { + return global.GVA_REDIS.Incr(context.Background(), key).Err() + } + } + } +} diff --git a/middleware/loadtls.go b/middleware/loadtls.go new file mode 100644 index 0000000..a17cf65 --- /dev/null +++ b/middleware/loadtls.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" +) + +// 用https把这个中间件在router里面use一下就好 + +func LoadTls() gin.HandlerFunc { + return func(c *gin.Context) { + middleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: "localhost:443", + }) + err := middleware.Process(c.Writer, c.Request) + if err != nil { + // 如果出现错误,请不要继续 + fmt.Println(err) + return + } + // 继续往下处理 + c.Next() + } +} diff --git a/middleware/logger.go b/middleware/logger.go new file mode 100644 index 0000000..fabc334 --- /dev/null +++ b/middleware/logger.go @@ -0,0 +1,89 @@ +package middleware + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +// LogLayout 日志layout +type LogLayout struct { + Time time.Time + Metadata map[string]interface{} // 存储自定义原数据 + Path string // 访问路径 + Query string // 携带query + Body string // 携带body数据 + IP string // ip地址 + UserAgent string // 代理 + Error string // 错误 + Cost time.Duration // 花费时间 + Source string // 来源 +} + +type Logger struct { + // Filter 用户自定义过滤 + Filter func(c *gin.Context) bool + // FilterKeyword 关键字过滤(key) + FilterKeyword func(layout *LogLayout) bool + // AuthProcess 鉴权处理 + AuthProcess func(c *gin.Context, layout *LogLayout) + // 日志处理 + Print func(LogLayout) + // Source 服务唯一标识 + Source string +} + +func (l Logger) SetLoggerMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + query := c.Request.URL.RawQuery + var body []byte + if l.Filter != nil && !l.Filter(c) { + body, _ = c.GetRawData() + // 将原body塞回去 + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + c.Next() + cost := time.Since(start) + layout := LogLayout{ + Time: time.Now(), + Path: path, + Query: query, + IP: c.ClientIP(), + UserAgent: c.Request.UserAgent(), + Error: strings.TrimRight(c.Errors.ByType(gin.ErrorTypePrivate).String(), "\n"), + Cost: cost, + Source: l.Source, + } + if l.Filter != nil && !l.Filter(c) { + layout.Body = string(body) + } + if l.AuthProcess != nil { + // 处理鉴权需要的信息 + l.AuthProcess(c, &layout) + } + if l.FilterKeyword != nil { + // 自行判断key/value 脱敏等 + l.FilterKeyword(&layout) + } + // 自行处理日志 + l.Print(layout) + } +} + +func DefaultLogger() gin.HandlerFunc { + return Logger{ + Print: func(layout LogLayout) { + // 标准输出,k8s做收集 + v, _ := json.Marshal(layout) + fmt.Println(string(v)) + }, + Source: "GVA", + }.SetLoggerMiddleware() +} diff --git a/middleware/operation.go b/middleware/operation.go new file mode 100644 index 0000000..f4f830e --- /dev/null +++ b/middleware/operation.go @@ -0,0 +1,133 @@ +package middleware + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "git.echol.cn/loser/xiecheng_server/utils" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService + +var respPool sync.Pool +var bufferSize = 1024 + +func init() { + respPool.New = func() interface{} { + return make([]byte, bufferSize) + } +} + +func OperationRecord() gin.HandlerFunc { + return func(c *gin.Context) { + var body []byte + var userId int + if c.Request.Method != http.MethodGet { + var err error + body, err = io.ReadAll(c.Request.Body) + if err != nil { + global.GVA_LOG.Error("read body from request error:", zap.Error(err)) + } else { + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + } else { + query := c.Request.URL.RawQuery + query, _ = url.QueryUnescape(query) + split := strings.Split(query, "&") + m := make(map[string]string) + for _, v := range split { + kv := strings.Split(v, "=") + if len(kv) == 2 { + m[kv[0]] = kv[1] + } + } + body, _ = json.Marshal(&m) + } + claims, _ := utils.GetClaims(c) + if claims != nil && claims.BaseClaims.ID != 0 { + userId = int(claims.BaseClaims.ID) + } else { + id, err := strconv.Atoi(c.Request.Header.Get("x-user-id")) + if err != nil { + userId = 0 + } + userId = id + } + record := system.SysOperationRecord{ + Ip: c.ClientIP(), + Method: c.Request.Method, + Path: c.Request.URL.Path, + Agent: c.Request.UserAgent(), + Body: "", + UserID: userId, + } + + // 上传文件时候 中间件日志进行裁断操作 + if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") { + record.Body = "[文件]" + } else { + if len(body) > bufferSize { + record.Body = "[超出记录长度]" + } else { + record.Body = string(body) + } + } + + writer := responseBodyWriter{ + ResponseWriter: c.Writer, + body: &bytes.Buffer{}, + } + c.Writer = writer + now := time.Now() + + c.Next() + + latency := time.Since(now) + record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String() + record.Status = c.Writer.Status() + record.Latency = latency + record.Resp = writer.body.String() + + if strings.Contains(c.Writer.Header().Get("Pragma"), "public") || + strings.Contains(c.Writer.Header().Get("Expires"), "0") || + strings.Contains(c.Writer.Header().Get("Cache-Control"), "must-revalidate, post-check=0, pre-check=0") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/force-download") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/octet-stream") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/vnd.ms-excel") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/download") || + strings.Contains(c.Writer.Header().Get("Content-Disposition"), "attachment") || + strings.Contains(c.Writer.Header().Get("Content-Transfer-Encoding"), "binary") { + if len(record.Resp) > bufferSize { + // 截断 + record.Body = "超出记录长度" + } + } + + if err := operationRecordService.CreateSysOperationRecord(record); err != nil { + global.GVA_LOG.Error("create operation record error:", zap.Error(err)) + } + } +} + +type responseBodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func (r responseBodyWriter) Write(b []byte) (int, error) { + r.body.Write(b) + return r.ResponseWriter.Write(b) +} diff --git a/model/common/basetypes.go b/model/common/basetypes.go new file mode 100644 index 0000000..1a133d5 --- /dev/null +++ b/model/common/basetypes.go @@ -0,0 +1,43 @@ +package common + +import ( + "database/sql/driver" + "encoding/json" + "errors" +) + +type JSONMap map[string]interface{} + +func (m JSONMap) Value() (driver.Value, error) { + if m == nil { + return nil, nil + } + return json.Marshal(m) +} + +func (m *JSONMap) Scan(value interface{}) error { + if value == nil { + *m = make(map[string]interface{}) + return nil + } + var err error + switch value.(type) { + case []byte: + err = json.Unmarshal(value.([]byte), m) + case string: + err = json.Unmarshal([]byte(value.(string)), m) + default: + err = errors.New("basetypes.JSONMap.Scan: invalid value type") + } + if err != nil { + return err + } + return nil +} + +type TreeNode[T any] interface { + GetChildren() []T + SetChildren(children T) + GetID() int + GetParentID() int +} diff --git a/model/common/clearDB.go b/model/common/clearDB.go new file mode 100644 index 0000000..e7fc757 --- /dev/null +++ b/model/common/clearDB.go @@ -0,0 +1,7 @@ +package common + +type ClearDB struct { + TableName string + CompareField string + Interval string +} diff --git a/model/common/request/common.go b/model/common/request/common.go new file mode 100644 index 0000000..c729f3d --- /dev/null +++ b/model/common/request/common.go @@ -0,0 +1,48 @@ +package request + +import ( + "gorm.io/gorm" +) + +// PageInfo Paging common input parameter structure +type PageInfo struct { + Page int `json:"page" form:"page"` // 页码 + PageSize int `json:"pageSize" form:"pageSize"` // 每页大小 + Keyword string `json:"keyword" form:"keyword"` // 关键字 +} + +func (r *PageInfo) Paginate() func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if r.Page <= 0 { + r.Page = 1 + } + switch { + case r.PageSize > 100: + r.PageSize = 100 + case r.PageSize <= 0: + r.PageSize = 10 + } + offset := (r.Page - 1) * r.PageSize + return db.Offset(offset).Limit(r.PageSize) + } +} + +// GetById Find by id structure +type GetById struct { + ID int `json:"id" form:"id"` // 主键ID +} + +func (r *GetById) Uint() uint { + return uint(r.ID) +} + +type IdsReq struct { + Ids []int `json:"ids" form:"ids"` +} + +// GetAuthorityId Get role by id structure +type GetAuthorityId struct { + AuthorityId uint `json:"authorityId" form:"authorityId"` // 角色ID +} + +type Empty struct{} diff --git a/model/common/response/common.go b/model/common/response/common.go new file mode 100644 index 0000000..7461096 --- /dev/null +++ b/model/common/response/common.go @@ -0,0 +1,8 @@ +package response + +type PageResult struct { + List interface{} `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} diff --git a/model/common/response/response.go b/model/common/response/response.go new file mode 100644 index 0000000..a429b12 --- /dev/null +++ b/model/common/response/response.go @@ -0,0 +1,63 @@ +package response + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Response struct { + Code int `json:"code"` + Data interface{} `json:"data"` + Msg string `json:"msg"` +} + +const ( + ERROR = 7 + SUCCESS = 0 +) + +func Result(code int, data interface{}, msg string, c *gin.Context) { + // 开始时间 + c.JSON(http.StatusOK, Response{ + code, + data, + msg, + }) +} + +func Ok(c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, "操作成功", c) +} + +func OkWithMessage(message string, c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, message, c) +} + +func OkWithData(data interface{}, c *gin.Context) { + Result(SUCCESS, data, "成功", c) +} + +func OkWithDetailed(data interface{}, message string, c *gin.Context) { + Result(SUCCESS, data, message, c) +} + +func Fail(c *gin.Context) { + Result(ERROR, map[string]interface{}{}, "操作失败", c) +} + +func FailWithMessage(message string, c *gin.Context) { + Result(ERROR, map[string]interface{}{}, message, c) +} + +func NoAuth(message string, c *gin.Context) { + c.JSON(http.StatusUnauthorized, Response{ + 7, + nil, + message, + }) +} + +func FailWithDetailed(data interface{}, message string, c *gin.Context) { + Result(ERROR, data, message, c) +} diff --git a/model/example/exa_attachment_category.go b/model/example/exa_attachment_category.go new file mode 100644 index 0000000..2bdc7e3 --- /dev/null +++ b/model/example/exa_attachment_category.go @@ -0,0 +1,16 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +type ExaAttachmentCategory struct { + global.GVA_MODEL + Name string `json:"name" form:"name" gorm:"default:null;type:varchar(255);column:name;comment:分类名称;"` + Pid uint `json:"pid" form:"pid" gorm:"default:0;type:int;column:pid;comment:父节点ID;"` + Children []*ExaAttachmentCategory `json:"children" gorm:"-"` +} + +func (ExaAttachmentCategory) TableName() string { + return "exa_attachment_category" +} diff --git a/model/example/exa_breakpoint_continue.go b/model/example/exa_breakpoint_continue.go new file mode 100644 index 0000000..4711faa --- /dev/null +++ b/model/example/exa_breakpoint_continue.go @@ -0,0 +1,24 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +// file struct, 文件结构体 +type ExaFile struct { + global.GVA_MODEL + FileName string + FileMd5 string + FilePath string + ExaFileChunk []ExaFileChunk + ChunkTotal int + IsFinish bool +} + +// file chunk struct, 切片结构体 +type ExaFileChunk struct { + global.GVA_MODEL + ExaFileID uint + FileChunkNumber int + FileChunkPath string +} diff --git a/model/example/exa_customer.go b/model/example/exa_customer.go new file mode 100644 index 0000000..3488f45 --- /dev/null +++ b/model/example/exa_customer.go @@ -0,0 +1,15 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +type ExaCustomer struct { + global.GVA_MODEL + CustomerName string `json:"customerName" form:"customerName" gorm:"comment:客户名"` // 客户名 + CustomerPhoneData string `json:"customerPhoneData" form:"customerPhoneData" gorm:"comment:客户手机号"` // 客户手机号 + SysUserID uint `json:"sysUserId" form:"sysUserId" gorm:"comment:管理ID"` // 管理ID + SysUserAuthorityID uint `json:"sysUserAuthorityID" form:"sysUserAuthorityID" gorm:"comment:管理角色ID"` // 管理角色ID + SysUser system.SysUser `json:"sysUser" form:"sysUser" gorm:"comment:管理详情"` // 管理详情 +} diff --git a/model/example/exa_file_upload_download.go b/model/example/exa_file_upload_download.go new file mode 100644 index 0000000..743d4a9 --- /dev/null +++ b/model/example/exa_file_upload_download.go @@ -0,0 +1,18 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +type ExaFileUploadAndDownload struct { + global.GVA_MODEL + Name string `json:"name" form:"name" gorm:"column:name;comment:文件名"` // 文件名 + ClassId int `json:"classId" form:"classId" gorm:"default:0;type:int;column:class_id;comment:分类id;"` // 分类id + Url string `json:"url" form:"url" gorm:"column:url;comment:文件地址"` // 文件地址 + Tag string `json:"tag" form:"tag" gorm:"column:tag;comment:文件标签"` // 文件标签 + Key string `json:"key" form:"key" gorm:"column:key;comment:编号"` // 编号 +} + +func (ExaFileUploadAndDownload) TableName() string { + return "exa_file_upload_and_downloads" +} diff --git a/model/example/request/exa_file_upload_and_downloads.go b/model/example/request/exa_file_upload_and_downloads.go new file mode 100644 index 0000000..ddbbf6f --- /dev/null +++ b/model/example/request/exa_file_upload_and_downloads.go @@ -0,0 +1,10 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" +) + +type ExaAttachmentCategorySearch struct { + ClassId int `json:"classId" form:"classId"` + request.PageInfo +} diff --git a/model/example/response/exa_breakpoint_continue.go b/model/example/response/exa_breakpoint_continue.go new file mode 100644 index 0000000..92baefc --- /dev/null +++ b/model/example/response/exa_breakpoint_continue.go @@ -0,0 +1,11 @@ +package response + +import "git.echol.cn/loser/xiecheng_server/model/example" + +type FilePathResponse struct { + FilePath string `json:"filePath"` +} + +type FileResponse struct { + File example.ExaFile `json:"file"` +} diff --git a/model/example/response/exa_customer.go b/model/example/response/exa_customer.go new file mode 100644 index 0000000..ead873c --- /dev/null +++ b/model/example/response/exa_customer.go @@ -0,0 +1,7 @@ +package response + +import "git.echol.cn/loser/xiecheng_server/model/example" + +type ExaCustomerResponse struct { + Customer example.ExaCustomer `json:"customer"` +} diff --git a/model/example/response/exa_file_upload_download.go b/model/example/response/exa_file_upload_download.go new file mode 100644 index 0000000..3b73b6a --- /dev/null +++ b/model/example/response/exa_file_upload_download.go @@ -0,0 +1,7 @@ +package response + +import "git.echol.cn/loser/xiecheng_server/model/example" + +type ExaFileResponse struct { + File example.ExaFileUploadAndDownload `json:"file"` +} diff --git a/model/system/request/jwt.go b/model/system/request/jwt.go new file mode 100644 index 0000000..1e1615d --- /dev/null +++ b/model/system/request/jwt.go @@ -0,0 +1,21 @@ +package request + +import ( + jwt "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" +) + +// CustomClaims structure +type CustomClaims struct { + BaseClaims + BufferTime int64 + jwt.RegisteredClaims +} + +type BaseClaims struct { + UUID uuid.UUID + ID uint + Username string + NickName string + AuthorityId uint +} diff --git a/model/system/request/sys_api.go b/model/system/request/sys_api.go new file mode 100644 index 0000000..53a775e --- /dev/null +++ b/model/system/request/sys_api.go @@ -0,0 +1,14 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +// api分页条件查询及排序结构体 +type SearchApiParams struct { + system.SysApi + request.PageInfo + OrderKey string `json:"orderKey"` // 排序 + Desc bool `json:"desc"` // 排序方式:升序false(默认)|降序true +} diff --git a/model/system/request/sys_authority_btn.go b/model/system/request/sys_authority_btn.go new file mode 100644 index 0000000..98493ff --- /dev/null +++ b/model/system/request/sys_authority_btn.go @@ -0,0 +1,7 @@ +package request + +type SysAuthorityBtnReq struct { + MenuID uint `json:"menuID"` + AuthorityId uint `json:"authorityId"` + Selected []uint `json:"selected"` +} diff --git a/model/system/request/sys_auto_code.go b/model/system/request/sys_auto_code.go new file mode 100644 index 0000000..b454452 --- /dev/null +++ b/model/system/request/sys_auto_code.go @@ -0,0 +1,286 @@ +package request + +import ( + "encoding/json" + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + model "git.echol.cn/loser/xiecheng_server/model/system" + "github.com/pkg/errors" + "go/token" + "strings" +) + +type AutoCode struct { + Package string `json:"package"` + PackageT string `json:"-"` + TableName string `json:"tableName" example:"表名"` // 表名 + BusinessDB string `json:"businessDB" example:"业务数据库"` // 业务数据库 + StructName string `json:"structName" example:"Struct名称"` // Struct名称 + PackageName string `json:"packageName" example:"文件名称"` // 文件名称 + Description string `json:"description" example:"Struct中文名称"` // Struct中文名称 + Abbreviation string `json:"abbreviation" example:"Struct简称"` // Struct简称 + HumpPackageName string `json:"humpPackageName" example:"go文件名称"` // go文件名称 + GvaModel bool `json:"gvaModel" example:"false"` // 是否使用gva默认Model + AutoMigrate bool `json:"autoMigrate" example:"false"` // 是否自动迁移表结构 + AutoCreateResource bool `json:"autoCreateResource" example:"false"` // 是否自动创建资源标识 + AutoCreateApiToSql bool `json:"autoCreateApiToSql" example:"false"` // 是否自动创建api + AutoCreateMenuToSql bool `json:"autoCreateMenuToSql" example:"false"` // 是否自动创建menu + AutoCreateBtnAuth bool `json:"autoCreateBtnAuth" example:"false"` // 是否自动创建按钮权限 + OnlyTemplate bool `json:"onlyTemplate" example:"false"` // 是否只生成模板 + IsTree bool `json:"isTree" example:"false"` // 是否树形结构 + TreeJson string `json:"treeJson" example:"展示的树json字段"` // 展示的树json字段 + IsAdd bool `json:"isAdd" example:"false"` // 是否新增 + Fields []*AutoCodeField `json:"fields"` + GenerateWeb bool `json:"generateWeb" example:"true"` // 是否生成web + GenerateServer bool `json:"generateServer" example:"true"` // 是否生成server + Module string `json:"-"` + DictTypes []string `json:"-"` + PrimaryField *AutoCodeField `json:"primaryField"` + DataSourceMap map[string]*DataSource `json:"-"` + HasPic bool `json:"-"` + HasFile bool `json:"-"` + HasTimer bool `json:"-"` + NeedSort bool `json:"-"` + NeedJSON bool `json:"-"` + HasRichText bool `json:"-"` + HasDataSource bool `json:"-"` + HasSearchTimer bool `json:"-"` + HasArray bool `json:"-"` + HasExcel bool `json:"-"` +} + +type DataSource struct { + DBName string `json:"dbName"` + Table string `json:"table"` + Label string `json:"label"` + Value string `json:"value"` + Association int `json:"association"` // 关联关系 1 一对一 2 一对多 + HasDeletedAt bool `json:"hasDeletedAt"` +} + +func (r *AutoCode) Apis() []model.SysApi { + return []model.SysApi{ + { + Path: "/" + r.Abbreviation + "/" + "create" + r.StructName, + Description: "新增" + r.Description, + ApiGroup: r.Description, + Method: "POST", + }, + { + Path: "/" + r.Abbreviation + "/" + "delete" + r.StructName, + Description: "删除" + r.Description, + ApiGroup: r.Description, + Method: "DELETE", + }, + { + Path: "/" + r.Abbreviation + "/" + "delete" + r.StructName + "ByIds", + Description: "批量删除" + r.Description, + ApiGroup: r.Description, + Method: "DELETE", + }, + { + Path: "/" + r.Abbreviation + "/" + "update" + r.StructName, + Description: "更新" + r.Description, + ApiGroup: r.Description, + Method: "PUT", + }, + { + Path: "/" + r.Abbreviation + "/" + "find" + r.StructName, + Description: "根据ID获取" + r.Description, + ApiGroup: r.Description, + Method: "GET", + }, + { + Path: "/" + r.Abbreviation + "/" + "get" + r.StructName + "List", + Description: "获取" + r.Description + "列表", + ApiGroup: r.Description, + Method: "GET", + }, + } +} + +func (r *AutoCode) Menu(template string) model.SysBaseMenu { + component := fmt.Sprintf("view/%s/%s/%s.vue", r.Package, r.PackageName, r.PackageName) + if template != "package" { + component = fmt.Sprintf("plugin/%s/view/%s.vue", r.Package, r.PackageName) + } + return model.SysBaseMenu{ + ParentId: 0, + Path: r.Abbreviation, + Name: r.Abbreviation, + Component: component, + Meta: model.Meta{ + Title: r.Description, + }, + } +} + +// Pretreatment 预处理 +// Author [SliverHorn](https://github.com/SliverHorn) +func (r *AutoCode) Pretreatment() error { + r.Module = global.GVA_CONFIG.AutoCode.Module + if token.IsKeyword(r.Abbreviation) { + r.Abbreviation = r.Abbreviation + "_" + } // go 关键字处理 + if strings.HasSuffix(r.HumpPackageName, "test") { + r.HumpPackageName = r.HumpPackageName + "_" + } // test + length := len(r.Fields) + dict := make(map[string]string, length) + r.DataSourceMap = make(map[string]*DataSource, length) + for i := 0; i < length; i++ { + if r.Fields[i].Excel { + r.HasExcel = true + } + if r.Fields[i].DictType != "" { + dict[r.Fields[i].DictType] = "" + } + if r.Fields[i].Sort { + r.NeedSort = true + } + switch r.Fields[i].FieldType { + case "file": + r.HasFile = true + r.NeedJSON = true + case "json": + r.NeedJSON = true + case "array": + r.NeedJSON = true + r.HasArray = true + case "video": + r.HasPic = true + case "richtext": + r.HasRichText = true + case "picture": + r.HasPic = true + case "pictures": + r.HasPic = true + r.NeedJSON = true + case "time.Time": + r.HasTimer = true + if r.Fields[i].FieldSearchType != "" { + r.HasSearchTimer = true + } + } + if r.Fields[i].DataSource != nil { + if r.Fields[i].DataSource.Table != "" && r.Fields[i].DataSource.Label != "" && r.Fields[i].DataSource.Value != "" { + r.HasDataSource = true + r.Fields[i].CheckDataSource = true + r.DataSourceMap[r.Fields[i].FieldJson] = r.Fields[i].DataSource + } + } + if !r.GvaModel && r.PrimaryField == nil && r.Fields[i].PrimaryKey { + r.PrimaryField = r.Fields[i] + } // 自定义主键 + } + { + for key := range dict { + r.DictTypes = append(r.DictTypes, key) + } + } // DictTypes => 字典 + { + if r.GvaModel { + r.PrimaryField = &AutoCodeField{ + FieldName: "ID", + FieldType: "uint", + FieldDesc: "ID", + FieldJson: "ID", + DataTypeLong: "20", + Comment: "主键ID", + ColumnName: "id", + } + } + } // GvaModel + { + if r.IsAdd && r.PrimaryField == nil { + r.PrimaryField = new(AutoCodeField) + } + } // 新增字段模式下不关注主键 + if r.Package == "" { + return errors.New("Package为空!") + } // 增加判断:Package不为空 + packages := []rune(r.Package) + if len(packages) > 0 { + if packages[0] >= 97 && packages[0] <= 122 { + packages[0] = packages[0] - 32 + } + r.PackageT = string(packages) + } // PackageT 是 Package 的首字母大写 + return nil +} + +func (r *AutoCode) History() SysAutoHistoryCreate { + bytes, _ := json.Marshal(r) + return SysAutoHistoryCreate{ + Table: r.TableName, + Package: r.Package, + Request: string(bytes), + StructName: r.StructName, + BusinessDB: r.BusinessDB, + Description: r.Description, + } +} + +type AutoCodeField struct { + FieldName string `json:"fieldName"` // Field名 + FieldDesc string `json:"fieldDesc"` // 中文名 + FieldType string `json:"fieldType"` // Field数据类型 + FieldJson string `json:"fieldJson"` // FieldJson + DataTypeLong string `json:"dataTypeLong"` // 数据库字段长度 + Comment string `json:"comment"` // 数据库字段描述 + ColumnName string `json:"columnName"` // 数据库字段 + FieldSearchType string `json:"fieldSearchType"` // 搜索条件 + FieldSearchHide bool `json:"fieldSearchHide"` // 是否隐藏查询条件 + DictType string `json:"dictType"` // 字典 + //Front bool `json:"front"` // 是否前端可见 + Form bool `json:"form"` // 是否前端新建/编辑 + Table bool `json:"table"` // 是否前端表格列 + Desc bool `json:"desc"` // 是否前端详情 + Excel bool `json:"excel"` // 是否导入/导出 + Require bool `json:"require"` // 是否必填 + DefaultValue string `json:"defaultValue"` // 是否必填 + ErrorText string `json:"errorText"` // 校验失败文字 + Clearable bool `json:"clearable"` // 是否可清空 + Sort bool `json:"sort"` // 是否增加排序 + PrimaryKey bool `json:"primaryKey"` // 是否主键 + DataSource *DataSource `json:"dataSource"` // 数据源 + CheckDataSource bool `json:"checkDataSource"` // 是否检查数据源 + FieldIndexType string `json:"fieldIndexType"` // 索引类型 +} + +type AutoFunc struct { + Package string `json:"package"` + FuncName string `json:"funcName"` // 方法名称 + Router string `json:"router"` // 路由名称 + FuncDesc string `json:"funcDesc"` // 方法介绍 + BusinessDB string `json:"businessDB"` // 业务库 + StructName string `json:"structName"` // Struct名称 + PackageName string `json:"packageName"` // 文件名称 + Description string `json:"description"` // Struct中文名称 + Abbreviation string `json:"abbreviation"` // Struct简称 + HumpPackageName string `json:"humpPackageName"` // go文件名称 + Method string `json:"method"` // 方法 + IsPlugin bool `json:"isPlugin"` // 是否插件 + IsAuth bool `json:"isAuth"` // 是否鉴权 + IsPreview bool `json:"isPreview"` // 是否预览 + IsAi bool `json:"isAi"` // 是否AI + ApiFunc string `json:"apiFunc"` // API方法 + ServerFunc string `json:"serverFunc"` // 服务方法 + JsFunc string `json:"jsFunc"` // JS方法 +} + +type InitMenu struct { + PlugName string `json:"plugName"` + ParentMenu string `json:"parentMenu"` + Menus []uint `json:"menus"` +} + +type InitApi struct { + PlugName string `json:"plugName"` + APIs []uint `json:"apis"` +} + +type LLMAutoCode struct { + Prompt string `json:"prompt" form:"prompt" gorm:"column:prompt;comment:提示语;type:text;"` //提示语 + Mode string `json:"mode" form:"mode" gorm:"column:mode;comment:模式;type:text;"` //模式 +} diff --git a/model/system/request/sys_auto_code_package.go b/model/system/request/sys_auto_code_package.go new file mode 100644 index 0000000..1e83207 --- /dev/null +++ b/model/system/request/sys_auto_code_package.go @@ -0,0 +1,31 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/global" + model "git.echol.cn/loser/xiecheng_server/model/system" +) + +type SysAutoCodePackageCreate struct { + Desc string `json:"desc" example:"描述"` + Label string `json:"label" example:"展示名"` + Template string `json:"template" example:"模版"` + PackageName string `json:"packageName" example:"包名"` + Module string `json:"-" example:"模块"` +} + +func (r *SysAutoCodePackageCreate) AutoCode() AutoCode { + return AutoCode{ + Package: r.PackageName, + Module: global.GVA_CONFIG.AutoCode.Module, + } +} + +func (r *SysAutoCodePackageCreate) Create() model.SysAutoCodePackage { + return model.SysAutoCodePackage{ + Desc: r.Desc, + Label: r.Label, + Template: r.Template, + PackageName: r.PackageName, + Module: global.GVA_CONFIG.AutoCode.Module, + } +} diff --git a/model/system/request/sys_auto_history.go b/model/system/request/sys_auto_history.go new file mode 100644 index 0000000..929606e --- /dev/null +++ b/model/system/request/sys_auto_history.go @@ -0,0 +1,57 @@ +package request + +import ( + common "git.echol.cn/loser/xiecheng_server/model/common/request" + model "git.echol.cn/loser/xiecheng_server/model/system" +) + +type SysAutoHistoryCreate struct { + Table string // 表名 + Package string // 模块名/插件名 + Request string // 前端传入的结构化信息 + StructName string // 结构体名称 + BusinessDB string // 业务库 + Description string // Struct中文名称 + Injections map[string]string // 注入路径 + Templates map[string]string // 模板信息 + ApiIDs []uint // api表注册内容 + MenuID uint // 菜单ID + ExportTemplateID uint // 导出模板ID +} + +func (r *SysAutoHistoryCreate) Create() model.SysAutoCodeHistory { + entity := model.SysAutoCodeHistory{ + Package: r.Package, + Request: r.Request, + Table: r.Table, + StructName: r.StructName, + Abbreviation: r.StructName, + BusinessDB: r.BusinessDB, + Description: r.Description, + Injections: r.Injections, + Templates: r.Templates, + ApiIDs: r.ApiIDs, + MenuID: r.MenuID, + ExportTemplateID: r.ExportTemplateID, + } + if entity.Table == "" { + entity.Table = r.StructName + } + return entity +} + +type SysAutoHistoryRollBack struct { + common.GetById + DeleteApi bool `json:"deleteApi" form:"deleteApi"` // 是否删除接口 + DeleteMenu bool `json:"deleteMenu" form:"deleteMenu"` // 是否删除菜单 + DeleteTable bool `json:"deleteTable" form:"deleteTable"` // 是否删除表 +} + +func (r *SysAutoHistoryRollBack) ApiIds(entity model.SysAutoCodeHistory) common.IdsReq { + length := len(entity.ApiIDs) + ids := make([]int, 0) + for i := 0; i < length; i++ { + ids = append(ids, int(entity.ApiIDs[i])) + } + return common.IdsReq{Ids: ids} +} diff --git a/model/system/request/sys_casbin.go b/model/system/request/sys_casbin.go new file mode 100644 index 0000000..3ca4212 --- /dev/null +++ b/model/system/request/sys_casbin.go @@ -0,0 +1,27 @@ +package request + +// CasbinInfo Casbin info structure +type CasbinInfo struct { + Path string `json:"path"` // 路径 + Method string `json:"method"` // 方法 +} + +// CasbinInReceive Casbin structure for input parameters +type CasbinInReceive struct { + AuthorityId uint `json:"authorityId"` // 权限id + CasbinInfos []CasbinInfo `json:"casbinInfos"` +} + +func DefaultCasbin() []CasbinInfo { + return []CasbinInfo{ + {Path: "/menu/getMenu", Method: "POST"}, + {Path: "/jwt/jsonInBlacklist", Method: "POST"}, + {Path: "/base/login", Method: "POST"}, + {Path: "/user/changePassword", Method: "POST"}, + {Path: "/user/setUserAuthority", Method: "POST"}, + {Path: "/user/getUserInfo", Method: "GET"}, + {Path: "/user/setSelfInfo", Method: "PUT"}, + {Path: "/fileUploadAndDownload/upload", Method: "POST"}, + {Path: "/sysDictionary/findSysDictionary", Method: "GET"}, + } +} diff --git a/model/system/request/sys_dictionary_detail.go b/model/system/request/sys_dictionary_detail.go new file mode 100644 index 0000000..33d5c30 --- /dev/null +++ b/model/system/request/sys_dictionary_detail.go @@ -0,0 +1,11 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +type SysDictionaryDetailSearch struct { + system.SysDictionaryDetail + request.PageInfo +} diff --git a/model/system/request/sys_export_template.go b/model/system/request/sys_export_template.go new file mode 100644 index 0000000..5951755 --- /dev/null +++ b/model/system/request/sys_export_template.go @@ -0,0 +1,14 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" + "time" +) + +type SysExportTemplateSearch struct { + system.SysExportTemplate + StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"` + EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"` + request.PageInfo +} diff --git a/model/system/request/sys_init.go b/model/system/request/sys_init.go new file mode 100644 index 0000000..f52e785 --- /dev/null +++ b/model/system/request/sys_init.go @@ -0,0 +1,124 @@ +package request + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/config" + "os" +) + +type InitDB struct { + AdminPassword string `json:"adminPassword" binding:"required"` + DBType string `json:"dbType"` // 数据库类型 + Host string `json:"host"` // 服务器地址 + Port string `json:"port"` // 数据库连接端口 + UserName string `json:"userName"` // 数据库用户名 + Password string `json:"password"` // 数据库密码 + DBName string `json:"dbName" binding:"required"` // 数据库名 + DBPath string `json:"dbPath"` // sqlite数据库文件路径 + Template string `json:"template"` // postgresql指定template +} + +// MysqlEmptyDsn msyql 空数据库 建库链接 +// Author SliverHorn +func (i *InitDB) MysqlEmptyDsn() string { + if i.Host == "" { + i.Host = "127.0.0.1" + } + if i.Port == "" { + i.Port = "3306" + } + return fmt.Sprintf("%s:%s@tcp(%s:%s)/", i.UserName, i.Password, i.Host, i.Port) +} + +// PgsqlEmptyDsn pgsql 空数据库 建库链接 +// Author SliverHorn +func (i *InitDB) PgsqlEmptyDsn() string { + if i.Host == "" { + i.Host = "127.0.0.1" + } + if i.Port == "" { + i.Port = "5432" + } + return "host=" + i.Host + " user=" + i.UserName + " password=" + i.Password + " port=" + i.Port + " dbname=" + "postgres" + " " + "sslmode=disable TimeZone=Asia/Shanghai" +} + +// SqliteEmptyDsn sqlite 空数据库 建库链接 +// Author Kafumio +func (i *InitDB) SqliteEmptyDsn() string { + separator := string(os.PathSeparator) + return i.DBPath + separator + i.DBName + ".db" +} + +func (i *InitDB) MssqlEmptyDsn() string { + return "sqlserver://" + i.UserName + ":" + i.Password + "@" + i.Host + ":" + i.Port + "?database=" + i.DBName + "&encrypt=disable" +} + +// ToMysqlConfig 转换 config.Mysql +// Author [SliverHorn](https://github.com/SliverHorn) +func (i *InitDB) ToMysqlConfig() config.Mysql { + return config.Mysql{ + GeneralDB: config.GeneralDB{ + Path: i.Host, + Port: i.Port, + Dbname: i.DBName, + Username: i.UserName, + Password: i.Password, + MaxIdleConns: 10, + MaxOpenConns: 100, + LogMode: "error", + Config: "charset=utf8mb4&parseTime=True&loc=Local", + }, + } +} + +// ToPgsqlConfig 转换 config.Pgsql +// Author [SliverHorn](https://github.com/SliverHorn) +func (i *InitDB) ToPgsqlConfig() config.Pgsql { + return config.Pgsql{ + GeneralDB: config.GeneralDB{ + Path: i.Host, + Port: i.Port, + Dbname: i.DBName, + Username: i.UserName, + Password: i.Password, + MaxIdleConns: 10, + MaxOpenConns: 100, + LogMode: "error", + Config: "sslmode=disable TimeZone=Asia/Shanghai", + }, + } +} + +// ToSqliteConfig 转换 config.Sqlite +// Author [Kafumio](https://github.com/Kafumio) +func (i *InitDB) ToSqliteConfig() config.Sqlite { + return config.Sqlite{ + GeneralDB: config.GeneralDB{ + Path: i.DBPath, + Port: i.Port, + Dbname: i.DBName, + Username: i.UserName, + Password: i.Password, + MaxIdleConns: 10, + MaxOpenConns: 100, + LogMode: "error", + Config: "", + }, + } +} + +func (i *InitDB) ToMssqlConfig() config.Mssql { + return config.Mssql{ + GeneralDB: config.GeneralDB{ + Path: i.DBPath, + Port: i.Port, + Dbname: i.DBName, + Username: i.UserName, + Password: i.Password, + MaxIdleConns: 10, + MaxOpenConns: 100, + LogMode: "error", + Config: "", + }, + } +} diff --git a/model/system/request/sys_menu.go b/model/system/request/sys_menu.go new file mode 100644 index 0000000..c125f31 --- /dev/null +++ b/model/system/request/sys_menu.go @@ -0,0 +1,27 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +// AddMenuAuthorityInfo Add menu authority info structure +type AddMenuAuthorityInfo struct { + Menus []system.SysBaseMenu `json:"menus"` + AuthorityId uint `json:"authorityId"` // 角色ID +} + +func DefaultMenu() []system.SysBaseMenu { + return []system.SysBaseMenu{{ + GVA_MODEL: global.GVA_MODEL{ID: 1}, + ParentId: 0, + Path: "dashboard", + Name: "dashboard", + Component: "view/dashboard/index.vue", + Sort: 1, + Meta: system.Meta{ + Title: "仪表盘", + Icon: "setting", + }, + }} +} diff --git a/model/system/request/sys_operation_record.go b/model/system/request/sys_operation_record.go new file mode 100644 index 0000000..eb67280 --- /dev/null +++ b/model/system/request/sys_operation_record.go @@ -0,0 +1,11 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +type SysOperationRecordSearch struct { + system.SysOperationRecord + request.PageInfo +} diff --git a/model/system/request/sys_params.go b/model/system/request/sys_params.go new file mode 100644 index 0000000..27c7397 --- /dev/null +++ b/model/system/request/sys_params.go @@ -0,0 +1,14 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "time" +) + +type SysParamsSearch struct { + StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"` + EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"` + Name string `json:"name" form:"name" ` + Key string `json:"key" form:"key" ` + request.PageInfo +} diff --git a/model/system/request/sys_user.go b/model/system/request/sys_user.go new file mode 100644 index 0000000..1c2e1bc --- /dev/null +++ b/model/system/request/sys_user.go @@ -0,0 +1,65 @@ +package request + +import ( + common "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +// Register User register structure +type Register struct { + Username string `json:"userName" example:"用户名"` + Password string `json:"passWord" example:"密码"` + NickName string `json:"nickName" example:"昵称"` + HeaderImg string `json:"headerImg" example:"头像链接"` + AuthorityId uint `json:"authorityId" swaggertype:"string" example:"int 角色id"` + Enable int `json:"enable" swaggertype:"string" example:"int 是否启用"` + AuthorityIds []uint `json:"authorityIds" swaggertype:"string" example:"[]uint 角色id"` + Phone string `json:"phone" example:"电话号码"` + Email string `json:"email" example:"电子邮箱"` +} + +// Login User login structure +type Login struct { + Username string `json:"username"` // 用户名 + Password string `json:"password"` // 密码 + Captcha string `json:"captcha"` // 验证码 + CaptchaId string `json:"captchaId"` // 验证码ID +} + +// ChangePasswordReq Modify password structure +type ChangePasswordReq struct { + ID uint `json:"-"` // 从 JWT 中提取 user id,避免越权 + Password string `json:"password"` // 密码 + NewPassword string `json:"newPassword"` // 新密码 +} + +// SetUserAuth Modify user's auth structure +type SetUserAuth struct { + AuthorityId uint `json:"authorityId"` // 角色ID +} + +// SetUserAuthorities Modify user's auth structure +type SetUserAuthorities struct { + ID uint + AuthorityIds []uint `json:"authorityIds"` // 角色ID +} + +type ChangeUserInfo struct { + ID uint `gorm:"primarykey"` // 主键ID + NickName string `json:"nickName" gorm:"default:系统用户;comment:用户昵称"` // 用户昵称 + Phone string `json:"phone" gorm:"comment:用户手机号"` // 用户手机号 + AuthorityIds []uint `json:"authorityIds" gorm:"-"` // 角色ID + Email string `json:"email" gorm:"comment:用户邮箱"` // 用户邮箱 + HeaderImg string `json:"headerImg" gorm:"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像"` // 用户头像 + SideMode string `json:"sideMode" gorm:"comment:用户侧边主题"` // 用户侧边主题 + Enable int `json:"enable" gorm:"comment:冻结用户"` //冻结用户 + Authorities []system.SysAuthority `json:"-" gorm:"many2many:sys_user_authority;"` +} + +type GetUserList struct { + common.PageInfo + Username string `json:"username" form:"username"` + NickName string `json:"nickName" form:"nickName"` + Phone string `json:"phone" form:"phone"` + Email string `json:"email" form:"email"` +} diff --git a/model/system/response/sys_api.go b/model/system/response/sys_api.go new file mode 100644 index 0000000..ae5d3d1 --- /dev/null +++ b/model/system/response/sys_api.go @@ -0,0 +1,18 @@ +package response + +import ( + "git.echol.cn/loser/xiecheng_server/model/system" +) + +type SysAPIResponse struct { + Api system.SysApi `json:"api"` +} + +type SysAPIListResponse struct { + Apis []system.SysApi `json:"apis"` +} + +type SysSyncApis struct { + NewApis []system.SysApi `json:"newApis"` + DeleteApis []system.SysApi `json:"deleteApis"` +} diff --git a/model/system/response/sys_authority.go b/model/system/response/sys_authority.go new file mode 100644 index 0000000..2fd51af --- /dev/null +++ b/model/system/response/sys_authority.go @@ -0,0 +1,12 @@ +package response + +import "git.echol.cn/loser/xiecheng_server/model/system" + +type SysAuthorityResponse struct { + Authority system.SysAuthority `json:"authority"` +} + +type SysAuthorityCopyResponse struct { + Authority system.SysAuthority `json:"authority"` + OldAuthorityId uint `json:"oldAuthorityId"` // 旧角色ID +} diff --git a/model/system/response/sys_authority_btn.go b/model/system/response/sys_authority_btn.go new file mode 100644 index 0000000..2f772cf --- /dev/null +++ b/model/system/response/sys_authority_btn.go @@ -0,0 +1,5 @@ +package response + +type SysAuthorityBtnRes struct { + Selected []uint `json:"selected"` +} diff --git a/model/system/response/sys_auto_code.go b/model/system/response/sys_auto_code.go new file mode 100644 index 0000000..9e99bde --- /dev/null +++ b/model/system/response/sys_auto_code.go @@ -0,0 +1,17 @@ +package response + +type Db struct { + Database string `json:"database" gorm:"column:database"` +} + +type Table struct { + TableName string `json:"tableName" gorm:"column:table_name"` +} + +type Column struct { + DataType string `json:"dataType" gorm:"column:data_type"` + ColumnName string `json:"columnName" gorm:"column:column_name"` + DataTypeLong string `json:"dataTypeLong" gorm:"column:data_type_long"` + ColumnComment string `json:"columnComment" gorm:"column:column_comment"` + PrimaryKey bool `json:"primaryKey" gorm:"column:primary_key"` +} diff --git a/model/system/response/sys_captcha.go b/model/system/response/sys_captcha.go new file mode 100644 index 0000000..0c3995a --- /dev/null +++ b/model/system/response/sys_captcha.go @@ -0,0 +1,8 @@ +package response + +type SysCaptchaResponse struct { + CaptchaId string `json:"captchaId"` + PicPath string `json:"picPath"` + CaptchaLength int `json:"captchaLength"` + OpenCaptcha bool `json:"openCaptcha"` +} diff --git a/model/system/response/sys_casbin.go b/model/system/response/sys_casbin.go new file mode 100644 index 0000000..46ad1c1 --- /dev/null +++ b/model/system/response/sys_casbin.go @@ -0,0 +1,9 @@ +package response + +import ( + "git.echol.cn/loser/xiecheng_server/model/system/request" +) + +type PolicyPathResponse struct { + Paths []request.CasbinInfo `json:"paths"` +} diff --git a/model/system/response/sys_menu.go b/model/system/response/sys_menu.go new file mode 100644 index 0000000..1713683 --- /dev/null +++ b/model/system/response/sys_menu.go @@ -0,0 +1,15 @@ +package response + +import "git.echol.cn/loser/xiecheng_server/model/system" + +type SysMenusResponse struct { + Menus []system.SysMenu `json:"menus"` +} + +type SysBaseMenusResponse struct { + Menus []system.SysBaseMenu `json:"menus"` +} + +type SysBaseMenuResponse struct { + Menu system.SysBaseMenu `json:"menu"` +} diff --git a/model/system/response/sys_system.go b/model/system/response/sys_system.go new file mode 100644 index 0000000..a33e5b6 --- /dev/null +++ b/model/system/response/sys_system.go @@ -0,0 +1,7 @@ +package response + +import "git.echol.cn/loser/xiecheng_server/config" + +type SysConfigResponse struct { + Config config.Server `json:"config"` +} diff --git a/model/system/response/sys_user.go b/model/system/response/sys_user.go new file mode 100644 index 0000000..ee18dce --- /dev/null +++ b/model/system/response/sys_user.go @@ -0,0 +1,15 @@ +package response + +import ( + "git.echol.cn/loser/xiecheng_server/model/system" +) + +type SysUserResponse struct { + User system.SysUser `json:"user"` +} + +type LoginResponse struct { + User system.SysUser `json:"user"` + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` +} diff --git a/model/system/sys_api.go b/model/system/sys_api.go new file mode 100644 index 0000000..3dda2bf --- /dev/null +++ b/model/system/sys_api.go @@ -0,0 +1,28 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +type SysApi struct { + global.GVA_MODEL + Path string `json:"path" gorm:"comment:api路径"` // api路径 + Description string `json:"description" gorm:"comment:api中文描述"` // api中文描述 + ApiGroup string `json:"apiGroup" gorm:"comment:api组"` // api组 + Method string `json:"method" gorm:"default:POST;comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE +} + +func (SysApi) TableName() string { + return "sys_apis" +} + +type SysIgnoreApi struct { + global.GVA_MODEL + Path string `json:"path" gorm:"comment:api路径"` // api路径 + Method string `json:"method" gorm:"default:POST;comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE + Flag bool `json:"flag" gorm:"-"` // 是否忽略 +} + +func (SysIgnoreApi) TableName() string { + return "sys_ignore_apis" +} diff --git a/model/system/sys_authority.go b/model/system/sys_authority.go new file mode 100644 index 0000000..01c5efa --- /dev/null +++ b/model/system/sys_authority.go @@ -0,0 +1,23 @@ +package system + +import ( + "time" +) + +type SysAuthority struct { + CreatedAt time.Time // 创建时间 + UpdatedAt time.Time // 更新时间 + DeletedAt *time.Time `sql:"index"` + AuthorityId uint `json:"authorityId" gorm:"not null;unique;primary_key;comment:角色ID;size:90"` // 角色ID + AuthorityName string `json:"authorityName" gorm:"comment:角色名"` // 角色名 + ParentId *uint `json:"parentId" gorm:"comment:父角色ID"` // 父角色ID + DataAuthorityId []*SysAuthority `json:"dataAuthorityId" gorm:"many2many:sys_data_authority_id;"` + Children []SysAuthority `json:"children" gorm:"-"` + SysBaseMenus []SysBaseMenu `json:"menus" gorm:"many2many:sys_authority_menus;"` + Users []SysUser `json:"-" gorm:"many2many:sys_user_authority;"` + DefaultRouter string `json:"defaultRouter" gorm:"comment:默认菜单;default:dashboard"` // 默认菜单(默认dashboard) +} + +func (SysAuthority) TableName() string { + return "sys_authorities" +} diff --git a/model/system/sys_authority_btn.go b/model/system/sys_authority_btn.go new file mode 100644 index 0000000..e005984 --- /dev/null +++ b/model/system/sys_authority_btn.go @@ -0,0 +1,8 @@ +package system + +type SysAuthorityBtn struct { + AuthorityId uint `gorm:"comment:角色ID"` + SysMenuID uint `gorm:"comment:菜单ID"` + SysBaseMenuBtnID uint `gorm:"comment:菜单按钮ID"` + SysBaseMenuBtn SysBaseMenuBtn ` gorm:"comment:按钮详情"` +} diff --git a/model/system/sys_authority_menu.go b/model/system/sys_authority_menu.go new file mode 100644 index 0000000..4467a7e --- /dev/null +++ b/model/system/sys_authority_menu.go @@ -0,0 +1,19 @@ +package system + +type SysMenu struct { + SysBaseMenu + MenuId uint `json:"menuId" gorm:"comment:菜单ID"` + AuthorityId uint `json:"-" gorm:"comment:角色ID"` + Children []SysMenu `json:"children" gorm:"-"` + Parameters []SysBaseMenuParameter `json:"parameters" gorm:"foreignKey:SysBaseMenuID;references:MenuId"` + Btns map[string]uint `json:"btns" gorm:"-"` +} + +type SysAuthorityMenu struct { + MenuId string `json:"menuId" gorm:"comment:菜单ID;column:sys_base_menu_id"` + AuthorityId string `json:"-" gorm:"comment:角色ID;column:sys_authority_authority_id"` +} + +func (s SysAuthorityMenu) TableName() string { + return "sys_authority_menus" +} diff --git a/model/system/sys_auto_code_history.go b/model/system/sys_auto_code_history.go new file mode 100644 index 0000000..b8324c6 --- /dev/null +++ b/model/system/sys_auto_code_history.go @@ -0,0 +1,68 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "gorm.io/gorm" + "os" + "path" + "path/filepath" + "strings" +) + +// SysAutoCodeHistory 自动迁移代码记录,用于回滚,重放使用 +type SysAutoCodeHistory struct { + global.GVA_MODEL + Table string `json:"tableName" gorm:"column:table_name;comment:表名"` + Package string `json:"package" gorm:"column:package;comment:模块名/插件名"` + Request string `json:"request" gorm:"type:text;column:request;comment:前端传入的结构化信息"` + StructName string `json:"structName" gorm:"column:struct_name;comment:结构体名称"` + Abbreviation string `json:"abbreviation" gorm:"column:abbreviation;comment:结构体名称缩写"` + BusinessDB string `json:"businessDb" gorm:"column:business_db;comment:业务库"` + Description string `json:"description" gorm:"column:description;comment:Struct中文名称"` + Templates map[string]string `json:"template" gorm:"serializer:json;type:text;column:templates;comment:模板信息"` + Injections map[string]string `json:"injections" gorm:"serializer:json;type:text;column:Injections;comment:注入路径"` + Flag int `json:"flag" gorm:"column:flag;comment:[0:创建,1:回滚]"` + ApiIDs []uint `json:"apiIDs" gorm:"serializer:json;column:api_ids;comment:api表注册内容"` + MenuID uint `json:"menuId" gorm:"column:menu_id;comment:菜单ID"` + ExportTemplateID uint `json:"exportTemplateID" gorm:"column:export_template_id;comment:导出模板ID"` + AutoCodePackage SysAutoCodePackage `json:"autoCodePackage" gorm:"foreignKey:ID;references:PackageID"` + PackageID uint `json:"packageID" gorm:"column:package_id;comment:包ID"` +} + +func (s *SysAutoCodeHistory) BeforeCreate(db *gorm.DB) error { + templates := make(map[string]string, len(s.Templates)) + for key, value := range s.Templates { + server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server) + { + hasServer := strings.Index(key, server) + if hasServer != -1 { + key = strings.TrimPrefix(key, server) + keys := strings.Split(key, string(os.PathSeparator)) + key = path.Join(keys...) + } + } // key + web := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot()) + hasWeb := strings.Index(value, web) + if hasWeb != -1 { + value = strings.TrimPrefix(value, web) + values := strings.Split(value, string(os.PathSeparator)) + value = path.Join(values...) + templates[key] = value + continue + } + hasServer := strings.Index(value, server) + if hasServer != -1 { + value = strings.TrimPrefix(value, server) + values := strings.Split(value, string(os.PathSeparator)) + value = path.Join(values...) + templates[key] = value + continue + } + } + s.Templates = templates + return nil +} + +func (s *SysAutoCodeHistory) TableName() string { + return "sys_auto_code_histories" +} diff --git a/model/system/sys_auto_code_package.go b/model/system/sys_auto_code_package.go new file mode 100644 index 0000000..4a970d0 --- /dev/null +++ b/model/system/sys_auto_code_package.go @@ -0,0 +1,18 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +type SysAutoCodePackage struct { + global.GVA_MODEL + Desc string `json:"desc" gorm:"comment:描述"` + Label string `json:"label" gorm:"comment:展示名"` + Template string `json:"template" gorm:"comment:模版"` + PackageName string `json:"packageName" gorm:"comment:包名"` + Module string `json:"-" example:"模块"` +} + +func (s *SysAutoCodePackage) TableName() string { + return "sys_auto_code_packages" +} diff --git a/model/system/sys_base_menu.go b/model/system/sys_base_menu.go new file mode 100644 index 0000000..6835183 --- /dev/null +++ b/model/system/sys_base_menu.go @@ -0,0 +1,42 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +type SysBaseMenu struct { + global.GVA_MODEL + MenuLevel uint `json:"-"` + ParentId uint `json:"parentId" gorm:"comment:父菜单ID"` // 父菜单ID + Path string `json:"path" gorm:"comment:路由path"` // 路由path + Name string `json:"name" gorm:"comment:路由name"` // 路由name + Hidden bool `json:"hidden" gorm:"comment:是否在列表隐藏"` // 是否在列表隐藏 + Component string `json:"component" gorm:"comment:对应前端文件路径"` // 对应前端文件路径 + Sort int `json:"sort" gorm:"comment:排序标记"` // 排序标记 + Meta `json:"meta" gorm:"embedded;comment:附加属性"` // 附加属性 + SysAuthoritys []SysAuthority `json:"authoritys" gorm:"many2many:sys_authority_menus;"` + Children []SysBaseMenu `json:"children" gorm:"-"` + Parameters []SysBaseMenuParameter `json:"parameters"` + MenuBtn []SysBaseMenuBtn `json:"menuBtn"` +} + +type Meta struct { + ActiveName string `json:"activeName" gorm:"comment:高亮菜单"` + KeepAlive bool `json:"keepAlive" gorm:"comment:是否缓存"` // 是否缓存 + DefaultMenu bool `json:"defaultMenu" gorm:"comment:是否是基础路由(开发中)"` // 是否是基础路由(开发中) + Title string `json:"title" gorm:"comment:菜单名"` // 菜单名 + Icon string `json:"icon" gorm:"comment:菜单图标"` // 菜单图标 + CloseTab bool `json:"closeTab" gorm:"comment:自动关闭tab"` // 自动关闭tab +} + +type SysBaseMenuParameter struct { + global.GVA_MODEL + SysBaseMenuID uint + Type string `json:"type" gorm:"comment:地址栏携带参数为params还是query"` // 地址栏携带参数为params还是query + Key string `json:"key" gorm:"comment:地址栏携带参数的key"` // 地址栏携带参数的key + Value string `json:"value" gorm:"comment:地址栏携带参数的值"` // 地址栏携带参数的值 +} + +func (SysBaseMenu) TableName() string { + return "sys_base_menus" +} diff --git a/model/system/sys_dictionary.go b/model/system/sys_dictionary.go new file mode 100644 index 0000000..cacea62 --- /dev/null +++ b/model/system/sys_dictionary.go @@ -0,0 +1,20 @@ +// 自动生成模板SysDictionary +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +// 如果含有time.Time 请自行import time包 +type SysDictionary struct { + global.GVA_MODEL + Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"` // 字典名(中) + Type string `json:"type" form:"type" gorm:"column:type;comment:字典名(英)"` // 字典名(英) + Status *bool `json:"status" form:"status" gorm:"column:status;comment:状态"` // 状态 + Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:描述"` // 描述 + SysDictionaryDetails []SysDictionaryDetail `json:"sysDictionaryDetails" form:"sysDictionaryDetails"` +} + +func (SysDictionary) TableName() string { + return "sys_dictionaries" +} diff --git a/model/system/sys_dictionary_detail.go b/model/system/sys_dictionary_detail.go new file mode 100644 index 0000000..e6e867c --- /dev/null +++ b/model/system/sys_dictionary_detail.go @@ -0,0 +1,21 @@ +// 自动生成模板SysDictionaryDetail +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +// 如果含有time.Time 请自行import time包 +type SysDictionaryDetail struct { + global.GVA_MODEL + Label string `json:"label" form:"label" gorm:"column:label;comment:展示值"` // 展示值 + Value string `json:"value" form:"value" gorm:"column:value;comment:字典值"` // 字典值 + Extend string `json:"extend" form:"extend" gorm:"column:extend;comment:扩展值"` // 扩展值 + Status *bool `json:"status" form:"status" gorm:"column:status;comment:启用状态"` // 启用状态 + Sort int `json:"sort" form:"sort" gorm:"column:sort;comment:排序标记"` // 排序标记 + SysDictionaryID int `json:"sysDictionaryID" form:"sysDictionaryID" gorm:"column:sys_dictionary_id;comment:关联标记"` // 关联标记 +} + +func (SysDictionaryDetail) TableName() string { + return "sys_dictionary_details" +} diff --git a/model/system/sys_export_template.go b/model/system/sys_export_template.go new file mode 100644 index 0000000..3b1614a --- /dev/null +++ b/model/system/sys_export_template.go @@ -0,0 +1,44 @@ +// 自动生成模板SysExportTemplate +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +// 导出模板 结构体 SysExportTemplate +type SysExportTemplate struct { + global.GVA_MODEL + DBName string `json:"dbName" form:"dbName" gorm:"column:db_name;comment:数据库名称;"` //数据库名称 + Name string `json:"name" form:"name" gorm:"column:name;comment:模板名称;"` //模板名称 + TableName string `json:"tableName" form:"tableName" gorm:"column:table_name;comment:表名称;"` //表名称 + TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识;"` //模板标识 + TemplateInfo string `json:"templateInfo" form:"templateInfo" gorm:"column:template_info;type:text;"` //模板信息 + Limit *int `json:"limit" form:"limit" gorm:"column:limit;comment:导出限制"` + Order string `json:"order" form:"order" gorm:"column:order;comment:排序"` + Conditions []Condition `json:"conditions" form:"conditions" gorm:"foreignKey:TemplateID;references:TemplateID;comment:条件"` + JoinTemplate []JoinTemplate `json:"joinTemplate" form:"joinTemplate" gorm:"foreignKey:TemplateID;references:TemplateID;comment:关联"` +} + +type JoinTemplate struct { + global.GVA_MODEL + TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识"` + JOINS string `json:"joins" form:"joins" gorm:"column:joins;comment:关联"` + Table string `json:"table" form:"table" gorm:"column:table;comment:关联表"` + ON string `json:"on" form:"on" gorm:"column:on;comment:关联条件"` +} + +func (JoinTemplate) TableName() string { + return "sys_export_template_join" +} + +type Condition struct { + global.GVA_MODEL + TemplateID string `json:"templateID" form:"templateID" gorm:"column:template_id;comment:模板标识"` + From string `json:"from" form:"from" gorm:"column:from;comment:条件取的key"` + Column string `json:"column" form:"column" gorm:"column:column;comment:作为查询条件的字段"` + Operator string `json:"operator" form:"operator" gorm:"column:operator;comment:操作符"` +} + +func (Condition) TableName() string { + return "sys_export_template_condition" +} diff --git a/model/system/sys_jwt_blacklist.go b/model/system/sys_jwt_blacklist.go new file mode 100644 index 0000000..e4bb108 --- /dev/null +++ b/model/system/sys_jwt_blacklist.go @@ -0,0 +1,10 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +type JwtBlacklist struct { + global.GVA_MODEL + Jwt string `gorm:"type:text;comment:jwt"` +} diff --git a/model/system/sys_menu_btn.go b/model/system/sys_menu_btn.go new file mode 100644 index 0000000..774f349 --- /dev/null +++ b/model/system/sys_menu_btn.go @@ -0,0 +1,10 @@ +package system + +import "git.echol.cn/loser/xiecheng_server/global" + +type SysBaseMenuBtn struct { + global.GVA_MODEL + Name string `json:"name" gorm:"comment:按钮关键key"` + Desc string `json:"desc" gorm:"按钮备注"` + SysBaseMenuID uint `json:"sysBaseMenuID" gorm:"comment:菜单ID"` +} diff --git a/model/system/sys_operation_record.go b/model/system/sys_operation_record.go new file mode 100644 index 0000000..3995a6c --- /dev/null +++ b/model/system/sys_operation_record.go @@ -0,0 +1,24 @@ +// 自动生成模板SysOperationRecord +package system + +import ( + "time" + + "git.echol.cn/loser/xiecheng_server/global" +) + +// 如果含有time.Time 请自行import time包 +type SysOperationRecord struct { + global.GVA_MODEL + Ip string `json:"ip" form:"ip" gorm:"column:ip;comment:请求ip"` // 请求ip + Method string `json:"method" form:"method" gorm:"column:method;comment:请求方法"` // 请求方法 + Path string `json:"path" form:"path" gorm:"column:path;comment:请求路径"` // 请求路径 + Status int `json:"status" form:"status" gorm:"column:status;comment:请求状态"` // 请求状态 + Latency time.Duration `json:"latency" form:"latency" gorm:"column:latency;comment:延迟" swaggertype:"string"` // 延迟 + Agent string `json:"agent" form:"agent" gorm:"type:text;column:agent;comment:代理"` // 代理 + ErrorMessage string `json:"error_message" form:"error_message" gorm:"column:error_message;comment:错误信息"` // 错误信息 + Body string `json:"body" form:"body" gorm:"type:text;column:body;comment:请求Body"` // 请求Body + Resp string `json:"resp" form:"resp" gorm:"type:text;column:resp;comment:响应Body"` // 响应Body + UserID int `json:"user_id" form:"user_id" gorm:"column:user_id;comment:用户id"` // 用户id + User SysUser `json:"user"` +} diff --git a/model/system/sys_params.go b/model/system/sys_params.go new file mode 100644 index 0000000..c82fb5c --- /dev/null +++ b/model/system/sys_params.go @@ -0,0 +1,20 @@ +// 自动生成模板SysParams +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" +) + +// 参数 结构体 SysParams +type SysParams struct { + global.GVA_MODEL + Name string `json:"name" form:"name" gorm:"column:name;comment:参数名称;" binding:"required"` //参数名称 + Key string `json:"key" form:"key" gorm:"column:key;comment:参数键;" binding:"required"` //参数键 + Value string `json:"value" form:"value" gorm:"column:value;comment:参数值;" binding:"required"` //参数值 + Desc string `json:"desc" form:"desc" gorm:"column:desc;comment:参数说明;"` //参数说明 +} + +// TableName 参数 SysParams自定义表名 sys_params +func (SysParams) TableName() string { + return "sys_params" +} diff --git a/model/system/sys_system.go b/model/system/sys_system.go new file mode 100644 index 0000000..d1dc70c --- /dev/null +++ b/model/system/sys_system.go @@ -0,0 +1,10 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/config" +) + +// 配置文件结构体 +type System struct { + Config config.Server `json:"config"` +} diff --git a/model/system/sys_user.go b/model/system/sys_user.go new file mode 100644 index 0000000..c8fd6e9 --- /dev/null +++ b/model/system/sys_user.go @@ -0,0 +1,62 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common" + "github.com/google/uuid" +) + +type Login interface { + GetUsername() string + GetNickname() string + GetUUID() uuid.UUID + GetUserId() uint + GetAuthorityId() uint + GetUserInfo() any +} + +var _ Login = new(SysUser) + +type SysUser struct { + global.GVA_MODEL + UUID uuid.UUID `json:"uuid" gorm:"index;comment:用户UUID"` // 用户UUID + Username string `json:"userName" gorm:"index;comment:用户登录名"` // 用户登录名 + Password string `json:"-" gorm:"comment:用户登录密码"` // 用户登录密码 + NickName string `json:"nickName" gorm:"default:系统用户;comment:用户昵称"` // 用户昵称 + HeaderImg string `json:"headerImg" gorm:"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像"` // 用户头像 + AuthorityId uint `json:"authorityId" gorm:"default:888;comment:用户角色ID"` // 用户角色ID + Authority SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"` // 用户角色 + Authorities []SysAuthority `json:"authorities" gorm:"many2many:sys_user_authority;"` // 多用户角色 + Phone string `json:"phone" gorm:"comment:用户手机号"` // 用户手机号 + Email string `json:"email" gorm:"comment:用户邮箱"` // 用户邮箱 + Enable int `json:"enable" gorm:"default:1;comment:用户是否被冻结 1正常 2冻结"` //用户是否被冻结 1正常 2冻结 + OriginSetting common.JSONMap `json:"originSetting" form:"originSetting" gorm:"type:text;default:null;column:origin_setting;comment:配置;"` //配置 +} + +func (SysUser) TableName() string { + return "sys_users" +} + +func (s *SysUser) GetUsername() string { + return s.Username +} + +func (s *SysUser) GetNickname() string { + return s.NickName +} + +func (s *SysUser) GetUUID() uuid.UUID { + return s.UUID +} + +func (s *SysUser) GetUserId() uint { + return s.ID +} + +func (s *SysUser) GetAuthorityId() uint { + return s.AuthorityId +} + +func (s *SysUser) GetUserInfo() any { + return *s +} diff --git a/model/system/sys_user_authority.go b/model/system/sys_user_authority.go new file mode 100644 index 0000000..1aa83cb --- /dev/null +++ b/model/system/sys_user_authority.go @@ -0,0 +1,11 @@ +package system + +// SysUserAuthority 是 sysUser 和 sysAuthority 的连接表 +type SysUserAuthority struct { + SysUserId uint `gorm:"column:sys_user_id"` + SysAuthorityAuthorityId uint `gorm:"column:sys_authority_authority_id"` +} + +func (s *SysUserAuthority) TableName() string { + return "sys_user_authority" +} diff --git a/plugin/announcement/api/enter.go b/plugin/announcement/api/enter.go new file mode 100644 index 0000000..7727d8e --- /dev/null +++ b/plugin/announcement/api/enter.go @@ -0,0 +1,10 @@ +package api + +import "git.echol.cn/loser/xiecheng_server/plugin/announcement/service" + +var ( + Api = new(api) + serviceInfo = service.Service.Info +) + +type api struct{ Info info } diff --git a/plugin/announcement/api/info.go b/plugin/announcement/api/info.go new file mode 100644 index 0000000..6262c42 --- /dev/null +++ b/plugin/announcement/api/info.go @@ -0,0 +1,183 @@ +package api + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var Info = new(info) + +type info struct{} + +// CreateInfo 创建公告 +// @Tags Info +// @Summary 创建公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "创建公告" +// @Success 200 {object} response.Response{msg=string} "创建成功" +// @Router /info/createInfo [post] +func (a *info) CreateInfo(c *gin.Context) { + var info model.Info + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = serviceInfo.CreateInfo(&info) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteInfo 删除公告 +// @Tags Info +// @Summary 删除公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "删除公告" +// @Success 200 {object} response.Response{msg=string} "删除成功" +// @Router /info/deleteInfo [delete] +func (a *info) DeleteInfo(c *gin.Context) { + ID := c.Query("ID") + err := serviceInfo.DeleteInfo(ID) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// DeleteInfoByIds 批量删除公告 +// @Tags Info +// @Summary 批量删除公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "批量删除成功" +// @Router /info/deleteInfoByIds [delete] +func (a *info) DeleteInfoByIds(c *gin.Context) { + IDs := c.QueryArray("IDs[]") + if err := serviceInfo.DeleteInfoByIds(IDs); err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败", c) + return + } + response.OkWithMessage("批量删除成功", c) +} + +// UpdateInfo 更新公告 +// @Tags Info +// @Summary 更新公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "更新公告" +// @Success 200 {object} response.Response{msg=string} "更新成功" +// @Router /info/updateInfo [put] +func (a *info) UpdateInfo(c *gin.Context) { + var info model.Info + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = serviceInfo.UpdateInfo(info) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// FindInfo 用id查询公告 +// @Tags Info +// @Summary 用id查询公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.Info true "用id查询公告" +// @Success 200 {object} response.Response{data=model.Info,msg=string} "查询成功" +// @Router /info/findInfo [get] +func (a *info) FindInfo(c *gin.Context) { + ID := c.Query("ID") + reinfo, err := serviceInfo.GetInfo(ID) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithData(reinfo, c) +} + +// GetInfoList 分页获取公告列表 +// @Tags Info +// @Summary 分页获取公告列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.InfoSearch true "分页获取公告列表" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /info/getInfoList [get] +func (a *info) GetInfoList(c *gin.Context) { + var pageInfo request.InfoSearch + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := serviceInfo.GetInfoInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} + +// GetInfoDataSource 获取Info的数据源 +// @Tags Info +// @Summary 获取Info的数据源 +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "查询成功" +// @Router /info/getInfoDataSource [get] +func (a *info) GetInfoDataSource(c *gin.Context) { + // 此接口为获取数据源定义的数据 + dataSource, err := serviceInfo.GetInfoDataSource() + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithData(dataSource, c) +} + +// GetInfoPublic 不需要鉴权的公告接口 +// @Tags Info +// @Summary 不需要鉴权的公告接口 +// @accept application/json +// @Produce application/json +// @Param data query request.InfoSearch true "分页获取公告列表" +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /info/getInfoPublic [get] +func (a *info) GetInfoPublic(c *gin.Context) { + // 此接口不需要鉴权 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑 + response.OkWithDetailed(gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功", c) +} diff --git a/plugin/announcement/config/config.go b/plugin/announcement/config/config.go new file mode 100644 index 0000000..809bc99 --- /dev/null +++ b/plugin/announcement/config/config.go @@ -0,0 +1,4 @@ +package config + +type Config struct { +} diff --git a/plugin/announcement/gen/gen.go b/plugin/announcement/gen/gen.go new file mode 100644 index 0000000..68594b1 --- /dev/null +++ b/plugin/announcement/gen/gen.go @@ -0,0 +1,17 @@ +package main + +import ( + "gorm.io/gen" + "path/filepath" //go:generate go mod tidy + //go:generate go mod download + //go:generate go run gen.go + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model" +) + +func main() { + g := gen.NewGenerator(gen.Config{OutPath: filepath.Join("..", "..", "..", "announcement", "blender", "model", "dao"), Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface}) + g.ApplyBasic( + new(model.Info), + ) + g.Execute() +} diff --git a/plugin/announcement/initialize/api.go b/plugin/announcement/initialize/api.go new file mode 100644 index 0000000..f423d96 --- /dev/null +++ b/plugin/announcement/initialize/api.go @@ -0,0 +1,49 @@ +package initialize + +import ( + "context" + model "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/plugin/plugin-tool/utils" +) + +func Api(ctx context.Context) { + entities := []model.SysApi{ + { + Path: "/info/createInfo", + Description: "新建公告", + ApiGroup: "公告", + Method: "POST", + }, + { + Path: "/info/deleteInfo", + Description: "删除公告", + ApiGroup: "公告", + Method: "DELETE", + }, + { + Path: "/info/deleteInfoByIds", + Description: "批量删除公告", + ApiGroup: "公告", + Method: "DELETE", + }, + { + Path: "/info/updateInfo", + Description: "更新公告", + ApiGroup: "公告", + Method: "PUT", + }, + { + Path: "/info/findInfo", + Description: "根据ID获取公告", + ApiGroup: "公告", + Method: "GET", + }, + { + Path: "/info/getInfoList", + Description: "获取公告列表", + ApiGroup: "公告", + Method: "GET", + }, + } + utils.RegisterApis(entities...) +} diff --git a/plugin/announcement/initialize/gorm.go b/plugin/announcement/initialize/gorm.go new file mode 100644 index 0000000..16c5dc1 --- /dev/null +++ b/plugin/announcement/initialize/gorm.go @@ -0,0 +1,20 @@ +package initialize + +import ( + "context" + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func Gorm(ctx context.Context) { + err := global.GVA_DB.WithContext(ctx).AutoMigrate( + new(model.Info), + ) + if err != nil { + err = errors.Wrap(err, "注册表失败!") + zap.L().Error(fmt.Sprintf("%+v", err)) + } +} diff --git a/plugin/announcement/initialize/menu.go b/plugin/announcement/initialize/menu.go new file mode 100644 index 0000000..95f8746 --- /dev/null +++ b/plugin/announcement/initialize/menu.go @@ -0,0 +1,22 @@ +package initialize + +import ( + "context" + model "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/plugin/plugin-tool/utils" +) + +func Menu(ctx context.Context) { + entities := []model.SysBaseMenu{ + { + ParentId: 24, + Path: "anInfo", + Name: "anInfo", + Hidden: false, + Component: "plugin/announcement/view/info.vue", + Sort: 5, + Meta: model.Meta{Title: "公告管理", Icon: "box"}, + }, + } + utils.RegisterMenus(entities...) +} diff --git a/plugin/announcement/initialize/router.go b/plugin/announcement/initialize/router.go new file mode 100644 index 0000000..bbdcad7 --- /dev/null +++ b/plugin/announcement/initialize/router.go @@ -0,0 +1,15 @@ +package initialize + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/middleware" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/router" + "github.com/gin-gonic/gin" +) + +func Router(engine *gin.Engine) { + public := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("") + private := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("") + private.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()) + router.Router.Info.Init(public, private) +} diff --git a/plugin/announcement/initialize/viper.go b/plugin/announcement/initialize/viper.go new file mode 100644 index 0000000..fa984e0 --- /dev/null +++ b/plugin/announcement/initialize/viper.go @@ -0,0 +1,17 @@ +package initialize + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/plugin" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func Viper() { + err := global.GVA_VP.UnmarshalKey("announcement", &plugin.Config) + if err != nil { + err = errors.Wrap(err, "初始化配置文件失败!") + zap.L().Error(fmt.Sprintf("%+v", err)) + } +} diff --git a/plugin/announcement/model/info.go b/plugin/announcement/model/info.go new file mode 100644 index 0000000..f169b67 --- /dev/null +++ b/plugin/announcement/model/info.go @@ -0,0 +1,20 @@ +package model + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "gorm.io/datatypes" +) + +// Info 公告 结构体 +type Info struct { + global.GVA_MODEL + Title string `json:"title" form:"title" gorm:"column:title;comment:公告标题;"` //标题 + Content string `json:"content" form:"content" gorm:"column:content;comment:公告内容;type:text;"` //内容 + UserID *int `json:"userID" form:"userID" gorm:"column:user_id;comment:发布者;"` //作者 + Attachments datatypes.JSON `json:"attachments" form:"attachments" gorm:"column:attachments;comment:相关附件;" swaggertype:"array,object"` //附件 +} + +// TableName 公告 Info自定义表名 gva_announcements_info +func (Info) TableName() string { + return "gva_announcements_info" +} diff --git a/plugin/announcement/model/request/info.go b/plugin/announcement/model/request/info.go new file mode 100644 index 0000000..39c5557 --- /dev/null +++ b/plugin/announcement/model/request/info.go @@ -0,0 +1,12 @@ +package request + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "time" +) + +type InfoSearch struct { + StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"` + EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"` + request.PageInfo +} diff --git a/plugin/announcement/plugin.go b/plugin/announcement/plugin.go new file mode 100644 index 0000000..4a63866 --- /dev/null +++ b/plugin/announcement/plugin.go @@ -0,0 +1,26 @@ +package announcement + +import ( + "context" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/initialize" + interfaces "git.echol.cn/loser/xiecheng_server/utils/plugin/v2" + "github.com/gin-gonic/gin" +) + +var _ interfaces.Plugin = (*plugin)(nil) + +var Plugin = new(plugin) + +type plugin struct{} + +func (p *plugin) Register(group *gin.Engine) { + ctx := context.Background() + // 如果需要配置文件,请到config.Config中填充配置结构,且到下方发放中填入其在config.yaml中的key + // initialize.Viper() + // 安装插件时候自动注册的api数据请到下方法.Api方法中实现 + initialize.Api(ctx) + // 安装插件时候自动注册的api数据请到下方法.Menu方法中实现 + initialize.Menu(ctx) + initialize.Gorm(ctx) + initialize.Router(group) +} diff --git a/plugin/announcement/plugin/plugin.go b/plugin/announcement/plugin/plugin.go new file mode 100644 index 0000000..c94edb1 --- /dev/null +++ b/plugin/announcement/plugin/plugin.go @@ -0,0 +1,5 @@ +package plugin + +import "git.echol.cn/loser/xiecheng_server/plugin/announcement/config" + +var Config config.Config diff --git a/plugin/announcement/router/enter.go b/plugin/announcement/router/enter.go new file mode 100644 index 0000000..d007ce8 --- /dev/null +++ b/plugin/announcement/router/enter.go @@ -0,0 +1,10 @@ +package router + +import "git.echol.cn/loser/xiecheng_server/plugin/announcement/api" + +var ( + Router = new(router) + apiInfo = api.Api.Info +) + +type router struct{ Info info } diff --git a/plugin/announcement/router/info.go b/plugin/announcement/router/info.go new file mode 100644 index 0000000..d728470 --- /dev/null +++ b/plugin/announcement/router/info.go @@ -0,0 +1,31 @@ +package router + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +var Info = new(info) + +type info struct{} + +// Init 初始化 公告 路由信息 +func (r *info) Init(public *gin.RouterGroup, private *gin.RouterGroup) { + { + group := private.Group("info").Use(middleware.OperationRecord()) + group.POST("createInfo", apiInfo.CreateInfo) // 新建公告 + group.DELETE("deleteInfo", apiInfo.DeleteInfo) // 删除公告 + group.DELETE("deleteInfoByIds", apiInfo.DeleteInfoByIds) // 批量删除公告 + group.PUT("updateInfo", apiInfo.UpdateInfo) // 更新公告 + } + { + group := private.Group("info") + group.GET("findInfo", apiInfo.FindInfo) // 根据ID获取公告 + group.GET("getInfoList", apiInfo.GetInfoList) // 获取公告列表 + } + { + group := public.Group("info") + group.GET("getInfoDataSource", apiInfo.GetInfoDataSource) // 获取公告数据源 + group.GET("getInfoPublic", apiInfo.GetInfoPublic) // 获取公告列表 + } +} diff --git a/plugin/announcement/service/enter.go b/plugin/announcement/service/enter.go new file mode 100644 index 0000000..988fbcd --- /dev/null +++ b/plugin/announcement/service/enter.go @@ -0,0 +1,5 @@ +package service + +var Service = new(service) + +type service struct{ Info info } diff --git a/plugin/announcement/service/info.go b/plugin/announcement/service/info.go new file mode 100644 index 0000000..fb198c2 --- /dev/null +++ b/plugin/announcement/service/info.go @@ -0,0 +1,78 @@ +package service + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model" + "git.echol.cn/loser/xiecheng_server/plugin/announcement/model/request" +) + +var Info = new(info) + +type info struct{} + +// CreateInfo 创建公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) CreateInfo(info *model.Info) (err error) { + err = global.GVA_DB.Create(info).Error + return err +} + +// DeleteInfo 删除公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) DeleteInfo(ID string) (err error) { + err = global.GVA_DB.Delete(&model.Info{}, "id = ?", ID).Error + return err +} + +// DeleteInfoByIds 批量删除公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) DeleteInfoByIds(IDs []string) (err error) { + err = global.GVA_DB.Delete(&[]model.Info{}, "id in ?", IDs).Error + return err +} + +// UpdateInfo 更新公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) UpdateInfo(info model.Info) (err error) { + err = global.GVA_DB.Model(&model.Info{}).Where("id = ?", info.ID).Updates(&info).Error + return err +} + +// GetInfo 根据ID获取公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) GetInfo(ID string) (info model.Info, err error) { + err = global.GVA_DB.Where("id = ?", ID).First(&info).Error + return +} + +// GetInfoInfoList 分页获取公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) GetInfoInfoList(info request.InfoSearch) (list []model.Info, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := global.GVA_DB.Model(&model.Info{}) + var infos []model.Info + // 如果有条件搜索 下方会自动创建搜索语句 + if info.StartCreatedAt != nil && info.EndCreatedAt != nil { + db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt) + } + err = db.Count(&total).Error + if err != nil { + return + } + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + err = db.Find(&infos).Error + return infos, total, err +} +func (s *info) GetInfoDataSource() (res map[string][]map[string]any, err error) { + res = make(map[string][]map[string]any) + + userID := make([]map[string]any, 0) + global.GVA_DB.Table("sys_users").Select("nick_name as label,id as value").Scan(&userID) + res["userID"] = userID + return +} diff --git a/plugin/email/README.MD b/plugin/email/README.MD new file mode 100644 index 0000000..1720283 --- /dev/null +++ b/plugin/email/README.MD @@ -0,0 +1,75 @@ +## GVA 邮件发送功能插件 +#### 开发者:GIN-VUE-ADMIN 官方 + +### 使用步骤 + +#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件 + 例: + 本插件可以采用gva的配置文件 也可以直接写死内容作为配置 建议为gva添加配置文件结构 然后将配置传入 + PluginInit(PrivateGroup, email.CreateEmailPlug( + global.GVA_CONFIG.Email.To, + global.GVA_CONFIG.Email.From, + global.GVA_CONFIG.Email.Host, + global.GVA_CONFIG.Email.Secret, + global.GVA_CONFIG.Email.Nickname, + global.GVA_CONFIG.Email.Port, + global.GVA_CONFIG.Email.IsSSL, + )) + + 同样也可以再传入时写死 + + PluginInit(PrivateGroup, email.CreateEmailPlug( + "a@qq.com", + "b@qq.com", + "smtp.qq.com", + "global.GVA_CONFIG.Email.Secret", + "登录密钥", + 465, + true, + )) + +### 2. 配置说明 + +#### 2-1 全局配置结构体说明 + //其中 Form 和 Secret 通常来说就是用户名和密码 + + type Email struct { + To string // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 此处配置主要用于发送错误监控邮件 + From string // 发件人 你自己要发邮件的邮箱 + Host string // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议 + Secret string // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥 + Nickname string // 昵称 发件人昵称 自定义即可 可以不填 + Port int // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465 + IsSSL bool // 是否SSL 是否开启SSL + } +#### 2-2 入参结构说明 + //其中 Form 和 Secret 通常来说就是用户名和密码 + + type Email struct { + To string `json:"to"` // 邮件发送给谁 + Subject string `json:"subject"` // 邮件标题 + Body string `json:"body"` // 邮件内容 + } + + +### 3. 方法API + + utils.EmailTest(邮件标题,邮件主体) 发送测试邮件 + 例:utils.EmailTest("测试邮件","测试邮件") + utils.ErrorToEmail(邮件标题,邮件主体) 错误监控 + 例:utils.ErrorToEmail("测试邮件","测试邮件") + utils.Email(目标邮箱多个的话用逗号分隔,邮件标题,邮件主体) 发送测试邮件 + 例:utils.Email(”a.qq.com,b.qq.com“,"测试邮件","测试邮件") + +### 4. 可直接调用的接口 + + 测试接口: /email/emailTest [post] 已配置swagger + + 发送邮件接口接口: /email/emailSend [post] 已配置swagger + 入参: + type Email struct { + To string `json:"to"` // 邮件发送给谁 + Subject string `json:"subject"` // 邮件标题 + Body string `json:"body"` // 邮件内容 + } + diff --git a/plugin/email/api/enter.go b/plugin/email/api/enter.go new file mode 100644 index 0000000..353404d --- /dev/null +++ b/plugin/email/api/enter.go @@ -0,0 +1,7 @@ +package api + +type ApiGroup struct { + EmailApi +} + +var ApiGroupApp = new(ApiGroup) diff --git a/plugin/email/api/sys_email.go b/plugin/email/api/sys_email.go new file mode 100644 index 0000000..24403e8 --- /dev/null +++ b/plugin/email/api/sys_email.go @@ -0,0 +1,53 @@ +package api + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/response" + email_response "git.echol.cn/loser/xiecheng_server/plugin/email/model/response" + "git.echol.cn/loser/xiecheng_server/plugin/email/service" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type EmailApi struct{} + +// EmailTest +// @Tags System +// @Summary 发送测试邮件 +// @Security ApiKeyAuth +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}" +// @Router /email/emailTest [post] +func (s *EmailApi) EmailTest(c *gin.Context) { + err := service.ServiceGroupApp.EmailTest() + if err != nil { + global.GVA_LOG.Error("发送失败!", zap.Error(err)) + response.FailWithMessage("发送失败", c) + return + } + response.OkWithMessage("发送成功", c) +} + +// SendEmail +// @Tags System +// @Summary 发送邮件 +// @Security ApiKeyAuth +// @Produce application/json +// @Param data body email_response.Email true "发送邮件必须的参数" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}" +// @Router /email/sendEmail [post] +func (s *EmailApi) SendEmail(c *gin.Context) { + var email email_response.Email + err := c.ShouldBindJSON(&email) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = service.ServiceGroupApp.SendEmail(email.To, email.Subject, email.Body) + if err != nil { + global.GVA_LOG.Error("发送失败!", zap.Error(err)) + response.FailWithMessage("发送失败", c) + return + } + response.OkWithMessage("发送成功", c) +} diff --git a/plugin/email/config/email.go b/plugin/email/config/email.go new file mode 100644 index 0000000..c535348 --- /dev/null +++ b/plugin/email/config/email.go @@ -0,0 +1,11 @@ +package config + +type Email struct { + To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 + From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱 + Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议 + Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥 + Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱 + Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465 + IsSSL bool `mapstructure:"is-ssl" json:"isSSL" yaml:"is-ssl"` // 是否SSL 是否开启SSL +} diff --git a/plugin/email/global/gloabl.go b/plugin/email/global/gloabl.go new file mode 100644 index 0000000..0270163 --- /dev/null +++ b/plugin/email/global/gloabl.go @@ -0,0 +1,5 @@ +package global + +import "git.echol.cn/loser/xiecheng_server/plugin/email/config" + +var GlobalConfig = new(config.Email) diff --git a/plugin/email/main.go b/plugin/email/main.go new file mode 100644 index 0000000..5045979 --- /dev/null +++ b/plugin/email/main.go @@ -0,0 +1,28 @@ +package email + +import ( + "git.echol.cn/loser/xiecheng_server/plugin/email/global" + "git.echol.cn/loser/xiecheng_server/plugin/email/router" + "github.com/gin-gonic/gin" +) + +type emailPlugin struct{} + +func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool) *emailPlugin { + global.GlobalConfig.To = To + global.GlobalConfig.From = From + global.GlobalConfig.Host = Host + global.GlobalConfig.Secret = Secret + global.GlobalConfig.Nickname = Nickname + global.GlobalConfig.Port = Port + global.GlobalConfig.IsSSL = IsSSL + return &emailPlugin{} +} + +func (*emailPlugin) Register(group *gin.RouterGroup) { + router.RouterGroupApp.InitEmailRouter(group) +} + +func (*emailPlugin) RouterPath() string { + return "email" +} diff --git a/plugin/email/model/response/email.go b/plugin/email/model/response/email.go new file mode 100644 index 0000000..ed25475 --- /dev/null +++ b/plugin/email/model/response/email.go @@ -0,0 +1,7 @@ +package response + +type Email struct { + To string `json:"to"` // 邮件发送给谁 + Subject string `json:"subject"` // 邮件标题 + Body string `json:"body"` // 邮件内容 +} diff --git a/plugin/email/router/enter.go b/plugin/email/router/enter.go new file mode 100644 index 0000000..e081a54 --- /dev/null +++ b/plugin/email/router/enter.go @@ -0,0 +1,7 @@ +package router + +type RouterGroup struct { + EmailRouter +} + +var RouterGroupApp = new(RouterGroup) diff --git a/plugin/email/router/sys_email.go b/plugin/email/router/sys_email.go new file mode 100644 index 0000000..dcce919 --- /dev/null +++ b/plugin/email/router/sys_email.go @@ -0,0 +1,19 @@ +package router + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "git.echol.cn/loser/xiecheng_server/plugin/email/api" + "github.com/gin-gonic/gin" +) + +type EmailRouter struct{} + +func (s *EmailRouter) InitEmailRouter(Router *gin.RouterGroup) { + emailRouter := Router.Use(middleware.OperationRecord()) + EmailApi := api.ApiGroupApp.EmailApi.EmailTest + SendEmail := api.ApiGroupApp.EmailApi.SendEmail + { + emailRouter.POST("emailTest", EmailApi) // 发送测试邮件 + emailRouter.POST("sendEmail", SendEmail) // 发送邮件 + } +} diff --git a/plugin/email/service/enter.go b/plugin/email/service/enter.go new file mode 100644 index 0000000..e96e267 --- /dev/null +++ b/plugin/email/service/enter.go @@ -0,0 +1,7 @@ +package service + +type ServiceGroup struct { + EmailService +} + +var ServiceGroupApp = new(ServiceGroup) diff --git a/plugin/email/service/sys_email.go b/plugin/email/service/sys_email.go new file mode 100644 index 0000000..1dee76b --- /dev/null +++ b/plugin/email/service/sys_email.go @@ -0,0 +1,32 @@ +package service + +import ( + "git.echol.cn/loser/xiecheng_server/plugin/email/utils" +) + +type EmailService struct{} + +//@author: [maplepie](https://github.com/maplepie) +//@function: EmailTest +//@description: 发送邮件测试 +//@return: err error + +func (e *EmailService) EmailTest() (err error) { + subject := "test" + body := "test" + err = utils.EmailTest(subject, body) + return err +} + +//@author: [maplepie](https://github.com/maplepie) +//@function: EmailTest +//@description: 发送邮件测试 +//@return: err error +//@params to string 收件人 +//@params subject string 标题(主题) +//@params body string 邮件内容 + +func (e *EmailService) SendEmail(to, subject, body string) (err error) { + err = utils.Email(to, subject, body) + return err +} diff --git a/plugin/email/utils/email.go b/plugin/email/utils/email.go new file mode 100644 index 0000000..9455fc5 --- /dev/null +++ b/plugin/email/utils/email.go @@ -0,0 +1,82 @@ +package utils + +import ( + "crypto/tls" + "fmt" + "net/smtp" + "strings" + + "git.echol.cn/loser/xiecheng_server/plugin/email/global" + + "github.com/jordan-wright/email" +) + +//@author: [maplepie](https://github.com/maplepie) +//@function: Email +//@description: Email发送方法 +//@param: subject string, body string +//@return: error + +func Email(To, subject string, body string) error { + to := strings.Split(To, ",") + return send(to, subject, body) +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: ErrorToEmail +//@description: 给email中间件错误发送邮件到指定邮箱 +//@param: subject string, body string +//@return: error + +func ErrorToEmail(subject string, body string) error { + to := strings.Split(global.GlobalConfig.To, ",") + if to[len(to)-1] == "" { // 判断切片的最后一个元素是否为空,为空则移除 + to = to[:len(to)-1] + } + return send(to, subject, body) +} + +//@author: [maplepie](https://github.com/maplepie) +//@function: EmailTest +//@description: Email测试方法 +//@param: subject string, body string +//@return: error + +func EmailTest(subject string, body string) error { + to := []string{global.GlobalConfig.To} + return send(to, subject, body) +} + +//@author: [maplepie](https://github.com/maplepie) +//@function: send +//@description: Email发送方法 +//@param: subject string, body string +//@return: error + +func send(to []string, subject string, body string) error { + from := global.GlobalConfig.From + nickname := global.GlobalConfig.Nickname + secret := global.GlobalConfig.Secret + host := global.GlobalConfig.Host + port := global.GlobalConfig.Port + isSSL := global.GlobalConfig.IsSSL + + auth := smtp.PlainAuth("", from, secret, host) + e := email.NewEmail() + if nickname != "" { + e.From = fmt.Sprintf("%s <%s>", nickname, from) + } else { + e.From = from + } + e.To = to + e.Subject = subject + e.HTML = []byte(body) + var err error + hostAddr := fmt.Sprintf("%s:%d", host, port) + if isSSL { + err = e.SendWithTLS(hostAddr, auth, &tls.Config{ServerName: host}) + } else { + err = e.Send(hostAddr, auth) + } + return err +} diff --git a/plugin/plugin-tool/utils/check.go b/plugin/plugin-tool/utils/check.go new file mode 100644 index 0000000..845244d --- /dev/null +++ b/plugin/plugin-tool/utils/check.go @@ -0,0 +1,50 @@ +package utils + +import ( + "fmt" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" +) + +func RegisterApis(apis ...system.SysApi) { + var count int64 + var apiPaths []string + for i := range apis { + apiPaths = append(apiPaths, apis[i].Path) + } + global.GVA_DB.Find(&[]system.SysApi{}, "path in (?)", apiPaths).Count(&count) + if count > 0 { + return + } + err := global.GVA_DB.Create(&apis).Error + if err != nil { + fmt.Println(err) + } +} + +func RegisterMenus(menus ...system.SysBaseMenu) { + var count int64 + var menuNames []string + parentMenu := menus[0] + otherMenus := menus[1:] + for i := range menus { + menuNames = append(menuNames, menus[i].Name) + } + global.GVA_DB.Find(&[]system.SysBaseMenu{}, "name in (?)", menuNames).Count(&count) + if count > 0 { + return + } + err := global.GVA_DB.Create(&parentMenu).Error + if err != nil { + fmt.Println(err) + } + for i := range otherMenus { + pid := parentMenu.ID + otherMenus[i].ParentId = pid + } + err = global.GVA_DB.Create(&otherMenus).Error + if err != nil { + fmt.Println(err) + } +} diff --git a/resource/function/api.go.tpl b/resource/function/api.go.tpl new file mode 100644 index 0000000..35a1cd5 --- /dev/null +++ b/resource/function/api.go.tpl @@ -0,0 +1,44 @@ +{{if .IsPlugin}} +// {{.FuncName}} {{.FuncDesc}} +// @Tags {{.StructName}} +// @Summary {{.FuncDesc}} +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}] +func (a *{{.Abbreviation}}) {{.FuncName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + // 请添加自己的业务逻辑 + err := service{{ .StructName }}.{{.FuncName}}(ctx) + if err != nil { + global.GVA_LOG.Error("失败!", zap.Error(err)) + response.FailWithMessage("失败", c) + return + } + response.OkWithData("返回数据",c) +} + +{{- else -}} + +// {{.FuncName}} {{.FuncDesc}} +// @Tags {{.StructName}} +// @Summary {{.FuncDesc}} +// @Accept application/json +// @Produce application/json +// @Param data query {{.Package}}Req.{{.StructName}}Search true "成功" +// @Success 200 {object} response.Response{data=object,msg=string} "成功" +// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}] +func ({{.Abbreviation}}Api *{{.StructName}}Api){{.FuncName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + // 请添加自己的业务逻辑 + err := {{.Abbreviation}}Service.{{.FuncName}}(ctx) + if err != nil { + global.GVA_LOG.Error("失败!", zap.Error(err)) + response.FailWithMessage("失败", c) + return + } + response.OkWithData("返回数据",c) +} +{{end}} diff --git a/resource/function/api.js.tpl b/resource/function/api.js.tpl new file mode 100644 index 0000000..a07b102 --- /dev/null +++ b/resource/function/api.js.tpl @@ -0,0 +1,32 @@ +{{if .IsPlugin}} +// {{.FuncName}} {{.FuncDesc}} +// @Tags {{.StructName}} +// @Summary {{.FuncDesc}} +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}] +export const {{.Router}} = () => { + return service({ + url: '/{{.Abbreviation}}/{{.Router}}', + method: '{{.Method}}' + }) +} + +{{- else -}} + +// {{.FuncName}} {{.FuncDesc}} +// @Tags {{.StructName}} +// @Summary {{.FuncDesc}} +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "成功" +// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}] +export const {{.Router}} = () => { + return service({ + url: '/{{.Abbreviation}}/{{.Router}}', + method: '{{.Method}}' + }) +} + +{{- end -}} diff --git a/resource/function/server.go.tpl b/resource/function/server.go.tpl new file mode 100644 index 0000000..7327604 --- /dev/null +++ b/resource/function/server.go.tpl @@ -0,0 +1,25 @@ +{{- $db := "" }} +{{- if eq .BusinessDB "" }} + {{- $db = "global.GVA_DB" }} +{{- else}} + {{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }} +{{- end}} +{{if .IsPlugin}} + +// {{.FuncName}} {{.FuncDesc}} +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) {{.FuncName}}(ctx context.Context) (err error) { + db := {{$db}}.Model(&model.{{.StructName}}{}) + return db.Error +} + +{{- else -}} + +// {{.FuncName}} {{.FuncDesc}} +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service){{.FuncName}}(ctx context.Context) (err error) { + // 请在这里实现自己的业务逻辑 + db := {{$db}}.Model(&{{.Package}}.{{.StructName}}{}) + return db.Error +} +{{end}} diff --git a/resource/package/readme.txt.tpl b/resource/package/readme.txt.tpl new file mode 100644 index 0000000..d1f302d --- /dev/null +++ b/resource/package/readme.txt.tpl @@ -0,0 +1,7 @@ +代码解压后把fe的api文件内容粘贴进前端api文件夹下并修改为自己想要的名字即可 + +后端代码解压后同理,放到自己想要的 mvc对应路径 并且到 initRouter中注册自动生成的路由 到registerTable中注册自动生成的model + +项目github:"https://github.com/piexlmax/git.echol.cn/loser/xiecheng_server" + +希望大家给个star多多鼓励 diff --git a/resource/package/server/api/api.go.tpl b/resource/package/server/api/api.go.tpl new file mode 100644 index 0000000..528487f --- /dev/null +++ b/resource/package/server/api/api.go.tpl @@ -0,0 +1,260 @@ +package {{.Package}} + +import ( + {{if not .OnlyTemplate}} + "{{.Module}}/global" + "{{.Module}}/model/common/response" + "{{.Module}}/model/{{.Package}}" + {{- if not .IsTree}} + {{.Package}}Req "{{.Module}}/model/{{.Package}}/request" + {{- end }} + "github.com/gin-gonic/gin" + "go.uber.org/zap" + {{- if .AutoCreateResource}} + "{{.Module}}/utils" + {{- end }} + {{- else}} + "{{.Module}}/model/common/response" + "github.com/gin-gonic/gin" + {{- end}} +) + +type {{.StructName}}Api struct {} + +{{if not .OnlyTemplate}} + +// Create{{.StructName}} 创建{{.Description}} +// @Tags {{.StructName}} +// @Summary 创建{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body {{.Package}}.{{.StructName}} true "创建{{.Description}}" +// @Success 200 {object} response.Response{msg=string} "创建成功" +// @Router /{{.Abbreviation}}/create{{.StructName}} [post] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Create{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + var {{.Abbreviation}} {{.Package}}.{{.StructName}} + err := c.ShouldBindJSON(&{{.Abbreviation}}) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + {{- if .AutoCreateResource }} + {{.Abbreviation}}.CreatedBy = utils.GetUserID(c) + {{- end }} + err = {{.Abbreviation}}Service.Create{{.StructName}}(ctx,&{{.Abbreviation}}) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败:" + err.Error(), c) + return + } + response.OkWithMessage("创建成功", c) +} + +// Delete{{.StructName}} 删除{{.Description}} +// @Tags {{.StructName}} +// @Summary 删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body {{.Package}}.{{.StructName}} true "删除{{.Description}}" +// @Success 200 {object} response.Response{msg=string} "删除成功" +// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Delete{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + {{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}") + {{- if .AutoCreateResource }} + userID := utils.GetUserID(c) + {{- end }} + err := {{.Abbreviation}}Service.Delete{{.StructName}}(ctx,{{.PrimaryField.FieldJson}} {{- if .AutoCreateResource -}},userID{{- end -}}) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败:" + err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} + +// Delete{{.StructName}}ByIds 批量删除{{.Description}} +// @Tags {{.StructName}} +// @Summary 批量删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "批量删除成功" +// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Delete{{.StructName}}ByIds(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + {{.PrimaryField.FieldJson}}s := c.QueryArray("{{.PrimaryField.FieldJson}}s[]") + {{- if .AutoCreateResource }} + userID := utils.GetUserID(c) + {{- end }} + err := {{.Abbreviation}}Service.Delete{{.StructName}}ByIds(ctx,{{.PrimaryField.FieldJson}}s{{- if .AutoCreateResource }},userID{{- end }}) + if err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败:" + err.Error(), c) + return + } + response.OkWithMessage("批量删除成功", c) +} + +// Update{{.StructName}} 更新{{.Description}} +// @Tags {{.StructName}} +// @Summary 更新{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body {{.Package}}.{{.StructName}} true "更新{{.Description}}" +// @Success 200 {object} response.Response{msg=string} "更新成功" +// @Router /{{.Abbreviation}}/update{{.StructName}} [put] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Update{{.StructName}}(c *gin.Context) { + // 从ctx获取标准context进行业务行为 + ctx := c.Request.Context() + + var {{.Abbreviation}} {{.Package}}.{{.StructName}} + err := c.ShouldBindJSON(&{{.Abbreviation}}) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + {{- if .AutoCreateResource }} + {{.Abbreviation}}.UpdatedBy = utils.GetUserID(c) + {{- end }} + err = {{.Abbreviation}}Service.Update{{.StructName}}(ctx,{{.Abbreviation}}) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败:" + err.Error(), c) + return + } + response.OkWithMessage("更新成功", c) +} + +// Find{{.StructName}} 用id查询{{.Description}} +// @Tags {{.StructName}} +// @Summary 用id查询{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param {{.PrimaryField.FieldJson}} query {{.PrimaryField.FieldType}} true "用id查询{{.Description}}" +// @Success 200 {object} response.Response{data={{.Package}}.{{.StructName}},msg=string} "查询成功" +// @Router /{{.Abbreviation}}/find{{.StructName}} [get] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Find{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + {{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}") + re{{.Abbreviation}}, err := {{.Abbreviation}}Service.Get{{.StructName}}(ctx,{{.PrimaryField.FieldJson}}) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败:" + err.Error(), c) + return + } + response.OkWithData(re{{.Abbreviation}}, c) +} + +{{- if .IsTree }} +// Get{{.StructName}}List 分页获取{{.Description}}列表,Tree模式下不接受参数 +// @Tags {{.StructName}} +// @Summary 分页获取{{.Description}}列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}List [get] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}List(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + list, err := {{.Abbreviation}}Service.Get{{.StructName}}InfoList(ctx) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败:" + err.Error(), c) + return + } + response.OkWithDetailed(list, "获取成功", c) +} +{{- else }} +// Get{{.StructName}}List 分页获取{{.Description}}列表 +// @Tags {{.StructName}} +// @Summary 分页获取{{.Description}}列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query {{.Package}}Req.{{.StructName}}Search true "分页获取{{.Description}}列表" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}List [get] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}List(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + var pageInfo {{.Package}}Req.{{.StructName}}Search + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := {{.Abbreviation}}Service.Get{{.StructName}}InfoList(ctx,pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败:" + err.Error(), c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} +{{- end }} + +{{- if .HasDataSource }} +// Get{{.StructName}}DataSource 获取{{.StructName}}的数据源 +// @Tags {{.StructName}} +// @Summary 获取{{.StructName}}的数据源 +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "查询成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}DataSource [get] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}DataSource(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + // 此接口为获取数据源定义的数据 + dataSource, err := {{.Abbreviation}}Service.Get{{.StructName}}DataSource(ctx) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败:" + err.Error(), c) + return + } + response.OkWithData(dataSource, c) +} +{{- end }} + +{{- end }} + +// Get{{.StructName}}Public 不需要鉴权的{{.Description}}接口 +// @Tags {{.StructName}} +// @Summary 不需要鉴权的{{.Description}}接口 +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get] +func ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}Public(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + // 此接口不需要鉴权 + // 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑 + {{.Abbreviation}}Service.Get{{.StructName}}Public(ctx) + response.OkWithDetailed(gin.H{ + "info": "不需要鉴权的{{.Description}}接口信息", + }, "获取成功", c) +} diff --git a/resource/package/server/api/enter.go.tpl b/resource/package/server/api/enter.go.tpl new file mode 100644 index 0000000..778b314 --- /dev/null +++ b/resource/package/server/api/enter.go.tpl @@ -0,0 +1,4 @@ +package {{ .Package }} + +type ApiGroup struct { +} \ No newline at end of file diff --git a/resource/package/server/model/model.go.tpl b/resource/package/server/model/model.go.tpl new file mode 100644 index 0000000..3114b9c --- /dev/null +++ b/resource/package/server/model/model.go.tpl @@ -0,0 +1,111 @@ +{{- if .IsAdd}} +// 在结构体中新增如下字段 +{{- range .Fields}} +{{- if eq .FieldType "enum" }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "picture" }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "video" }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "file" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` +{{- else if eq .FieldType "pictures" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` +{{- else if eq .FieldType "richtext" }} +{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "json" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"` +{{- else if eq .FieldType "array" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` +{{- else }} +{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` +{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }} +{{- end }} + +{{ else }} +// 自动生成模板{{.StructName}} +package {{.Package}} + +{{- if not .OnlyTemplate}} +import ( + {{- if .GvaModel }} + "{{.Module}}/global" + {{- end }} + {{- if or .HasTimer }} + "time" + {{- end }} + {{- if .NeedJSON }} + "gorm.io/datatypes" + {{- end }} +) +{{- end }} + +// {{.Description}} 结构体 {{.StructName}} +type {{.StructName}} struct { +{{- if not .OnlyTemplate}} +{{- if .GvaModel }} + global.GVA_MODEL +{{- end }} +{{- range .Fields}} + {{- if eq .FieldType "enum" }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "picture" }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "video" }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "file" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` + {{- else if eq .FieldType "pictures" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` + {{- else if eq .FieldType "richtext" }} + {{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "json" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"` + {{- else if eq .FieldType "array" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` + {{- else }} + {{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` + {{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }} +{{- end }} + {{- if .AutoCreateResource }} + CreatedBy uint `gorm:"column:created_by;comment:创建者"` + UpdatedBy uint `gorm:"column:updated_by;comment:更新者"` + DeletedBy uint `gorm:"column:deleted_by;comment:删除者"` + {{- end }} + {{- if .IsTree }} + Children []*{{.StructName}} `json:"children" gorm:"-"` //子节点 + ParentID int `json:"parentID" gorm:"column:parent_id;comment:父节点"` + {{- end }} +{{- end }} +} + +{{ if .TableName }} +// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}} +func ({{.StructName}}) TableName() string { + return "{{.TableName}}" +} +{{ end }} + +{{if .IsTree }} +// GetChildren 实现TreeNode接口 +func (s *{{.StructName}}) GetChildren() []*{{.StructName}} { + return s.Children +} + +// SetChildren 实现TreeNode接口 +func (s *{{.StructName}}) SetChildren(children *{{.StructName}}) { + s.Children = append(s.Children, children) +} + +// GetID 实现TreeNode接口 +func (s *{{.StructName}}) GetID() int { + return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}}) +} + +// GetParentID 实现TreeNode接口 +func (s *{{.StructName}}) GetParentID() int { + return s.ParentID +} +{{ end }} + +{{ end }} diff --git a/resource/package/server/model/request/request.go.tpl b/resource/package/server/model/request/request.go.tpl new file mode 100644 index 0000000..ee5816d --- /dev/null +++ b/resource/package/server/model/request/request.go.tpl @@ -0,0 +1,58 @@ +{{- if .IsAdd}} +// 在结构体中新增如下字段 +{{- range .Fields}} + {{- if ne .FieldSearchType ""}} + {{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} +Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"` +End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"` + {{- else }} + {{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- else }} +{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- end }} + {{- end }} + {{- end}} +{{- end }} +{{- if .NeedSort}} +Sort string `json:"sort" form:"sort"` +Order string `json:"order" form:"order"` +{{- end}} +{{- else }} +package request + +import ( +{{- if not .OnlyTemplate }} + "{{.Module}}/model/common/request" + {{ if or .HasSearchTimer .GvaModel}}"time"{{ end }} +{{- end }} +) + +type {{.StructName}}Search struct{ +{{- if not .OnlyTemplate}} +{{- if .GvaModel }} + StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"` + EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"` +{{- end }} +{{- range .Fields}} + {{- if ne .FieldSearchType ""}} + {{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"` + End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"` + {{- else }} + {{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- else }} + {{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- end }} + {{- end }} + {{- end}} +{{- end }} + request.PageInfo + {{- if .NeedSort}} + Sort string `json:"sort" form:"sort"` + Order string `json:"order" form:"order"` + {{- end}} +{{- end}} +} +{{- end }} diff --git a/resource/package/server/router/enter.go.tpl b/resource/package/server/router/enter.go.tpl new file mode 100644 index 0000000..178aecf --- /dev/null +++ b/resource/package/server/router/enter.go.tpl @@ -0,0 +1,4 @@ +package {{ .Package }} + +type RouterGroup struct { +} \ No newline at end of file diff --git a/resource/package/server/router/router.go.tpl b/resource/package/server/router/router.go.tpl new file mode 100644 index 0000000..cac47ab --- /dev/null +++ b/resource/package/server/router/router.go.tpl @@ -0,0 +1,42 @@ +package {{.Package}} + +import ( + {{if .OnlyTemplate}}// {{ end}}"{{.Module}}/middleware" + "github.com/gin-gonic/gin" +) + +type {{.StructName}}Router struct {} + +// Init{{.StructName}}Router 初始化 {{.Description}} 路由信息 +func (s *{{.StructName}}Router) Init{{.StructName}}Router(Router *gin.RouterGroup,PublicRouter *gin.RouterGroup) { + {{- if not .OnlyTemplate}} + {{.Abbreviation}}Router := Router.Group("{{.Abbreviation}}").Use(middleware.OperationRecord()) + {{.Abbreviation}}RouterWithoutRecord := Router.Group("{{.Abbreviation}}") + {{- else }} + // {{.Abbreviation}}Router := Router.Group("{{.Abbreviation}}").Use(middleware.OperationRecord()) + // {{.Abbreviation}}RouterWithoutRecord := Router.Group("{{.Abbreviation}}") + {{- end}} + {{.Abbreviation}}RouterWithoutAuth := PublicRouter.Group("{{.Abbreviation}}") + {{- if not .OnlyTemplate}} + { + {{.Abbreviation}}Router.POST("create{{.StructName}}", {{.Abbreviation}}Api.Create{{.StructName}}) // 新建{{.Description}} + {{.Abbreviation}}Router.DELETE("delete{{.StructName}}", {{.Abbreviation}}Api.Delete{{.StructName}}) // 删除{{.Description}} + {{.Abbreviation}}Router.DELETE("delete{{.StructName}}ByIds", {{.Abbreviation}}Api.Delete{{.StructName}}ByIds) // 批量删除{{.Description}} + {{.Abbreviation}}Router.PUT("update{{.StructName}}", {{.Abbreviation}}Api.Update{{.StructName}}) // 更新{{.Description}} + } + { + {{.Abbreviation}}RouterWithoutRecord.GET("find{{.StructName}}", {{.Abbreviation}}Api.Find{{.StructName}}) // 根据ID获取{{.Description}} + {{.Abbreviation}}RouterWithoutRecord.GET("get{{.StructName}}List", {{.Abbreviation}}Api.Get{{.StructName}}List) // 获取{{.Description}}列表 + } + { + {{- if .HasDataSource}} + {{.Abbreviation}}RouterWithoutAuth.GET("get{{.StructName}}DataSource", {{.Abbreviation}}Api.Get{{.StructName}}DataSource) // 获取{{.Description}}数据源 + {{- end}} + {{.Abbreviation}}RouterWithoutAuth.GET("get{{.StructName}}Public", {{.Abbreviation}}Api.Get{{.StructName}}Public) // {{.Description}}开放接口 + } + {{- else}} + { + {{.Abbreviation}}RouterWithoutAuth.GET("get{{.StructName}}Public", {{.Abbreviation}}Api.Get{{.StructName}}Public) // {{.Description}}开放接口 + } + {{ end }} +} diff --git a/resource/package/server/service/enter.go.tpl b/resource/package/server/service/enter.go.tpl new file mode 100644 index 0000000..adf1db0 --- /dev/null +++ b/resource/package/server/service/enter.go.tpl @@ -0,0 +1,4 @@ +package {{ .Package }} + +type ServiceGroup struct { +} \ No newline at end of file diff --git a/resource/package/server/service/service.go.tpl b/resource/package/server/service/service.go.tpl new file mode 100644 index 0000000..d0cbb4f --- /dev/null +++ b/resource/package/server/service/service.go.tpl @@ -0,0 +1,251 @@ +{{- $db := "" }} +{{- if eq .BusinessDB "" }} + {{- $db = "global.GVA_DB" }} +{{- else}} + {{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }} +{{- end}} + +{{- if .IsAdd}} + +// Get{{.StructName}}InfoList 新增搜索语句 + {{- range .Fields}} + {{- if .FieldSearchType}} + {{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }} +if info.{{.FieldName}} != "" { + {{- if or (eq .FieldType "enum") }} + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) + {{- else}} +// 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务 + {{- end}} +} + {{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} +if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}}) +} + {{- else}} +if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) +} + {{- end }} + {{- end }} + {{- end }} + + +// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容 + {{- range .Fields}} + {{- if .Sort}} +orderMap["{{.ColumnName}}"] = true + {{- end}} + {{- end}} + + +{{- if .HasDataSource }} +// Get{{.StructName}}DataSource()方法新增关联语句 + {{range $key, $value := .DataSourceMap}} +{{$key}} := make([]map[string]any, 0) +{{ $dataDB := "" }} +{{- if eq $value.DBName "" }} +{{ $dataDB = $db }} +{{- else}} +{{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }} +{{- end}} +{{$dataDB}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}}) +res["{{$key}}"] = {{$key}} + {{- end }} +{{- end }} +{{- else}} +package {{.Package}} + +import ( +{{- if not .OnlyTemplate }} + "context" + "{{.Module}}/global" + "{{.Module}}/model/{{.Package}}" + {{- if not .IsTree}} + {{.Package}}Req "{{.Module}}/model/{{.Package}}/request" + {{- else }} + "{{.Module}}/utils" + "errors" + {{- end }} + {{- if .AutoCreateResource }} + "gorm.io/gorm" + {{- end}} +{{- end }} +) + +type {{.StructName}}Service struct {} + +{{- if not .OnlyTemplate }} +// Create{{.StructName}} 创建{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service) Create{{.StructName}}(ctx context.Context, {{.Abbreviation}} *{{.Package}}.{{.StructName}}) (err error) { + err = {{$db}}.Create({{.Abbreviation}}).Error + return err +} + +// Delete{{.StructName}} 删除{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service)Delete{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string{{- if .AutoCreateResource -}},userID uint{{- end -}}) (err error) { + {{- if .IsTree }} + var count int64 + err = {{$db}}.Find(&{{.Package}}.{{.StructName}}{},"parent_id = ?",{{.PrimaryField.FieldJson}}).Count(&count).Error + if count > 0 { + return errors.New("此节点存在子节点不允许删除") + } + if err != nil { + return err + } + {{- end }} + + {{- if .AutoCreateResource }} + err = {{$db}}.Transaction(func(tx *gorm.DB) error { + if err := tx.Model(&{{.Package}}.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).Update("deleted_by", userID).Error; err != nil { + return err + } + if err = tx.Delete(&{{.Package}}.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error; err != nil { + return err + } + return nil + }) + {{- else }} + err = {{$db}}.Delete(&{{.Package}}.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error + {{- end }} + return err +} + +// Delete{{.StructName}}ByIds 批量删除{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service)Delete{{.StructName}}ByIds(ctx context.Context, {{.PrimaryField.FieldJson}}s []string {{- if .AutoCreateResource }},deleted_by uint{{- end}}) (err error) { + {{- if .AutoCreateResource }} + err = {{$db}}.Transaction(func(tx *gorm.DB) error { + if err := tx.Model(&{{.Package}}.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Update("deleted_by", deleted_by).Error; err != nil { + return err + } + if err := tx.Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Delete(&{{.Package}}.{{.StructName}}{}).Error; err != nil { + return err + } + return nil + }) + {{- else}} + err = {{$db}}.Delete(&[]{{.Package}}.{{.StructName}}{},"{{.PrimaryField.ColumnName}} in ?",{{.PrimaryField.FieldJson}}s).Error + {{- end}} + return err +} + +// Update{{.StructName}} 更新{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service)Update{{.StructName}}(ctx context.Context, {{.Abbreviation}} {{.Package}}.{{.StructName}}) (err error) { + err = {{$db}}.Model(&{{.Package}}.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?",{{.Abbreviation}}.{{.PrimaryField.FieldName}}).Updates(&{{.Abbreviation}}).Error + return err +} + +// Get{{.StructName}} 根据{{.PrimaryField.FieldJson}}获取{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string) ({{.Abbreviation}} {{.Package}}.{{.StructName}}, err error) { + err = {{$db}}.Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).First(&{{.Abbreviation}}).Error + return +} + + +{{- if .IsTree }} +// Get{{.StructName}}InfoList 分页获取{{.Description}}记录,Tree模式下不添加分页和搜索 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoList(ctx context.Context) (list []*{{.Package}}.{{.StructName}},err error) { + // 创建db + db := {{$db}}.Model(&{{.Package}}.{{.StructName}}{}) + var {{.Abbreviation}}s []*{{.Package}}.{{.StructName}} + + err = db.Find(&{{.Abbreviation}}s).Error + + return utils.BuildTree({{.Abbreviation}}s), err +} +{{- else }} +// Get{{.StructName}}InfoList 分页获取{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoList(ctx context.Context, info {{.Package}}Req.{{.StructName}}Search) (list []{{.Package}}.{{.StructName}}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := {{$db}}.Model(&{{.Package}}.{{.StructName}}{}) + var {{.Abbreviation}}s []{{.Package}}.{{.StructName}} + // 如果有条件搜索 下方会自动创建搜索语句 +{{- if .GvaModel }} + if info.StartCreatedAt !=nil && info.EndCreatedAt !=nil { + db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt) + } +{{- end }} + {{- range .Fields}} + {{- if .FieldSearchType}} + {{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }} + if info.{{.FieldName}} != "" { + {{- if or (eq .FieldType "enum")}} + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) + {{- else}} + // 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务 + {{- end}} + } + {{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}}) + } + {{- else}} + if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) + } + {{- end }} + {{- end }} + {{- end }} + err = db.Count(&total).Error + if err!=nil { + return + } + {{- if .NeedSort}} + var OrderStr string + orderMap := make(map[string]bool) + {{- range .Fields}} + {{- if .Sort}} + orderMap["{{.ColumnName}}"] = true + {{- end}} + {{- end}} + if orderMap[info.Sort] { + OrderStr = info.Sort + if info.Order == "descending" { + OrderStr = OrderStr + " desc" + } + db = db.Order(OrderStr) + } + {{- end}} + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + + err = db.Find(&{{.Abbreviation}}s).Error + return {{.Abbreviation}}s, total, err +} + +{{- end }} + +{{- if .HasDataSource }} +func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}DataSource(ctx context.Context) (res map[string][]map[string]any, err error) { + res = make(map[string][]map[string]any) + {{range $key, $value := .DataSourceMap}} + {{$key}} := make([]map[string]any, 0) + {{ $dataDB := "" }} + {{- if eq $value.DBName "" }} + {{ $dataDB = $db }} + {{- else}} + {{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }} + {{- end}} + {{$dataDB}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}}) + res["{{$key}}"] = {{$key}} + {{- end }} + return +} +{{- end }} +{{- end }} +func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}Public(ctx context.Context) { + // 此方法为获取数据源定义的数据 + // 请自行实现 +} +{{- end }} diff --git a/resource/package/web/api/api.js.tpl b/resource/package/web/api/api.js.tpl new file mode 100644 index 0000000..a41ef6f --- /dev/null +++ b/resource/package/web/api/api.js.tpl @@ -0,0 +1,130 @@ +import service from '@/utils/request' + +{{- if not .OnlyTemplate}} +// @Tags {{.StructName}} +// @Summary 创建{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "创建{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /{{.Abbreviation}}/create{{.StructName}} [post] +export const create{{.StructName}} = (data) => { + return service({ + url: '/{{.Abbreviation}}/create{{.StructName}}', + method: 'post', + data + }) +} + +// @Tags {{.StructName}} +// @Summary 删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "删除{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete] +export const delete{{.StructName}} = (params) => { + return service({ + url: '/{{.Abbreviation}}/delete{{.StructName}}', + method: 'delete', + params + }) +} + +// @Tags {{.StructName}} +// @Summary 批量删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete] +export const delete{{.StructName}}ByIds = (params) => { + return service({ + url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds', + method: 'delete', + params + }) +} + +// @Tags {{.StructName}} +// @Summary 更新{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "更新{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /{{.Abbreviation}}/update{{.StructName}} [put] +export const update{{.StructName}} = (data) => { + return service({ + url: '/{{.Abbreviation}}/update{{.StructName}}', + method: 'put', + data + }) +} + +// @Tags {{.StructName}} +// @Summary 用id查询{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query model.{{.StructName}} true "用id查询{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /{{.Abbreviation}}/find{{.StructName}} [get] +export const find{{.StructName}} = (params) => { + return service({ + url: '/{{.Abbreviation}}/find{{.StructName}}', + method: 'get', + params + }) +} + +// @Tags {{.StructName}} +// @Summary 分页获取{{.Description}}列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取{{.Description}}列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /{{.Abbreviation}}/get{{.StructName}}List [get] +export const get{{.StructName}}List = (params) => { + return service({ + url: '/{{.Abbreviation}}/get{{.StructName}}List', + method: 'get', + params + }) +} + +{{- if .HasDataSource}} +// @Tags {{.StructName}} +// @Summary 获取数据源 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /{{.Abbreviation}}/find{{.StructName}}DataSource [get] +export const get{{.StructName}}DataSource = () => { + return service({ + url: '/{{.Abbreviation}}/get{{.StructName}}DataSource', + method: 'get', + }) +} +{{- end}} + +{{- end}} + +// @Tags {{.StructName}} +// @Summary 不需要鉴权的{{.Description}}接口 +// @Accept application/json +// @Produce application/json +// @Param data query {{.Package}}Req.{{.StructName}}Search true "分页获取{{.Description}}列表" +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get] +export const get{{.StructName}}Public = () => { + return service({ + url: '/{{.Abbreviation}}/get{{.StructName}}Public', + method: 'get', + }) +} diff --git a/resource/package/web/view/form.vue.tpl b/resource/package/web/view/form.vue.tpl new file mode 100644 index 0000000..e45b375 --- /dev/null +++ b/resource/package/web/view/form.vue.tpl @@ -0,0 +1,464 @@ +{{- if .IsAdd }} +// 新增表单中增加如下代码 +{{- range .Fields}} + {{- if .Form}} + + {{- if .CheckDataSource}} + + + + {{- else }} + {{- if eq .FieldType "bool" }} + + {{- end }} + {{- if eq .FieldType "string" }} + {{- if .DictType}} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- if eq .FieldType "richtext" }} + + {{- end }} + {{- if eq .FieldType "json" }} + // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取 + {{"{{"}} formData.{{.FieldJson}} {{"}}"}} + {{- end }} + {{- if eq .FieldType "array" }} + + {{- end }} + {{- if eq .FieldType "int" }} + + {{- end }} + {{- if eq .FieldType "time.Time" }} + + {{- end }} + {{- if eq .FieldType "float64" }} + + {{- end }} + {{- if eq .FieldType "enum" }} + + + + {{- end }} + {{- if eq .FieldType "picture" }} + + {{- end }} + {{- if eq .FieldType "pictures" }} + + {{- end }} + {{- if eq .FieldType "video" }} + + {{- end }} + {{- if eq .FieldType "file" }} + + {{- end }} + {{- end }} + + {{- end }} + {{- end }} + +// 字典增加如下代码 + {{- range $index, $element := .DictTypes}} +const {{ $element }}Options = ref([]) + {{- end }} + +// init方法中增加如下调用 + +{{- range $index, $element := .DictTypes }} + {{ $element }}Options.value = await getDictFunc('{{$element}}') +{{- end }} + +// 基础formData结构增加如下字段 +{{- range .Fields}} + {{- if .Form}} + {{- if eq .FieldType "bool" }} +{{.FieldJson}}: false, + {{- end }} + {{- if eq .FieldType "string" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "richtext" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "int" }} +{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }}, + {{- end }} + {{- if eq .FieldType "time.Time" }} +{{.FieldJson}}: new Date(), + {{- end }} + {{- if eq .FieldType "float64" }} +{{.FieldJson}}: 0, + {{- end }} + {{- if eq .FieldType "picture" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "video" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "pictures" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "file" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "json" }} +{{.FieldJson}}: {}, + {{- end }} + {{- if eq .FieldType "array" }} +{{.FieldJson}}: [], + {{- end }} + {{- end }} + {{- end }} +// 验证规则中增加如下字段 + +{{- range .Fields }} + {{- if .Form }} + {{- if eq .Require true }} +{{.FieldJson }} : [{ + required: true, + message: '{{ .ErrorText }}', + trigger: ['input','blur'], +}, + {{- if eq .FieldType "string" }} +{ + whitespace: true, + message: '不能只输入空格', + trigger: ['input', 'blur'], +} + {{- end }} +], + {{- end }} + {{- end }} + {{- end }} + +{{- if .HasDataSource }} +// 请引用 +get{{.StructName}}DataSource, + +// 获取数据源 +const dataSource = ref([]) +const getDataSourceFunc = async()=>{ + const res = await get{{.StructName}}DataSource() + if (res.code === 0) { + dataSource.value = res.data + } +} +getDataSourceFunc() +{{- end }} +{{- else }} +{{- if not .OnlyTemplate }} + + + + + +{{- else }} + + + +{{- end }} +{{- end }} diff --git a/resource/package/web/view/table.vue.tpl b/resource/package/web/view/table.vue.tpl new file mode 100644 index 0000000..c6d5f55 --- /dev/null +++ b/resource/package/web/view/table.vue.tpl @@ -0,0 +1,1363 @@ +{{- $global := . }} +{{- $templateID := printf "%s_%s" .Package .StructName }} +{{- if .IsAdd }} +// 请在搜索条件中增加如下代码 +{{- range .Fields}} {{- if .FieldSearchType}} {{- if eq .FieldType "bool" }} + + + + + + + + + {{- else if .DictType}} + + + + + + {{- else if .CheckDataSource}} + + + + + + {{- else}} + + {{- if eq .FieldType "float64" "int"}} + {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + +— + + {{- else}} + + {{- end}} + {{- else if eq .FieldType "time.Time"}} + {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + + +— + + {{- else}} + + {{- end}} + {{- else}} + + {{- end}} +{{ end }}{{ end }}{{ end }} + + +// 表格增加如下列代码 + +{{- range .Fields}} + {{- if .Table}} + {{- if .CheckDataSource }} + + + + {{- else if .DictType}} + + + + {{- else if eq .FieldType "bool" }} + + + + {{- else if eq .FieldType "time.Time" }} + + + + {{- else if eq .FieldType "picture" }} + + + + {{- else if eq .FieldType "pictures" }} + + + + {{- else if eq .FieldType "video" }} + + + + {{- else if eq .FieldType "richtext" }} + + + + {{- else if eq .FieldType "file" }} + + + + {{- else if eq .FieldType "json" }} + + + + {{- else if eq .FieldType "array" }} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- end }} + +// 新增表单中增加如下代码 +{{- range .Fields}} + {{- if .Form}} + + {{- if .CheckDataSource}} + + + + {{- else }} + {{- if eq .FieldType "bool" }} + + {{- end }} + {{- if eq .FieldType "string" }} + {{- if .DictType}} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- if eq .FieldType "richtext" }} + + {{- end }} + {{- if eq .FieldType "json" }} + // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取 + {{"{{"}} formData.{{.FieldJson}} {{"}}"}} + {{- end }} + {{- if eq .FieldType "array" }} + {{- if .DictType}} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- if eq .FieldType "int" }} + + {{- end }} + {{- if eq .FieldType "time.Time" }} + + {{- end }} + {{- if eq .FieldType "float64" }} + + {{- end }} + {{- if eq .FieldType "enum" }} + + + + {{- end }} + {{- if eq .FieldType "picture" }} + + {{- end }} + {{- if eq .FieldType "pictures" }} + + {{- end }} + {{- if eq .FieldType "video" }} + + {{- end }} + {{- if eq .FieldType "file" }} + + {{- end }} + {{- end }} + + {{- end }} + {{- end }} + +// 查看抽屉中增加如下代码 + +{{- range .Fields}} + {{- if .Desc }} + +{{- if .CheckDataSource }} + + {{- else if and (ne .FieldType "picture" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }} + {{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}} + {{- else }} + {{- if eq .FieldType "picture" }} + + {{- end }} + {{- if eq .FieldType "array" }} + + {{- end }} + {{- if eq .FieldType "pictures" }} + + {{- end }} + {{- if eq .FieldType "richtext" }} + + {{- end }} + {{- if eq .FieldType "file" }} +
+ + + {{"{{"}}item.name{{"}}"}} + +
+ {{- end }} + {{- end }} +
+ {{- end }} + {{- end }} + +// 字典增加如下代码 + {{- range $index, $element := .DictTypes}} +const {{ $element }}Options = ref([]) + {{- end }} + +// setOptions方法中增加如下调用 + +{{- range $index, $element := .DictTypes }} + {{ $element }}Options.value = await getDictFunc('{{$element}}') +{{- end }} + +// 基础formData结构(变量处和关闭表单处)增加如下字段 +{{- range .Fields}} + {{- if .Form}} + {{- if eq .FieldType "bool" }} +{{.FieldJson}}: false, + {{- end }} + {{- if eq .FieldType "string" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "richtext" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "int" }} +{{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }}, + {{- end }} + {{- if eq .FieldType "time.Time" }} +{{.FieldJson}}: new Date(), + {{- end }} + {{- if eq .FieldType "float64" }} +{{.FieldJson}}: 0, + {{- end }} + {{- if eq .FieldType "picture" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "video" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "pictures" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "file" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "json" }} +{{.FieldJson}}: {}, + {{- end }} + {{- if eq .FieldType "array" }} +{{.FieldJson}}: [], + {{- end }} + {{- end }} + {{- end }} +// 验证规则中增加如下字段 + +{{- range .Fields }} + {{- if .Form }} + {{- if eq .Require true }} +{{.FieldJson }} : [{ + required: true, + message: '{{ .ErrorText }}', + trigger: ['input','blur'], +}, + {{- if eq .FieldType "string" }} +{ + whitespace: true, + message: '不能只输入空格', + trigger: ['input', 'blur'], +} + {{- end }} +], + {{- end }} + {{- end }} + {{- end }} + + + +{{- if .HasDataSource }} +// 请引用 +get{{.StructName}}DataSource, + +// 获取数据源 +const dataSource = ref({}) +const getDataSourceFunc = async()=>{ + const res = await get{{.StructName}}DataSource() + if (res.code === 0) { + dataSource.value = res.data + } +} +getDataSourceFunc() +{{- end }} + +{{- else }} + +{{- if not .OnlyTemplate}} + + + + + +{{- else}} + + + +{{- end }} + +{{- end }} diff --git a/resource/plugin/server/api/api.go.template b/resource/plugin/server/api/api.go.template new file mode 100644 index 0000000..e69ae82 --- /dev/null +++ b/resource/plugin/server/api/api.go.template @@ -0,0 +1,255 @@ +package api + +import ( +{{if not .OnlyTemplate}} + "{{.Module}}/global" + "{{.Module}}/model/common/response" + "{{.Module}}/plugin/{{.Package}}/model" + {{- if not .IsTree}} + "{{.Module}}/plugin/{{.Package}}/model/request" + {{- end }} + "github.com/gin-gonic/gin" + "go.uber.org/zap" + {{- if .AutoCreateResource}} + "{{.Module}}/utils" + {{- end }} +{{- else }} + "{{.Module}}/model/common/response" + "github.com/gin-gonic/gin" +{{- end }} +) + +var {{.StructName}} = new({{.Abbreviation}}) + +type {{.Abbreviation}} struct {} +{{if not .OnlyTemplate}} +// Create{{.StructName}} 创建{{.Description}} +// @Tags {{.StructName}} +// @Summary 创建{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "创建{{.Description}}" +// @Success 200 {object} response.Response{msg=string} "创建成功" +// @Router /{{.Abbreviation}}/create{{.StructName}} [post] +func (a *{{.Abbreviation}}) Create{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + var info model.{{.StructName}} + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + {{- if .AutoCreateResource }} + info.CreatedBy = utils.GetUserID(c) + {{- end }} + err = service{{ .StructName }}.Create{{.StructName}}(ctx,&info) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败:" + err.Error(), c) + return + } + response.OkWithMessage("创建成功", c) +} + +// Delete{{.StructName}} 删除{{.Description}} +// @Tags {{.StructName}} +// @Summary 删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "删除{{.Description}}" +// @Success 200 {object} response.Response{msg=string} "删除成功" +// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete] +func (a *{{.Abbreviation}}) Delete{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + {{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}") +{{- if .AutoCreateResource }} + userID := utils.GetUserID(c) +{{- end }} + err := service{{ .StructName }}.Delete{{.StructName}}(ctx,{{.PrimaryField.FieldJson}} {{- if .AutoCreateResource -}},userID{{- end -}}) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败:" + err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} + +// Delete{{.StructName}}ByIds 批量删除{{.Description}} +// @Tags {{.StructName}} +// @Summary 批量删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "批量删除成功" +// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete] +func (a *{{.Abbreviation}}) Delete{{.StructName}}ByIds(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + {{.PrimaryField.FieldJson}}s := c.QueryArray("{{.PrimaryField.FieldJson}}s[]") +{{- if .AutoCreateResource }} + userID := utils.GetUserID(c) +{{- end }} + err := service{{ .StructName }}.Delete{{.StructName}}ByIds(ctx,{{.PrimaryField.FieldJson}}s{{- if .AutoCreateResource }},userID{{- end }}) + if err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败:" + err.Error(), c) + return + } + response.OkWithMessage("批量删除成功", c) +} + +// Update{{.StructName}} 更新{{.Description}} +// @Tags {{.StructName}} +// @Summary 更新{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "更新{{.Description}}" +// @Success 200 {object} response.Response{msg=string} "更新成功" +// @Router /{{.Abbreviation}}/update{{.StructName}} [put] +func (a *{{.Abbreviation}}) Update{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + var info model.{{.StructName}} + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } +{{- if .AutoCreateResource }} + info.UpdatedBy = utils.GetUserID(c) +{{- end }} + err = service{{ .StructName }}.Update{{.StructName}}(ctx,info) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败:" + err.Error(), c) + return + } + response.OkWithMessage("更新成功", c) +} + +// Find{{.StructName}} 用id查询{{.Description}} +// @Tags {{.StructName}} +// @Summary 用id查询{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param {{.PrimaryField.FieldJson}} query {{.PrimaryField.FieldType}} true "用id查询{{.Description}}" +// @Success 200 {object} response.Response{data=model.{{.StructName}},msg=string} "查询成功" +// @Router /{{.Abbreviation}}/find{{.StructName}} [get] +func (a *{{.Abbreviation}}) Find{{.StructName}}(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + {{.PrimaryField.FieldJson}} := c.Query("{{.PrimaryField.FieldJson}}") + re{{.Abbreviation}}, err := service{{ .StructName }}.Get{{.StructName}}(ctx,{{.PrimaryField.FieldJson}}) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败:" + err.Error(), c) + return + } + response.OkWithData(re{{.Abbreviation}}, c) +} + +{{- if .IsTree }} +// Get{{.StructName}}List 分页获取{{.Description}}列表 +// @Tags {{.StructName}} +// @Summary 分页获取{{.Description}}列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}List [get] +func (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + list, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败:" + err.Error(), c) + return + } + response.OkWithDetailed(list, "获取成功", c) +} +{{- else }} +// Get{{.StructName}}List 分页获取{{.Description}}列表 +// @Tags {{.StructName}} +// @Summary 分页获取{{.Description}}列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query request.{{.StructName}}Search true "分页获取{{.Description}}列表" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}List [get] +func (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + var pageInfo request.{{.StructName}}Search + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx,pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败:" + err.Error(), c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} +{{- end }} + +{{- if .HasDataSource }} +// Get{{.StructName}}DataSource 获取{{.StructName}}的数据源 +// @Tags {{.StructName}} +// @Summary 获取{{.StructName}}的数据源 +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "查询成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}DataSource [get] +func (a *{{.Abbreviation}}) Get{{.StructName}}DataSource(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + // 此接口为获取数据源定义的数据 + dataSource, err := service{{ .StructName }}.Get{{.StructName}}DataSource(ctx) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败:" + err.Error(), c) + return + } + response.OkWithData(dataSource, c) +} +{{- end }} +{{- end }} +// Get{{.StructName}}Public 不需要鉴权的{{.Description}}接口 +// @Tags {{.StructName}} +// @Summary 不需要鉴权的{{.Description}}接口 +// @Accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get] +func (a *{{.Abbreviation}}) Get{{.StructName}}Public(c *gin.Context) { + // 创建业务用Context + ctx := c.Request.Context() + + // 此接口不需要鉴权 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑 + service{{ .StructName }}.Get{{.StructName}}Public(ctx) + response.OkWithDetailed(gin.H{"info": "不需要鉴权的{{.Description}}接口信息"}, "获取成功", c) +} diff --git a/resource/plugin/server/api/enter.go.template b/resource/plugin/server/api/enter.go.template new file mode 100644 index 0000000..989fb35 --- /dev/null +++ b/resource/plugin/server/api/enter.go.template @@ -0,0 +1,6 @@ +package api + +var Api = new(api) + +type api struct { +} diff --git a/resource/plugin/server/config/config.go.template b/resource/plugin/server/config/config.go.template new file mode 100644 index 0000000..809bc99 --- /dev/null +++ b/resource/plugin/server/config/config.go.template @@ -0,0 +1,4 @@ +package config + +type Config struct { +} diff --git a/resource/plugin/server/gen/gen.go.template b/resource/plugin/server/gen/gen.go.template new file mode 100644 index 0000000..5639d4a --- /dev/null +++ b/resource/plugin/server/gen/gen.go.template @@ -0,0 +1,18 @@ +package main + +import ( + "gorm.io/gen" + "path/filepath" +) + +//go:generate go mod tidy +//go:generate go mod download +//go:generate go run gen.go +func main() { + g := gen.NewGenerator(gen.Config{ + OutPath: filepath.Join("..", "..", "..", "{{ .Package }}", "blender", "model", "dao"), + Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, + }) + g.ApplyBasic() + g.Execute() +} diff --git a/resource/plugin/server/initialize/api.go.template b/resource/plugin/server/initialize/api.go.template new file mode 100644 index 0000000..dfbea23 --- /dev/null +++ b/resource/plugin/server/initialize/api.go.template @@ -0,0 +1,12 @@ +package initialize + +import ( + "context" + model "{{.Module}}/model/system" + "{{.Module}}/plugin/plugin-tool/utils" +) + +func Api(ctx context.Context) { + entities := []model.SysApi{} + utils.RegisterApis(entities...) +} diff --git a/resource/plugin/server/initialize/gorm.go.template b/resource/plugin/server/initialize/gorm.go.template new file mode 100644 index 0000000..52c8183 --- /dev/null +++ b/resource/plugin/server/initialize/gorm.go.template @@ -0,0 +1,17 @@ +package initialize + +import ( + "context" + "fmt" + "{{.Module}}/global" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func Gorm(ctx context.Context) { + err := global.GVA_DB.WithContext(ctx).AutoMigrate() + if err != nil { + err = errors.Wrap(err, "注册表失败!") + zap.L().Error(fmt.Sprintf("%+v", err)) + } +} diff --git a/resource/plugin/server/initialize/menu.go.template b/resource/plugin/server/initialize/menu.go.template new file mode 100644 index 0000000..8774f35 --- /dev/null +++ b/resource/plugin/server/initialize/menu.go.template @@ -0,0 +1,12 @@ +package initialize + +import ( + "context" + model "{{.Module}}/model/system" + "{{.Module}}/plugin/plugin-tool/utils" +) + +func Menu(ctx context.Context) { + entities := []model.SysBaseMenu{} + utils.RegisterMenus(entities...) +} diff --git a/resource/plugin/server/initialize/router.go.template b/resource/plugin/server/initialize/router.go.template new file mode 100644 index 0000000..fbf03a3 --- /dev/null +++ b/resource/plugin/server/initialize/router.go.template @@ -0,0 +1,14 @@ +package initialize + +import ( + "{{.Module}}/global" + "{{.Module}}/middleware" + "github.com/gin-gonic/gin" +) + +func Router(engine *gin.Engine) { + public := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("") + public.Use() + private := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("") + private.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()) +} diff --git a/resource/plugin/server/initialize/viper.go.template b/resource/plugin/server/initialize/viper.go.template new file mode 100644 index 0000000..e759ad6 --- /dev/null +++ b/resource/plugin/server/initialize/viper.go.template @@ -0,0 +1,17 @@ +package initialize + +import ( + "fmt" + "{{.Module}}/global" + "{{.Module}}/plugin/{{ .Package }}/plugin" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func Viper() { + err := global.GVA_VP.UnmarshalKey("{{ .Package }}", &plugin.Config) + if err != nil { + err = errors.Wrap(err, "初始化配置文件失败!") + zap.L().Error(fmt.Sprintf("%+v", err)) + } +} diff --git a/resource/plugin/server/model/model.go.template b/resource/plugin/server/model/model.go.template new file mode 100644 index 0000000..0483771 --- /dev/null +++ b/resource/plugin/server/model/model.go.template @@ -0,0 +1,112 @@ +{{- if .IsAdd}} +// 在结构体中新增如下字段 +{{- range .Fields}} +{{- if eq .FieldType "enum" }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "picture" }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "video" }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "file" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` +{{- else if eq .FieldType "pictures" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` +{{- else if eq .FieldType "richtext" }} +{{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}` +{{- else if eq .FieldType "json" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"` +{{- else if eq .FieldType "array" }} +{{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` +{{- else }} +{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` +{{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }} +{{- end }} + +{{ else }} +package model + +{{- if not .OnlyTemplate}} +import ( + {{- if .GvaModel }} + "{{.Module}}/global" + {{- end }} + {{- if or .HasTimer }} + "time" + {{- end }} + {{- if .NeedJSON }} + "gorm.io/datatypes" + {{- end }} +) +{{- end }} + +// {{.StructName}} {{.Description}} 结构体 +type {{.StructName}} struct { +{{- if not .OnlyTemplate}} +{{- if .GvaModel }} + global.GVA_MODEL +{{- end }} +{{- range .Fields}} + {{- if eq .FieldType "enum" }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};type:enum({{.DataTypeLong}});comment:{{.Comment}};" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "picture" }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "video" }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "file" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` + {{- else if eq .FieldType "pictures" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` + {{- else if eq .FieldType "richtext" }} + {{.FieldName}} *string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end -}}` + {{- else if eq .FieldType "json" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"object"` + {{- else if eq .FieldType "array" }} + {{.FieldName}} datatypes.JSON `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}type:text;" {{- if .Require }} binding:"required"{{- end }} swaggertype:"array,object"` + {{- else }} + {{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if ne .FieldIndexType "" -}}{{ .FieldIndexType }};{{- end -}}{{- if .PrimaryKey -}}primarykey;{{- end -}}{{- if .DefaultValue -}}default:{{ .DefaultValue }};{{- end -}}column:{{.ColumnName}};comment:{{.Comment}};{{- if .DataTypeLong -}}size:{{.DataTypeLong}};{{- end -}}" {{- if .Require }} binding:"required"{{- end -}}` + {{- end }} {{ if .FieldDesc }}//{{.FieldDesc}}{{ end }} +{{- end }} + {{- if .AutoCreateResource }} + CreatedBy uint `gorm:"column:created_by;comment:创建者"` + UpdatedBy uint `gorm:"column:updated_by;comment:更新者"` + DeletedBy uint `gorm:"column:deleted_by;comment:删除者"` + {{- end }} + {{- if .IsTree }} + Children []*{{.StructName}} `json:"children" gorm:"-"` //子节点 + ParentID int `json:"parentID" gorm:"column:parent_id;comment:父节点"` + {{- end }} + {{- end }} +} + +{{ if .TableName }} +// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}} +func ({{.StructName}}) TableName() string { + return "{{.TableName}}" +} +{{ end }} + + +{{if .IsTree }} +// GetChildren 实现TreeNode接口 +func (s *{{.StructName}}) GetChildren() []*{{.StructName}} { + return s.Children +} + +// SetChildren 实现TreeNode接口 +func (s *{{.StructName}}) SetChildren(children *{{.StructName}}) { + s.Children = append(s.Children, children) +} + +// GetID 实现TreeNode接口 +func (s *{{.StructName}}) GetID() int { + return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}}) +} + +// GetParentID 实现TreeNode接口 +func (s *{{.StructName}}) GetParentID() int { + return s.ParentID +} +{{ end }} + + +{{ end }} diff --git a/resource/plugin/server/model/request/request.go.template b/resource/plugin/server/model/request/request.go.template new file mode 100644 index 0000000..2100a64 --- /dev/null +++ b/resource/plugin/server/model/request/request.go.template @@ -0,0 +1,57 @@ +{{- if .IsAdd}} +// 在结构体中新增如下字段 +{{- range .Fields}} + {{- if ne .FieldSearchType ""}} + {{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} +Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"` +End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"` + {{- else }} + {{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }} +{{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- else }} +{{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- end }} + {{- end }} + {{- end}} +{{- end }} +{{- if .NeedSort}} +Sort string `json:"sort" form:"sort"` +Order string `json:"order" form:"order"` +{{- end}} +{{- else }} +package request +{{- if not .OnlyTemplate}} +import ( + "{{.Module}}/model/common/request" + {{ if or .HasSearchTimer .GvaModel}}"time"{{ end }} +) +{{- end}} +type {{.StructName}}Search struct{ +{{- if not .OnlyTemplate}} + +{{- if .GvaModel }} + StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"` + EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"` +{{- end }} +{{- range .Fields}} + {{- if ne .FieldSearchType ""}} + {{- if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + Start{{.FieldName}} *{{.FieldType}} `json:"start{{.FieldName}}" form:"start{{.FieldName}}"` + End{{.FieldName}} *{{.FieldType}} `json:"end{{.FieldName}}" form:"end{{.FieldName}}"` + {{- else }} + {{- if or (eq .FieldType "enum") (eq .FieldType "picture") (eq .FieldType "pictures") (eq .FieldType "video") (eq .FieldType "json") }} + {{.FieldName}} string `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- else }} + {{.FieldName}} *{{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" ` + {{- end }} + {{- end }} + {{- end}} +{{- end }} + request.PageInfo + {{- if .NeedSort}} + Sort string `json:"sort" form:"sort"` + Order string `json:"order" form:"order"` + {{- end}} +{{- end }} +} +{{- end }} \ No newline at end of file diff --git a/resource/plugin/server/plugin.go.template b/resource/plugin/server/plugin.go.template new file mode 100644 index 0000000..255b7af --- /dev/null +++ b/resource/plugin/server/plugin.go.template @@ -0,0 +1,26 @@ +package {{ .Package }} + +import ( + "context" + "{{.Module}}/plugin/{{ .Package }}/initialize" + interfaces "{{.Module}}/utils/plugin/v2" + "github.com/gin-gonic/gin" +) + +var _ interfaces.Plugin = (*plugin)(nil) + +var Plugin = new(plugin) + +type plugin struct{} + +// 如果需要配置文件,请到config.Config中填充配置结构,且到下方发放中填入其在config.yaml中的key并添加如下方法 +// initialize.Viper() +// 安装插件时候自动注册的api数据请到下方法.Api方法中实现并添加如下方法 +// initialize.Api(ctx) +// 安装插件时候自动注册的api数据请到下方法.Menu方法中实现并添加如下方法 +// initialize.Menu(ctx) +func (p *plugin) Register(group *gin.Engine) { + ctx := context.Background() + initialize.Gorm(ctx) + initialize.Router(group) +} diff --git a/resource/plugin/server/plugin/plugin.go.template b/resource/plugin/server/plugin/plugin.go.template new file mode 100644 index 0000000..7e25e07 --- /dev/null +++ b/resource/plugin/server/plugin/plugin.go.template @@ -0,0 +1,5 @@ +package plugin + +import "{{.Module}}/plugin/{{ .Package }}/config" + +var Config config.Config diff --git a/resource/plugin/server/router/enter.go.template b/resource/plugin/server/router/enter.go.template new file mode 100644 index 0000000..78517b3 --- /dev/null +++ b/resource/plugin/server/router/enter.go.template @@ -0,0 +1,6 @@ +package router + +var Router = new(router) + +type router struct { +} diff --git a/resource/plugin/server/router/router.go.template b/resource/plugin/server/router/router.go.template new file mode 100644 index 0000000..34bf4d8 --- /dev/null +++ b/resource/plugin/server/router/router.go.template @@ -0,0 +1,46 @@ +package router + +import ( + {{if .OnlyTemplate }} // {{end}}"{{.Module}}/middleware" + "github.com/gin-gonic/gin" +) + +var {{.StructName}} = new({{.Abbreviation}}) + +type {{.Abbreviation}} struct {} + +// Init 初始化 {{.Description}} 路由信息 +func (r *{{.Abbreviation}}) Init(public *gin.RouterGroup, private *gin.RouterGroup) { +{{- if not .OnlyTemplate }} + { + group := private.Group("{{.Abbreviation}}").Use(middleware.OperationRecord()) + group.POST("create{{.StructName}}", api{{.StructName}}.Create{{.StructName}}) // 新建{{.Description}} + group.DELETE("delete{{.StructName}}", api{{.StructName}}.Delete{{.StructName}}) // 删除{{.Description}} + group.DELETE("delete{{.StructName}}ByIds", api{{.StructName}}.Delete{{.StructName}}ByIds) // 批量删除{{.Description}} + group.PUT("update{{.StructName}}", api{{.StructName}}.Update{{.StructName}}) // 更新{{.Description}} + } + { + group := private.Group("{{.Abbreviation}}") + group.GET("find{{.StructName}}", api{{.StructName}}.Find{{.StructName}}) // 根据ID获取{{.Description}} + group.GET("get{{.StructName}}List", api{{.StructName}}.Get{{.StructName}}List) // 获取{{.Description}}列表 + } + { + group := public.Group("{{.Abbreviation}}") + {{- if .HasDataSource}} + group.GET("get{{.StructName}}DataSource", api{{.StructName}}.Get{{.StructName}}DataSource) // 获取{{.Description}}数据源 + {{- end}} + group.GET("get{{.StructName}}Public", api{{.StructName}}.Get{{.StructName}}Public) // {{.Description}}开放接口 + } +{{- else}} + // { + // group := private.Group("{{.Abbreviation}}").Use(middleware.OperationRecord()) + // } + // { + // group := private.Group("{{.Abbreviation}}") + // } + { + group := public.Group("{{.Abbreviation}}") + group.GET("get{{.StructName}}Public", api{{.StructName}}.Get{{.StructName}}Public) // {{.Description}}开放接口 + } +{{- end}} +} diff --git a/resource/plugin/server/service/enter.go.template b/resource/plugin/server/service/enter.go.template new file mode 100644 index 0000000..034facb --- /dev/null +++ b/resource/plugin/server/service/enter.go.template @@ -0,0 +1,7 @@ +package service + +var Service = new(service) + +type service struct { +} + diff --git a/resource/plugin/server/service/service.go.template b/resource/plugin/server/service/service.go.template new file mode 100644 index 0000000..6d11da1 --- /dev/null +++ b/resource/plugin/server/service/service.go.template @@ -0,0 +1,259 @@ +{{- $db := "" }} +{{- if eq .BusinessDB "" }} + {{- $db = "global.GVA_DB" }} +{{- else}} + {{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }} +{{- end}} + +{{- if .IsAdd}} + +// Get{{.StructName}}InfoList 新增搜索语句 + {{- range .Fields}} + {{- if .FieldSearchType}} + {{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }} +if info.{{.FieldName}} != "" { + {{- if or (eq .FieldType "enum") }} + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) + {{- else}} +// 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务 + {{- end}} +} + {{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} +if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}}) +} + {{- else}} +if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) +} + {{- end }} + {{- end }} + {{- end }} + + +// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容 + {{- range .Fields}} + {{- if .Sort}} +orderMap["{{.ColumnName}}"] = true + {{- end}} + {{- end}} + + +{{- if .HasDataSource }} +// Get{{.StructName}}DataSource()方法新增关联语句 + {{range $key, $value := .DataSourceMap}} +{{$key}} := make([]map[string]any, 0) +{{ $dataDB := "" }} +{{- if eq $value.DBName "" }} +{{ $dataDB = $db }} +{{- else}} +{{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }} +{{- end}} +{{$dataDB}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}}) +res["{{$key}}"] = {{$key}} + {{- end }} +{{- end }} +{{- else}} +package service + +import ( +{{- if not .OnlyTemplate }} + "context" + "{{.Module}}/global" + "{{.Module}}/plugin/{{.Package}}/model" + {{- if not .IsTree }} + "{{.Module}}/plugin/{{.Package}}/model/request" + {{- else }} + "errors" + {{- end }} + {{- if .AutoCreateResource }} + "gorm.io/gorm" + {{- end}} +{{- if .IsTree }} + "{{.Module}}/utils" +{{- end }} +{{- end }} +) + +var {{.StructName}} = new({{.Abbreviation}}) + +type {{.Abbreviation}} struct {} + +{{- $db := "" }} +{{- if eq .BusinessDB "" }} + {{- $db = "global.GVA_DB" }} +{{- else}} + {{- $db = printf "global.MustGetGlobalDBByDBName(\"%s\")" .BusinessDB }} +{{- end}} +{{- if not .OnlyTemplate }} +// Create{{.StructName}} 创建{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Create{{.StructName}}(ctx context.Context, {{.Abbreviation}} *model.{{.StructName}}) (err error) { + err = {{$db}}.Create({{.Abbreviation}}).Error + return err +} + +// Delete{{.StructName}} 删除{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Delete{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string{{- if .AutoCreateResource -}},userID uint{{- end -}}) (err error) { + + {{- if .IsTree }} + var count int64 + err = {{$db}}.Find(&model.{{.StructName}}{},"parent_id = ?",{{.PrimaryField.FieldJson}}).Count(&count).Error + if count > 0 { + return errors.New("此节点存在子节点不允许删除") + } + if err != nil { + return err + } + {{- end }} + + {{- if .AutoCreateResource }} + err = {{$db}}.Transaction(func(tx *gorm.DB) error { + if err := tx.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).Update("deleted_by", userID).Error; err != nil { + return err + } + if err = tx.Delete(&model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error; err != nil { + return err + } + return nil + }) + {{- else }} + err = {{$db}}.Delete(&model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} = ?",{{.PrimaryField.FieldJson}}).Error + {{- end }} + return err +} + +// Delete{{.StructName}}ByIds 批量删除{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Delete{{.StructName}}ByIds(ctx context.Context, {{.PrimaryField.FieldJson}}s []string {{- if .AutoCreateResource }},deleted_by uint{{- end}}) (err error) { + {{- if .AutoCreateResource }} + err = {{$db}}.Transaction(func(tx *gorm.DB) error { + if err := tx.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Update("deleted_by", deleted_by).Error; err != nil { + return err + } + if err := tx.Where("{{.PrimaryField.ColumnName}} in ?", {{.PrimaryField.FieldJson}}s).Delete(&model.{{.StructName}}{}).Error; err != nil { + return err + } + return nil + }) + {{- else}} + err = {{$db}}.Delete(&[]model.{{.StructName}}{},"{{.PrimaryField.ColumnName}} in ?",{{.PrimaryField.FieldJson}}s).Error + {{- end}} + return err +} + +// Update{{.StructName}} 更新{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Update{{.StructName}}(ctx context.Context, {{.Abbreviation}} model.{{.StructName}}) (err error) { + err = {{$db}}.Model(&model.{{.StructName}}{}).Where("{{.PrimaryField.ColumnName}} = ?",{{.Abbreviation}}.{{.PrimaryField.FieldName}}).Updates(&{{.Abbreviation}}).Error + return err +} + +// Get{{.StructName}} 根据{{.PrimaryField.FieldJson}}获取{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Get{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string) ({{.Abbreviation}} model.{{.StructName}}, err error) { + err = {{$db}}.Where("{{.PrimaryField.ColumnName}} = ?", {{.PrimaryField.FieldJson}}).First(&{{.Abbreviation}}).Error + return +} + + +{{- if .IsTree }} +// Get{{.StructName}}InfoList 分页获取{{.Description}}记录,Tree模式下不添加分页和搜索 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context) (list []*model.{{.StructName}},err error) { + // 创建db + db := {{$db}}.Model(&model.{{.StructName}}{}) + var {{.Abbreviation}}s []*model.{{.StructName}} + + err = db.Find(&{{.Abbreviation}}s).Error + + return utils.BuildTree({{.Abbreviation}}s), err +} +{{- else }} +// Get{{.StructName}}InfoList 分页获取{{.Description}}记录 +// Author [yourname](https://github.com/yourname) +func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info request.{{.StructName}}Search) (list []model.{{.StructName}}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := {{$db}}.Model(&model.{{.StructName}}{}) + var {{.Abbreviation}}s []model.{{.StructName}} + // 如果有条件搜索 下方会自动创建搜索语句 +{{- if .GvaModel }} + if info.StartCreatedAt !=nil && info.EndCreatedAt !=nil { + db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt) + } +{{- end }} + {{- range .Fields}} + {{- if .FieldSearchType}} + {{- if or (eq .FieldType "enum") (eq .FieldType "pictures") (eq .FieldType "picture") (eq .FieldType "video") (eq .FieldType "json") }} + if info.{{.FieldName}} != "" { + {{- if or (eq .FieldType "enum")}} + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+ {{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) + {{- else}} + // 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务 + {{- end}} + } + {{- else if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + if info.Start{{.FieldName}} != nil && info.End{{.FieldName}} != nil { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ? AND ? ",info.Start{{.FieldName}},info.End{{.FieldName}}) + } + {{- else}} + if info.{{.FieldName}} != nil{{- if eq .FieldType "string" }} && *info.{{.FieldName}} != ""{{- end }} { + db = db.Where("{{.ColumnName}} {{.FieldSearchType}} ?",{{if eq .FieldSearchType "LIKE"}}"%"+{{ end }}*info.{{.FieldName}}{{if eq .FieldSearchType "LIKE"}}+"%"{{ end }}) + } + {{- end }} + {{- end }} + {{- end }} + err = db.Count(&total).Error + if err!=nil { + return + } + {{- if .NeedSort}} + var OrderStr string + orderMap := make(map[string]bool) + {{- range .Fields}} + {{- if .Sort}} + orderMap["{{.ColumnName}}"] = true + {{- end}} + {{- end}} + if orderMap[info.Sort] { + OrderStr = info.Sort + if info.Order == "descending" { + OrderStr = OrderStr + " desc" + } + db = db.Order(OrderStr) + } + {{- end}} + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + err = db.Find(&{{.Abbreviation}}s).Error + return {{.Abbreviation}}s, total, err +} +{{- end }} +{{- if .HasDataSource }} +func (s *{{.Abbreviation}})Get{{.StructName}}DataSource(ctx context.Context) (res map[string][]map[string]any, err error) { + res = make(map[string][]map[string]any) + {{range $key, $value := .DataSourceMap}} + {{$key}} := make([]map[string]any, 0) + {{ $dataDB := "" }} + {{- if eq $value.DBName "" }} + {{ $dataDB = $db }} + {{- else}} + {{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }} + {{- end}} + {{$dataDB}}.Table("{{$value.Table}}"){{- if $value.HasDeletedAt}}.Where("deleted_at IS NULL"){{ end }}.Select("{{$value.Label}} as label,{{$value.Value}} as value").Scan(&{{$key}}) + res["{{$key}}"] = {{$key}} + {{- end }} + return +} +{{- end }} +{{- end }} + +func (s *{{.Abbreviation}})Get{{.StructName}}Public(ctx context.Context) { + +} +{{- end }} diff --git a/resource/plugin/web/api/api.js.template b/resource/plugin/web/api/api.js.template new file mode 100644 index 0000000..0462fde --- /dev/null +++ b/resource/plugin/web/api/api.js.template @@ -0,0 +1,127 @@ +import service from '@/utils/request' +{{- if not .OnlyTemplate}} +// @Tags {{.StructName}} +// @Summary 创建{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "创建{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}" +// @Router /{{.Abbreviation}}/create{{.StructName}} [post] +export const create{{.StructName}} = (data) => { + return service({ + url: '/{{.Abbreviation}}/create{{.StructName}}', + method: 'post', + data + }) +} + +// @Tags {{.StructName}} +// @Summary 删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "删除{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete] +export const delete{{.StructName}} = (params) => { + return service({ + url: '/{{.Abbreviation}}/delete{{.StructName}}', + method: 'delete', + params + }) +} + +// @Tags {{.StructName}} +// @Summary 批量删除{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body request.IdsReq true "批量删除{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}" +// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete] +export const delete{{.StructName}}ByIds = (params) => { + return service({ + url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds', + method: 'delete', + params + }) +} + +// @Tags {{.StructName}} +// @Summary 更新{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data body model.{{.StructName}} true "更新{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}" +// @Router /{{.Abbreviation}}/update{{.StructName}} [put] +export const update{{.StructName}} = (data) => { + return service({ + url: '/{{.Abbreviation}}/update{{.StructName}}', + method: 'put', + data + }) +} + +// @Tags {{.StructName}} +// @Summary 用id查询{{.Description}} +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query model.{{.StructName}} true "用id查询{{.Description}}" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /{{.Abbreviation}}/find{{.StructName}} [get] +export const find{{.StructName}} = (params) => { + return service({ + url: '/{{.Abbreviation}}/find{{.StructName}}', + method: 'get', + params + }) +} + +// @Tags {{.StructName}} +// @Summary 分页获取{{.Description}}列表 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Param data query request.PageInfo true "分页获取{{.Description}}列表" +// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}" +// @Router /{{.Abbreviation}}/get{{.StructName}}List [get] +export const get{{.StructName}}List = (params) => { + return service({ + url: '/{{.Abbreviation}}/get{{.StructName}}List', + method: 'get', + params + }) +} + +{{- if .HasDataSource}} +// @Tags {{.StructName}} +// @Summary 获取数据源 +// @Security ApiKeyAuth +// @Accept application/json +// @Produce application/json +// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}" +// @Router /{{.Abbreviation}}/find{{.StructName}}DataSource [get] +export const get{{.StructName}}DataSource = () => { + return service({ + url: '/{{.Abbreviation}}/get{{.StructName}}DataSource', + method: 'get', + }) +} +{{- end}} +{{- end}} +// @Tags {{.StructName}} +// @Summary 不需要鉴权的{{.Description}}接口 +// @Accept application/json +// @Produce application/json +// @Param data query request.{{.StructName}}Search true "分页获取{{.Description}}列表" +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get] +export const get{{.StructName}}Public = () => { + return service({ + url: '/{{.Abbreviation}}/get{{.StructName}}Public', + method: 'get', + }) +} diff --git a/resource/plugin/web/form/form.vue.template b/resource/plugin/web/form/form.vue.template new file mode 100644 index 0000000..e45b375 --- /dev/null +++ b/resource/plugin/web/form/form.vue.template @@ -0,0 +1,464 @@ +{{- if .IsAdd }} +// 新增表单中增加如下代码 +{{- range .Fields}} + {{- if .Form}} + + {{- if .CheckDataSource}} + + + + {{- else }} + {{- if eq .FieldType "bool" }} + + {{- end }} + {{- if eq .FieldType "string" }} + {{- if .DictType}} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- if eq .FieldType "richtext" }} + + {{- end }} + {{- if eq .FieldType "json" }} + // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取 + {{"{{"}} formData.{{.FieldJson}} {{"}}"}} + {{- end }} + {{- if eq .FieldType "array" }} + + {{- end }} + {{- if eq .FieldType "int" }} + + {{- end }} + {{- if eq .FieldType "time.Time" }} + + {{- end }} + {{- if eq .FieldType "float64" }} + + {{- end }} + {{- if eq .FieldType "enum" }} + + + + {{- end }} + {{- if eq .FieldType "picture" }} + + {{- end }} + {{- if eq .FieldType "pictures" }} + + {{- end }} + {{- if eq .FieldType "video" }} + + {{- end }} + {{- if eq .FieldType "file" }} + + {{- end }} + {{- end }} + + {{- end }} + {{- end }} + +// 字典增加如下代码 + {{- range $index, $element := .DictTypes}} +const {{ $element }}Options = ref([]) + {{- end }} + +// init方法中增加如下调用 + +{{- range $index, $element := .DictTypes }} + {{ $element }}Options.value = await getDictFunc('{{$element}}') +{{- end }} + +// 基础formData结构增加如下字段 +{{- range .Fields}} + {{- if .Form}} + {{- if eq .FieldType "bool" }} +{{.FieldJson}}: false, + {{- end }} + {{- if eq .FieldType "string" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "richtext" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "int" }} +{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }}, + {{- end }} + {{- if eq .FieldType "time.Time" }} +{{.FieldJson}}: new Date(), + {{- end }} + {{- if eq .FieldType "float64" }} +{{.FieldJson}}: 0, + {{- end }} + {{- if eq .FieldType "picture" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "video" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "pictures" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "file" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "json" }} +{{.FieldJson}}: {}, + {{- end }} + {{- if eq .FieldType "array" }} +{{.FieldJson}}: [], + {{- end }} + {{- end }} + {{- end }} +// 验证规则中增加如下字段 + +{{- range .Fields }} + {{- if .Form }} + {{- if eq .Require true }} +{{.FieldJson }} : [{ + required: true, + message: '{{ .ErrorText }}', + trigger: ['input','blur'], +}, + {{- if eq .FieldType "string" }} +{ + whitespace: true, + message: '不能只输入空格', + trigger: ['input', 'blur'], +} + {{- end }} +], + {{- end }} + {{- end }} + {{- end }} + +{{- if .HasDataSource }} +// 请引用 +get{{.StructName}}DataSource, + +// 获取数据源 +const dataSource = ref([]) +const getDataSourceFunc = async()=>{ + const res = await get{{.StructName}}DataSource() + if (res.code === 0) { + dataSource.value = res.data + } +} +getDataSourceFunc() +{{- end }} +{{- else }} +{{- if not .OnlyTemplate }} + + + + + +{{- else }} + + + +{{- end }} +{{- end }} diff --git a/resource/plugin/web/view/view.vue.template b/resource/plugin/web/view/view.vue.template new file mode 100644 index 0000000..aa13244 --- /dev/null +++ b/resource/plugin/web/view/view.vue.template @@ -0,0 +1,1361 @@ +{{- $global := . }} +{{- $templateID := printf "%s_%s" .Package .StructName }} +{{- if .IsAdd }} +// 请在搜索条件中增加如下代码 +{{- range .Fields}} {{- if .FieldSearchType}} {{- if eq .FieldType "bool" }} + + + + + + + + + {{- else if .DictType}} + + + + + + {{- else if .CheckDataSource}} + + + + + + {{- else}} + + {{- if eq .FieldType "float64" "int"}} + {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + +— + + {{- else}} + + {{- end}} + {{- else if eq .FieldType "time.Time"}} + {{if eq .FieldSearchType "BETWEEN" "NOT BETWEEN"}} + + +— + + {{- else}} + + {{- end}} + {{- else}} + + {{- end}} +{{ end }}{{ end }}{{ end }} + + +// 表格增加如下列代码 + +{{- range .Fields}} + {{- if .Table}} + {{- if .CheckDataSource }} + + + + {{- else if .DictType}} + + + + {{- else if eq .FieldType "bool" }} + + + + {{- else if eq .FieldType "time.Time" }} + + + + {{- else if eq .FieldType "picture" }} + + + + {{- else if eq .FieldType "pictures" }} + + + + {{- else if eq .FieldType "video" }} + + + + {{- else if eq .FieldType "richtext" }} + + + + {{- else if eq .FieldType "file" }} + + + + {{- else if eq .FieldType "json" }} + + + + {{- else if eq .FieldType "array" }} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- end }} + +// 新增表单中增加如下代码 +{{- range .Fields}} + {{- if .Form}} + + {{- if .CheckDataSource}} + + + + {{- else }} + {{- if eq .FieldType "bool" }} + + {{- end }} + {{- if eq .FieldType "string" }} + {{- if .DictType}} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- if eq .FieldType "richtext" }} + + {{- end }} + {{- if eq .FieldType "json" }} + // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取 + {{"{{"}} formData.{{.FieldJson}} {{"}}"}} + {{- end }} + {{- if eq .FieldType "array" }} + {{- if .DictType}} + + + + {{- else }} + + {{- end }} + {{- end }} + {{- if eq .FieldType "int" }} + + {{- end }} + {{- if eq .FieldType "time.Time" }} + + {{- end }} + {{- if eq .FieldType "float64" }} + + {{- end }} + {{- if eq .FieldType "enum" }} + + + + {{- end }} + {{- if eq .FieldType "picture" }} + + {{- end }} + {{- if eq .FieldType "pictures" }} + + {{- end }} + {{- if eq .FieldType "video" }} + + {{- end }} + {{- if eq .FieldType "file" }} + + {{- end }} + {{- end }} + + {{- end }} + {{- end }} + +// 查看抽屉中增加如下代码 + +{{- range .Fields}} + {{- if .Desc }} + +{{- if .CheckDataSource }} + + {{- else if and (ne .FieldType "picture" ) (ne .FieldType "pictures" ) (ne .FieldType "file" ) (ne .FieldType "array" ) }} + {{"{{"}} detailFrom.{{.FieldJson}} {{"}}"}} + {{- else }} + {{- if eq .FieldType "picture" }} + + {{- end }} + {{- if eq .FieldType "array" }} + + {{- end }} + {{- if eq .FieldType "pictures" }} + + {{- end }} + {{- if eq .FieldType "richtext" }} + + {{- end }} + {{- if eq .FieldType "file" }} +
+ + + {{"{{"}}item.name{{"}}"}} + +
+ {{- end }} + {{- end }} +
+ {{- end }} + {{- end }} + +// 字典增加如下代码 + {{- range $index, $element := .DictTypes}} +const {{ $element }}Options = ref([]) + {{- end }} + +// setOptions方法中增加如下调用 + +{{- range $index, $element := .DictTypes }} + {{ $element }}Options.value = await getDictFunc('{{$element}}') +{{- end }} + +// 基础formData结构(变量处和关闭表单处)增加如下字段 +{{- range .Fields}} + {{- if .Form}} + {{- if eq .FieldType "bool" }} +{{.FieldJson}}: false, + {{- end }} + {{- if eq .FieldType "string" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "richtext" }} +{{.FieldJson}}: '', + {{- end }} + {{- if eq .FieldType "int" }} +{{.FieldJson}}: {{- if .DataSource}} undefined{{ else }} 0{{- end }}, + {{- end }} + {{- if eq .FieldType "time.Time" }} +{{.FieldJson}}: new Date(), + {{- end }} + {{- if eq .FieldType "float64" }} +{{.FieldJson}}: 0, + {{- end }} + {{- if eq .FieldType "picture" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "video" }} +{{.FieldJson}}: "", + {{- end }} + {{- if eq .FieldType "pictures" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "file" }} +{{.FieldJson}}: [], + {{- end }} + {{- if eq .FieldType "json" }} +{{.FieldJson}}: {}, + {{- end }} + {{- if eq .FieldType "array" }} +{{.FieldJson}}: [], + {{- end }} + {{- end }} + {{- end }} +// 验证规则中增加如下字段 + +{{- range .Fields }} + {{- if .Form }} + {{- if eq .Require true }} +{{.FieldJson }} : [{ + required: true, + message: '{{ .ErrorText }}', + trigger: ['input','blur'], +}, + {{- if eq .FieldType "string" }} +{ + whitespace: true, + message: '不能只输入空格', + trigger: ['input', 'blur'], +} + {{- end }} +], + {{- end }} + {{- end }} + {{- end }} + + + +{{- if .HasDataSource }} +// 请引用 +get{{.StructName}}DataSource, + +// 获取数据源 +const dataSource = ref({}) +const getDataSourceFunc = async()=>{ + const res = await get{{.StructName}}DataSource() + if (res.code === 0) { + dataSource.value = res.data || [] + } +} +getDataSourceFunc() +{{- end }} + +{{- else }} + +{{- if not .OnlyTemplate}} + + + + + +{{- else}} + + + +{{- end }} + +{{- end }} diff --git a/router/enter.go b/router/enter.go new file mode 100644 index 0000000..af6314d --- /dev/null +++ b/router/enter.go @@ -0,0 +1,13 @@ +package router + +import ( + "git.echol.cn/loser/xiecheng_server/router/example" + "git.echol.cn/loser/xiecheng_server/router/system" +) + +var RouterGroupApp = new(RouterGroup) + +type RouterGroup struct { + System system.RouterGroup + Example example.RouterGroup +} diff --git a/router/example/enter.go b/router/example/enter.go new file mode 100644 index 0000000..81b038a --- /dev/null +++ b/router/example/enter.go @@ -0,0 +1,17 @@ +package example + +import ( + api "git.echol.cn/loser/xiecheng_server/api/v1" +) + +type RouterGroup struct { + CustomerRouter + FileUploadAndDownloadRouter + AttachmentCategoryRouter +} + +var ( + exaCustomerApi = api.ApiGroupApp.ExampleApiGroup.CustomerApi + exaFileUploadAndDownloadApi = api.ApiGroupApp.ExampleApiGroup.FileUploadAndDownloadApi + attachmentCategoryApi = api.ApiGroupApp.ExampleApiGroup.AttachmentCategoryApi +) diff --git a/router/example/exa_attachment_category.go b/router/example/exa_attachment_category.go new file mode 100644 index 0000000..4900292 --- /dev/null +++ b/router/example/exa_attachment_category.go @@ -0,0 +1,16 @@ +package example + +import ( + "github.com/gin-gonic/gin" +) + +type AttachmentCategoryRouter struct{} + +func (r *AttachmentCategoryRouter) InitAttachmentCategoryRouterRouter(Router *gin.RouterGroup) { + router := Router.Group("attachmentCategory") + { + router.GET("getCategoryList", attachmentCategoryApi.GetCategoryList) // 分类列表 + router.POST("addCategory", attachmentCategoryApi.AddCategory) // 添加/编辑分类 + router.POST("deleteCategory", attachmentCategoryApi.DeleteCategory) // 删除分类 + } +} diff --git a/router/example/exa_customer.go b/router/example/exa_customer.go new file mode 100644 index 0000000..e981b08 --- /dev/null +++ b/router/example/exa_customer.go @@ -0,0 +1,22 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type CustomerRouter struct{} + +func (e *CustomerRouter) InitCustomerRouter(Router *gin.RouterGroup) { + customerRouter := Router.Group("customer").Use(middleware.OperationRecord()) + customerRouterWithoutRecord := Router.Group("customer") + { + customerRouter.POST("customer", exaCustomerApi.CreateExaCustomer) // 创建客户 + customerRouter.PUT("customer", exaCustomerApi.UpdateExaCustomer) // 更新客户 + customerRouter.DELETE("customer", exaCustomerApi.DeleteExaCustomer) // 删除客户 + } + { + customerRouterWithoutRecord.GET("customer", exaCustomerApi.GetExaCustomer) // 获取单一客户信息 + customerRouterWithoutRecord.GET("customerList", exaCustomerApi.GetExaCustomerList) // 获取客户列表 + } +} diff --git a/router/example/exa_file_upload_and_download.go b/router/example/exa_file_upload_and_download.go new file mode 100644 index 0000000..84f6ecd --- /dev/null +++ b/router/example/exa_file_upload_and_download.go @@ -0,0 +1,22 @@ +package example + +import ( + "github.com/gin-gonic/gin" +) + +type FileUploadAndDownloadRouter struct{} + +func (e *FileUploadAndDownloadRouter) InitFileUploadAndDownloadRouter(Router *gin.RouterGroup) { + fileUploadAndDownloadRouter := Router.Group("fileUploadAndDownload") + { + fileUploadAndDownloadRouter.POST("upload", exaFileUploadAndDownloadApi.UploadFile) // 上传文件 + fileUploadAndDownloadRouter.POST("getFileList", exaFileUploadAndDownloadApi.GetFileList) // 获取上传文件列表 + fileUploadAndDownloadRouter.POST("deleteFile", exaFileUploadAndDownloadApi.DeleteFile) // 删除指定文件 + fileUploadAndDownloadRouter.POST("editFileName", exaFileUploadAndDownloadApi.EditFileName) // 编辑文件名或者备注 + fileUploadAndDownloadRouter.POST("breakpointContinue", exaFileUploadAndDownloadApi.BreakpointContinue) // 断点续传 + fileUploadAndDownloadRouter.GET("findFile", exaFileUploadAndDownloadApi.FindFile) // 查询当前文件成功的切片 + fileUploadAndDownloadRouter.POST("breakpointContinueFinish", exaFileUploadAndDownloadApi.BreakpointContinueFinish) // 切片传输完成 + fileUploadAndDownloadRouter.POST("removeChunk", exaFileUploadAndDownloadApi.RemoveChunk) // 删除切片 + fileUploadAndDownloadRouter.POST("importURL", exaFileUploadAndDownloadApi.ImportURL) // 导入URL + } +} diff --git a/router/system/enter.go b/router/system/enter.go new file mode 100644 index 0000000..5c66e4d --- /dev/null +++ b/router/system/enter.go @@ -0,0 +1,44 @@ +package system + +import api "git.echol.cn/loser/xiecheng_server/api/v1" + +type RouterGroup struct { + ApiRouter + JwtRouter + SysRouter + BaseRouter + InitRouter + MenuRouter + UserRouter + CasbinRouter + AutoCodeRouter + AuthorityRouter + DictionaryRouter + OperationRecordRouter + DictionaryDetailRouter + AuthorityBtnRouter + SysExportTemplateRouter + SysParamsRouter +} + +var ( + dbApi = api.ApiGroupApp.SystemApiGroup.DBApi + jwtApi = api.ApiGroupApp.SystemApiGroup.JwtApi + baseApi = api.ApiGroupApp.SystemApiGroup.BaseApi + casbinApi = api.ApiGroupApp.SystemApiGroup.CasbinApi + systemApi = api.ApiGroupApp.SystemApiGroup.SystemApi + sysParamsApi = api.ApiGroupApp.SystemApiGroup.SysParamsApi + autoCodeApi = api.ApiGroupApp.SystemApiGroup.AutoCodeApi + authorityApi = api.ApiGroupApp.SystemApiGroup.AuthorityApi + apiRouterApi = api.ApiGroupApp.SystemApiGroup.SystemApiApi + dictionaryApi = api.ApiGroupApp.SystemApiGroup.DictionaryApi + authorityBtnApi = api.ApiGroupApp.SystemApiGroup.AuthorityBtnApi + authorityMenuApi = api.ApiGroupApp.SystemApiGroup.AuthorityMenuApi + autoCodePluginApi = api.ApiGroupApp.SystemApiGroup.AutoCodePluginApi + autocodeHistoryApi = api.ApiGroupApp.SystemApiGroup.AutoCodeHistoryApi + operationRecordApi = api.ApiGroupApp.SystemApiGroup.OperationRecordApi + autoCodePackageApi = api.ApiGroupApp.SystemApiGroup.AutoCodePackageApi + dictionaryDetailApi = api.ApiGroupApp.SystemApiGroup.DictionaryDetailApi + autoCodeTemplateApi = api.ApiGroupApp.SystemApiGroup.AutoCodeTemplateApi + exportTemplateApi = api.ApiGroupApp.SystemApiGroup.SysExportTemplateApi +) diff --git a/router/system/sys_api.go b/router/system/sys_api.go new file mode 100644 index 0000000..d87b1f2 --- /dev/null +++ b/router/system/sys_api.go @@ -0,0 +1,33 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type ApiRouter struct{} + +func (s *ApiRouter) InitApiRouter(Router *gin.RouterGroup, RouterPub *gin.RouterGroup) { + apiRouter := Router.Group("api").Use(middleware.OperationRecord()) + apiRouterWithoutRecord := Router.Group("api") + + apiPublicRouterWithoutRecord := RouterPub.Group("api") + { + apiRouter.GET("getApiGroups", apiRouterApi.GetApiGroups) // 获取路由组 + apiRouter.GET("syncApi", apiRouterApi.SyncApi) // 同步Api + apiRouter.POST("ignoreApi", apiRouterApi.IgnoreApi) // 忽略Api + apiRouter.POST("enterSyncApi", apiRouterApi.EnterSyncApi) // 确认同步Api + apiRouter.POST("createApi", apiRouterApi.CreateApi) // 创建Api + apiRouter.POST("deleteApi", apiRouterApi.DeleteApi) // 删除Api + apiRouter.POST("getApiById", apiRouterApi.GetApiById) // 获取单条Api消息 + apiRouter.POST("updateApi", apiRouterApi.UpdateApi) // 更新api + apiRouter.DELETE("deleteApisByIds", apiRouterApi.DeleteApisByIds) // 删除选中api + } + { + apiRouterWithoutRecord.POST("getAllApis", apiRouterApi.GetAllApis) // 获取所有api + apiRouterWithoutRecord.POST("getApiList", apiRouterApi.GetApiList) // 获取Api列表 + } + { + apiPublicRouterWithoutRecord.GET("freshCasbin", apiRouterApi.FreshCasbin) // 刷新casbin权限 + } +} diff --git a/router/system/sys_authority.go b/router/system/sys_authority.go new file mode 100644 index 0000000..71edbfa --- /dev/null +++ b/router/system/sys_authority.go @@ -0,0 +1,23 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type AuthorityRouter struct{} + +func (s *AuthorityRouter) InitAuthorityRouter(Router *gin.RouterGroup) { + authorityRouter := Router.Group("authority").Use(middleware.OperationRecord()) + authorityRouterWithoutRecord := Router.Group("authority") + { + authorityRouter.POST("createAuthority", authorityApi.CreateAuthority) // 创建角色 + authorityRouter.POST("deleteAuthority", authorityApi.DeleteAuthority) // 删除角色 + authorityRouter.PUT("updateAuthority", authorityApi.UpdateAuthority) // 更新角色 + authorityRouter.POST("copyAuthority", authorityApi.CopyAuthority) // 拷贝角色 + authorityRouter.POST("setDataAuthority", authorityApi.SetDataAuthority) // 设置角色资源权限 + } + { + authorityRouterWithoutRecord.POST("getAuthorityList", authorityApi.GetAuthorityList) // 获取角色列表 + } +} diff --git a/router/system/sys_authority_btn.go b/router/system/sys_authority_btn.go new file mode 100644 index 0000000..370db85 --- /dev/null +++ b/router/system/sys_authority_btn.go @@ -0,0 +1,19 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type AuthorityBtnRouter struct{} + +var AuthorityBtnRouterApp = new(AuthorityBtnRouter) + +func (s *AuthorityBtnRouter) InitAuthorityBtnRouterRouter(Router *gin.RouterGroup) { + // authorityRouter := Router.Group("authorityBtn").Use(middleware.OperationRecord()) + authorityRouterWithoutRecord := Router.Group("authorityBtn") + { + authorityRouterWithoutRecord.POST("getAuthorityBtn", authorityBtnApi.GetAuthorityBtn) + authorityRouterWithoutRecord.POST("setAuthorityBtn", authorityBtnApi.SetAuthorityBtn) + authorityRouterWithoutRecord.POST("canRemoveAuthorityBtn", authorityBtnApi.CanRemoveAuthorityBtn) + } +} diff --git a/router/system/sys_auto_code.go b/router/system/sys_auto_code.go new file mode 100644 index 0000000..e25e1ce --- /dev/null +++ b/router/system/sys_auto_code.go @@ -0,0 +1,40 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type AutoCodeRouter struct{} + +func (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPublic *gin.RouterGroup) { + autoCodeRouter := Router.Group("autoCode") + publicAutoCodeRouter := RouterPublic.Group("autoCode") + { + autoCodeRouter.GET("getDB", autoCodeApi.GetDB) // 获取数据库 + autoCodeRouter.GET("getTables", autoCodeApi.GetTables) // 获取对应数据库的表 + autoCodeRouter.GET("getColumn", autoCodeApi.GetColumn) // 获取指定表所有字段信息 + } + { + autoCodeRouter.POST("preview", autoCodeTemplateApi.Preview) // 获取自动创建代码预览 + autoCodeRouter.POST("createTemp", autoCodeTemplateApi.Create) // 创建自动化代码 + autoCodeRouter.POST("addFunc", autoCodeTemplateApi.AddFunc) // 为代码插入方法 + } + { + autoCodeRouter.POST("getPackage", autoCodePackageApi.All) // 获取package包 + autoCodeRouter.POST("delPackage", autoCodePackageApi.Delete) // 删除package包 + autoCodeRouter.POST("createPackage", autoCodePackageApi.Create) // 创建package包 + } + { + autoCodeRouter.GET("getTemplates", autoCodePackageApi.Templates) // 创建package包 + } + { + autoCodeRouter.POST("pubPlug", autoCodePluginApi.Packaged) // 打包插件 + autoCodeRouter.POST("installPlugin", autoCodePluginApi.Install) // 自动安装插件 + + } + { + publicAutoCodeRouter.POST("llmAuto", autoCodeApi.LLMAuto) + publicAutoCodeRouter.POST("initMenu", autoCodePluginApi.InitMenu) // 同步插件菜单 + publicAutoCodeRouter.POST("initAPI", autoCodePluginApi.InitAPI) // 同步插件API + } +} diff --git a/router/system/sys_auto_code_history.go b/router/system/sys_auto_code_history.go new file mode 100644 index 0000000..42a2bef --- /dev/null +++ b/router/system/sys_auto_code_history.go @@ -0,0 +1,17 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type AutoCodeHistoryRouter struct{} + +func (s *AutoCodeRouter) InitAutoCodeHistoryRouter(Router *gin.RouterGroup) { + autoCodeHistoryRouter := Router.Group("autoCode") + { + autoCodeHistoryRouter.POST("getMeta", autocodeHistoryApi.First) // 根据id获取meta信息 + autoCodeHistoryRouter.POST("rollback", autocodeHistoryApi.RollBack) // 回滚 + autoCodeHistoryRouter.POST("delSysHistory", autocodeHistoryApi.Delete) // 删除回滚记录 + autoCodeHistoryRouter.POST("getSysHistory", autocodeHistoryApi.GetList) // 获取回滚记录分页 + } +} diff --git a/router/system/sys_base.go b/router/system/sys_base.go new file mode 100644 index 0000000..7d959bb --- /dev/null +++ b/router/system/sys_base.go @@ -0,0 +1,16 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type BaseRouter struct{} + +func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) (R gin.IRoutes) { + baseRouter := Router.Group("base") + { + baseRouter.POST("login", baseApi.Login) + baseRouter.POST("captcha", baseApi.Captcha) + } + return baseRouter +} diff --git a/router/system/sys_casbin.go b/router/system/sys_casbin.go new file mode 100644 index 0000000..0d77f5c --- /dev/null +++ b/router/system/sys_casbin.go @@ -0,0 +1,19 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type CasbinRouter struct{} + +func (s *CasbinRouter) InitCasbinRouter(Router *gin.RouterGroup) { + casbinRouter := Router.Group("casbin").Use(middleware.OperationRecord()) + casbinRouterWithoutRecord := Router.Group("casbin") + { + casbinRouter.POST("updateCasbin", casbinApi.UpdateCasbin) + } + { + casbinRouterWithoutRecord.POST("getPolicyPathByAuthorityId", casbinApi.GetPolicyPathByAuthorityId) + } +} diff --git a/router/system/sys_dictionary.go b/router/system/sys_dictionary.go new file mode 100644 index 0000000..b510054 --- /dev/null +++ b/router/system/sys_dictionary.go @@ -0,0 +1,22 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type DictionaryRouter struct{} + +func (s *DictionaryRouter) InitSysDictionaryRouter(Router *gin.RouterGroup) { + sysDictionaryRouter := Router.Group("sysDictionary").Use(middleware.OperationRecord()) + sysDictionaryRouterWithoutRecord := Router.Group("sysDictionary") + { + sysDictionaryRouter.POST("createSysDictionary", dictionaryApi.CreateSysDictionary) // 新建SysDictionary + sysDictionaryRouter.DELETE("deleteSysDictionary", dictionaryApi.DeleteSysDictionary) // 删除SysDictionary + sysDictionaryRouter.PUT("updateSysDictionary", dictionaryApi.UpdateSysDictionary) // 更新SysDictionary + } + { + sysDictionaryRouterWithoutRecord.GET("findSysDictionary", dictionaryApi.FindSysDictionary) // 根据ID获取SysDictionary + sysDictionaryRouterWithoutRecord.GET("getSysDictionaryList", dictionaryApi.GetSysDictionaryList) // 获取SysDictionary列表 + } +} diff --git a/router/system/sys_dictionary_detail.go b/router/system/sys_dictionary_detail.go new file mode 100644 index 0000000..8b09f0d --- /dev/null +++ b/router/system/sys_dictionary_detail.go @@ -0,0 +1,22 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type DictionaryDetailRouter struct{} + +func (s *DictionaryDetailRouter) InitSysDictionaryDetailRouter(Router *gin.RouterGroup) { + dictionaryDetailRouter := Router.Group("sysDictionaryDetail").Use(middleware.OperationRecord()) + dictionaryDetailRouterWithoutRecord := Router.Group("sysDictionaryDetail") + { + dictionaryDetailRouter.POST("createSysDictionaryDetail", dictionaryDetailApi.CreateSysDictionaryDetail) // 新建SysDictionaryDetail + dictionaryDetailRouter.DELETE("deleteSysDictionaryDetail", dictionaryDetailApi.DeleteSysDictionaryDetail) // 删除SysDictionaryDetail + dictionaryDetailRouter.PUT("updateSysDictionaryDetail", dictionaryDetailApi.UpdateSysDictionaryDetail) // 更新SysDictionaryDetail + } + { + dictionaryDetailRouterWithoutRecord.GET("findSysDictionaryDetail", dictionaryDetailApi.FindSysDictionaryDetail) // 根据ID获取SysDictionaryDetail + dictionaryDetailRouterWithoutRecord.GET("getSysDictionaryDetailList", dictionaryDetailApi.GetSysDictionaryDetailList) // 获取SysDictionaryDetail列表 + } +} diff --git a/router/system/sys_export_template.go b/router/system/sys_export_template.go new file mode 100644 index 0000000..f38a598 --- /dev/null +++ b/router/system/sys_export_template.go @@ -0,0 +1,34 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type SysExportTemplateRouter struct { +} + +// InitSysExportTemplateRouter 初始化 导出模板 路由信息 +func (s *SysExportTemplateRouter) InitSysExportTemplateRouter(Router *gin.RouterGroup, pubRouter *gin.RouterGroup) { + sysExportTemplateRouter := Router.Group("sysExportTemplate").Use(middleware.OperationRecord()) + sysExportTemplateRouterWithoutRecord := Router.Group("sysExportTemplate") + sysExportTemplateRouterWithoutAuth := pubRouter.Group("sysExportTemplate") + + { + sysExportTemplateRouter.POST("createSysExportTemplate", exportTemplateApi.CreateSysExportTemplate) // 新建导出模板 + sysExportTemplateRouter.DELETE("deleteSysExportTemplate", exportTemplateApi.DeleteSysExportTemplate) // 删除导出模板 + sysExportTemplateRouter.DELETE("deleteSysExportTemplateByIds", exportTemplateApi.DeleteSysExportTemplateByIds) // 批量删除导出模板 + sysExportTemplateRouter.PUT("updateSysExportTemplate", exportTemplateApi.UpdateSysExportTemplate) // 更新导出模板 + sysExportTemplateRouter.POST("importExcel", exportTemplateApi.ImportExcel) // 导入excel模板数据 + } + { + sysExportTemplateRouterWithoutRecord.GET("findSysExportTemplate", exportTemplateApi.FindSysExportTemplate) // 根据ID获取导出模板 + sysExportTemplateRouterWithoutRecord.GET("getSysExportTemplateList", exportTemplateApi.GetSysExportTemplateList) // 获取导出模板列表 + sysExportTemplateRouterWithoutRecord.GET("exportExcel", exportTemplateApi.ExportExcel) // 获取导出token + sysExportTemplateRouterWithoutRecord.GET("exportTemplate", exportTemplateApi.ExportTemplate) // 导出表格模板 + } + { + sysExportTemplateRouterWithoutAuth.GET("exportExcelByToken", exportTemplateApi.ExportExcelByToken) // 通过token导出表格 + sysExportTemplateRouterWithoutAuth.GET("exportTemplateByToken", exportTemplateApi.ExportTemplateByToken) // 通过token导出模板 + } +} diff --git a/router/system/sys_initdb.go b/router/system/sys_initdb.go new file mode 100644 index 0000000..3a6de50 --- /dev/null +++ b/router/system/sys_initdb.go @@ -0,0 +1,15 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type InitRouter struct{} + +func (s *InitRouter) InitInitRouter(Router *gin.RouterGroup) { + initRouter := Router.Group("init") + { + initRouter.POST("initdb", dbApi.InitDB) // 初始化数据库 + initRouter.POST("checkdb", dbApi.CheckDB) // 检测是否需要初始化数据库 + } +} diff --git a/router/system/sys_jwt.go b/router/system/sys_jwt.go new file mode 100644 index 0000000..4716031 --- /dev/null +++ b/router/system/sys_jwt.go @@ -0,0 +1,14 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type JwtRouter struct{} + +func (s *JwtRouter) InitJwtRouter(Router *gin.RouterGroup) { + jwtRouter := Router.Group("jwt") + { + jwtRouter.POST("jsonInBlacklist", jwtApi.JsonInBlacklist) // jwt加入黑名单 + } +} diff --git a/router/system/sys_menu.go b/router/system/sys_menu.go new file mode 100644 index 0000000..caab84a --- /dev/null +++ b/router/system/sys_menu.go @@ -0,0 +1,27 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type MenuRouter struct{} + +func (s *MenuRouter) InitMenuRouter(Router *gin.RouterGroup) (R gin.IRoutes) { + menuRouter := Router.Group("menu").Use(middleware.OperationRecord()) + menuRouterWithoutRecord := Router.Group("menu") + { + menuRouter.POST("addBaseMenu", authorityMenuApi.AddBaseMenu) // 新增菜单 + menuRouter.POST("addMenuAuthority", authorityMenuApi.AddMenuAuthority) // 增加menu和角色关联关系 + menuRouter.POST("deleteBaseMenu", authorityMenuApi.DeleteBaseMenu) // 删除菜单 + menuRouter.POST("updateBaseMenu", authorityMenuApi.UpdateBaseMenu) // 更新菜单 + } + { + menuRouterWithoutRecord.POST("getMenu", authorityMenuApi.GetMenu) // 获取菜单树 + menuRouterWithoutRecord.POST("getMenuList", authorityMenuApi.GetMenuList) // 分页获取基础menu列表 + menuRouterWithoutRecord.POST("getBaseMenuTree", authorityMenuApi.GetBaseMenuTree) // 获取用户动态路由 + menuRouterWithoutRecord.POST("getMenuAuthority", authorityMenuApi.GetMenuAuthority) // 获取指定角色menu + menuRouterWithoutRecord.POST("getBaseMenuById", authorityMenuApi.GetBaseMenuById) // 根据id获取菜单 + } + return menuRouter +} diff --git a/router/system/sys_operation_record.go b/router/system/sys_operation_record.go new file mode 100644 index 0000000..11b841d --- /dev/null +++ b/router/system/sys_operation_record.go @@ -0,0 +1,19 @@ +package system + +import ( + "github.com/gin-gonic/gin" +) + +type OperationRecordRouter struct{} + +func (s *OperationRecordRouter) InitSysOperationRecordRouter(Router *gin.RouterGroup) { + operationRecordRouter := Router.Group("sysOperationRecord") + { + operationRecordRouter.POST("createSysOperationRecord", operationRecordApi.CreateSysOperationRecord) // 新建SysOperationRecord + operationRecordRouter.DELETE("deleteSysOperationRecord", operationRecordApi.DeleteSysOperationRecord) // 删除SysOperationRecord + operationRecordRouter.DELETE("deleteSysOperationRecordByIds", operationRecordApi.DeleteSysOperationRecordByIds) // 批量删除SysOperationRecord + operationRecordRouter.GET("findSysOperationRecord", operationRecordApi.FindSysOperationRecord) // 根据ID获取SysOperationRecord + operationRecordRouter.GET("getSysOperationRecordList", operationRecordApi.GetSysOperationRecordList) // 获取SysOperationRecord列表 + + } +} diff --git a/router/system/sys_params.go b/router/system/sys_params.go new file mode 100644 index 0000000..9f6a339 --- /dev/null +++ b/router/system/sys_params.go @@ -0,0 +1,25 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type SysParamsRouter struct{} + +// InitSysParamsRouter 初始化 参数 路由信息 +func (s *SysParamsRouter) InitSysParamsRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) { + sysParamsRouter := Router.Group("sysParams").Use(middleware.OperationRecord()) + sysParamsRouterWithoutRecord := Router.Group("sysParams") + { + sysParamsRouter.POST("createSysParams", sysParamsApi.CreateSysParams) // 新建参数 + sysParamsRouter.DELETE("deleteSysParams", sysParamsApi.DeleteSysParams) // 删除参数 + sysParamsRouter.DELETE("deleteSysParamsByIds", sysParamsApi.DeleteSysParamsByIds) // 批量删除参数 + sysParamsRouter.PUT("updateSysParams", sysParamsApi.UpdateSysParams) // 更新参数 + } + { + sysParamsRouterWithoutRecord.GET("findSysParams", sysParamsApi.FindSysParams) // 根据ID获取参数 + sysParamsRouterWithoutRecord.GET("getSysParamsList", sysParamsApi.GetSysParamsList) // 获取参数列表 + sysParamsRouterWithoutRecord.GET("getSysParam", sysParamsApi.GetSysParam) // 根据Key获取参数 + } +} diff --git a/router/system/sys_system.go b/router/system/sys_system.go new file mode 100644 index 0000000..17439e9 --- /dev/null +++ b/router/system/sys_system.go @@ -0,0 +1,22 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type SysRouter struct{} + +func (s *SysRouter) InitSystemRouter(Router *gin.RouterGroup) { + sysRouter := Router.Group("system").Use(middleware.OperationRecord()) + sysRouterWithoutRecord := Router.Group("system") + + { + sysRouter.POST("setSystemConfig", systemApi.SetSystemConfig) // 设置配置文件内容 + sysRouter.POST("reloadSystem", systemApi.ReloadSystem) // 重启服务 + } + { + sysRouterWithoutRecord.POST("getSystemConfig", systemApi.GetSystemConfig) // 获取配置文件内容 + sysRouterWithoutRecord.POST("getServerInfo", systemApi.GetServerInfo) // 获取服务器信息 + } +} diff --git a/router/system/sys_user.go b/router/system/sys_user.go new file mode 100644 index 0000000..756519c --- /dev/null +++ b/router/system/sys_user.go @@ -0,0 +1,28 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/middleware" + "github.com/gin-gonic/gin" +) + +type UserRouter struct{} + +func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup) { + userRouter := Router.Group("user").Use(middleware.OperationRecord()) + userRouterWithoutRecord := Router.Group("user") + { + userRouter.POST("admin_register", baseApi.Register) // 管理员注册账号 + userRouter.POST("changePassword", baseApi.ChangePassword) // 用户修改密码 + userRouter.POST("setUserAuthority", baseApi.SetUserAuthority) // 设置用户权限 + userRouter.DELETE("deleteUser", baseApi.DeleteUser) // 删除用户 + userRouter.PUT("setUserInfo", baseApi.SetUserInfo) // 设置用户信息 + userRouter.PUT("setSelfInfo", baseApi.SetSelfInfo) // 设置自身信息 + userRouter.POST("setUserAuthorities", baseApi.SetUserAuthorities) // 设置用户权限组 + userRouter.POST("resetPassword", baseApi.ResetPassword) // 设置用户权限组 + userRouter.PUT("setSelfSetting", baseApi.SetSelfSetting) // 用户界面配置 + } + { + userRouterWithoutRecord.POST("getUserList", baseApi.GetUserList) // 分页获取用户列表 + userRouterWithoutRecord.GET("getUserInfo", baseApi.GetUserInfo) // 获取自身信息 + } +} diff --git a/service/enter.go b/service/enter.go new file mode 100644 index 0000000..5a07b30 --- /dev/null +++ b/service/enter.go @@ -0,0 +1,13 @@ +package service + +import ( + "git.echol.cn/loser/xiecheng_server/service/example" + "git.echol.cn/loser/xiecheng_server/service/system" +) + +var ServiceGroupApp = new(ServiceGroup) + +type ServiceGroup struct { + SystemServiceGroup system.ServiceGroup + ExampleServiceGroup example.ServiceGroup +} diff --git a/service/example/enter.go b/service/example/enter.go new file mode 100644 index 0000000..f7198da --- /dev/null +++ b/service/example/enter.go @@ -0,0 +1,7 @@ +package example + +type ServiceGroup struct { + CustomerService + FileUploadAndDownloadService + AttachmentCategoryService +} diff --git a/service/example/exa_attachment_category.go b/service/example/exa_attachment_category.go new file mode 100644 index 0000000..81d20be --- /dev/null +++ b/service/example/exa_attachment_category.go @@ -0,0 +1,66 @@ +package example + +import ( + "errors" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/example" + "gorm.io/gorm" +) + +type AttachmentCategoryService struct{} + +// AddCategory 创建/更新的分类 +func (a *AttachmentCategoryService) AddCategory(req *example.ExaAttachmentCategory) (err error) { + // 检查是否已存在相同名称的分类 + if (!errors.Is(global.GVA_DB.Take(&example.ExaAttachmentCategory{}, "name = ? and pid = ?", req.Name, req.Pid).Error, gorm.ErrRecordNotFound)) { + return errors.New("分类名称已存在") + } + if req.ID > 0 { + if err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("id = ?", req.ID).Updates(&example.ExaAttachmentCategory{ + Name: req.Name, + Pid: req.Pid, + }).Error; err != nil { + return err + } + } else { + if err = global.GVA_DB.Create(&example.ExaAttachmentCategory{ + Name: req.Name, + Pid: req.Pid, + }).Error; err != nil { + return err + } + } + return nil +} + +// DeleteCategory 删除分类 +func (a *AttachmentCategoryService) DeleteCategory(id *int) error { + var childCount int64 + global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where("pid = ?", id).Count(&childCount) + if childCount > 0 { + return errors.New("请先删除子级") + } + return global.GVA_DB.Where("id = ?", id).Unscoped().Delete(&example.ExaAttachmentCategory{}).Error +} + +// GetCategoryList 分类列表 +func (a *AttachmentCategoryService) GetCategoryList() (res []*example.ExaAttachmentCategory, err error) { + var fileLists []example.ExaAttachmentCategory + err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Find(&fileLists).Error + if err != nil { + return res, err + } + return a.getChildrenList(fileLists, 0), nil +} + +// getChildrenList 子类 +func (a *AttachmentCategoryService) getChildrenList(categories []example.ExaAttachmentCategory, parentID uint) []*example.ExaAttachmentCategory { + var tree []*example.ExaAttachmentCategory + for _, category := range categories { + if category.Pid == parentID { + category.Children = a.getChildrenList(categories, category.ID) + tree = append(tree, &category) + } + } + return tree +} diff --git a/service/example/exa_breakpoint_continue.go b/service/example/exa_breakpoint_continue.go new file mode 100644 index 0000000..0bb4c10 --- /dev/null +++ b/service/example/exa_breakpoint_continue.go @@ -0,0 +1,71 @@ +package example + +import ( + "errors" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/example" + "gorm.io/gorm" +) + +type FileUploadAndDownloadService struct{} + +var FileUploadAndDownloadServiceApp = new(FileUploadAndDownloadService) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: FindOrCreateFile +//@description: 上传文件时检测当前文件属性,如果没有文件则创建,有则返回文件的当前切片 +//@param: fileMd5 string, fileName string, chunkTotal int +//@return: file model.ExaFile, err error + +func (e *FileUploadAndDownloadService) FindOrCreateFile(fileMd5 string, fileName string, chunkTotal int) (file example.ExaFile, err error) { + var cfile example.ExaFile + cfile.FileMd5 = fileMd5 + cfile.FileName = fileName + cfile.ChunkTotal = chunkTotal + + if errors.Is(global.GVA_DB.Where("file_md5 = ? AND is_finish = ?", fileMd5, true).First(&file).Error, gorm.ErrRecordNotFound) { + err = global.GVA_DB.Where("file_md5 = ? AND file_name = ?", fileMd5, fileName).Preload("ExaFileChunk").FirstOrCreate(&file, cfile).Error + return file, err + } + cfile.IsFinish = true + cfile.FilePath = file.FilePath + err = global.GVA_DB.Create(&cfile).Error + return cfile, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateFileChunk +//@description: 创建文件切片记录 +//@param: id uint, fileChunkPath string, fileChunkNumber int +//@return: error + +func (e *FileUploadAndDownloadService) CreateFileChunk(id uint, fileChunkPath string, fileChunkNumber int) error { + var chunk example.ExaFileChunk + chunk.FileChunkPath = fileChunkPath + chunk.ExaFileID = id + chunk.FileChunkNumber = fileChunkNumber + err := global.GVA_DB.Create(&chunk).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteFileChunk +//@description: 删除文件切片记录 +//@param: fileMd5 string, fileName string, filePath string +//@return: error + +func (e *FileUploadAndDownloadService) DeleteFileChunk(fileMd5 string, filePath string) error { + var chunks []example.ExaFileChunk + var file example.ExaFile + err := global.GVA_DB.Where("file_md5 = ?", fileMd5).First(&file). + Updates(map[string]interface{}{ + "IsFinish": true, + "file_path": filePath, + }).Error + if err != nil { + return err + } + err = global.GVA_DB.Where("exa_file_id = ?", file.ID).Delete(&chunks).Unscoped().Error + return err +} diff --git a/service/example/exa_customer.go b/service/example/exa_customer.go new file mode 100644 index 0000000..afb1d4f --- /dev/null +++ b/service/example/exa_customer.go @@ -0,0 +1,87 @@ +package example + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/example" + "git.echol.cn/loser/xiecheng_server/model/system" + systemService "git.echol.cn/loser/xiecheng_server/service/system" +) + +type CustomerService struct{} + +var CustomerServiceApp = new(CustomerService) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateExaCustomer +//@description: 创建客户 +//@param: e model.ExaCustomer +//@return: err error + +func (exa *CustomerService) CreateExaCustomer(e example.ExaCustomer) (err error) { + err = global.GVA_DB.Create(&e).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteFileChunk +//@description: 删除客户 +//@param: e model.ExaCustomer +//@return: err error + +func (exa *CustomerService) DeleteExaCustomer(e example.ExaCustomer) (err error) { + err = global.GVA_DB.Delete(&e).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateExaCustomer +//@description: 更新客户 +//@param: e *model.ExaCustomer +//@return: err error + +func (exa *CustomerService) UpdateExaCustomer(e *example.ExaCustomer) (err error) { + err = global.GVA_DB.Save(e).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetExaCustomer +//@description: 获取客户信息 +//@param: id uint +//@return: customer model.ExaCustomer, err error + +func (exa *CustomerService) GetExaCustomer(id uint) (customer example.ExaCustomer, err error) { + err = global.GVA_DB.Where("id = ?", id).First(&customer).Error + return +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetCustomerInfoList +//@description: 分页获取客户列表 +//@param: sysUserAuthorityID string, info request.PageInfo +//@return: list interface{}, total int64, err error + +func (exa *CustomerService) GetCustomerInfoList(sysUserAuthorityID uint, info request.PageInfo) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + db := global.GVA_DB.Model(&example.ExaCustomer{}) + var a system.SysAuthority + a.AuthorityId = sysUserAuthorityID + auth, err := systemService.AuthorityServiceApp.GetAuthorityInfo(a) + if err != nil { + return + } + var dataId []uint + for _, v := range auth.DataAuthorityId { + dataId = append(dataId, v.AuthorityId) + } + var CustomerList []example.ExaCustomer + err = db.Where("sys_user_authority_id in ?", dataId).Count(&total).Error + if err != nil { + return CustomerList, total, err + } else { + err = db.Limit(limit).Offset(offset).Preload("SysUser").Where("sys_user_authority_id in ?", dataId).Find(&CustomerList).Error + } + return CustomerList, total, err +} diff --git a/service/example/exa_file_upload_download.go b/service/example/exa_file_upload_download.go new file mode 100644 index 0000000..54349e0 --- /dev/null +++ b/service/example/exa_file_upload_download.go @@ -0,0 +1,123 @@ +package example + +import ( + "errors" + "mime/multipart" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/example" + "git.echol.cn/loser/xiecheng_server/model/example/request" + "git.echol.cn/loser/xiecheng_server/utils/upload" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Upload +//@description: 创建文件上传记录 +//@param: file model.ExaFileUploadAndDownload +//@return: error + +func (e *FileUploadAndDownloadService) Upload(file example.ExaFileUploadAndDownload) error { + return global.GVA_DB.Create(&file).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: FindFile +//@description: 查询文件记录 +//@param: id uint +//@return: model.ExaFileUploadAndDownload, error + +func (e *FileUploadAndDownloadService) FindFile(id uint) (example.ExaFileUploadAndDownload, error) { + var file example.ExaFileUploadAndDownload + err := global.GVA_DB.Where("id = ?", id).First(&file).Error + return file, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteFile +//@description: 删除文件记录 +//@param: file model.ExaFileUploadAndDownload +//@return: err error + +func (e *FileUploadAndDownloadService) DeleteFile(file example.ExaFileUploadAndDownload) (err error) { + var fileFromDb example.ExaFileUploadAndDownload + fileFromDb, err = e.FindFile(file.ID) + if err != nil { + return + } + oss := upload.NewOss() + if err = oss.DeleteFile(fileFromDb.Key); err != nil { + return errors.New("文件删除失败") + } + err = global.GVA_DB.Where("id = ?", file.ID).Unscoped().Delete(&file).Error + return err +} + +// EditFileName 编辑文件名或者备注 +func (e *FileUploadAndDownloadService) EditFileName(file example.ExaFileUploadAndDownload) (err error) { + var fileFromDb example.ExaFileUploadAndDownload + return global.GVA_DB.Where("id = ?", file.ID).First(&fileFromDb).Update("name", file.Name).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetFileRecordInfoList +//@description: 分页获取数据 +//@param: info request.ExaAttachmentCategorySearch +//@return: list interface{}, total int64, err error + +func (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.ExaAttachmentCategorySearch) (list []example.ExaFileUploadAndDownload, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + db := global.GVA_DB.Model(&example.ExaFileUploadAndDownload{}) + + if len(info.Keyword) > 0 { + db = db.Where("name LIKE ?", "%"+info.Keyword+"%") + } + + if info.ClassId > 0 { + db = db.Where("class_id = ?", info.ClassId) + } + + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Limit(limit).Offset(offset).Order("id desc").Find(&list).Error + return list, total, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UploadFile +//@description: 根据配置文件判断是文件上传到本地或者七牛云 +//@param: header *multipart.FileHeader, noSave string +//@return: file model.ExaFileUploadAndDownload, err error + +func (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader, noSave string, classId int) (file example.ExaFileUploadAndDownload, err error) { + oss := upload.NewOss() + filePath, key, uploadErr := oss.UploadFile(header) + if uploadErr != nil { + return file, uploadErr + } + s := strings.Split(header.Filename, ".") + f := example.ExaFileUploadAndDownload{ + Url: filePath, + Name: header.Filename, + ClassId: classId, + Tag: s[len(s)-1], + Key: key, + } + if noSave == "0" { + return f, e.Upload(f) + } + return f, nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: ImportURL +//@description: 导入URL +//@param: file model.ExaFileUploadAndDownload +//@return: error + +func (e *FileUploadAndDownloadService) ImportURL(file *[]example.ExaFileUploadAndDownload) error { + return global.GVA_DB.Create(&file).Error +} diff --git a/service/system/auto_code_history.go b/service/system/auto_code_history.go new file mode 100644 index 0000000..642b32e --- /dev/null +++ b/service/system/auto_code_history.go @@ -0,0 +1,217 @@ +package system + +import ( + "context" + "encoding/json" + "fmt" + "git.echol.cn/loser/xiecheng_server/utils/ast" + "github.com/pkg/errors" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + common "git.echol.cn/loser/xiecheng_server/model/common/request" + model "git.echol.cn/loser/xiecheng_server/model/system" + request "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + + "go.uber.org/zap" +) + +var AutocodeHistory = new(autoCodeHistory) + +type autoCodeHistory struct{} + +// Create 创建代码生成器历史记录 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [songzhibin97](https://github.com/songzhibin97) +func (s *autoCodeHistory) Create(ctx context.Context, info request.SysAutoHistoryCreate) error { + create := info.Create() + err := global.GVA_DB.WithContext(ctx).Create(&create).Error + if err != nil { + return errors.Wrap(err, "创建失败!") + } + return nil +} + +// First 根据id获取代码生成器历史的数据 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [songzhibin97](https://github.com/songzhibin97) +func (s *autoCodeHistory) First(ctx context.Context, info common.GetById) (string, error) { + var meta string + err := global.GVA_DB.WithContext(ctx).Model(model.SysAutoCodeHistory{}).Where("id = ?", info.ID).Pluck("request", &meta).Error + if err != nil { + return "", errors.Wrap(err, "获取失败!") + } + return meta, nil +} + +// Repeat 检测重复 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [songzhibin97](https://github.com/songzhibin97) +func (s *autoCodeHistory) Repeat(businessDB, structName, abbreviation, Package string) bool { + var count int64 + global.GVA_DB.Model(&model.SysAutoCodeHistory{}).Where("business_db = ? and (struct_name = ? OR abbreviation = ?) and package = ? and flag = ?", businessDB, structName, abbreviation, Package, 0).Count(&count).Debug() + return count > 0 +} + +// RollBack 回滚 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [songzhibin97](https://github.com/songzhibin97) +func (s *autoCodeHistory) RollBack(ctx context.Context, info request.SysAutoHistoryRollBack) error { + var history model.SysAutoCodeHistory + err := global.GVA_DB.Where("id = ?", info.ID).First(&history).Error + if err != nil { + return err + } + if history.ExportTemplateID != 0 { + err = global.GVA_DB.Delete(&model.SysExportTemplate{}, "id = ?", history.ExportTemplateID).Error + if err != nil { + return err + } + } + if info.DeleteApi { + ids := info.ApiIds(history) + err = ApiServiceApp.DeleteApisByIds(ids) + if err != nil { + global.GVA_LOG.Error("ClearTag DeleteApiByIds:", zap.Error(err)) + } + } // 清除API表 + if info.DeleteMenu { + err = BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID)) + if err != nil { + return errors.Wrap(err, "删除菜单失败!") + } + } // 清除菜单表 + if info.DeleteTable { + err = s.DropTable(history.BusinessDB, history.Table) + if err != nil { + return errors.Wrap(err, "删除表失败!") + } + } // 删除表 + templates := make(map[string]string, len(history.Templates)) + for key, template := range history.Templates { + { + server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server) + keys := strings.Split(key, "/") + key = filepath.Join(keys...) + key = strings.TrimPrefix(key, server) + } // key + { + web := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot()) + server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server) + slices := strings.Split(template, "/") + template = filepath.Join(slices...) + ext := path.Ext(template) + switch ext { + case ".js", ".vue": + template = filepath.Join(web, template) + case ".go": + template = filepath.Join(server, template) + } + } // value + templates[key] = template + } + history.Templates = templates + for key, value := range history.Injections { + var injection ast.Ast + switch key { + case ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter: + + case ast.TypePackageApiModuleEnter, ast.TypePackageRouterModuleEnter, ast.TypePackageServiceModuleEnter: + var entity ast.PackageModuleEnter + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + case ast.TypePackageInitializeGorm: + var entity ast.PackageInitializeGorm + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + case ast.TypePackageInitializeRouter: + var entity ast.PackageInitializeRouter + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + case ast.TypePluginGen: + var entity ast.PluginGen + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + case ast.TypePluginApiEnter, ast.TypePluginRouterEnter, ast.TypePluginServiceEnter: + var entity ast.PluginEnter + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + case ast.TypePluginInitializeGorm: + var entity ast.PluginInitializeGorm + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + case ast.TypePluginInitializeRouter: + var entity ast.PluginInitializeRouter + _ = json.Unmarshal([]byte(value), &entity) + injection = &entity + } + if injection == nil { + continue + } + file, _ := injection.Parse("", nil) + if file != nil { + _ = injection.Rollback(file) + err = injection.Format("", nil, file) + if err != nil { + return err + } + fmt.Printf("[filepath:%s]回滚注入代码成功!\n", key) + } + } // 清除注入代码 + removeBasePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, "rm_file", strconv.FormatInt(int64(time.Now().Nanosecond()), 10)) + for _, value := range history.Templates { + if !filepath.IsAbs(value) { + continue + } + removePath := filepath.Join(removeBasePath, strings.TrimPrefix(value, global.GVA_CONFIG.AutoCode.Root)) + err = utils.FileMove(value, removePath) + if err != nil { + return errors.Wrapf(err, "[src:%s][dst:%s]文件移动失败!", value, removePath) + } + } // 移动文件 + err = global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{}).Where("id = ?", info.ID).Update("flag", 1).Error + if err != nil { + return errors.Wrap(err, "更新失败!") + } + return nil +} + +// Delete 删除历史数据 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [songzhibin97](https://github.com/songzhibin97) +func (s *autoCodeHistory) Delete(ctx context.Context, info common.GetById) error { + err := global.GVA_DB.WithContext(ctx).Where("id = ?", info.Uint()).Delete(&model.SysAutoCodeHistory{}).Error + if err != nil { + return errors.Wrap(err, "删除失败!") + } + return nil +} + +// GetList 获取系统历史数据 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [songzhibin97](https://github.com/songzhibin97) +func (s *autoCodeHistory) GetList(ctx context.Context, info common.PageInfo) (list []model.SysAutoCodeHistory, total int64, err error) { + var entities []model.SysAutoCodeHistory + db := global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{}) + err = db.Count(&total).Error + if err != nil { + return nil, total, err + } + err = db.Scopes(info.Paginate()).Order("updated_at desc").Find(&entities).Error + return entities, total, err +} + +// DropTable 获取指定数据库和指定数据表的所有字段名,类型值等 +// @author: [piexlmax](https://github.com/piexlmax) +func (s *autoCodeHistory) DropTable(BusinessDb, tableName string) error { + if BusinessDb != "" { + return global.MustGetGlobalDBByDBName(BusinessDb).Exec("DROP TABLE " + tableName).Error + } else { + return global.GVA_DB.Exec("DROP TABLE " + tableName).Error + } +} diff --git a/service/system/auto_code_package.go b/service/system/auto_code_package.go new file mode 100644 index 0000000..4074396 --- /dev/null +++ b/service/system/auto_code_package.go @@ -0,0 +1,680 @@ +package system + +import ( + "context" + "fmt" + "go/token" + "os" + "path/filepath" + "strings" + "text/template" + + "git.echol.cn/loser/xiecheng_server/global" + common "git.echol.cn/loser/xiecheng_server/model/common/request" + model "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "git.echol.cn/loser/xiecheng_server/utils/ast" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +var AutoCodePackage = new(autoCodePackage) + +type autoCodePackage struct{} + +// Create 创建包信息 +// @author: [piexlmax](https://github.com/piexlmax) +// @author: [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodePackage) Create(ctx context.Context, info *request.SysAutoCodePackageCreate) error { + switch { + case info.Template == "": + return errors.New("模板不能为空!") + case info.Template == "page": + return errors.New("page为表单生成器!") + case info.PackageName == "": + return errors.New("PackageName不能为空!") + case token.IsKeyword(info.PackageName): + return errors.Errorf("%s为go的关键字!", info.PackageName) + case info.Template == "package": + if info.PackageName == "system" || info.PackageName == "example" { + return errors.New("不能使用已保留的package name") + } + default: + break + } + if !errors.Is(global.GVA_DB.Where("package_name = ? and template = ?", info.PackageName, info.Template).First(&model.SysAutoCodePackage{}).Error, gorm.ErrRecordNotFound) { + return errors.New("存在相同PackageName") + } + create := info.Create() + return global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + err := tx.Create(&create).Error + if err != nil { + return errors.Wrap(err, "创建失败!") + } + code := info.AutoCode() + _, asts, creates, err := s.templates(ctx, create, code, true) + if err != nil { + return err + } + for key, value := range creates { // key 为 模版绝对路径 + var files *template.Template + files, err = template.ParseFiles(key) + if err != nil { + return errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", key) + } + err = os.MkdirAll(filepath.Dir(value), os.ModePerm) + if err != nil { + return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", value) + } + var file *os.File + file, err = os.Create(value) + if err != nil { + return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", value) + } + err = files.Execute(file, code) + _ = file.Close() + if err != nil { + return errors.Wrapf(err, "[filepath:%s]生成失败!", value) + } + fmt.Printf("[template:%s][filepath:%s]生成成功!\n", key, value) + } + for key, value := range asts { + keys := strings.Split(key, "=>") + if len(keys) == 2 { + switch keys[1] { + case ast.TypePluginInitializeV2, ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter: + file, _ := value.Parse("", nil) + if file != nil { + err = value.Injection(file) + if err != nil { + return err + } + err = value.Format("", nil, file) + if err != nil { + return err + } + } + fmt.Printf("[type:%s]注入成功!\n", key) + } + } + } + return nil + }) +} + +// Delete 删除包记录 +// @author: [piexlmax](https://github.com/piexlmax) +// @author: [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodePackage) Delete(ctx context.Context, info common.GetById) error { + err := global.GVA_DB.WithContext(ctx).Delete(&model.SysAutoCodePackage{}, info.Uint()).Error + if err != nil { + return errors.Wrap(err, "删除失败!") + } + return nil +} + +// All 获取所有包 +// @author: [piexlmax](https://github.com/piexlmax) +// @author: [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodePackage) All(ctx context.Context) (entities []model.SysAutoCodePackage, err error) { + server := make([]model.SysAutoCodePackage, 0) + plugin := make([]model.SysAutoCodePackage, 0) + serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service") + pluginPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin") + serverDir, err := os.ReadDir(serverPath) + if err != nil { + return nil, errors.Wrap(err, "读取service文件夹失败!") + } + pluginDir, err := os.ReadDir(pluginPath) + if err != nil { + return nil, errors.Wrap(err, "读取plugin文件夹失败!") + } + for i := 0; i < len(serverDir); i++ { + if serverDir[i].IsDir() { + serverPackage := model.SysAutoCodePackage{ + PackageName: serverDir[i].Name(), + Template: "package", + Label: serverDir[i].Name() + "包", + Desc: "系统自动读取" + serverDir[i].Name() + "包", + Module: global.GVA_CONFIG.AutoCode.Module, + } + server = append(server, serverPackage) + } + } + for i := 0; i < len(pluginDir); i++ { + if pluginDir[i].IsDir() { + dirNameMap := map[string]bool{ + "api": true, + "config": true, + "initialize": true, + "model": true, + "plugin": true, + "router": true, + "service": true, + } + dir, e := os.ReadDir(filepath.Join(pluginPath, pluginDir[i].Name())) + if e != nil { + return nil, errors.Wrap(err, "读取plugin文件夹失败!") + } + //dir目录需要包含所有的dirNameMap + for k := 0; k < len(dir); k++ { + if dir[k].IsDir() { + if ok := dirNameMap[dir[k].Name()]; ok { + delete(dirNameMap, dir[k].Name()) + } + } + } + if len(dirNameMap) != 0 { + continue + } + pluginPackage := model.SysAutoCodePackage{ + PackageName: pluginDir[i].Name(), + Template: "plugin", + Label: pluginDir[i].Name() + "插件", + Desc: "系统自动读取" + pluginDir[i].Name() + "插件,使用前请确认是否为v2版本插件", + Module: global.GVA_CONFIG.AutoCode.Module, + } + plugin = append(plugin, pluginPackage) + } + } + + err = global.GVA_DB.WithContext(ctx).Find(&entities).Error + if err != nil { + return nil, errors.Wrap(err, "获取所有包失败!") + } + entitiesMap := make(map[string]model.SysAutoCodePackage) + for i := 0; i < len(entities); i++ { + entitiesMap[entities[i].PackageName] = entities[i] + } + createEntity := []model.SysAutoCodePackage{} + for i := 0; i < len(server); i++ { + if _, ok := entitiesMap[server[i].PackageName]; !ok { + if server[i].Template == "package" { + createEntity = append(createEntity, server[i]) + } + } + } + for i := 0; i < len(plugin); i++ { + if _, ok := entitiesMap[plugin[i].PackageName]; !ok { + if plugin[i].Template == "plugin" { + createEntity = append(createEntity, plugin[i]) + } + } + } + + if len(createEntity) > 0 { + err = global.GVA_DB.WithContext(ctx).Create(&createEntity).Error + if err != nil { + return nil, errors.Wrap(err, "同步失败!") + } + entities = append(entities, createEntity...) + } + + return entities, nil +} + +// Templates 获取所有模版文件夹 +// @author: [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodePackage) Templates(ctx context.Context) ([]string, error) { + templates := make([]string, 0) + entries, err := os.ReadDir("resource") + if err != nil { + return nil, errors.Wrap(err, "读取模版文件夹失败!") + } + for i := 0; i < len(entries); i++ { + if entries[i].IsDir() { + if entries[i].Name() == "page" { + continue + } // page 为表单生成器 + if entries[i].Name() == "function" { + continue + } // function 为函数生成器 + if entries[i].Name() == "preview" { + continue + } // preview 为预览代码生成器的代码 + templates = append(templates, entries[i].Name()) + } + } + return templates, nil +} + +func (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCodePackage, info request.AutoCode, isPackage bool) (code map[string]string, asts map[string]ast.Ast, creates map[string]string, err error) { + code = make(map[string]string) + asts = make(map[string]ast.Ast) + creates = make(map[string]string) + templateDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", entity.Template) + templateDirs, err := os.ReadDir(templateDir) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", templateDir) + } + for i := 0; i < len(templateDirs); i++ { + second := filepath.Join(templateDir, templateDirs[i].Name()) + switch templateDirs[i].Name() { + case "server": + if !info.GenerateServer && !isPackage { + break + } + var secondDirs []os.DirEntry + secondDirs, err = os.ReadDir(second) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", second) + } + for j := 0; j < len(secondDirs); j++ { + if secondDirs[j].Name() == ".DS_Store" { + continue + } + three := filepath.Join(second, secondDirs[j].Name()) + if !secondDirs[j].IsDir() { + ext := filepath.Ext(secondDirs[j].Name()) + if ext != ".template" && ext != ".tpl" { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", three) + } + name := strings.TrimSuffix(secondDirs[j].Name(), ext) + if name == "main.go" || name == "plugin.go" { + pluginInitialize := &ast.PluginInitializeV2{ + Type: ast.TypePluginInitializeV2, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, name), + PluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "plugin_biz_v2.go"), + ImportPath: fmt.Sprintf(`"%s/plugin/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + PackageName: entity.PackageName, + } + asts[pluginInitialize.PluginPath+"=>"+pluginInitialize.Type.String()] = pluginInitialize + creates[three] = pluginInitialize.Path + continue + } + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", three) + } + switch secondDirs[j].Name() { + case "api", "router", "service": + var threeDirs []os.DirEntry + threeDirs, err = os.ReadDir(three) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three) + } + for k := 0; k < len(threeDirs); k++ { + if threeDirs[k].Name() == ".DS_Store" { + continue + } + four := filepath.Join(three, threeDirs[k].Name()) + if threeDirs[k].IsDir() { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four) + } + ext := filepath.Ext(four) + if ext != ".template" && ext != ".tpl" { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four) + } + api := strings.Index(threeDirs[k].Name(), "api") + hasEnter := strings.Index(threeDirs[k].Name(), "enter") + router := strings.Index(threeDirs[k].Name(), "router") + service := strings.Index(threeDirs[k].Name(), "service") + if router == -1 && api == -1 && service == -1 && hasEnter == -1 { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four) + } + if entity.Template == "package" { + create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+".go") + if api != -1 { + create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", entity.PackageName, info.HumpPackageName+".go") + } + if hasEnter != -1 { + isApi := strings.Index(secondDirs[j].Name(), "api") + isRouter := strings.Index(secondDirs[j].Name(), "router") + isService := strings.Index(secondDirs[j].Name(), "service") + if isApi != -1 { + packageApiEnter := &ast.PackageEnter{ + Type: ast.TypePackageApiEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", "enter.go"), + ImportPath: fmt.Sprintf(`"%s/%s/%s/%s"`, global.GVA_CONFIG.AutoCode.Module, "api", "v1", entity.PackageName), + StructName: utils.FirstUpper(entity.PackageName) + "ApiGroup", + PackageName: entity.PackageName, + PackageStructName: "ApiGroup", + } + asts[packageApiEnter.Path+"=>"+packageApiEnter.Type.String()] = packageApiEnter + packageApiModuleEnter := &ast.PackageModuleEnter{ + Type: ast.TypePackageApiModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "v1", entity.PackageName, "enter.go"), + ImportPath: fmt.Sprintf(`"%s/service"`, global.GVA_CONFIG.AutoCode.Module), + StructName: info.StructName + "Api", + AppName: "ServiceGroupApp", + GroupName: utils.FirstUpper(entity.PackageName) + "ServiceGroup", + ModuleName: info.Abbreviation + "Service", + PackageName: "service", + ServiceName: info.StructName + "Service", + } + asts[packageApiModuleEnter.Path+"=>"+packageApiModuleEnter.Type.String()] = packageApiModuleEnter + creates[four] = packageApiModuleEnter.Path + } + if isRouter != -1 { + packageRouterEnter := &ast.PackageEnter{ + Type: ast.TypePackageRouterEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), "enter.go"), + ImportPath: fmt.Sprintf(`"%s/%s/%s"`, global.GVA_CONFIG.AutoCode.Module, secondDirs[j].Name(), entity.PackageName), + StructName: utils.FirstUpper(entity.PackageName), + PackageName: entity.PackageName, + PackageStructName: "RouterGroup", + } + asts[packageRouterEnter.Path+"=>"+packageRouterEnter.Type.String()] = packageRouterEnter + packageRouterModuleEnter := &ast.PackageModuleEnter{ + Type: ast.TypePackageRouterModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, "enter.go"), + ImportPath: fmt.Sprintf(`api "%s/api/v1"`, global.GVA_CONFIG.AutoCode.Module), + StructName: info.StructName + "Router", + AppName: "ApiGroupApp", + GroupName: utils.FirstUpper(entity.PackageName) + "ApiGroup", + ModuleName: info.Abbreviation + "Api", + PackageName: "api", + ServiceName: info.StructName + "Api", + } + creates[four] = packageRouterModuleEnter.Path + asts[packageRouterModuleEnter.Path+"=>"+packageRouterModuleEnter.Type.String()] = packageRouterModuleEnter + packageInitializeRouter := &ast.PackageInitializeRouter{ + Type: ast.TypePackageInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"), + ImportPath: fmt.Sprintf(`"%s/router"`, global.GVA_CONFIG.AutoCode.Module), + AppName: "RouterGroupApp", + GroupName: utils.FirstUpper(entity.PackageName), + ModuleName: entity.PackageName + "Router", + PackageName: "router", + FunctionName: "Init" + info.StructName + "Router", + LeftRouterGroupName: "privateGroup", + RightRouterGroupName: "publicGroup", + } + asts[packageInitializeRouter.Path+"=>"+packageInitializeRouter.Type.String()] = packageInitializeRouter + } + if isService != -1 { + path := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)) + importPath := fmt.Sprintf(`"%s/service/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName) + packageServiceEnter := &ast.PackageEnter{ + Type: ast.TypePackageServiceEnter, + Path: path, + ImportPath: importPath, + StructName: utils.FirstUpper(entity.PackageName) + "ServiceGroup", + PackageName: entity.PackageName, + PackageStructName: "ServiceGroup", + } + asts[packageServiceEnter.Path+"=>"+packageServiceEnter.Type.String()] = packageServiceEnter + packageServiceModuleEnter := &ast.PackageModuleEnter{ + Type: ast.TypePackageServiceModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, "enter.go"), + StructName: info.StructName + "Service", + } + asts[packageServiceModuleEnter.Path+"=>"+packageServiceModuleEnter.Type.String()] = packageServiceModuleEnter + creates[four] = packageServiceModuleEnter.Path + } + continue + } + code[four] = create + continue + } + if hasEnter != -1 { + isApi := strings.Index(secondDirs[j].Name(), "api") + isRouter := strings.Index(secondDirs[j].Name(), "router") + isService := strings.Index(secondDirs[j].Name(), "service") + if isRouter != -1 { + pluginRouterEnter := &ast.PluginEnter{ + Type: ast.TypePluginRouterEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)), + ImportPath: fmt.Sprintf(`"%s/plugin/%s/api"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + StructName: info.StructName, + StructCamelName: info.Abbreviation, + ModuleName: "api" + info.StructName, + GroupName: "Api", + PackageName: "api", + ServiceName: info.StructName, + } + asts[pluginRouterEnter.Path+"=>"+pluginRouterEnter.Type.String()] = pluginRouterEnter + creates[four] = pluginRouterEnter.Path + } + if isApi != -1 { + pluginApiEnter := &ast.PluginEnter{ + Type: ast.TypePluginApiEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)), + ImportPath: fmt.Sprintf(`"%s/plugin/%s/service"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + StructName: info.StructName, + StructCamelName: info.Abbreviation, + ModuleName: "service" + info.StructName, + GroupName: "Service", + PackageName: "service", + ServiceName: info.StructName, + } + asts[pluginApiEnter.Path+"=>"+pluginApiEnter.Type.String()] = pluginApiEnter + creates[four] = pluginApiEnter.Path + } + if isService != -1 { + pluginServiceEnter := &ast.PluginEnter{ + Type: ast.TypePluginServiceEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)), + StructName: info.StructName, + StructCamelName: info.Abbreviation, + } + asts[pluginServiceEnter.Path+"=>"+pluginServiceEnter.Type.String()] = pluginServiceEnter + creates[four] = pluginServiceEnter.Path + } + continue + } // enter.go + create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+".go") + code[four] = create + } + case "gen", "config", "initialize", "plugin", "response": + if entity.Template == "package" { + continue + } // package模板不需要生成gen, config, initialize + var threeDirs []os.DirEntry + threeDirs, err = os.ReadDir(three) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three) + } + for k := 0; k < len(threeDirs); k++ { + if threeDirs[k].Name() == ".DS_Store" { + continue + } + four := filepath.Join(three, threeDirs[k].Name()) + if threeDirs[k].IsDir() { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four) + } + ext := filepath.Ext(four) + if ext != ".template" && ext != ".tpl" { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four) + } + gen := strings.Index(threeDirs[k].Name(), "gen") + api := strings.Index(threeDirs[k].Name(), "api") + menu := strings.Index(threeDirs[k].Name(), "menu") + viper := strings.Index(threeDirs[k].Name(), "viper") + plugin := strings.Index(threeDirs[k].Name(), "plugin") + config := strings.Index(threeDirs[k].Name(), "config") + router := strings.Index(threeDirs[k].Name(), "router") + hasGorm := strings.Index(threeDirs[k].Name(), "gorm") + response := strings.Index(threeDirs[k].Name(), "response") + if gen != -1 && api != -1 && menu != -1 && viper != -1 && plugin != -1 && config != -1 && router != -1 && hasGorm != -1 && response != -1 { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four) + } + if api != -1 || menu != -1 || viper != -1 || response != -1 || plugin != -1 || config != -1 { + creates[four] = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)) + } + if gen != -1 { + pluginGen := &ast.PluginGen{ + Type: ast.TypePluginGen, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)), + ImportPath: fmt.Sprintf(`"%s/plugin/%s/model"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + StructName: info.StructName, + PackageName: "model", + IsNew: true, + } + asts[pluginGen.Path+"=>"+pluginGen.Type.String()] = pluginGen + creates[four] = pluginGen.Path + } + if hasGorm != -1 { + pluginInitializeGorm := &ast.PluginInitializeGorm{ + Type: ast.TypePluginInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)), + ImportPath: fmt.Sprintf(`"%s/plugin/%s/model"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + StructName: info.StructName, + PackageName: "model", + IsNew: true, + } + asts[pluginInitializeGorm.Path+"=>"+pluginInitializeGorm.Type.String()] = pluginInitializeGorm + creates[four] = pluginInitializeGorm.Path + } + if router != -1 { + pluginInitializeRouter := &ast.PluginInitializeRouter{ + Type: ast.TypePluginInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)), + ImportPath: fmt.Sprintf(`"%s/plugin/%s/router"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + AppName: "Router", + GroupName: info.StructName, + PackageName: "router", + FunctionName: "Init", + LeftRouterGroupName: "public", + RightRouterGroupName: "private", + } + asts[pluginInitializeRouter.Path+"=>"+pluginInitializeRouter.Type.String()] = pluginInitializeRouter + creates[four] = pluginInitializeRouter.Path + } + } + case "model": + var threeDirs []os.DirEntry + threeDirs, err = os.ReadDir(three) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three) + } + for k := 0; k < len(threeDirs); k++ { + if threeDirs[k].Name() == ".DS_Store" { + continue + } + four := filepath.Join(three, threeDirs[k].Name()) + if threeDirs[k].IsDir() { + var fourDirs []os.DirEntry + fourDirs, err = os.ReadDir(four) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", four) + } + for l := 0; l < len(fourDirs); l++ { + if fourDirs[l].Name() == ".DS_Store" { + continue + } + five := filepath.Join(four, fourDirs[l].Name()) + if fourDirs[l].IsDir() { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", five) + } + ext := filepath.Ext(five) + if ext != ".template" && ext != ".tpl" { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", five) + } + hasRequest := strings.Index(fourDirs[l].Name(), "request") + if hasRequest == -1 { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", five) + } + create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), threeDirs[k].Name(), info.HumpPackageName+".go") + if entity.Template == "package" { + create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, threeDirs[k].Name(), info.HumpPackageName+".go") + } + code[five] = create + } + continue + } + ext := filepath.Ext(threeDirs[k].Name()) + if ext != ".template" && ext != ".tpl" { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four) + } + hasModel := strings.Index(threeDirs[k].Name(), "model") + if hasModel == -1 { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four) + } + create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+".go") + if entity.Template == "package" { + packageInitializeGorm := &ast.PackageInitializeGorm{ + Type: ast.TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: fmt.Sprintf(`"%s/model/%s"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName), + Business: info.BusinessDB, + StructName: info.StructName, + PackageName: entity.PackageName, + IsNew: true, + } + code[four] = packageInitializeGorm.Path + asts[packageInitializeGorm.Path+"=>"+packageInitializeGorm.Type.String()] = packageInitializeGorm + create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+".go") + } + code[four] = create + } + default: + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", three) + } + } + case "web": + if !info.GenerateWeb && !isPackage { + break + } + var secondDirs []os.DirEntry + secondDirs, err = os.ReadDir(second) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", second) + } + for j := 0; j < len(secondDirs); j++ { + if secondDirs[j].Name() == ".DS_Store" { + continue + } + three := filepath.Join(second, secondDirs[j].Name()) + if !secondDirs[j].IsDir() { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", three) + } + switch secondDirs[j].Name() { + case "api", "form", "view", "table": + var threeDirs []os.DirEntry + threeDirs, err = os.ReadDir(three) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "读取模版文件夹[%s]失败!", three) + } + for k := 0; k < len(threeDirs); k++ { + if threeDirs[k].Name() == ".DS_Store" { + continue + } + four := filepath.Join(three, threeDirs[k].Name()) + if threeDirs[k].IsDir() { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", four) + } + ext := filepath.Ext(four) + if ext != ".template" && ext != ".tpl" { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版后缀!", four) + } + api := strings.Index(threeDirs[k].Name(), "api") + form := strings.Index(threeDirs[k].Name(), "form") + view := strings.Index(threeDirs[k].Name(), "view") + table := strings.Index(threeDirs[k].Name(), "table") + if api == -1 && form == -1 && view == -1 && table == -1 { + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", four) + } + if entity.Template == "package" { + if view != -1 || table != -1 { + formPath := filepath.Join(three, "form.vue"+ext) + value, ok := code[formPath] + if ok { + value = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.PackageName+"Form"+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext))) + code[formPath] = value + } + } + create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext))) + if api != -1 { + create = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext))) + } + code[four] = create + continue + } + create := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), "plugin", entity.PackageName, secondDirs[j].Name(), info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext))) + code[four] = create + } + default: + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件夹!", three) + } + } + case "readme.txt.tpl", "readme.txt.template": + continue + default: + if templateDirs[i].Name() == ".DS_Store" { + continue + } + return nil, nil, nil, errors.Errorf("[filpath:%s]非法模版文件!", second) + } + } + return code, asts, creates, nil +} diff --git a/service/system/auto_code_package_test.go b/service/system/auto_code_package_test.go new file mode 100644 index 0000000..3d1ad67 --- /dev/null +++ b/service/system/auto_code_package_test.go @@ -0,0 +1,105 @@ +package system + +import ( + "context" + model "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "reflect" + "testing" +) + +func Test_autoCodePackage_Create(t *testing.T) { + type args struct { + ctx context.Context + info *request.SysAutoCodePackageCreate + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "测试 package", + args: args{ + ctx: context.Background(), + info: &request.SysAutoCodePackageCreate{ + Template: "package", + PackageName: "gva", + }, + }, + wantErr: false, + }, + { + name: "测试 plugin", + args: args{ + ctx: context.Background(), + info: &request.SysAutoCodePackageCreate{ + Template: "plugin", + PackageName: "gva", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &autoCodePackage{} + if err := a.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr { + t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_autoCodePackage_templates(t *testing.T) { + type args struct { + ctx context.Context + entity model.SysAutoCodePackage + info request.AutoCode + } + tests := []struct { + name string + args args + wantCode map[string]string + wantEnter map[string]map[string]string + wantErr bool + }{ + { + name: "测试1", + args: args{ + ctx: context.Background(), + entity: model.SysAutoCodePackage{ + Desc: "描述", + Label: "展示名", + Template: "plugin", + PackageName: "preview", + }, + info: request.AutoCode{ + Abbreviation: "user", + HumpPackageName: "user", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &autoCodePackage{} + gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info) + if (err != nil) != tt.wantErr { + t.Errorf("templates() error = %v, wantErr %v", err, tt.wantErr) + return + } + for key, value := range gotCode { + t.Logf("\n") + t.Logf(key) + t.Logf(value) + t.Logf("\n") + } + t.Log(gotCreates) + if !reflect.DeepEqual(gotEnter, tt.wantEnter) { + t.Errorf("templates() gotEnter = %v, want %v", gotEnter, tt.wantEnter) + } + }) + } +} diff --git a/service/system/auto_code_plugin.go b/service/system/auto_code_plugin.go new file mode 100644 index 0000000..0f9ecba --- /dev/null +++ b/service/system/auto_code_plugin.go @@ -0,0 +1,249 @@ +package system + +import ( + "bytes" + "context" + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "git.echol.cn/loser/xiecheng_server/utils/ast" + "github.com/mholt/archiver/v4" + cp "github.com/otiai10/copy" + "github.com/pkg/errors" + "go.uber.org/zap" + "go/parser" + "go/printer" + "go/token" + "io" + "mime/multipart" + "os" + "path/filepath" + "strings" +) + +var AutoCodePlugin = new(autoCodePlugin) + +type autoCodePlugin struct{} + +// Install 插件安装 +func (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, err error) { + const GVAPLUGPINATH = "./gva-plug-temp/" + defer os.RemoveAll(GVAPLUGPINATH) + _, err = os.Stat(GVAPLUGPINATH) + if os.IsNotExist(err) { + os.Mkdir(GVAPLUGPINATH, os.ModePerm) + } + + src, err := file.Open() + if err != nil { + return -1, -1, err + } + defer src.Close() + + out, err := os.Create(GVAPLUGPINATH + file.Filename) + if err != nil { + return -1, -1, err + } + defer out.Close() + + _, err = io.Copy(out, src) + + paths, err := utils.Unzip(GVAPLUGPINATH+file.Filename, GVAPLUGPINATH) + paths = filterFile(paths) + var webIndex = -1 + var serverIndex = -1 + webPlugin := "" + serverPlugin := "" + + for i := range paths { + paths[i] = filepath.ToSlash(paths[i]) + pathArr := strings.Split(paths[i], "/") + ln := len(pathArr) + + if ln < 4 { + continue + } + if pathArr[2]+"/"+pathArr[3] == `server/plugin` && len(serverPlugin) == 0 { + serverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3]) + } + if pathArr[2]+"/"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 { + webPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3]) + } + } + if len(serverPlugin) == 0 && len(webPlugin) == 0 { + zap.L().Error("非标准插件,请按照文档自动迁移使用") + return webIndex, serverIndex, errors.New("非标准插件,请按照文档自动迁移使用") + } + + if len(serverPlugin) != 0 { + err = installation(serverPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Server) + if err != nil { + return webIndex, serverIndex, err + } + } + + if len(webPlugin) != 0 { + err = installation(webPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Web) + if err != nil { + return webIndex, serverIndex, err + } + } + + return 1, 1, err +} + +func installation(path string, formPath string, toPath string) error { + arr := strings.Split(filepath.ToSlash(path), "/") + ln := len(arr) + if ln < 3 { + return errors.New("arr") + } + name := arr[ln-3] + + var form = filepath.Join(global.GVA_CONFIG.AutoCode.Root, formPath, path) + var to = filepath.Join(global.GVA_CONFIG.AutoCode.Root, toPath, "plugin") + _, err := os.Stat(to + name) + if err == nil { + zap.L().Error("autoPath 已存在同名插件,请自行手动安装", zap.String("to", to)) + return errors.New(toPath + "已存在同名插件,请自行手动安装") + } + return cp.Copy(form, to, cp.Options{Skip: skipMacSpecialDocument}) +} + +func filterFile(paths []string) []string { + np := make([]string, 0, len(paths)) + for _, path := range paths { + if ok, _ := skipMacSpecialDocument(nil, path, ""); ok { + continue + } + np = append(np, path) + } + return np +} + +func skipMacSpecialDocument(_ os.FileInfo, src, _ string) (bool, error) { + if strings.Contains(src, ".DS_Store") || strings.Contains(src, "__MACOSX") { + return true, nil + } + return false, nil +} + +func (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) { + if plugName == "" { + return "", errors.New("插件名称不能为空") + } + + // 防止路径穿越 + plugName = filepath.Clean(plugName) + + webPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", plugName) + serverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", plugName) + // 创建一个新的zip文件 + + // 判断目录是否存在 + _, err = os.Stat(webPath) + if err != nil { + return "", errors.New("web路径不存在") + } + _, err = os.Stat(serverPath) + if err != nil { + return "", errors.New("server路径不存在") + } + + fileName := plugName + ".zip" + // 创建一个新的zip文件 + files, err := archiver.FilesFromDisk(nil, map[string]string{ + webPath: plugName + "/web/plugin/" + plugName, + serverPath: plugName + "/server/plugin/" + plugName, + }) + + // create the output file we'll write to + out, err := os.Create(fileName) + if err != nil { + return + } + defer out.Close() + + // we can use the CompressedArchive type to gzip a tarball + // (compression is not required; you could use Tar directly) + format := archiver.Archive{ + Archival: archiver.Zip{}, + } + + // create the archive + err = format.Archive(context.Background(), out, files) + if err != nil { + return + } + + return filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fileName), nil +} + +func (s *autoCodePlugin) InitMenu(menuInfo request.InitMenu) (err error) { + menuPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", menuInfo.PlugName, "initialize", "menu.go") + src, err := os.ReadFile(menuPath) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + arrayAst := ast.FindArray(astFile, "model", "SysBaseMenu") + var menus []system.SysBaseMenu + + parentMenu := []system.SysBaseMenu{ + { + ParentId: 0, + Path: menuInfo.PlugName + "Menu", + Name: menuInfo.PlugName + "Menu", + Hidden: false, + Component: "view/routerHolder.vue", + Sort: 0, + Meta: system.Meta{ + Title: menuInfo.ParentMenu, + Icon: "school", + }, + }, + } + + err = global.GVA_DB.Find(&menus, "id in (?)", menuInfo.Menus).Error + if err != nil { + return err + } + menus = append(parentMenu, menus...) + menuExpr := ast.CreateMenuStructAst(menus) + arrayAst.Elts = *menuExpr + + var out []byte + bf := bytes.NewBuffer(out) + printer.Fprint(bf, fileSet, astFile) + + os.WriteFile(menuPath, bf.Bytes(), 0666) + return nil +} + +func (s *autoCodePlugin) InitAPI(apiInfo request.InitApi) (err error) { + apiPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", apiInfo.PlugName, "initialize", "api.go") + src, err := os.ReadFile(apiPath) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + arrayAst := ast.FindArray(astFile, "model", "SysApi") + var apis []system.SysApi + err = global.GVA_DB.Find(&apis, "id in (?)", apiInfo.APIs).Error + if err != nil { + return err + } + apisExpr := ast.CreateApiStructAst(apis) + arrayAst.Elts = *apisExpr + + var out []byte + bf := bytes.NewBuffer(out) + printer.Fprint(bf, fileSet, astFile) + + os.WriteFile(apiPath, bf.Bytes(), 0666) + return nil +} diff --git a/service/system/auto_code_template.go b/service/system/auto_code_template.go new file mode 100644 index 0000000..7050b26 --- /dev/null +++ b/service/system/auto_code_template.go @@ -0,0 +1,452 @@ +package system + +import ( + "context" + "encoding/json" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + "text/template" + + "git.echol.cn/loser/xiecheng_server/global" + model "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" + utilsAst "git.echol.cn/loser/xiecheng_server/utils/ast" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +var AutoCodeTemplate = new(autoCodeTemplate) + +type autoCodeTemplate struct{} + +func (s *autoCodeTemplate) checkPackage(Pkg string, template string) (err error) { + switch template { + case "package": + apiEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", Pkg, "enter.go") + _, err = os.Stat(apiEnter) + if err != nil { + return fmt.Errorf("package结构异常,缺少api/v1/%s/enter.go", Pkg) + } + serviceEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", Pkg, "enter.go") + _, err = os.Stat(serviceEnter) + if err != nil { + return fmt.Errorf("package结构异常,缺少service/%s/enter.go", Pkg) + } + routerEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", Pkg, "enter.go") + _, err = os.Stat(routerEnter) + if err != nil { + return fmt.Errorf("package结构异常,缺少router/%s/enter.go", Pkg) + } + case "plugin": + pluginEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", Pkg, "plugin.go") + _, err = os.Stat(pluginEnter) + if err != nil { + return fmt.Errorf("plugin结构异常,缺少plugin/%s/plugin.go", Pkg) + } + } + return nil +} + +// Create 创建生成自动化代码 +func (s *autoCodeTemplate) Create(ctx context.Context, info request.AutoCode) error { + history := info.History() + var autoPkg model.SysAutoCodePackage + err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&autoPkg).Error + if err != nil { + return errors.Wrap(err, "查询包失败!") + } + err = s.checkPackage(info.Package, autoPkg.Template) + if err != nil { + return err + } + // 增加判断: 重复创建struct 或者重复的简称 + if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) { + return errors.New("已经创建过此数据结构,请勿重复创建!") + } + + generate, templates, injections, err := s.generate(ctx, info, autoPkg) + if err != nil { + return err + } + for key, builder := range generate { + err = os.MkdirAll(filepath.Dir(key), os.ModePerm) + if err != nil { + return errors.Wrapf(err, "[filepath:%s]创建文件夹失败!", key) + } + err = os.WriteFile(key, []byte(builder.String()), 0666) + if err != nil { + return errors.Wrapf(err, "[filepath:%s]写入文件失败!", key) + } + } + + // 自动创建api + if info.AutoCreateApiToSql && !info.OnlyTemplate { + apis := info.Apis() + err := global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + for _, v := range apis { + var api model.SysApi + var id uint + err := tx.Where("path = ? AND method = ?", v.Path, v.Method).First(&api).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + if err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务 + return err + } + id = v.ID + } else { + id = api.ID + } + history.ApiIDs = append(history.ApiIDs, id) + } + return nil + }) + if err != nil { + return err + } + } + + // 自动创建menu + if info.AutoCreateMenuToSql { + var entity model.SysBaseMenu + var id uint + err := global.GVA_DB.WithContext(ctx).First(&entity, "name = ?", info.Abbreviation).Error + if err == nil { + id = entity.ID + } else { + entity = info.Menu(autoPkg.Template) + if info.AutoCreateBtnAuth && !info.OnlyTemplate { + entity.MenuBtn = []model.SysBaseMenuBtn{ + {SysBaseMenuID: entity.ID, Name: "add", Desc: "新增"}, + {SysBaseMenuID: entity.ID, Name: "batchDelete", Desc: "批量删除"}, + {SysBaseMenuID: entity.ID, Name: "delete", Desc: "删除"}, + {SysBaseMenuID: entity.ID, Name: "edit", Desc: "编辑"}, + {SysBaseMenuID: entity.ID, Name: "info", Desc: "详情"}, + } + if info.HasExcel { + excelBtn := []model.SysBaseMenuBtn{ + {SysBaseMenuID: entity.ID, Name: "exportTemplate", Desc: "导出模板"}, + {SysBaseMenuID: entity.ID, Name: "exportExcel", Desc: "导出Excel"}, + {SysBaseMenuID: entity.ID, Name: "importExcel", Desc: "导入Excel"}, + } + entity.MenuBtn = append(entity.MenuBtn, excelBtn...) + } + } + err = global.GVA_DB.WithContext(ctx).Create(&entity).Error + id = entity.ID + if err != nil { + return errors.Wrap(err, "创建菜单失败!") + } + } + history.MenuID = id + } + + if info.HasExcel { + dbName := info.BusinessDB + name := info.Package + "_" + info.StructName + tableName := info.TableName + fieldsMap := make(map[string]string, len(info.Fields)) + for _, field := range info.Fields { + if field.Excel { + fieldsMap[field.ColumnName] = field.FieldDesc + } + } + templateInfo, _ := json.Marshal(fieldsMap) + sysExportTemplate := model.SysExportTemplate{ + DBName: dbName, + Name: name, + TableName: tableName, + TemplateID: name, + TemplateInfo: string(templateInfo), + } + err = SysExportTemplateServiceApp.CreateSysExportTemplate(&sysExportTemplate) + if err != nil { + return err + } + history.ExportTemplateID = sysExportTemplate.ID + } + + // 创建历史记录 + history.Templates = templates + history.Injections = make(map[string]string, len(injections)) + for key, value := range injections { + bytes, _ := json.Marshal(value) + history.Injections[key] = string(bytes) + } + err = AutocodeHistory.Create(ctx, history) + if err != nil { + return err + } + return nil +} + +// Preview 预览自动化代码 +func (s *autoCodeTemplate) Preview(ctx context.Context, info request.AutoCode) (map[string]string, error) { + var entity model.SysAutoCodePackage + err := global.GVA_DB.WithContext(ctx).Where("package_name = ?", info.Package).First(&entity).Error + if err != nil { + return nil, errors.Wrap(err, "查询包失败!") + } + // 增加判断: 重复创建struct 或者重复的简称 + if AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) && !info.IsAdd { + return nil, errors.New("已经创建过此数据结构或重复简称,请勿重复创建!") + } + + preview := make(map[string]string) + codes, _, _, err := s.generate(ctx, info, entity) + if err != nil { + return nil, err + } + for key, writer := range codes { + if len(key) > len(global.GVA_CONFIG.AutoCode.Root) { + key, _ = filepath.Rel(global.GVA_CONFIG.AutoCode.Root, key) + } + // 获取key的后缀 取消. + suffix := filepath.Ext(key)[1:] + var builder strings.Builder + builder.WriteString("```" + suffix + "\n\n") + builder.WriteString(writer.String()) + builder.WriteString("\n\n```") + preview[key] = builder.String() + } + return preview, nil +} + +func (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode, entity model.SysAutoCodePackage) (map[string]strings.Builder, map[string]string, map[string]utilsAst.Ast, error) { + templates, asts, _, err := AutoCodePackage.templates(ctx, entity, info, false) + if err != nil { + return nil, nil, nil, err + } + code := make(map[string]strings.Builder) + for key, create := range templates { + var files *template.Template + files, err = template.ParseFiles(key) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]读取模版文件失败!", key) + } + var builder strings.Builder + err = files.Execute(&builder, info) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "[filpath:%s]生成文件失败!", create) + } + code[create] = builder + } // 生成文件 + injections := make(map[string]utilsAst.Ast, len(asts)) + for key, value := range asts { + keys := strings.Split(key, "=>") + if len(keys) == 2 { + if keys[1] == utilsAst.TypePluginInitializeV2 { + continue + } + if info.OnlyTemplate { + if keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm { + continue + } + } + if !info.AutoMigrate { + if keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm { + continue + } + } + var builder strings.Builder + parse, _ := value.Parse("", &builder) + if parse != nil { + _ = value.Injection(parse) + err = value.Format("", &builder, parse) + if err != nil { + return nil, nil, nil, err + } + code[keys[0]] = builder + injections[keys[1]] = value + fmt.Println(keys[0], "注入成功!") + } + } + } + // 注入代码 + return code, templates, injections, nil +} + +func (s *autoCodeTemplate) AddFunc(info request.AutoFunc) error { + autoPkg := model.SysAutoCodePackage{} + err := global.GVA_DB.First(&autoPkg, "package_name = ?", info.Package).Error + if err != nil { + return err + } + if autoPkg.Template != "package" { + info.IsPlugin = true + } + err = s.addTemplateToFile("api.go", info) + if err != nil { + return err + } + err = s.addTemplateToFile("server.go", info) + if err != nil { + return err + } + err = s.addTemplateToFile("api.js", info) + if err != nil { + return err + } + return s.addTemplateToAst("router", info) +} + +func (s *autoCodeTemplate) GetApiAndServer(info request.AutoFunc) (map[string]string, error) { + autoPkg := model.SysAutoCodePackage{} + err := global.GVA_DB.First(&autoPkg, "package_name = ?", info.Package).Error + if err != nil { + return nil, err + } + if autoPkg.Template != "package" { + info.IsPlugin = true + } + + apiStr, err := s.getTemplateStr("api.go", info) + if err != nil { + return nil, err + } + serverStr, err := s.getTemplateStr("server.go", info) + if err != nil { + return nil, err + } + jsStr, err := s.getTemplateStr("api.js", info) + if err != nil { + return nil, err + } + return map[string]string{"api": apiStr, "server": serverStr, "js": jsStr}, nil + +} + +func (s *autoCodeTemplate) getTemplateStr(t string, info request.AutoFunc) (string, error) { + tempPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "resource", "function", t+".tpl") + files, err := template.ParseFiles(tempPath) + if err != nil { + return "", errors.Wrapf(err, "[filepath:%s]读取模版文件失败!", tempPath) + } + var builder strings.Builder + err = files.Execute(&builder, info) + if err != nil { + fmt.Println(err.Error()) + return "", errors.Wrapf(err, "[filpath:%s]生成文件失败!", tempPath) + } + return builder.String(), nil +} + +func (s *autoCodeTemplate) addTemplateToAst(t string, info request.AutoFunc) error { + tPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", info.Package, info.HumpPackageName+".go") + funcName := fmt.Sprintf("Init%sRouter", info.StructName) + + routerStr := "RouterWithoutAuth" + if info.IsAuth { + routerStr = "Router" + } + + stmtStr := fmt.Sprintf("%s%s.%s(\"%s\", %sApi.%s)", info.Abbreviation, routerStr, info.Method, info.Router, info.Abbreviation, info.FuncName) + if info.IsPlugin { + tPath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "router", info.HumpPackageName+".go") + stmtStr = fmt.Sprintf("group.%s(\"%s\", api%s.%s)", info.Method, info.Router, info.StructName, info.FuncName) + funcName = "Init" + } + + src, err := os.ReadFile(tPath) + if err != nil { + return err + } + + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + if err != nil { + return err + } + funcDecl := utilsAst.FindFunction(astFile, funcName) + stmtNode := utilsAst.CreateStmt(stmtStr) + + if info.IsAuth { + for i := 0; i < len(funcDecl.Body.List); i++ { + st := funcDecl.Body.List[i] + // 使用类型断言来检查stmt是否是一个块语句 + if blockStmt, ok := st.(*ast.BlockStmt); ok { + // 如果是,插入代码 跳出 + blockStmt.List = append(blockStmt.List, stmtNode) + break + } + } + } else { + for i := len(funcDecl.Body.List) - 1; i >= 0; i-- { + st := funcDecl.Body.List[i] + // 使用类型断言来检查stmt是否是一个块语句 + if blockStmt, ok := st.(*ast.BlockStmt); ok { + // 如果是,插入代码 跳出 + blockStmt.List = append(blockStmt.List, stmtNode) + break + } + } + } + + // 创建一个新的文件 + f, err := os.Create(tPath) + if err != nil { + return err + } + defer f.Close() + + if err := format.Node(f, fileSet, astFile); err != nil { + return err + } + return err +} + +func (s *autoCodeTemplate) addTemplateToFile(t string, info request.AutoFunc) error { + getTemplateStr, err := s.getTemplateStr(t, info) + if err != nil { + return err + } + var target string + + switch t { + case "api.go": + if info.IsAi && info.ApiFunc != "" { + getTemplateStr = info.ApiFunc + } + target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", info.Package, info.HumpPackageName+".go") + case "server.go": + if info.IsAi && info.ServerFunc != "" { + getTemplateStr = info.ServerFunc + } + target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", info.Package, info.HumpPackageName+".go") + case "api.js": + if info.IsAi && info.JsFunc != "" { + getTemplateStr = info.JsFunc + } + target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "api", info.Package, info.PackageName+".js") + } + if info.IsPlugin { + switch t { + case "api.go": + target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "api", info.HumpPackageName+".go") + case "server.go": + target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", info.Package, "service", info.HumpPackageName+".go") + case "api.js": + target = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin", info.Package, "api", info.PackageName+".js") + } + } + + // 打开文件,如果不存在则返回错误 + file, err := os.OpenFile(target, os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + defer file.Close() + + // 写入内容 + _, err = fmt.Fprintln(file, getTemplateStr) + if err != nil { + fmt.Printf("写入文件失败: %s\n", err.Error()) + return err + } + + return nil +} diff --git a/service/system/auto_code_template_test.go b/service/system/auto_code_template_test.go new file mode 100644 index 0000000..d632660 --- /dev/null +++ b/service/system/auto_code_template_test.go @@ -0,0 +1,84 @@ +package system + +import ( + "context" + "encoding/json" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "reflect" + "testing" +) + +func Test_autoCodeTemplate_Create(t *testing.T) { + type args struct { + ctx context.Context + info request.AutoCode + } + tests := []struct { + name string + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &autoCodeTemplate{} + if err := s.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr { + t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_autoCodeTemplate_Preview(t *testing.T) { + type args struct { + ctx context.Context + info request.AutoCode + } + tests := []struct { + name string + args args + want map[string]string + wantErr bool + }{ + { + name: "测试 package", + args: args{ + ctx: context.Background(), + info: request.AutoCode{}, + }, + wantErr: false, + }, + { + name: "测试 plugin", + args: args{ + ctx: context.Background(), + info: request.AutoCode{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testJson := `{"structName":"SysUser","tableName":"sys_users","packageName":"sysUsers","package":"gva","abbreviation":"sysUsers","description":"sysUsers表","businessDB":"","autoCreateApiToSql":true,"autoCreateMenuToSql":true,"autoMigrate":true,"gvaModel":true,"autoCreateResource":false,"fields":[{"fieldName":"Uuid","fieldDesc":"用户UUID","fieldType":"string","dataType":"varchar","fieldJson":"uuid","primaryKey":false,"dataTypeLong":"191","columnName":"uuid","comment":"用户UUID","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Username","fieldDesc":"用户登录名","fieldType":"string","dataType":"varchar","fieldJson":"username","primaryKey":false,"dataTypeLong":"191","columnName":"username","comment":"用户登录名","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Password","fieldDesc":"用户登录密码","fieldType":"string","dataType":"varchar","fieldJson":"password","primaryKey":false,"dataTypeLong":"191","columnName":"password","comment":"用户登录密码","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"NickName","fieldDesc":"用户昵称","fieldType":"string","dataType":"varchar","fieldJson":"nickName","primaryKey":false,"dataTypeLong":"191","columnName":"nick_name","comment":"用户昵称","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"SideMode","fieldDesc":"用户侧边主题","fieldType":"string","dataType":"varchar","fieldJson":"sideMode","primaryKey":false,"dataTypeLong":"191","columnName":"side_mode","comment":"用户侧边主题","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"HeaderImg","fieldDesc":"用户头像","fieldType":"string","dataType":"varchar","fieldJson":"headerImg","primaryKey":false,"dataTypeLong":"191","columnName":"header_img","comment":"用户头像","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"BaseColor","fieldDesc":"基础颜色","fieldType":"string","dataType":"varchar","fieldJson":"baseColor","primaryKey":false,"dataTypeLong":"191","columnName":"base_color","comment":"基础颜色","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"AuthorityId","fieldDesc":"用户角色ID","fieldType":"int","dataType":"bigint","fieldJson":"authorityId","primaryKey":false,"dataTypeLong":"20","columnName":"authority_id","comment":"用户角色ID","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Phone","fieldDesc":"用户手机号","fieldType":"string","dataType":"varchar","fieldJson":"phone","primaryKey":false,"dataTypeLong":"191","columnName":"phone","comment":"用户手机号","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Email","fieldDesc":"用户邮箱","fieldType":"string","dataType":"varchar","fieldJson":"email","primaryKey":false,"dataTypeLong":"191","columnName":"email","comment":"用户邮箱","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}},{"fieldName":"Enable","fieldDesc":"用户是否被冻结 1正常 2冻结","fieldType":"int","dataType":"bigint","fieldJson":"enable","primaryKey":false,"dataTypeLong":"19","columnName":"enable","comment":"用户是否被冻结 1正常 2冻结","require":false,"errorText":"","clearable":true,"fieldSearchType":"","fieldIndexType":"","dictType":"","front":true,"dataSource":{"association":1,"table":"","label":"","value":""}}],"humpPackageName":"sys_users"}` + err := json.Unmarshal([]byte(testJson), &tt.args.info) + if err != nil { + t.Error(err) + return + } + err = tt.args.info.Pretreatment() + if err != nil { + t.Error(err) + return + } + got, err := AutoCodeTemplate.Preview(tt.args.ctx, tt.args.info) + if (err != nil) != tt.wantErr { + t.Errorf("Preview() error = %+v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Preview() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/service/system/enter.go b/service/system/enter.go new file mode 100644 index 0000000..634cd00 --- /dev/null +++ b/service/system/enter.go @@ -0,0 +1,24 @@ +package system + +type ServiceGroup struct { + JwtService + ApiService + MenuService + UserService + CasbinService + InitDBService + AutoCodeService + BaseMenuService + AuthorityService + DictionaryService + SystemConfigService + OperationRecordService + DictionaryDetailService + AuthorityBtnService + SysExportTemplateService + SysParamsService + AutoCodePlugin autoCodePlugin + AutoCodePackage autoCodePackage + AutoCodeHistory autoCodeHistory + AutoCodeTemplate autoCodeTemplate +} diff --git a/service/system/jwt_black_list.go b/service/system/jwt_black_list.go new file mode 100644 index 0000000..26ca6a2 --- /dev/null +++ b/service/system/jwt_black_list.go @@ -0,0 +1,84 @@ +package system + +import ( + "context" + + "go.uber.org/zap" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/utils" +) + +type JwtService struct{} + +var JwtServiceApp = new(JwtService) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: JsonInBlacklist +//@description: 拉黑jwt +//@param: jwtList model.JwtBlacklist +//@return: err error + +func (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err error) { + err = global.GVA_DB.Create(&jwtList).Error + if err != nil { + return + } + global.BlackCache.SetDefault(jwtList.Jwt, struct{}{}) + return +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: IsBlacklist +//@description: 判断JWT是否在黑名单内部 +//@param: jwt string +//@return: bool + +func (jwtService *JwtService) IsBlacklist(jwt string) bool { + _, ok := global.BlackCache.Get(jwt) + return ok + // err := global.GVA_DB.Where("jwt = ?", jwt).First(&system.JwtBlacklist{}).Error + // isNotFound := errors.Is(err, gorm.ErrRecordNotFound) + // return !isNotFound +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetRedisJWT +//@description: 从redis取jwt +//@param: userName string +//@return: redisJWT string, err error + +func (jwtService *JwtService) GetRedisJWT(userName string) (redisJWT string, err error) { + redisJWT, err = global.GVA_REDIS.Get(context.Background(), userName).Result() + return redisJWT, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetRedisJWT +//@description: jwt存入redis并设置过期时间 +//@param: jwt string, userName string +//@return: err error + +func (jwtService *JwtService) SetRedisJWT(jwt string, userName string) (err error) { + // 此处过期时间等于jwt过期时间 + dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + if err != nil { + return err + } + timer := dr + err = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err() + return err +} + +func LoadAll() { + var data []string + err := global.GVA_DB.Model(&system.JwtBlacklist{}).Select("jwt").Find(&data).Error + if err != nil { + global.GVA_LOG.Error("加载数据库jwt黑名单失败!", zap.Error(err)) + return + } + for i := 0; i < len(data); i++ { + global.BlackCache.SetDefault(data[i], struct{}{}) + } // jwt黑名单 加入 BlackCache 中 +} diff --git a/service/system/sys_api.go b/service/system/sys_api.go new file mode 100644 index 0000000..a185916 --- /dev/null +++ b/service/system/sys_api.go @@ -0,0 +1,326 @@ +package system + +import ( + "errors" + "fmt" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" + systemRes "git.echol.cn/loser/xiecheng_server/model/system/response" + "gorm.io/gorm" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateApi +//@description: 新增基础api +//@param: api model.SysApi +//@return: err error + +type ApiService struct{} + +var ApiServiceApp = new(ApiService) + +func (apiService *ApiService) CreateApi(api system.SysApi) (err error) { + if !errors.Is(global.GVA_DB.Where("path = ? AND method = ?", api.Path, api.Method).First(&system.SysApi{}).Error, gorm.ErrRecordNotFound) { + return errors.New("存在相同api") + } + return global.GVA_DB.Create(&api).Error +} + +func (apiService *ApiService) GetApiGroups() (groups []string, groupApiMap map[string]string, err error) { + var apis []system.SysApi + err = global.GVA_DB.Find(&apis).Error + if err != nil { + return + } + groupApiMap = make(map[string]string, 0) + for i := range apis { + pathArr := strings.Split(apis[i].Path, "/") + newGroup := true + for i2 := range groups { + if groups[i2] == apis[i].ApiGroup { + newGroup = false + } + } + if newGroup { + groups = append(groups, apis[i].ApiGroup) + } + groupApiMap[pathArr[1]] = apis[i].ApiGroup + } + return +} + +func (apiService *ApiService) SyncApi() (newApis, deleteApis, ignoreApis []system.SysApi, err error) { + newApis = make([]system.SysApi, 0) + deleteApis = make([]system.SysApi, 0) + ignoreApis = make([]system.SysApi, 0) + var apis []system.SysApi + err = global.GVA_DB.Find(&apis).Error + if err != nil { + return + } + var ignores []system.SysIgnoreApi + err = global.GVA_DB.Find(&ignores).Error + if err != nil { + return + } + + for i := range ignores { + ignoreApis = append(ignoreApis, system.SysApi{ + Path: ignores[i].Path, + Description: "", + ApiGroup: "", + Method: ignores[i].Method, + }) + } + + var cacheApis []system.SysApi + for i := range global.GVA_ROUTERS { + ignoresFlag := false + for j := range ignores { + if ignores[j].Path == global.GVA_ROUTERS[i].Path && ignores[j].Method == global.GVA_ROUTERS[i].Method { + ignoresFlag = true + } + } + if !ignoresFlag { + cacheApis = append(cacheApis, system.SysApi{ + Path: global.GVA_ROUTERS[i].Path, + Method: global.GVA_ROUTERS[i].Method, + }) + } + } + + //对比数据库中的api和内存中的api,如果数据库中的api不存在于内存中,则把api放入删除数组,如果内存中的api不存在于数据库中,则把api放入新增数组 + for i := range cacheApis { + var flag bool + // 如果存在于内存不存在于api数组中 + for j := range apis { + if cacheApis[i].Path == apis[j].Path && cacheApis[i].Method == apis[j].Method { + flag = true + } + } + if !flag { + newApis = append(newApis, system.SysApi{ + Path: cacheApis[i].Path, + Description: "", + ApiGroup: "", + Method: cacheApis[i].Method, + }) + } + } + + for i := range apis { + var flag bool + // 如果存在于api数组不存在于内存 + for j := range cacheApis { + if cacheApis[j].Path == apis[i].Path && cacheApis[j].Method == apis[i].Method { + flag = true + } + } + if !flag { + deleteApis = append(deleteApis, apis[i]) + } + } + return +} + +func (apiService *ApiService) IgnoreApi(ignoreApi system.SysIgnoreApi) (err error) { + if ignoreApi.Flag { + return global.GVA_DB.Create(&ignoreApi).Error + } + return global.GVA_DB.Unscoped().Delete(&ignoreApi, "path = ? AND method = ?", ignoreApi.Path, ignoreApi.Method).Error +} + +func (apiService *ApiService) EnterSyncApi(syncApis systemRes.SysSyncApis) (err error) { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + var txErr error + if len(syncApis.NewApis) > 0 { + txErr = tx.Create(&syncApis.NewApis).Error + if txErr != nil { + return txErr + } + } + for i := range syncApis.DeleteApis { + CasbinServiceApp.ClearCasbin(1, syncApis.DeleteApis[i].Path, syncApis.DeleteApis[i].Method) + txErr = tx.Delete(&system.SysApi{}, "path = ? AND method = ?", syncApis.DeleteApis[i].Path, syncApis.DeleteApis[i].Method).Error + if txErr != nil { + return txErr + } + } + return nil + }) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteApi +//@description: 删除基础api +//@param: api model.SysApi +//@return: err error + +func (apiService *ApiService) DeleteApi(api system.SysApi) (err error) { + var entity system.SysApi + err = global.GVA_DB.First(&entity, "id = ?", api.ID).Error // 根据id查询api记录 + if errors.Is(err, gorm.ErrRecordNotFound) { // api记录不存在 + return err + } + err = global.GVA_DB.Delete(&entity).Error + if err != nil { + return err + } + CasbinServiceApp.ClearCasbin(1, entity.Path, entity.Method) + return nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetAPIInfoList +//@description: 分页获取数据, +//@param: api model.SysApi, info request.PageInfo, order string, desc bool +//@return: list interface{}, total int64, err error + +func (apiService *ApiService) GetAPIInfoList(api system.SysApi, info request.PageInfo, order string, desc bool) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + db := global.GVA_DB.Model(&system.SysApi{}) + var apiList []system.SysApi + + if api.Path != "" { + db = db.Where("path LIKE ?", "%"+api.Path+"%") + } + + if api.Description != "" { + db = db.Where("description LIKE ?", "%"+api.Description+"%") + } + + if api.Method != "" { + db = db.Where("method = ?", api.Method) + } + + if api.ApiGroup != "" { + db = db.Where("api_group = ?", api.ApiGroup) + } + + err = db.Count(&total).Error + + if err != nil { + return apiList, total, err + } + + db = db.Limit(limit).Offset(offset) + OrderStr := "id desc" + if order != "" { + orderMap := make(map[string]bool, 5) + orderMap["id"] = true + orderMap["path"] = true + orderMap["api_group"] = true + orderMap["description"] = true + orderMap["method"] = true + if !orderMap[order] { + err = fmt.Errorf("非法的排序字段: %v", order) + return apiList, total, err + } + OrderStr = order + if desc { + OrderStr = order + " desc" + } + } + err = db.Order(OrderStr).Find(&apiList).Error + return apiList, total, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetAllApis +//@description: 获取所有的api +//@return: apis []model.SysApi, err error + +func (apiService *ApiService) GetAllApis(authorityID uint) (apis []system.SysApi, err error) { + parentAuthorityID, err := AuthorityServiceApp.GetParentAuthorityID(authorityID) + if err != nil { + return nil, err + } + err = global.GVA_DB.Order("id desc").Find(&apis).Error + if parentAuthorityID == 0 || !global.GVA_CONFIG.System.UseStrictAuth { + return + } + paths := CasbinServiceApp.GetPolicyPathByAuthorityId(authorityID) + // 挑选 apis里面的path和method也在paths里面的api + var authApis []system.SysApi + for i := range apis { + for j := range paths { + if paths[j].Path == apis[i].Path && paths[j].Method == apis[i].Method { + authApis = append(authApis, apis[i]) + } + } + } + return authApis, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetApiById +//@description: 根据id获取api +//@param: id float64 +//@return: api model.SysApi, err error + +func (apiService *ApiService) GetApiById(id int) (api system.SysApi, err error) { + err = global.GVA_DB.First(&api, "id = ?", id).Error + return +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateApi +//@description: 根据id更新api +//@param: api model.SysApi +//@return: err error + +func (apiService *ApiService) UpdateApi(api system.SysApi) (err error) { + var oldA system.SysApi + err = global.GVA_DB.First(&oldA, "id = ?", api.ID).Error + if oldA.Path != api.Path || oldA.Method != api.Method { + var duplicateApi system.SysApi + if ferr := global.GVA_DB.First(&duplicateApi, "path = ? AND method = ?", api.Path, api.Method).Error; ferr != nil { + if !errors.Is(ferr, gorm.ErrRecordNotFound) { + return ferr + } + } else { + if duplicateApi.ID != api.ID { + return errors.New("存在相同api路径") + } + } + + } + if err != nil { + return err + } + + err = CasbinServiceApp.UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method) + if err != nil { + return err + } + + return global.GVA_DB.Save(&api).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteApisByIds +//@description: 删除选中API +//@param: apis []model.SysApi +//@return: err error + +func (apiService *ApiService) DeleteApisByIds(ids request.IdsReq) (err error) { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + var apis []system.SysApi + err = tx.Find(&apis, "id in ?", ids.Ids).Error + if err != nil { + return err + } + err = tx.Delete(&[]system.SysApi{}, "id in ?", ids.Ids).Error + if err != nil { + return err + } + for _, sysApi := range apis { + CasbinServiceApp.ClearCasbin(1, sysApi.Path, sysApi.Method) + } + return err + }) +} diff --git a/service/system/sys_authority.go b/service/system/sys_authority.go new file mode 100644 index 0000000..5549971 --- /dev/null +++ b/service/system/sys_authority.go @@ -0,0 +1,330 @@ +package system + +import ( + "errors" + "strconv" + + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/response" + "gorm.io/gorm" +) + +var ErrRoleExistence = errors.New("存在相同角色id") + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateAuthority +//@description: 创建一个角色 +//@param: auth model.SysAuthority +//@return: authority system.SysAuthority, err error + +type AuthorityService struct{} + +var AuthorityServiceApp = new(AuthorityService) + +func (authorityService *AuthorityService) CreateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) { + + if err = global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&system.SysAuthority{}).Error; !errors.Is(err, gorm.ErrRecordNotFound) { + return auth, ErrRoleExistence + } + + e := global.GVA_DB.Transaction(func(tx *gorm.DB) error { + + if err = tx.Create(&auth).Error; err != nil { + return err + } + + auth.SysBaseMenus = systemReq.DefaultMenu() + if err = tx.Model(&auth).Association("SysBaseMenus").Replace(&auth.SysBaseMenus); err != nil { + return err + } + casbinInfos := systemReq.DefaultCasbin() + authorityId := strconv.Itoa(int(auth.AuthorityId)) + rules := [][]string{} + for _, v := range casbinInfos { + rules = append(rules, []string{authorityId, v.Path, v.Method}) + } + return CasbinServiceApp.AddPolicies(tx, rules) + }) + + return auth, e +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CopyAuthority +//@description: 复制一个角色 +//@param: copyInfo response.SysAuthorityCopyResponse +//@return: authority system.SysAuthority, err error + +func (authorityService *AuthorityService) CopyAuthority(adminAuthorityID uint, copyInfo response.SysAuthorityCopyResponse) (authority system.SysAuthority, err error) { + var authorityBox system.SysAuthority + if !errors.Is(global.GVA_DB.Where("authority_id = ?", copyInfo.Authority.AuthorityId).First(&authorityBox).Error, gorm.ErrRecordNotFound) { + return authority, ErrRoleExistence + } + copyInfo.Authority.Children = []system.SysAuthority{} + menus, err := MenuServiceApp.GetMenuAuthority(&request.GetAuthorityId{AuthorityId: copyInfo.OldAuthorityId}) + if err != nil { + return + } + var baseMenu []system.SysBaseMenu + for _, v := range menus { + intNum := v.MenuId + v.SysBaseMenu.ID = uint(intNum) + baseMenu = append(baseMenu, v.SysBaseMenu) + } + copyInfo.Authority.SysBaseMenus = baseMenu + err = global.GVA_DB.Create(©Info.Authority).Error + if err != nil { + return + } + + var btns []system.SysAuthorityBtn + + err = global.GVA_DB.Find(&btns, "authority_id = ?", copyInfo.OldAuthorityId).Error + if err != nil { + return + } + if len(btns) > 0 { + for i := range btns { + btns[i].AuthorityId = copyInfo.Authority.AuthorityId + } + err = global.GVA_DB.Create(&btns).Error + + if err != nil { + return + } + } + paths := CasbinServiceApp.GetPolicyPathByAuthorityId(copyInfo.OldAuthorityId) + err = CasbinServiceApp.UpdateCasbin(adminAuthorityID, copyInfo.Authority.AuthorityId, paths) + if err != nil { + _ = authorityService.DeleteAuthority(©Info.Authority) + } + return copyInfo.Authority, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateAuthority +//@description: 更改一个角色 +//@param: auth model.SysAuthority +//@return: authority system.SysAuthority, err error + +func (authorityService *AuthorityService) UpdateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) { + var oldAuthority system.SysAuthority + err = global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&oldAuthority).Error + if err != nil { + global.GVA_LOG.Debug(err.Error()) + return system.SysAuthority{}, errors.New("查询角色数据失败") + } + err = global.GVA_DB.Model(&oldAuthority).Updates(&auth).Error + return auth, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteAuthority +//@description: 删除角色 +//@param: auth *model.SysAuthority +//@return: err error + +func (authorityService *AuthorityService) DeleteAuthority(auth *system.SysAuthority) error { + if errors.Is(global.GVA_DB.Debug().Preload("Users").First(&auth).Error, gorm.ErrRecordNotFound) { + return errors.New("该角色不存在") + } + if len(auth.Users) != 0 { + return errors.New("此角色有用户正在使用禁止删除") + } + if !errors.Is(global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&system.SysUser{}).Error, gorm.ErrRecordNotFound) { + return errors.New("此角色有用户正在使用禁止删除") + } + if !errors.Is(global.GVA_DB.Where("parent_id = ?", auth.AuthorityId).First(&system.SysAuthority{}).Error, gorm.ErrRecordNotFound) { + return errors.New("此角色存在子角色不允许删除") + } + + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + var err error + if err = tx.Preload("SysBaseMenus").Preload("DataAuthorityId").Where("authority_id = ?", auth.AuthorityId).First(auth).Unscoped().Delete(auth).Error; err != nil { + return err + } + + if len(auth.SysBaseMenus) > 0 { + if err = tx.Model(auth).Association("SysBaseMenus").Delete(auth.SysBaseMenus); err != nil { + return err + } + // err = db.Association("SysBaseMenus").Delete(&auth) + } + if len(auth.DataAuthorityId) > 0 { + if err = tx.Model(auth).Association("DataAuthorityId").Delete(auth.DataAuthorityId); err != nil { + return err + } + } + + if err = tx.Delete(&system.SysUserAuthority{}, "sys_authority_authority_id = ?", auth.AuthorityId).Error; err != nil { + return err + } + if err = tx.Where("authority_id = ?", auth.AuthorityId).Delete(&[]system.SysAuthorityBtn{}).Error; err != nil { + return err + } + + authorityId := strconv.Itoa(int(auth.AuthorityId)) + + if err = CasbinServiceApp.RemoveFilteredPolicy(tx, authorityId); err != nil { + return err + } + + return nil + }) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetAuthorityInfoList +//@description: 分页获取数据 +//@param: info request.PageInfo +//@return: list interface{}, total int64, err error + +func (authorityService *AuthorityService) GetAuthorityInfoList(authorityID uint) (list []system.SysAuthority, err error) { + var authority system.SysAuthority + err = global.GVA_DB.Where("authority_id = ?", authorityID).First(&authority).Error + if err != nil { + return nil, err + } + var authorities []system.SysAuthority + db := global.GVA_DB.Model(&system.SysAuthority{}) + if global.GVA_CONFIG.System.UseStrictAuth { + // 当开启了严格树形结构后 + if *authority.ParentId == 0 { + // 只有顶级角色可以修改自己的权限和以下权限 + err = db.Preload("DataAuthorityId").Where("authority_id = ?", authorityID).Find(&authorities).Error + } else { + // 非顶级角色只能修改以下权限 + err = db.Debug().Preload("DataAuthorityId").Where("parent_id = ?", authorityID).Find(&authorities).Error + } + } else { + err = db.Preload("DataAuthorityId").Where("parent_id = ?", "0").Find(&authorities).Error + } + + for k := range authorities { + err = authorityService.findChildrenAuthority(&authorities[k]) + } + return authorities, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetAuthorityInfoList +//@description: 分页获取数据 +//@param: info request.PageInfo +//@return: list interface{}, total int64, err error + +func (authorityService *AuthorityService) GetStructAuthorityList(authorityID uint) (list []uint, err error) { + var auth system.SysAuthority + _ = global.GVA_DB.First(&auth, "authority_id = ?", authorityID).Error + var authorities []system.SysAuthority + err = global.GVA_DB.Preload("DataAuthorityId").Where("parent_id = ?", authorityID).Find(&authorities).Error + if len(authorities) > 0 { + for k := range authorities { + list = append(list, authorities[k].AuthorityId) + childrenList, err := authorityService.GetStructAuthorityList(authorities[k].AuthorityId) + if err == nil { + list = append(list, childrenList...) + } + } + } + if *auth.ParentId == 0 { + list = append(list, authorityID) + } + return list, err +} + +func (authorityService *AuthorityService) CheckAuthorityIDAuth(authorityID, targetID uint) (err error) { + if !global.GVA_CONFIG.System.UseStrictAuth { + return nil + } + authIDS, err := authorityService.GetStructAuthorityList(authorityID) + if err != nil { + return err + } + hasAuth := false + for _, v := range authIDS { + if v == targetID { + hasAuth = true + break + } + } + if !hasAuth { + return errors.New("您提交的角色ID不合法") + } + return nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetAuthorityInfo +//@description: 获取所有角色信息 +//@param: auth model.SysAuthority +//@return: sa system.SysAuthority, err error + +func (authorityService *AuthorityService) GetAuthorityInfo(auth system.SysAuthority) (sa system.SysAuthority, err error) { + err = global.GVA_DB.Preload("DataAuthorityId").Where("authority_id = ?", auth.AuthorityId).First(&sa).Error + return sa, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetDataAuthority +//@description: 设置角色资源权限 +//@param: auth model.SysAuthority +//@return: error + +func (authorityService *AuthorityService) SetDataAuthority(adminAuthorityID uint, auth system.SysAuthority) error { + var checkIDs []uint + checkIDs = append(checkIDs, auth.AuthorityId) + for i := range auth.DataAuthorityId { + checkIDs = append(checkIDs, auth.DataAuthorityId[i].AuthorityId) + } + + for i := range checkIDs { + err := authorityService.CheckAuthorityIDAuth(adminAuthorityID, checkIDs[i]) + if err != nil { + return err + } + } + + var s system.SysAuthority + global.GVA_DB.Preload("DataAuthorityId").First(&s, "authority_id = ?", auth.AuthorityId) + err := global.GVA_DB.Model(&s).Association("DataAuthorityId").Replace(&auth.DataAuthorityId) + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetMenuAuthority +//@description: 菜单与角色绑定 +//@param: auth *model.SysAuthority +//@return: error + +func (authorityService *AuthorityService) SetMenuAuthority(auth *system.SysAuthority) error { + var s system.SysAuthority + global.GVA_DB.Preload("SysBaseMenus").First(&s, "authority_id = ?", auth.AuthorityId) + err := global.GVA_DB.Model(&s).Association("SysBaseMenus").Replace(&auth.SysBaseMenus) + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: findChildrenAuthority +//@description: 查询子角色 +//@param: authority *model.SysAuthority +//@return: err error + +func (authorityService *AuthorityService) findChildrenAuthority(authority *system.SysAuthority) (err error) { + err = global.GVA_DB.Preload("DataAuthorityId").Where("parent_id = ?", authority.AuthorityId).Find(&authority.Children).Error + if len(authority.Children) > 0 { + for k := range authority.Children { + err = authorityService.findChildrenAuthority(&authority.Children[k]) + } + } + return err +} + +func (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint) (parentID uint, err error) { + var authority system.SysAuthority + err = global.GVA_DB.Where("authority_id = ?", authorityID).First(&authority).Error + return *authority.ParentId, err +} diff --git a/service/system/sys_authority_btn.go b/service/system/sys_authority_btn.go new file mode 100644 index 0000000..99b4062 --- /dev/null +++ b/service/system/sys_authority_btn.go @@ -0,0 +1,60 @@ +package system + +import ( + "errors" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/model/system/response" + "gorm.io/gorm" +) + +type AuthorityBtnService struct{} + +var AuthorityBtnServiceApp = new(AuthorityBtnService) + +func (a *AuthorityBtnService) GetAuthorityBtn(req request.SysAuthorityBtnReq) (res response.SysAuthorityBtnRes, err error) { + var authorityBtn []system.SysAuthorityBtn + err = global.GVA_DB.Find(&authorityBtn, "authority_id = ? and sys_menu_id = ?", req.AuthorityId, req.MenuID).Error + if err != nil { + return + } + var selected []uint + for _, v := range authorityBtn { + selected = append(selected, v.SysBaseMenuBtnID) + } + res.Selected = selected + return res, err +} + +func (a *AuthorityBtnService) SetAuthorityBtn(req request.SysAuthorityBtnReq) (err error) { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + var authorityBtn []system.SysAuthorityBtn + err = tx.Delete(&[]system.SysAuthorityBtn{}, "authority_id = ? and sys_menu_id = ?", req.AuthorityId, req.MenuID).Error + if err != nil { + return err + } + for _, v := range req.Selected { + authorityBtn = append(authorityBtn, system.SysAuthorityBtn{ + AuthorityId: req.AuthorityId, + SysMenuID: req.MenuID, + SysBaseMenuBtnID: v, + }) + } + if len(authorityBtn) > 0 { + err = tx.Create(&authorityBtn).Error + } + if err != nil { + return err + } + return err + }) +} + +func (a *AuthorityBtnService) CanRemoveAuthorityBtn(ID string) (err error) { + fErr := global.GVA_DB.First(&system.SysAuthorityBtn{}, "sys_base_menu_btn_id = ?", ID).Error + if errors.Is(fErr, gorm.ErrRecordNotFound) { + return nil + } + return errors.New("此按钮正在被使用无法删除") +} diff --git a/service/system/sys_auto_code_interface.go b/service/system/sys_auto_code_interface.go new file mode 100644 index 0000000..a730ccf --- /dev/null +++ b/service/system/sys_auto_code_interface.go @@ -0,0 +1,55 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/response" +) + +type AutoCodeService struct{} + +type Database interface { + GetDB(businessDB string) (data []response.Db, err error) + GetTables(businessDB string, dbName string) (data []response.Table, err error) + GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) +} + +func (autoCodeService *AutoCodeService) Database(businessDB string) Database { + + if businessDB == "" { + switch global.GVA_CONFIG.System.DbType { + case "mysql": + return AutoCodeMysql + case "pgsql": + return AutoCodePgsql + case "mssql": + return AutoCodeMssql + case "oracle": + return AutoCodeOracle + case "sqlite": + return AutoCodeSqlite + default: + return AutoCodeMysql + } + } else { + for _, info := range global.GVA_CONFIG.DBList { + if info.AliasName == businessDB { + switch info.Type { + case "mysql": + return AutoCodeMysql + case "mssql": + return AutoCodeMssql + case "pgsql": + return AutoCodePgsql + case "oracle": + return AutoCodeOracle + case "sqlite": + return AutoCodeSqlite + default: + return AutoCodeMysql + } + } + } + return AutoCodeMysql + } + +} diff --git a/service/system/sys_auto_code_mssql.go b/service/system/sys_auto_code_mssql.go new file mode 100644 index 0000000..fabed13 --- /dev/null +++ b/service/system/sys_auto_code_mssql.go @@ -0,0 +1,83 @@ +package system + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/response" +) + +var AutoCodeMssql = new(autoCodeMssql) + +type autoCodeMssql struct{} + +// GetDB 获取数据库的所有数据库名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeMssql) GetDB(businessDB string) (data []response.Db, err error) { + var entities []response.Db + sql := "select name AS 'database' from sys.databases;" + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error + } + return entities, err +} + +// GetTables 获取数据库的所有表名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeMssql) GetTables(businessDB string, dbName string) (data []response.Table, err error) { + var entities []response.Table + + sql := fmt.Sprintf(`select name as 'table_name' from %s.DBO.sysobjects where xtype='U'`, dbName) + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error + } + + return entities, err +} + +// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeMssql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) { + var entities []response.Column + sql := fmt.Sprintf(` +SELECT + sc.name AS column_name, + st.name AS data_type, + sc.max_length AS data_type_long, + CASE + WHEN pk.object_id IS NOT NULL THEN 1 + ELSE 0 + END AS primary_key, + sc.column_id +FROM + %s.sys.columns sc +JOIN + sys.types st ON sc.user_type_id=st.user_type_id +LEFT JOIN + %s.sys.objects so ON so.name='%s' AND so.type='U' +LEFT JOIN + %s.sys.indexes si ON si.object_id = so.object_id AND si.is_primary_key = 1 +LEFT JOIN + %s.sys.index_columns sic ON sic.object_id = si.object_id AND sic.index_id = si.index_id AND sic.column_id = sc.column_id +LEFT JOIN + %s.sys.key_constraints pk ON pk.object_id = si.object_id +WHERE + st.is_user_defined=0 AND sc.object_id = so.object_id +ORDER BY + sc.column_id +`, dbName, dbName, tableName, dbName, dbName, dbName) + + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error + } + + return entities, err +} diff --git a/service/system/sys_auto_code_mysql.go b/service/system/sys_auto_code_mysql.go new file mode 100644 index 0000000..ab5bc6b --- /dev/null +++ b/service/system/sys_auto_code_mysql.go @@ -0,0 +1,83 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/response" +) + +var AutoCodeMysql = new(autoCodeMysql) + +type autoCodeMysql struct{} + +// GetDB 获取数据库的所有数据库名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeMysql) GetDB(businessDB string) (data []response.Db, err error) { + var entities []response.Db + sql := "SELECT SCHEMA_NAME AS `database` FROM INFORMATION_SCHEMA.SCHEMATA;" + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error + } + return entities, err +} + +// GetTables 获取数据库的所有表名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeMysql) GetTables(businessDB string, dbName string) (data []response.Table, err error) { + var entities []response.Table + sql := `select table_name as table_name from information_schema.tables where table_schema = ?` + if businessDB == "" { + err = global.GVA_DB.Raw(sql, dbName).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql, dbName).Scan(&entities).Error + } + + return entities, err +} + +// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeMysql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) { + var entities []response.Column + sql := ` + SELECT + c.COLUMN_NAME column_name, + c.DATA_TYPE data_type, + CASE c.DATA_TYPE + WHEN 'longtext' THEN c.CHARACTER_MAXIMUM_LENGTH + WHEN 'varchar' THEN c.CHARACTER_MAXIMUM_LENGTH + WHEN 'double' THEN CONCAT_WS(',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE) + WHEN 'decimal' THEN CONCAT_WS(',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE) + WHEN 'int' THEN c.NUMERIC_PRECISION + WHEN 'bigint' THEN c.NUMERIC_PRECISION + ELSE '' + END AS data_type_long, + c.COLUMN_COMMENT column_comment, + CASE WHEN kcu.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS primary_key, + c.ORDINAL_POSITION +FROM + INFORMATION_SCHEMA.COLUMNS c +LEFT JOIN + INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu +ON + c.TABLE_SCHEMA = kcu.TABLE_SCHEMA + AND c.TABLE_NAME = kcu.TABLE_NAME + AND c.COLUMN_NAME = kcu.COLUMN_NAME + AND kcu.CONSTRAINT_NAME = 'PRIMARY' +WHERE + c.TABLE_NAME = ? + AND c.TABLE_SCHEMA = ? +ORDER BY + c.ORDINAL_POSITION;` + if businessDB == "" { + err = global.GVA_DB.Raw(sql, tableName, dbName).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error + } + + return entities, err +} diff --git a/service/system/sys_auto_code_oracle.go b/service/system/sys_auto_code_oracle.go new file mode 100644 index 0000000..1d9ffae --- /dev/null +++ b/service/system/sys_auto_code_oracle.go @@ -0,0 +1,72 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/response" +) + +var AutoCodeOracle = new(autoCodeOracle) + +type autoCodeOracle struct{} + +// GetDB 获取数据库的所有数据库名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeOracle) GetDB(businessDB string) (data []response.Db, err error) { + var entities []response.Db + sql := `SELECT lower(username) AS "database" FROM all_users` + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error + return entities, err +} + +// GetTables 获取数据库的所有表名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeOracle) GetTables(businessDB string, dbName string) (data []response.Table, err error) { + var entities []response.Table + sql := `select lower(table_name) as "table_name" from all_tables where lower(owner) = ?` + + err = global.GVA_DBList[businessDB].Raw(sql, dbName).Scan(&entities).Error + return entities, err +} + +// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (s *autoCodeOracle) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) { + var entities []response.Column + sql := ` + SELECT + lower(a.COLUMN_NAME) as "column_name", + (CASE WHEN a.DATA_TYPE = 'NUMBER' AND a.DATA_SCALE=0 THEN 'int' else lower(a.DATA_TYPE) end) as "data_type", + (CASE WHEN a.DATA_TYPE = 'NUMBER' THEN a.DATA_PRECISION else a.DATA_LENGTH end) as "data_type_long", + b.COMMENTS as "column_comment", + (CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END) as "primary_key", + a.COLUMN_ID +FROM + all_tab_columns a +JOIN + all_col_comments b ON a.OWNER = b.OWNER AND a.TABLE_NAME = b.TABLE_NAME AND a.COLUMN_NAME = b.COLUMN_NAME +LEFT JOIN + ( + SELECT + acc.OWNER, + acc.TABLE_NAME, + acc.COLUMN_NAME + FROM + all_cons_columns acc + JOIN + all_constraints ac ON acc.OWNER = ac.OWNER AND acc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME + WHERE + ac.CONSTRAINT_TYPE = 'P' + ) pk ON a.OWNER = pk.OWNER AND a.TABLE_NAME = pk.TABLE_NAME AND a.COLUMN_NAME = pk.COLUMN_NAME +WHERE + lower(a.table_name) = ? + AND lower(a.OWNER) = ? +ORDER BY + a.COLUMN_ID; +` + + err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error + return entities, err +} diff --git a/service/system/sys_auto_code_pgsql.go b/service/system/sys_auto_code_pgsql.go new file mode 100644 index 0000000..6f53781 --- /dev/null +++ b/service/system/sys_auto_code_pgsql.go @@ -0,0 +1,135 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/response" +) + +var AutoCodePgsql = new(autoCodePgsql) + +type autoCodePgsql struct{} + +// GetDB 获取数据库的所有数据库名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (a *autoCodePgsql) GetDB(businessDB string) (data []response.Db, err error) { + var entities []response.Db + sql := `SELECT datname as database FROM pg_database WHERE datistemplate = false` + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Scan(&entities).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error + } + + return entities, err +} + +// GetTables 获取数据库的所有表名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (a *autoCodePgsql) GetTables(businessDB string, dbName string) (data []response.Table, err error) { + var entities []response.Table + sql := `select table_name as table_name from information_schema.tables where table_catalog = ? and table_schema = ?` + + db := global.GVA_DB + if businessDB != "" { + db = global.GVA_DBList[businessDB] + } + + err = db.Raw(sql, dbName, "public").Scan(&entities).Error + return entities, err +} + +// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (a *autoCodePgsql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) { + // todo 数据获取不全, 待完善sql + sql := ` +SELECT + psc.COLUMN_NAME AS COLUMN_NAME, + psc.udt_name AS data_type, + CASE + psc.udt_name + WHEN 'text' THEN + concat_ws ( '', '', psc.CHARACTER_MAXIMUM_LENGTH ) + WHEN 'varchar' THEN + concat_ws ( '', '', psc.CHARACTER_MAXIMUM_LENGTH ) + WHEN 'smallint' THEN + concat_ws ( ',', psc.NUMERIC_PRECISION, psc.NUMERIC_SCALE ) + WHEN 'decimal' THEN + concat_ws ( ',', psc.NUMERIC_PRECISION, psc.NUMERIC_SCALE ) + WHEN 'integer' THEN + concat_ws ( '', '', psc.NUMERIC_PRECISION ) + WHEN 'int4' THEN + concat_ws ( '', '', psc.NUMERIC_PRECISION ) + WHEN 'int8' THEN + concat_ws ( '', '', psc.NUMERIC_PRECISION ) + WHEN 'bigint' THEN + concat_ws ( '', '', psc.NUMERIC_PRECISION ) + WHEN 'timestamp' THEN + concat_ws ( '', '', psc.datetime_precision ) + ELSE '' + END AS data_type_long, + ( + SELECT + pd.description + FROM + pg_description pd + WHERE + (pd.objoid,pd.objsubid) in ( + SELECT pa.attrelid,pa.attnum + FROM + pg_attribute pa + WHERE pa.attrelid = ( SELECT oid FROM pg_class pc WHERE + pc.relname = psc.table_name + ) + and attname = psc.column_name + ) + ) AS column_comment, + ( + SELECT + COUNT(*) + FROM + pg_constraint + WHERE + contype = 'p' + AND conrelid = ( + SELECT + oid + FROM + pg_class + WHERE + relname = psc.table_name + ) + AND conkey::int[] @> ARRAY[( + SELECT + attnum::integer + FROM + pg_attribute + WHERE + attrelid = conrelid + AND attname = psc.column_name + )] + ) > 0 AS primary_key, + psc.ordinal_position +FROM + INFORMATION_SCHEMA.COLUMNS psc +WHERE + table_catalog = ? + AND table_schema = 'public' + AND TABLE_NAME = ? +ORDER BY + psc.ordinal_position; +` + var entities []response.Column + //sql = strings.ReplaceAll(sql, "@table_catalog", dbName) + //sql = strings.ReplaceAll(sql, "@table_name", tableName) + db := global.GVA_DB + if businessDB != "" { + db = global.GVA_DBList[businessDB] + } + + err = db.Raw(sql, dbName, tableName).Scan(&entities).Error + return entities, err +} diff --git a/service/system/sys_auto_code_sqlite.go b/service/system/sys_auto_code_sqlite.go new file mode 100644 index 0000000..d4b93c6 --- /dev/null +++ b/service/system/sys_auto_code_sqlite.go @@ -0,0 +1,84 @@ +package system + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/response" + "path/filepath" + "strings" +) + +var AutoCodeSqlite = new(autoCodeSqlite) + +type autoCodeSqlite struct{} + +// GetDB 获取数据库的所有数据库名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (a *autoCodeSqlite) GetDB(businessDB string) (data []response.Db, err error) { + var entities []response.Db + sql := "PRAGMA database_list;" + var databaseList []struct { + File string `gorm:"column:file"` + } + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Find(&databaseList).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Find(&databaseList).Error + } + for _, database := range databaseList { + if database.File != "" { + fileName := filepath.Base(database.File) + fileExt := filepath.Ext(fileName) + fileNameWithoutExt := strings.TrimSuffix(fileName, fileExt) + + entities = append(entities, response.Db{fileNameWithoutExt}) + } + } + // entities = append(entities, response.Db{global.GVA_CONFIG.Sqlite.Dbname}) + return entities, err +} + +// GetTables 获取数据库的所有表名 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (a *autoCodeSqlite) GetTables(businessDB string, dbName string) (data []response.Table, err error) { + var entities []response.Table + sql := `SELECT name FROM sqlite_master WHERE type='table'` + tabelNames := []string{} + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Find(&tabelNames).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Find(&tabelNames).Error + } + for _, tabelName := range tabelNames { + entities = append(entities, response.Table{tabelName}) + } + return entities, err +} + +// GetColumn 获取指定数据表的所有字段名,类型值等 +// Author [piexlmax](https://github.com/piexlmax) +// Author [SliverHorn](https://github.com/SliverHorn) +func (a *autoCodeSqlite) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) { + var entities []response.Column + sql := fmt.Sprintf("PRAGMA table_info(%s);", tableName) + var columnInfos []struct { + Name string `gorm:"column:name"` + Type string `gorm:"column:type"` + Pk int `gorm:"column:pk"` + } + if businessDB == "" { + err = global.GVA_DB.Raw(sql).Scan(&columnInfos).Error + } else { + err = global.GVA_DBList[businessDB].Raw(sql).Scan(&columnInfos).Error + } + for _, columnInfo := range columnInfos { + entities = append(entities, response.Column{ + ColumnName: columnInfo.Name, + DataType: columnInfo.Type, + PrimaryKey: columnInfo.Pk == 1, + }) + } + return entities, err +} diff --git a/service/system/sys_base_menu.go b/service/system/sys_base_menu.go new file mode 100644 index 0000000..1b92716 --- /dev/null +++ b/service/system/sys_base_menu.go @@ -0,0 +1,146 @@ +package system + +import ( + "errors" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "gorm.io/gorm" +) + +type BaseMenuService struct{} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteBaseMenu +//@description: 删除基础路由 +//@param: id float64 +//@return: err error + +var BaseMenuServiceApp = new(BaseMenuService) + +func (baseMenuService *BaseMenuService) DeleteBaseMenu(id int) (err error) { + err = global.GVA_DB.First(&system.SysBaseMenu{}, "parent_id = ?", id).Error + if err == nil { + return errors.New("此菜单存在子菜单不可删除") + } + var menu system.SysBaseMenu + err = global.GVA_DB.First(&menu, id).Error + if err != nil { + return errors.New("记录不存在") + } + err = global.GVA_DB.First(&system.SysAuthority{}, "default_router = ?", menu.Name).Error + if err == nil { + return errors.New("此菜单有角色正在作为首页,不可删除") + } + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + + err = tx.Delete(&system.SysBaseMenu{}, "id = ?", id).Error + if err != nil { + return err + } + + err = tx.Delete(&system.SysBaseMenuParameter{}, "sys_base_menu_id = ?", id).Error + if err != nil { + return err + } + + err = tx.Delete(&system.SysBaseMenuBtn{}, "sys_base_menu_id = ?", id).Error + if err != nil { + return err + } + err = tx.Delete(&system.SysAuthorityBtn{}, "sys_menu_id = ?", id).Error + if err != nil { + return err + } + + err = tx.Delete(&system.SysAuthorityMenu{}, "sys_base_menu_id = ?", id).Error + if err != nil { + return err + } + return nil + }) + +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateBaseMenu +//@description: 更新路由 +//@param: menu model.SysBaseMenu +//@return: err error + +func (baseMenuService *BaseMenuService) UpdateBaseMenu(menu system.SysBaseMenu) (err error) { + var oldMenu system.SysBaseMenu + upDateMap := make(map[string]interface{}) + upDateMap["keep_alive"] = menu.KeepAlive + upDateMap["close_tab"] = menu.CloseTab + upDateMap["default_menu"] = menu.DefaultMenu + upDateMap["parent_id"] = menu.ParentId + upDateMap["path"] = menu.Path + upDateMap["name"] = menu.Name + upDateMap["hidden"] = menu.Hidden + upDateMap["component"] = menu.Component + upDateMap["title"] = menu.Title + upDateMap["active_name"] = menu.ActiveName + upDateMap["icon"] = menu.Icon + upDateMap["sort"] = menu.Sort + + err = global.GVA_DB.Transaction(func(tx *gorm.DB) error { + tx.Where("id = ?", menu.ID).Find(&oldMenu) + if oldMenu.Name != menu.Name { + if !errors.Is(tx.Where("id <> ? AND name = ?", menu.ID, menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) { + global.GVA_LOG.Debug("存在相同name修改失败") + return errors.New("存在相同name修改失败") + } + } + txErr := tx.Unscoped().Delete(&system.SysBaseMenuParameter{}, "sys_base_menu_id = ?", menu.ID).Error + if txErr != nil { + global.GVA_LOG.Debug(txErr.Error()) + return txErr + } + txErr = tx.Unscoped().Delete(&system.SysBaseMenuBtn{}, "sys_base_menu_id = ?", menu.ID).Error + if txErr != nil { + global.GVA_LOG.Debug(txErr.Error()) + return txErr + } + if len(menu.Parameters) > 0 { + for k := range menu.Parameters { + menu.Parameters[k].SysBaseMenuID = menu.ID + } + txErr = tx.Create(&menu.Parameters).Error + if txErr != nil { + global.GVA_LOG.Debug(txErr.Error()) + return txErr + } + } + + if len(menu.MenuBtn) > 0 { + for k := range menu.MenuBtn { + menu.MenuBtn[k].SysBaseMenuID = menu.ID + } + txErr = tx.Create(&menu.MenuBtn).Error + if txErr != nil { + global.GVA_LOG.Debug(txErr.Error()) + return txErr + } + } + + txErr = tx.Model(&oldMenu).Updates(upDateMap).Error + if txErr != nil { + global.GVA_LOG.Debug(txErr.Error()) + return txErr + } + return nil + }) + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetBaseMenuById +//@description: 返回当前选中menu +//@param: id float64 +//@return: menu system.SysBaseMenu, err error + +func (baseMenuService *BaseMenuService) GetBaseMenuById(id int) (menu system.SysBaseMenu, err error) { + err = global.GVA_DB.Preload("MenuBtn").Preload("Parameters").Where("id = ?", id).First(&menu).Error + return +} diff --git a/service/system/sys_casbin.go b/service/system/sys_casbin.go new file mode 100644 index 0000000..f9a0216 --- /dev/null +++ b/service/system/sys_casbin.go @@ -0,0 +1,221 @@ +package system + +import ( + "errors" + "strconv" + "sync" + + "gorm.io/gorm" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + gormadapter "github.com/casbin/gorm-adapter/v3" + _ "github.com/go-sql-driver/mysql" + "go.uber.org/zap" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateCasbin +//@description: 更新casbin权限 +//@param: authorityId string, casbinInfos []request.CasbinInfo +//@return: error + +type CasbinService struct{} + +var CasbinServiceApp = new(CasbinService) + +func (casbinService *CasbinService) UpdateCasbin(adminAuthorityID, AuthorityID uint, casbinInfos []request.CasbinInfo) error { + + err := AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, AuthorityID) + if err != nil { + return err + } + + if global.GVA_CONFIG.System.UseStrictAuth { + apis, e := ApiServiceApp.GetAllApis(adminAuthorityID) + if e != nil { + return e + } + + for i := range casbinInfos { + hasApi := false + for j := range apis { + if apis[j].Path == casbinInfos[i].Path && apis[j].Method == casbinInfos[i].Method { + hasApi = true + break + } + } + if !hasApi { + return errors.New("存在api不在权限列表中") + } + } + } + + authorityId := strconv.Itoa(int(AuthorityID)) + casbinService.ClearCasbin(0, authorityId) + rules := [][]string{} + //做权限去重处理 + deduplicateMap := make(map[string]bool) + for _, v := range casbinInfos { + key := authorityId + v.Path + v.Method + if _, ok := deduplicateMap[key]; !ok { + deduplicateMap[key] = true + rules = append(rules, []string{authorityId, v.Path, v.Method}) + } + } + if len(rules) == 0 { + return nil + } // 设置空权限无需调用 AddPolicies 方法 + e := casbinService.Casbin() + success, _ := e.AddPolicies(rules) + if !success { + return errors.New("存在相同api,添加失败,请联系管理员") + } + return nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateCasbinApi +//@description: API更新随动 +//@param: oldPath string, newPath string, oldMethod string, newMethod string +//@return: error + +func (casbinService *CasbinService) UpdateCasbinApi(oldPath string, newPath string, oldMethod string, newMethod string) error { + err := global.GVA_DB.Model(&gormadapter.CasbinRule{}).Where("v1 = ? AND v2 = ?", oldPath, oldMethod).Updates(map[string]interface{}{ + "v1": newPath, + "v2": newMethod, + }).Error + if err != nil { + return err + } + + e := casbinService.Casbin() + return e.LoadPolicy() +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetPolicyPathByAuthorityId +//@description: 获取权限列表 +//@param: authorityId string +//@return: pathMaps []request.CasbinInfo + +func (casbinService *CasbinService) GetPolicyPathByAuthorityId(AuthorityID uint) (pathMaps []request.CasbinInfo) { + e := casbinService.Casbin() + authorityId := strconv.Itoa(int(AuthorityID)) + list, _ := e.GetFilteredPolicy(0, authorityId) + for _, v := range list { + pathMaps = append(pathMaps, request.CasbinInfo{ + Path: v[1], + Method: v[2], + }) + } + return pathMaps +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: ClearCasbin +//@description: 清除匹配的权限 +//@param: v int, p ...string +//@return: bool + +func (casbinService *CasbinService) ClearCasbin(v int, p ...string) bool { + e := casbinService.Casbin() + success, _ := e.RemoveFilteredPolicy(v, p...) + return success +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: RemoveFilteredPolicy +//@description: 使用数据库方法清理筛选的politicy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效 +//@param: db *gorm.DB, authorityId string +//@return: error + +func (casbinService *CasbinService) RemoveFilteredPolicy(db *gorm.DB, authorityId string) error { + return db.Delete(&gormadapter.CasbinRule{}, "v0 = ?", authorityId).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SyncPolicy +//@description: 同步目前数据库的policy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效 +//@param: db *gorm.DB, authorityId string, rules [][]string +//@return: error + +func (casbinService *CasbinService) SyncPolicy(db *gorm.DB, authorityId string, rules [][]string) error { + err := casbinService.RemoveFilteredPolicy(db, authorityId) + if err != nil { + return err + } + return casbinService.AddPolicies(db, rules) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: AddPolicies +//@description: 添加匹配的权限 +//@param: v int, p ...string +//@return: bool + +func (casbinService *CasbinService) AddPolicies(db *gorm.DB, rules [][]string) error { + var casbinRules []gormadapter.CasbinRule + for i := range rules { + casbinRules = append(casbinRules, gormadapter.CasbinRule{ + Ptype: "p", + V0: rules[i][0], + V1: rules[i][1], + V2: rules[i][2], + }) + } + return db.Create(&casbinRules).Error +} + +func (casbinService *CasbinService) FreshCasbin() (err error) { + e := casbinService.Casbin() + err = e.LoadPolicy() + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Casbin +//@description: 持久化到数据库 引入自定义规则 +//@return: *casbin.Enforcer + +var ( + syncedCachedEnforcer *casbin.SyncedCachedEnforcer + once sync.Once +) + +func (casbinService *CasbinService) Casbin() *casbin.SyncedCachedEnforcer { + once.Do(func() { + a, err := gormadapter.NewAdapterByDB(global.GVA_DB) + if err != nil { + zap.L().Error("适配数据库失败请检查casbin表是否为InnoDB引擎!", zap.Error(err)) + return + } + text := ` + [request_definition] + r = sub, obj, act + + [policy_definition] + p = sub, obj, act + + [role_definition] + g = _, _ + + [policy_effect] + e = some(where (p.eft == allow)) + + [matchers] + m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act + ` + m, err := model.NewModelFromString(text) + if err != nil { + zap.L().Error("字符串加载模型失败!", zap.Error(err)) + return + } + syncedCachedEnforcer, _ = casbin.NewSyncedCachedEnforcer(m, a) + syncedCachedEnforcer.SetExpireTime(60 * 60) + _ = syncedCachedEnforcer.LoadPolicy() + }) + return syncedCachedEnforcer +} diff --git a/service/system/sys_dictionary.go b/service/system/sys_dictionary.go new file mode 100644 index 0000000..ffcf03e --- /dev/null +++ b/service/system/sys_dictionary.go @@ -0,0 +1,112 @@ +package system + +import ( + "errors" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "gorm.io/gorm" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateSysDictionary +//@description: 创建字典数据 +//@param: sysDictionary model.SysDictionary +//@return: err error + +type DictionaryService struct{} + +var DictionaryServiceApp = new(DictionaryService) + +func (dictionaryService *DictionaryService) CreateSysDictionary(sysDictionary system.SysDictionary) (err error) { + if (!errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) { + return errors.New("存在相同的type,不允许创建") + } + err = global.GVA_DB.Create(&sysDictionary).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteSysDictionary +//@description: 删除字典数据 +//@param: sysDictionary model.SysDictionary +//@return: err error + +func (dictionaryService *DictionaryService) DeleteSysDictionary(sysDictionary system.SysDictionary) (err error) { + err = global.GVA_DB.Where("id = ?", sysDictionary.ID).Preload("SysDictionaryDetails").First(&sysDictionary).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("请不要搞事") + } + if err != nil { + return err + } + err = global.GVA_DB.Delete(&sysDictionary).Error + if err != nil { + return err + } + + if sysDictionary.SysDictionaryDetails != nil { + return global.GVA_DB.Where("sys_dictionary_id=?", sysDictionary.ID).Delete(sysDictionary.SysDictionaryDetails).Error + } + return +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateSysDictionary +//@description: 更新字典数据 +//@param: sysDictionary *model.SysDictionary +//@return: err error + +func (dictionaryService *DictionaryService) UpdateSysDictionary(sysDictionary *system.SysDictionary) (err error) { + var dict system.SysDictionary + sysDictionaryMap := map[string]interface{}{ + "Name": sysDictionary.Name, + "Type": sysDictionary.Type, + "Status": sysDictionary.Status, + "Desc": sysDictionary.Desc, + } + err = global.GVA_DB.Where("id = ?", sysDictionary.ID).First(&dict).Error + if err != nil { + global.GVA_LOG.Debug(err.Error()) + return errors.New("查询字典数据失败") + } + if dict.Type != sysDictionary.Type { + if !errors.Is(global.GVA_DB.First(&system.SysDictionary{}, "type = ?", sysDictionary.Type).Error, gorm.ErrRecordNotFound) { + return errors.New("存在相同的type,不允许创建") + } + } + err = global.GVA_DB.Model(&dict).Updates(sysDictionaryMap).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetSysDictionary +//@description: 根据id或者type获取字典单条数据 +//@param: Type string, Id uint +//@return: err error, sysDictionary model.SysDictionary + +func (dictionaryService *DictionaryService) GetSysDictionary(Type string, Id uint, status *bool) (sysDictionary system.SysDictionary, err error) { + var flag = false + if status == nil { + flag = true + } else { + flag = *status + } + err = global.GVA_DB.Where("(type = ? OR id = ?) and status = ?", Type, Id, flag).Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB { + return db.Where("status = ?", true).Order("sort") + }).First(&sysDictionary).Error + return +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: GetSysDictionaryInfoList +//@description: 分页获取字典列表 +//@param: info request.SysDictionarySearch +//@return: err error, list interface{}, total int64 + +func (dictionaryService *DictionaryService) GetSysDictionaryInfoList() (list interface{}, err error) { + var sysDictionarys []system.SysDictionary + err = global.GVA_DB.Find(&sysDictionarys).Error + return sysDictionarys, err +} diff --git a/service/system/sys_dictionary_detail.go b/service/system/sys_dictionary_detail.go new file mode 100644 index 0000000..a9caef8 --- /dev/null +++ b/service/system/sys_dictionary_detail.go @@ -0,0 +1,118 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/model/system/request" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateSysDictionaryDetail +//@description: 创建字典详情数据 +//@param: sysDictionaryDetail model.SysDictionaryDetail +//@return: err error + +type DictionaryDetailService struct{} + +var DictionaryDetailServiceApp = new(DictionaryDetailService) + +func (dictionaryDetailService *DictionaryDetailService) CreateSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) { + err = global.GVA_DB.Create(&sysDictionaryDetail).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteSysDictionaryDetail +//@description: 删除字典详情数据 +//@param: sysDictionaryDetail model.SysDictionaryDetail +//@return: err error + +func (dictionaryDetailService *DictionaryDetailService) DeleteSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) { + err = global.GVA_DB.Delete(&sysDictionaryDetail).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: UpdateSysDictionaryDetail +//@description: 更新字典详情数据 +//@param: sysDictionaryDetail *model.SysDictionaryDetail +//@return: err error + +func (dictionaryDetailService *DictionaryDetailService) UpdateSysDictionaryDetail(sysDictionaryDetail *system.SysDictionaryDetail) (err error) { + err = global.GVA_DB.Save(sysDictionaryDetail).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetSysDictionaryDetail +//@description: 根据id获取字典详情单条数据 +//@param: id uint +//@return: sysDictionaryDetail system.SysDictionaryDetail, err error + +func (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetail(id uint) (sysDictionaryDetail system.SysDictionaryDetail, err error) { + err = global.GVA_DB.Where("id = ?", id).First(&sysDictionaryDetail).Error + return +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetSysDictionaryDetailInfoList +//@description: 分页获取字典详情列表 +//@param: info request.SysDictionaryDetailSearch +//@return: list interface{}, total int64, err error + +func (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetailInfoList(info request.SysDictionaryDetailSearch) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := global.GVA_DB.Model(&system.SysDictionaryDetail{}) + var sysDictionaryDetails []system.SysDictionaryDetail + // 如果有条件搜索 下方会自动创建搜索语句 + if info.Label != "" { + db = db.Where("label LIKE ?", "%"+info.Label+"%") + } + if info.Value != "" { + db = db.Where("value = ?", info.Value) + } + if info.Status != nil { + db = db.Where("status = ?", info.Status) + } + if info.SysDictionaryID != 0 { + db = db.Where("sys_dictionary_id = ?", info.SysDictionaryID) + } + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Limit(limit).Offset(offset).Order("sort").Find(&sysDictionaryDetails).Error + return sysDictionaryDetails, total, err +} + +// 按照字典id获取字典全部内容的方法 +func (dictionaryDetailService *DictionaryDetailService) GetDictionaryList(dictionaryID uint) (list []system.SysDictionaryDetail, err error) { + var sysDictionaryDetails []system.SysDictionaryDetail + err = global.GVA_DB.Find(&sysDictionaryDetails, "sys_dictionary_id = ?", dictionaryID).Error + return sysDictionaryDetails, err +} + +// 按照字典type获取字典全部内容的方法 +func (dictionaryDetailService *DictionaryDetailService) GetDictionaryListByType(t string) (list []system.SysDictionaryDetail, err error) { + var sysDictionaryDetails []system.SysDictionaryDetail + db := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id") + err = db.Debug().Find(&sysDictionaryDetails, "type = ?", t).Error + return sysDictionaryDetails, err +} + +// 按照字典id+字典内容value获取单条字典内容 +func (dictionaryDetailService *DictionaryDetailService) GetDictionaryInfoByValue(dictionaryID uint, value string) (detail system.SysDictionaryDetail, err error) { + var sysDictionaryDetail system.SysDictionaryDetail + err = global.GVA_DB.First(&sysDictionaryDetail, "sys_dictionary_id = ? and value = ?", dictionaryID, value).Error + return sysDictionaryDetail, err +} + +// 按照字典type+字典内容value获取单条字典内容 +func (dictionaryDetailService *DictionaryDetailService) GetDictionaryInfoByTypeValue(t string, value string) (detail system.SysDictionaryDetail, err error) { + var sysDictionaryDetails system.SysDictionaryDetail + db := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id") + err = db.First(&sysDictionaryDetails, "sys_dictionaries.type = ? and sys_dictionary_details.value = ?", t, value).Error + return sysDictionaryDetails, err +} diff --git a/service/system/sys_export_template.go b/service/system/sys_export_template.go new file mode 100644 index 0000000..6a2273a --- /dev/null +++ b/service/system/sys_export_template.go @@ -0,0 +1,424 @@ +package system + +import ( + "bytes" + "encoding/json" + "fmt" + "mime/multipart" + "net/url" + "strconv" + "strings" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/xuri/excelize/v2" + "gorm.io/gorm" +) + +type SysExportTemplateService struct { +} + +var SysExportTemplateServiceApp = new(SysExportTemplateService) + +// CreateSysExportTemplate 创建导出模板记录 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) CreateSysExportTemplate(sysExportTemplate *system.SysExportTemplate) (err error) { + err = global.GVA_DB.Create(sysExportTemplate).Error + return err +} + +// DeleteSysExportTemplate 删除导出模板记录 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) { + err = global.GVA_DB.Delete(&sysExportTemplate).Error + return err +} + +// DeleteSysExportTemplateByIds 批量删除导出模板记录 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplateByIds(ids request.IdsReq) (err error) { + err = global.GVA_DB.Delete(&[]system.SysExportTemplate{}, "id in ?", ids.Ids).Error + return err +} + +// UpdateSysExportTemplate 更新导出模板记录 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) UpdateSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + conditions := sysExportTemplate.Conditions + e := tx.Delete(&[]system.Condition{}, "template_id = ?", sysExportTemplate.TemplateID).Error + if e != nil { + return e + } + sysExportTemplate.Conditions = nil + + joins := sysExportTemplate.JoinTemplate + e = tx.Delete(&[]system.JoinTemplate{}, "template_id = ?", sysExportTemplate.TemplateID).Error + if e != nil { + return e + } + sysExportTemplate.JoinTemplate = nil + + e = tx.Updates(&sysExportTemplate).Error + if e != nil { + return e + } + if len(conditions) > 0 { + for i := range conditions { + conditions[i].ID = 0 + } + e = tx.Create(&conditions).Error + } + if len(joins) > 0 { + for i := range joins { + joins[i].ID = 0 + } + e = tx.Create(&joins).Error + } + return e + }) +} + +// GetSysExportTemplate 根据id获取导出模板记录 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplate(id uint) (sysExportTemplate system.SysExportTemplate, err error) { + err = global.GVA_DB.Where("id = ?", id).Preload("JoinTemplate").Preload("Conditions").First(&sysExportTemplate).Error + return +} + +// GetSysExportTemplateInfoList 分页获取导出模板记录 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateInfoList(info systemReq.SysExportTemplateSearch) (list []system.SysExportTemplate, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := global.GVA_DB.Model(&system.SysExportTemplate{}) + var sysExportTemplates []system.SysExportTemplate + // 如果有条件搜索 下方会自动创建搜索语句 + if info.StartCreatedAt != nil && info.EndCreatedAt != nil { + db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt) + } + if info.Name != "" { + db = db.Where("name LIKE ?", "%"+info.Name+"%") + } + if info.TableName != "" { + db = db.Where("table_name = ?", info.TableName) + } + if info.TemplateID != "" { + db = db.Where("template_id = ?", info.TemplateID) + } + err = db.Count(&total).Error + if err != nil { + return + } + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + + err = db.Find(&sysExportTemplates).Error + return sysExportTemplates, total, err +} + +// ExportExcel 导出Excel +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) { + var template system.SysExportTemplate + err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error + if err != nil { + return nil, "", err + } + f := excelize.NewFile() + defer func() { + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() + // Create a new sheet. + index, err := f.NewSheet("Sheet1") + if err != nil { + fmt.Println(err) + return + } + var templateInfoMap = make(map[string]string) + columns, err := utils.GetJSONKeys(template.TemplateInfo) + if err != nil { + return nil, "", err + } + err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap) + if err != nil { + return nil, "", err + } + var tableTitle []string + var selectKeyFmt []string + for _, key := range columns { + selectKeyFmt = append(selectKeyFmt, key) + tableTitle = append(tableTitle, templateInfoMap[key]) + } + + selects := strings.Join(selectKeyFmt, ", ") + var tableMap []map[string]interface{} + db := global.GVA_DB + if template.DBName != "" { + db = global.MustGetGlobalDBByDBName(template.DBName) + } + + if len(template.JoinTemplate) > 0 { + for _, join := range template.JoinTemplate { + db = db.Joins(join.JOINS + " " + join.Table + " ON " + join.ON) + } + } + + db = db.Select(selects).Table(template.TableName) + + if len(template.Conditions) > 0 { + for _, condition := range template.Conditions { + sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator) + value := values.Get(condition.From) + if value != "" { + if condition.Operator == "LIKE" { + value = "%" + value + "%" + } + db = db.Where(sql, value) + } + } + } + // 通过参数传入limit + limit := values.Get("limit") + if limit != "" { + l, e := strconv.Atoi(limit) + if e == nil { + db = db.Limit(l) + } + } + // 模板的默认limit + if limit == "" && template.Limit != nil && *template.Limit != 0 { + db = db.Limit(*template.Limit) + } + + // 通过参数传入offset + offset := values.Get("offset") + if offset != "" { + o, e := strconv.Atoi(offset) + if e == nil { + db = db.Offset(o) + } + } + + // 获取当前表的所有字段 + table := template.TableName + orderColumns, err := db.Migrator().ColumnTypes(table) + if err != nil { + return nil, "", err + } + + // 创建一个 map 来存储字段名 + fields := make(map[string]bool) + + for _, column := range orderColumns { + fields[column.Name()] = true + } + + // 通过参数传入order + order := values.Get("order") + + if order == "" && template.Order != "" { + // 如果没有order入参,这里会使用模板的默认排序 + order = template.Order + } + + if order != "" { + checkOrderArr := strings.Split(order, " ") + orderStr := "" + // 检查请求的排序字段是否在字段列表中 + if _, ok := fields[checkOrderArr[0]]; !ok { + return nil, "", fmt.Errorf("order by %s is not in the fields", order) + } + orderStr = checkOrderArr[0] + if len(checkOrderArr) > 1 { + if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" { + return nil, "", fmt.Errorf("order by %s is not secure", order) + } + orderStr = orderStr + " " + checkOrderArr[1] + } + db = db.Order(orderStr) + } + + err = db.Debug().Find(&tableMap).Error + if err != nil { + return nil, "", err + } + var rows [][]string + rows = append(rows, tableTitle) + for _, exTable := range tableMap { + var row []string + for _, column := range columns { + column = strings.ReplaceAll(column, "\"", "") + column = strings.ReplaceAll(column, "`", "") + if len(template.JoinTemplate) > 0 { + columnAs := strings.Split(column, " as ") + if len(columnAs) > 1 { + column = strings.TrimSpace(strings.Split(column, " as ")[1]) + } else { + columnArr := strings.Split(column, ".") + if len(columnArr) > 1 { + column = strings.Split(column, ".")[1] + } + } + } + // 需要对时间类型特殊处理 + if t, ok := exTable[column].(time.Time); ok { + row = append(row, t.Format("2006-01-02 15:04:05")) + } else { + row = append(row, fmt.Sprintf("%v", exTable[column])) + } + } + rows = append(rows, row) + } + for i, row := range rows { + for j, colCell := range row { + sErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(j+1), i+1), colCell) + if sErr != nil { + return nil, "", sErr + } + } + } + f.SetActiveSheet(index) + file, err = f.WriteToBuffer() + if err != nil { + return nil, "", err + } + + return file, template.Name, nil +} + +// ExportTemplate 导出Excel模板 +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templateID string) (file *bytes.Buffer, name string, err error) { + var template system.SysExportTemplate + err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error + if err != nil { + return nil, "", err + } + f := excelize.NewFile() + defer func() { + if err := f.Close(); err != nil { + fmt.Println(err) + } + }() + // Create a new sheet. + index, err := f.NewSheet("Sheet1") + if err != nil { + fmt.Println(err) + return + } + var templateInfoMap = make(map[string]string) + + columns, err := utils.GetJSONKeys(template.TemplateInfo) + + err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap) + if err != nil { + return nil, "", err + } + var tableTitle []string + for _, key := range columns { + tableTitle = append(tableTitle, templateInfoMap[key]) + } + + for i := range tableTitle { + fErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(i+1), 1), tableTitle[i]) + if fErr != nil { + return nil, "", fErr + } + } + f.SetActiveSheet(index) + file, err = f.WriteToBuffer() + if err != nil { + return nil, "", err + } + + return file, template.Name, nil +} + +// ImportExcel 导入Excel +// Author [piexlmax](https://github.com/piexlmax) +func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID string, file *multipart.FileHeader) (err error) { + var template system.SysExportTemplate + err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error + if err != nil { + return err + } + + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + f, err := excelize.OpenReader(src) + if err != nil { + return err + } + + rows, err := f.GetRows("Sheet1") + if err != nil { + return err + } + + var templateInfoMap = make(map[string]string) + err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap) + if err != nil { + return err + } + + var titleKeyMap = make(map[string]string) + for key, title := range templateInfoMap { + titleKeyMap[title] = key + } + + db := global.GVA_DB + if template.DBName != "" { + db = global.MustGetGlobalDBByDBName(template.DBName) + } + + return db.Transaction(func(tx *gorm.DB) error { + excelTitle := rows[0] + values := rows[1:] + items := make([]map[string]interface{}, 0, len(values)) + for _, row := range values { + var item = make(map[string]interface{}) + for ii, value := range row { + key := titleKeyMap[excelTitle[ii]] + item[key] = value + } + + needCreated := tx.Migrator().HasColumn(template.TableName, "created_at") + needUpdated := tx.Migrator().HasColumn(template.TableName, "updated_at") + + if item["created_at"] == nil && needCreated { + item["created_at"] = time.Now() + } + if item["updated_at"] == nil && needUpdated { + item["updated_at"] = time.Now() + } + + items = append(items, item) + } + cErr := tx.Table(template.TableName).CreateInBatches(&items, 1000).Error + return cErr + }) +} + +func getColumnName(n int) string { + columnName := "" + for n > 0 { + n-- + columnName = string(rune('A'+n%26)) + columnName + n /= 26 + } + return columnName +} diff --git a/service/system/sys_initdb.go b/service/system/sys_initdb.go new file mode 100644 index 0000000..f140bbc --- /dev/null +++ b/service/system/sys_initdb.go @@ -0,0 +1,189 @@ +package system + +import ( + "context" + "database/sql" + "errors" + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "gorm.io/gorm" + "sort" +) + +const ( + Mysql = "mysql" + Pgsql = "pgsql" + Sqlite = "sqlite" + Mssql = "mssql" + InitSuccess = "\n[%v] --> 初始数据成功!\n" + InitDataExist = "\n[%v] --> %v 的初始数据已存在!\n" + InitDataFailed = "\n[%v] --> %v 初始数据失败! \nerr: %+v\n" + InitDataSuccess = "\n[%v] --> %v 初始数据成功!\n" +) + +const ( + InitOrderSystem = 10 + InitOrderInternal = 1000 + InitOrderExternal = 100000 +) + +var ( + ErrMissingDBContext = errors.New("missing db in context") + ErrMissingDependentContext = errors.New("missing dependent value in context") + ErrDBTypeMismatch = errors.New("db type mismatch") +) + +// SubInitializer 提供 source/*/init() 使用的接口,每个 initializer 完成一个初始化过程 +type SubInitializer interface { + InitializerName() string // 不一定代表单独一个表,所以改成了更宽泛的语义 + MigrateTable(ctx context.Context) (next context.Context, err error) + InitializeData(ctx context.Context) (next context.Context, err error) + TableCreated(ctx context.Context) bool + DataInserted(ctx context.Context) bool +} + +// TypedDBInitHandler 执行传入的 initializer +type TypedDBInitHandler interface { + EnsureDB(ctx context.Context, conf *request.InitDB) (context.Context, error) // 建库,失败属于 fatal error,因此让它 panic + WriteConfig(ctx context.Context) error // 回写配置 + InitTables(ctx context.Context, inits initSlice) error // 建表 handler + InitData(ctx context.Context, inits initSlice) error // 建数据 handler +} + +// orderedInitializer 组合一个顺序字段,以供排序 +type orderedInitializer struct { + order int + SubInitializer +} + +// initSlice 供 initializer 排序依赖时使用 +type initSlice []*orderedInitializer + +var ( + initializers initSlice + cache map[string]*orderedInitializer +) + +// RegisterInit 注册要执行的初始化过程,会在 InitDB() 时调用 +func RegisterInit(order int, i SubInitializer) { + if initializers == nil { + initializers = initSlice{} + } + if cache == nil { + cache = map[string]*orderedInitializer{} + } + name := i.InitializerName() + if _, existed := cache[name]; existed { + panic(fmt.Sprintf("Name conflict on %s", name)) + } + ni := orderedInitializer{order, i} + initializers = append(initializers, &ni) + cache[name] = &ni +} + +/* ---- * service * ---- */ + +type InitDBService struct{} + +// InitDB 创建数据库并初始化 总入口 +func (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) { + ctx := context.TODO() + ctx = context.WithValue(ctx, "adminPassword", conf.AdminPassword) + if len(initializers) == 0 { + return errors.New("无可用初始化过程,请检查初始化是否已执行完成") + } + sort.Sort(&initializers) // 保证有依赖的 initializer 排在后面执行 + // Note: 若 initializer 只有单一依赖,可以写为 B=A+1, C=A+1; 由于 BC 之间没有依赖关系,所以谁先谁后并不影响初始化 + // 若存在多个依赖,可以写为 C=A+B, D=A+B+C, E=A+1; + // C必然>A|B,因此在AB之后执行,D必然>A|B|C,因此在ABC后执行,而E只依赖A,顺序与CD无关,因此E与CD哪个先执行并不影响 + var initHandler TypedDBInitHandler + switch conf.DBType { + case "mysql": + initHandler = NewMysqlInitHandler() + ctx = context.WithValue(ctx, "dbtype", "mysql") + case "pgsql": + initHandler = NewPgsqlInitHandler() + ctx = context.WithValue(ctx, "dbtype", "pgsql") + case "sqlite": + initHandler = NewSqliteInitHandler() + ctx = context.WithValue(ctx, "dbtype", "sqlite") + case "mssql": + initHandler = NewMssqlInitHandler() + ctx = context.WithValue(ctx, "dbtype", "mssql") + default: + initHandler = NewMysqlInitHandler() + ctx = context.WithValue(ctx, "dbtype", "mysql") + } + ctx, err = initHandler.EnsureDB(ctx, &conf) + if err != nil { + return err + } + + db := ctx.Value("db").(*gorm.DB) + global.GVA_DB = db + + if err = initHandler.InitTables(ctx, initializers); err != nil { + return err + } + if err = initHandler.InitData(ctx, initializers); err != nil { + return err + } + + if err = initHandler.WriteConfig(ctx); err != nil { + return err + } + initializers = initSlice{} + cache = map[string]*orderedInitializer{} + return nil +} + +// createDatabase 创建数据库( EnsureDB() 中调用 ) +func createDatabase(dsn string, driver string, createSql string) error { + db, err := sql.Open(driver, dsn) + if err != nil { + return err + } + defer func(db *sql.DB) { + err = db.Close() + if err != nil { + fmt.Println(err) + } + }(db) + if err = db.Ping(); err != nil { + return err + } + _, err = db.Exec(createSql) + return err +} + +// createTables 创建表(默认 dbInitHandler.initTables 行为) +func createTables(ctx context.Context, inits initSlice) error { + next, cancel := context.WithCancel(ctx) + defer func(c func()) { c() }(cancel) + for _, init := range inits { + if init.TableCreated(next) { + continue + } + if n, err := init.MigrateTable(next); err != nil { + return err + } else { + next = n + } + } + return nil +} + +/* -- sortable interface -- */ + +func (a initSlice) Len() int { + return len(a) +} + +func (a initSlice) Less(i, j int) bool { + return a[i].order < a[j].order +} + +func (a initSlice) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} diff --git a/service/system/sys_initdb_mssql.go b/service/system/sys_initdb_mssql.go new file mode 100644 index 0000000..af7ec41 --- /dev/null +++ b/service/system/sys_initdb_mssql.go @@ -0,0 +1,92 @@ +package system + +import ( + "context" + "errors" + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/google/uuid" + "github.com/gookit/color" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" + "path/filepath" +) + +type MssqlInitHandler struct{} + +func NewMssqlInitHandler() *MssqlInitHandler { + return &MssqlInitHandler{} +} + +// WriteConfig mssql回写配置 +func (h MssqlInitHandler) WriteConfig(ctx context.Context) error { + c, ok := ctx.Value("config").(config.Mssql) + if !ok { + return errors.New("mssql config invalid") + } + global.GVA_CONFIG.System.DbType = "mssql" + global.GVA_CONFIG.Mssql = c + global.GVA_CONFIG.JWT.SigningKey = uuid.New().String() + cs := utils.StructToMap(global.GVA_CONFIG) + for k, v := range cs { + global.GVA_VP.Set(k, v) + } + global.GVA_ACTIVE_DBNAME = &c.Dbname + return global.GVA_VP.WriteConfig() +} + +// EnsureDB 创建数据库并初始化 mssql +func (h MssqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) { + if s, ok := ctx.Value("dbtype").(string); !ok || s != "mssql" { + return ctx, ErrDBTypeMismatch + } + + c := conf.ToMssqlConfig() + next = context.WithValue(ctx, "config", c) + if c.Dbname == "" { + return ctx, nil + } // 如果没有数据库名, 则跳出初始化数据 + + dsn := conf.MssqlEmptyDsn() + + mssqlConfig := sqlserver.Config{ + DSN: dsn, // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + } + + var db *gorm.DB + + if db, err = gorm.Open(sqlserver.New(mssqlConfig), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil { + return nil, err + } + + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..") + next = context.WithValue(next, "db", db) + return next, err +} + +func (h MssqlInitHandler) InitTables(ctx context.Context, inits initSlice) error { + return createTables(ctx, inits) +} + +func (h MssqlInitHandler) InitData(ctx context.Context, inits initSlice) error { + next, cancel := context.WithCancel(ctx) + defer func(c func()) { c() }(cancel) + for _, init := range inits { + if init.DataInserted(next) { + color.Info.Printf(InitDataExist, Mssql, init.InitializerName()) + continue + } + if n, err := init.InitializeData(next); err != nil { + color.Info.Printf(InitDataFailed, Mssql, init.InitializerName(), err) + return err + } else { + next = n + color.Info.Printf(InitDataSuccess, Mssql, init.InitializerName()) + } + } + color.Info.Printf(InitSuccess, Mssql) + return nil +} diff --git a/service/system/sys_initdb_mysql.go b/service/system/sys_initdb_mysql.go new file mode 100644 index 0000000..d53380e --- /dev/null +++ b/service/system/sys_initdb_mysql.go @@ -0,0 +1,97 @@ +package system + +import ( + "context" + "errors" + "fmt" + "path/filepath" + + "git.echol.cn/loser/xiecheng_server/config" + "github.com/gookit/color" + + "git.echol.cn/loser/xiecheng_server/utils" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/google/uuid" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +type MysqlInitHandler struct{} + +func NewMysqlInitHandler() *MysqlInitHandler { + return &MysqlInitHandler{} +} + +// WriteConfig mysql回写配置 +func (h MysqlInitHandler) WriteConfig(ctx context.Context) error { + c, ok := ctx.Value("config").(config.Mysql) + if !ok { + return errors.New("mysql config invalid") + } + global.GVA_CONFIG.System.DbType = "mysql" + global.GVA_CONFIG.Mysql = c + global.GVA_CONFIG.JWT.SigningKey = uuid.New().String() + cs := utils.StructToMap(global.GVA_CONFIG) + for k, v := range cs { + global.GVA_VP.Set(k, v) + } + global.GVA_ACTIVE_DBNAME = &c.Dbname + return global.GVA_VP.WriteConfig() +} + +// EnsureDB 创建数据库并初始化 mysql +func (h MysqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) { + if s, ok := ctx.Value("dbtype").(string); !ok || s != "mysql" { + return ctx, ErrDBTypeMismatch + } + + c := conf.ToMysqlConfig() + next = context.WithValue(ctx, "config", c) + if c.Dbname == "" { + return ctx, nil + } // 如果没有数据库名, 则跳出初始化数据 + + dsn := conf.MysqlEmptyDsn() + createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", c.Dbname) + if err = createDatabase(dsn, "mysql", createSql); err != nil { + return nil, err + } // 创建数据库 + + var db *gorm.DB + if db, err = gorm.Open(mysql.New(mysql.Config{ + DSN: c.Dsn(), // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + SkipInitializeWithVersion: true, // 根据版本自动配置 + }), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil { + return ctx, err + } + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..") + next = context.WithValue(next, "db", db) + return next, err +} + +func (h MysqlInitHandler) InitTables(ctx context.Context, inits initSlice) error { + return createTables(ctx, inits) +} + +func (h MysqlInitHandler) InitData(ctx context.Context, inits initSlice) error { + next, cancel := context.WithCancel(ctx) + defer func(c func()) { c() }(cancel) + for _, init := range inits { + if init.DataInserted(next) { + color.Info.Printf(InitDataExist, Mysql, init.InitializerName()) + continue + } + if n, err := init.InitializeData(next); err != nil { + color.Info.Printf(InitDataFailed, Mysql, init.InitializerName(), err) + return err + } else { + next = n + color.Info.Printf(InitDataSuccess, Mysql, init.InitializerName()) + } + } + color.Info.Printf(InitSuccess, Mysql) + return nil +} diff --git a/service/system/sys_initdb_pgsql.go b/service/system/sys_initdb_pgsql.go new file mode 100644 index 0000000..23b20dd --- /dev/null +++ b/service/system/sys_initdb_pgsql.go @@ -0,0 +1,101 @@ +package system + +import ( + "context" + "errors" + "fmt" + "path/filepath" + + "git.echol.cn/loser/xiecheng_server/config" + "github.com/gookit/color" + + "git.echol.cn/loser/xiecheng_server/utils" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/google/uuid" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +type PgsqlInitHandler struct{} + +func NewPgsqlInitHandler() *PgsqlInitHandler { + return &PgsqlInitHandler{} +} + +// WriteConfig pgsql 回写配置 +func (h PgsqlInitHandler) WriteConfig(ctx context.Context) error { + c, ok := ctx.Value("config").(config.Pgsql) + if !ok { + return errors.New("postgresql config invalid") + } + global.GVA_CONFIG.System.DbType = "pgsql" + global.GVA_CONFIG.Pgsql = c + global.GVA_CONFIG.JWT.SigningKey = uuid.New().String() + cs := utils.StructToMap(global.GVA_CONFIG) + for k, v := range cs { + global.GVA_VP.Set(k, v) + } + global.GVA_ACTIVE_DBNAME = &c.Dbname + return global.GVA_VP.WriteConfig() +} + +// EnsureDB 创建数据库并初始化 pg +func (h PgsqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) { + if s, ok := ctx.Value("dbtype").(string); !ok || s != "pgsql" { + return ctx, ErrDBTypeMismatch + } + + c := conf.ToPgsqlConfig() + next = context.WithValue(ctx, "config", c) + if c.Dbname == "" { + return ctx, nil + } // 如果没有数据库名, 则跳出初始化数据 + + dsn := conf.PgsqlEmptyDsn() + var createSql string + if conf.Template != "" { + createSql = fmt.Sprintf("CREATE DATABASE %s WITH TEMPLATE %s;", c.Dbname, conf.Template) + } else { + createSql = fmt.Sprintf("CREATE DATABASE %s;", c.Dbname) + } + if err = createDatabase(dsn, "pgx", createSql); err != nil { + return nil, err + } // 创建数据库 + + var db *gorm.DB + if db, err = gorm.Open(postgres.New(postgres.Config{ + DSN: c.Dsn(), // DSN data source name + PreferSimpleProtocol: false, + }), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil { + return ctx, err + } + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..") + next = context.WithValue(next, "db", db) + return next, err +} + +func (h PgsqlInitHandler) InitTables(ctx context.Context, inits initSlice) error { + return createTables(ctx, inits) +} + +func (h PgsqlInitHandler) InitData(ctx context.Context, inits initSlice) error { + next, cancel := context.WithCancel(ctx) + defer func(c func()) { c() }(cancel) + for i := 0; i < len(inits); i++ { + if inits[i].DataInserted(next) { + color.Info.Printf(InitDataExist, Pgsql, inits[i].InitializerName()) + continue + } + if n, err := inits[i].InitializeData(next); err != nil { + color.Info.Printf(InitDataFailed, Pgsql, inits[i].InitializerName(), err) + return err + } else { + next = n + color.Info.Printf(InitDataSuccess, Pgsql, inits[i].InitializerName()) + } + } + color.Info.Printf(InitSuccess, Pgsql) + return nil +} diff --git a/service/system/sys_initdb_sqlite.go b/service/system/sys_initdb_sqlite.go new file mode 100644 index 0000000..56b6ae7 --- /dev/null +++ b/service/system/sys_initdb_sqlite.go @@ -0,0 +1,88 @@ +package system + +import ( + "context" + "errors" + "github.com/glebarez/sqlite" + "github.com/google/uuid" + "github.com/gookit/color" + "gorm.io/gorm" + "path/filepath" + + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + "git.echol.cn/loser/xiecheng_server/utils" +) + +type SqliteInitHandler struct{} + +func NewSqliteInitHandler() *SqliteInitHandler { + return &SqliteInitHandler{} +} + +// WriteConfig mysql回写配置 +func (h SqliteInitHandler) WriteConfig(ctx context.Context) error { + c, ok := ctx.Value("config").(config.Sqlite) + if !ok { + return errors.New("sqlite config invalid") + } + global.GVA_CONFIG.System.DbType = "sqlite" + global.GVA_CONFIG.Sqlite = c + global.GVA_CONFIG.JWT.SigningKey = uuid.New().String() + cs := utils.StructToMap(global.GVA_CONFIG) + for k, v := range cs { + global.GVA_VP.Set(k, v) + } + global.GVA_ACTIVE_DBNAME = &c.Dbname + return global.GVA_VP.WriteConfig() +} + +// EnsureDB 创建数据库并初始化 sqlite +func (h SqliteInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) { + if s, ok := ctx.Value("dbtype").(string); !ok || s != "sqlite" { + return ctx, ErrDBTypeMismatch + } + + c := conf.ToSqliteConfig() + next = context.WithValue(ctx, "config", c) + if c.Dbname == "" { + return ctx, nil + } // 如果没有数据库名, 则跳出初始化数据 + + dsn := conf.SqliteEmptyDsn() + + var db *gorm.DB + if db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + }); err != nil { + return ctx, err + } + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..") + next = context.WithValue(next, "db", db) + return next, err +} + +func (h SqliteInitHandler) InitTables(ctx context.Context, inits initSlice) error { + return createTables(ctx, inits) +} + +func (h SqliteInitHandler) InitData(ctx context.Context, inits initSlice) error { + next, cancel := context.WithCancel(ctx) + defer func(c func()) { c() }(cancel) + for _, init := range inits { + if init.DataInserted(next) { + color.Info.Printf(InitDataExist, Sqlite, init.InitializerName()) + continue + } + if n, err := init.InitializeData(next); err != nil { + color.Info.Printf(InitDataFailed, Sqlite, init.InitializerName(), err) + return err + } else { + next = n + color.Info.Printf(InitDataSuccess, Sqlite, init.InitializerName()) + } + } + color.Info.Printf(InitSuccess, Sqlite) + return nil +} diff --git a/service/system/sys_menu.go b/service/system/sys_menu.go new file mode 100644 index 0000000..f5ff780 --- /dev/null +++ b/service/system/sys_menu.go @@ -0,0 +1,289 @@ +package system + +import ( + "errors" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" + "gorm.io/gorm" + "strconv" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: getMenuTreeMap +//@description: 获取路由总树map +//@param: authorityId string +//@return: treeMap map[string][]system.SysMenu, err error + +type MenuService struct{} + +var MenuServiceApp = new(MenuService) + +func (menuService *MenuService) getMenuTreeMap(authorityId uint) (treeMap map[uint][]system.SysMenu, err error) { + var allMenus []system.SysMenu + var baseMenu []system.SysBaseMenu + var btns []system.SysAuthorityBtn + treeMap = make(map[uint][]system.SysMenu) + + var SysAuthorityMenus []system.SysAuthorityMenu + err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&SysAuthorityMenus).Error + if err != nil { + return + } + + var MenuIds []string + + for i := range SysAuthorityMenus { + MenuIds = append(MenuIds, SysAuthorityMenus[i].MenuId) + } + + err = global.GVA_DB.Where("id in (?)", MenuIds).Order("sort").Preload("Parameters").Find(&baseMenu).Error + if err != nil { + return + } + + for i := range baseMenu { + allMenus = append(allMenus, system.SysMenu{ + SysBaseMenu: baseMenu[i], + AuthorityId: authorityId, + MenuId: baseMenu[i].ID, + Parameters: baseMenu[i].Parameters, + }) + } + + err = global.GVA_DB.Where("authority_id = ?", authorityId).Preload("SysBaseMenuBtn").Find(&btns).Error + if err != nil { + return + } + var btnMap = make(map[uint]map[string]uint) + for _, v := range btns { + if btnMap[v.SysMenuID] == nil { + btnMap[v.SysMenuID] = make(map[string]uint) + } + btnMap[v.SysMenuID][v.SysBaseMenuBtn.Name] = authorityId + } + for _, v := range allMenus { + v.Btns = btnMap[v.SysBaseMenu.ID] + treeMap[v.ParentId] = append(treeMap[v.ParentId], v) + } + return treeMap, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetMenuTree +//@description: 获取动态菜单树 +//@param: authorityId string +//@return: menus []system.SysMenu, err error + +func (menuService *MenuService) GetMenuTree(authorityId uint) (menus []system.SysMenu, err error) { + menuTree, err := menuService.getMenuTreeMap(authorityId) + menus = menuTree[0] + for i := 0; i < len(menus); i++ { + err = menuService.getChildrenList(&menus[i], menuTree) + } + return menus, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: getChildrenList +//@description: 获取子菜单 +//@param: menu *model.SysMenu, treeMap map[string][]model.SysMenu +//@return: err error + +func (menuService *MenuService) getChildrenList(menu *system.SysMenu, treeMap map[uint][]system.SysMenu) (err error) { + menu.Children = treeMap[menu.MenuId] + for i := 0; i < len(menu.Children); i++ { + err = menuService.getChildrenList(&menu.Children[i], treeMap) + } + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetInfoList +//@description: 获取路由分页 +//@return: list interface{}, total int64,err error + +func (menuService *MenuService) GetInfoList(authorityID uint) (list interface{}, err error) { + var menuList []system.SysBaseMenu + treeMap, err := menuService.getBaseMenuTreeMap(authorityID) + menuList = treeMap[0] + for i := 0; i < len(menuList); i++ { + err = menuService.getBaseChildrenList(&menuList[i], treeMap) + } + return menuList, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: getBaseChildrenList +//@description: 获取菜单的子菜单 +//@param: menu *model.SysBaseMenu, treeMap map[string][]model.SysBaseMenu +//@return: err error + +func (menuService *MenuService) getBaseChildrenList(menu *system.SysBaseMenu, treeMap map[uint][]system.SysBaseMenu) (err error) { + menu.Children = treeMap[menu.ID] + for i := 0; i < len(menu.Children); i++ { + err = menuService.getBaseChildrenList(&menu.Children[i], treeMap) + } + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: AddBaseMenu +//@description: 添加基础路由 +//@param: menu model.SysBaseMenu +//@return: error + +func (menuService *MenuService) AddBaseMenu(menu system.SysBaseMenu) error { + if !errors.Is(global.GVA_DB.Where("name = ?", menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) { + return errors.New("存在重复name,请修改name") + } + return global.GVA_DB.Create(&menu).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: getBaseMenuTreeMap +//@description: 获取路由总树map +//@return: treeMap map[string][]system.SysBaseMenu, err error + +func (menuService *MenuService) getBaseMenuTreeMap(authorityID uint) (treeMap map[uint][]system.SysBaseMenu, err error) { + parentAuthorityID, err := AuthorityServiceApp.GetParentAuthorityID(authorityID) + if err != nil { + return nil, err + } + + var allMenus []system.SysBaseMenu + treeMap = make(map[uint][]system.SysBaseMenu) + db := global.GVA_DB.Order("sort").Preload("MenuBtn").Preload("Parameters") + + // 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选 + if global.GVA_CONFIG.System.UseStrictAuth && parentAuthorityID != 0 { + var authorityMenus []system.SysAuthorityMenu + err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityID).Find(&authorityMenus).Error + if err != nil { + return nil, err + } + var menuIds []string + for i := range authorityMenus { + menuIds = append(menuIds, authorityMenus[i].MenuId) + } + db = db.Where("id in (?)", menuIds) + } + + err = db.Find(&allMenus).Error + for _, v := range allMenus { + treeMap[v.ParentId] = append(treeMap[v.ParentId], v) + } + return treeMap, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetBaseMenuTree +//@description: 获取基础路由树 +//@return: menus []system.SysBaseMenu, err error + +func (menuService *MenuService) GetBaseMenuTree(authorityID uint) (menus []system.SysBaseMenu, err error) { + treeMap, err := menuService.getBaseMenuTreeMap(authorityID) + menus = treeMap[0] + for i := 0; i < len(menus); i++ { + err = menuService.getBaseChildrenList(&menus[i], treeMap) + } + return menus, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: AddMenuAuthority +//@description: 为角色增加menu树 +//@param: menus []model.SysBaseMenu, authorityId string +//@return: err error + +func (menuService *MenuService) AddMenuAuthority(menus []system.SysBaseMenu, adminAuthorityID, authorityId uint) (err error) { + var auth system.SysAuthority + auth.AuthorityId = authorityId + auth.SysBaseMenus = menus + + err = AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, authorityId) + if err != nil { + return err + } + + var authority system.SysAuthority + _ = global.GVA_DB.First(&authority, "authority_id = ?", adminAuthorityID).Error + var menuIds []string + + // 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选 + if global.GVA_CONFIG.System.UseStrictAuth && *authority.ParentId != 0 { + var authorityMenus []system.SysAuthorityMenu + err = global.GVA_DB.Where("sys_authority_authority_id = ?", adminAuthorityID).Find(&authorityMenus).Error + if err != nil { + return err + } + for i := range authorityMenus { + menuIds = append(menuIds, authorityMenus[i].MenuId) + } + + for i := range menus { + hasMenu := false + for j := range menuIds { + idStr := strconv.Itoa(int(menus[i].ID)) + if idStr == menuIds[j] { + hasMenu = true + } + } + if !hasMenu { + return errors.New("添加失败,请勿跨级操作") + } + } + } + + err = AuthorityServiceApp.SetMenuAuthority(&auth) + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetMenuAuthority +//@description: 查看当前角色树 +//@param: info *request.GetAuthorityId +//@return: menus []system.SysMenu, err error + +func (menuService *MenuService) GetMenuAuthority(info *request.GetAuthorityId) (menus []system.SysMenu, err error) { + var baseMenu []system.SysBaseMenu + var SysAuthorityMenus []system.SysAuthorityMenu + err = global.GVA_DB.Where("sys_authority_authority_id = ?", info.AuthorityId).Find(&SysAuthorityMenus).Error + if err != nil { + return + } + + var MenuIds []string + + for i := range SysAuthorityMenus { + MenuIds = append(MenuIds, SysAuthorityMenus[i].MenuId) + } + + err = global.GVA_DB.Where("id in (?) ", MenuIds).Order("sort").Find(&baseMenu).Error + + for i := range baseMenu { + menus = append(menus, system.SysMenu{ + SysBaseMenu: baseMenu[i], + AuthorityId: info.AuthorityId, + MenuId: baseMenu[i].ID, + Parameters: baseMenu[i].Parameters, + }) + } + return menus, err +} + +// UserAuthorityDefaultRouter 用户角色默认路由检查 +// +// Author [SliverHorn](https://github.com/SliverHorn) +func (menuService *MenuService) UserAuthorityDefaultRouter(user *system.SysUser) { + var menuIds []string + err := global.GVA_DB.Model(&system.SysAuthorityMenu{}).Where("sys_authority_authority_id = ?", user.AuthorityId).Pluck("sys_base_menu_id", &menuIds).Error + if err != nil { + return + } + var am system.SysBaseMenu + err = global.GVA_DB.First(&am, "name = ? and id in (?)", user.Authority.DefaultRouter, menuIds).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + user.Authority.DefaultRouter = "404" + } +} diff --git a/service/system/sys_operation_record.go b/service/system/sys_operation_record.go new file mode 100644 index 0000000..2e570cc --- /dev/null +++ b/service/system/sys_operation_record.go @@ -0,0 +1,88 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/common/request" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" +) + +//@author: [granty1](https://github.com/granty1) +//@function: CreateSysOperationRecord +//@description: 创建记录 +//@param: sysOperationRecord model.SysOperationRecord +//@return: err error + +type OperationRecordService struct{} + +var OperationRecordServiceApp = new(OperationRecordService) + +func (operationRecordService *OperationRecordService) CreateSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) { + err = global.GVA_DB.Create(&sysOperationRecord).Error + return err +} + +//@author: [granty1](https://github.com/granty1) +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteSysOperationRecordByIds +//@description: 批量删除记录 +//@param: ids request.IdsReq +//@return: err error + +func (operationRecordService *OperationRecordService) DeleteSysOperationRecordByIds(ids request.IdsReq) (err error) { + err = global.GVA_DB.Delete(&[]system.SysOperationRecord{}, "id in (?)", ids.Ids).Error + return err +} + +//@author: [granty1](https://github.com/granty1) +//@function: DeleteSysOperationRecord +//@description: 删除操作记录 +//@param: sysOperationRecord model.SysOperationRecord +//@return: err error + +func (operationRecordService *OperationRecordService) DeleteSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) { + err = global.GVA_DB.Delete(&sysOperationRecord).Error + return err +} + +//@author: [granty1](https://github.com/granty1) +//@function: GetSysOperationRecord +//@description: 根据id获取单条操作记录 +//@param: id uint +//@return: sysOperationRecord system.SysOperationRecord, err error + +func (operationRecordService *OperationRecordService) GetSysOperationRecord(id uint) (sysOperationRecord system.SysOperationRecord, err error) { + err = global.GVA_DB.Where("id = ?", id).First(&sysOperationRecord).Error + return +} + +//@author: [granty1](https://github.com/granty1) +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetSysOperationRecordInfoList +//@description: 分页获取操作记录列表 +//@param: info systemReq.SysOperationRecordSearch +//@return: list interface{}, total int64, err error + +func (operationRecordService *OperationRecordService) GetSysOperationRecordInfoList(info systemReq.SysOperationRecordSearch) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := global.GVA_DB.Model(&system.SysOperationRecord{}) + var sysOperationRecords []system.SysOperationRecord + // 如果有条件搜索 下方会自动创建搜索语句 + if info.Method != "" { + db = db.Where("method = ?", info.Method) + } + if info.Path != "" { + db = db.Where("path LIKE ?", "%"+info.Path+"%") + } + if info.Status != 0 { + db = db.Where("status = ?", info.Status) + } + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Order("id desc").Limit(limit).Offset(offset).Preload("User").Find(&sysOperationRecords).Error + return sysOperationRecords, total, err +} diff --git a/service/system/sys_params.go b/service/system/sys_params.go new file mode 100644 index 0000000..4f2c3dc --- /dev/null +++ b/service/system/sys_params.go @@ -0,0 +1,82 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" +) + +type SysParamsService struct{} + +// CreateSysParams 创建参数记录 +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) CreateSysParams(sysParams *system.SysParams) (err error) { + err = global.GVA_DB.Create(sysParams).Error + return err +} + +// DeleteSysParams 删除参数记录 +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) DeleteSysParams(ID string) (err error) { + err = global.GVA_DB.Delete(&system.SysParams{}, "id = ?", ID).Error + return err +} + +// DeleteSysParamsByIds 批量删除参数记录 +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) DeleteSysParamsByIds(IDs []string) (err error) { + err = global.GVA_DB.Delete(&[]system.SysParams{}, "id in ?", IDs).Error + return err +} + +// UpdateSysParams 更新参数记录 +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) UpdateSysParams(sysParams system.SysParams) (err error) { + err = global.GVA_DB.Model(&system.SysParams{}).Where("id = ?", sysParams.ID).Updates(&sysParams).Error + return err +} + +// GetSysParams 根据ID获取参数记录 +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) GetSysParams(ID string) (sysParams system.SysParams, err error) { + err = global.GVA_DB.Where("id = ?", ID).First(&sysParams).Error + return +} + +// GetSysParamsInfoList 分页获取参数记录 +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) GetSysParamsInfoList(info systemReq.SysParamsSearch) (list []system.SysParams, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := global.GVA_DB.Model(&system.SysParams{}) + var sysParamss []system.SysParams + // 如果有条件搜索 下方会自动创建搜索语句 + if info.StartCreatedAt != nil && info.EndCreatedAt != nil { + db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt) + } + if info.Name != "" { + db = db.Where("name LIKE ?", "%"+info.Name+"%") + } + if info.Key != "" { + db = db.Where("key LIKE ?", "%"+info.Key+"%") + } + err = db.Count(&total).Error + if err != nil { + return + } + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + + err = db.Find(&sysParamss).Error + return sysParamss, total, err +} + +// GetSysParam 根据key获取参数value +// Author [Mr.奇淼](https://github.com/pixelmaxQm) +func (sysParamsService *SysParamsService) GetSysParam(key string) (param system.SysParams, err error) { + err = global.GVA_DB.Where(system.SysParams{Key: key}).First(¶m).Error + return +} diff --git a/service/system/sys_system.go b/service/system/sys_system.go new file mode 100644 index 0000000..738eba5 --- /dev/null +++ b/service/system/sys_system.go @@ -0,0 +1,62 @@ +package system + +import ( + "git.echol.cn/loser/xiecheng_server/config" + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/utils" + "go.uber.org/zap" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetSystemConfig +//@description: 读取配置文件 +//@return: conf config.Server, err error + +type SystemConfigService struct{} + +var SystemConfigServiceApp = new(SystemConfigService) + +func (systemConfigService *SystemConfigService) GetSystemConfig() (conf config.Server, err error) { + return global.GVA_CONFIG, nil +} + +// @description set system config, +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetSystemConfig +//@description: 设置配置文件 +//@param: system model.System +//@return: err error + +func (systemConfigService *SystemConfigService) SetSystemConfig(system system.System) (err error) { + cs := utils.StructToMap(system.Config) + for k, v := range cs { + global.GVA_VP.Set(k, v) + } + err = global.GVA_VP.WriteConfig() + return err +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: GetServerInfo +//@description: 获取服务器信息 +//@return: server *utils.Server, err error + +func (systemConfigService *SystemConfigService) GetServerInfo() (server *utils.Server, err error) { + var s utils.Server + s.Os = utils.InitOS() + if s.Cpu, err = utils.InitCPU(); err != nil { + global.GVA_LOG.Error("func utils.InitCPU() Failed", zap.String("err", err.Error())) + return &s, err + } + if s.Ram, err = utils.InitRAM(); err != nil { + global.GVA_LOG.Error("func utils.InitRAM() Failed", zap.String("err", err.Error())) + return &s, err + } + if s.Disk, err = utils.InitDisk(); err != nil { + global.GVA_LOG.Error("func utils.InitDisk() Failed", zap.String("err", err.Error())) + return &s, err + } + + return &s, nil +} diff --git a/service/system/sys_user.go b/service/system/sys_user.go new file mode 100644 index 0000000..847c070 --- /dev/null +++ b/service/system/sys_user.go @@ -0,0 +1,317 @@ +package system + +import ( + "errors" + "fmt" + "git.echol.cn/loser/xiecheng_server/model/common" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/google/uuid" + "gorm.io/gorm" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Register +//@description: 用户注册 +//@param: u model.SysUser +//@return: userInter system.SysUser, err error + +type UserService struct{} + +var UserServiceApp = new(UserService) + +func (userService *UserService) Register(u system.SysUser) (userInter system.SysUser, err error) { + var user system.SysUser + if !errors.Is(global.GVA_DB.Where("username = ?", u.Username).First(&user).Error, gorm.ErrRecordNotFound) { // 判断用户名是否注册 + return userInter, errors.New("用户名已注册") + } + // 否则 附加uuid 密码hash加密 注册 + u.Password = utils.BcryptHash(u.Password) + u.UUID = uuid.New() + err = global.GVA_DB.Create(&u).Error + return u, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: Login +//@description: 用户登录 +//@param: u *model.SysUser +//@return: err error, userInter *model.SysUser + +func (userService *UserService) Login(u *system.SysUser) (userInter *system.SysUser, err error) { + if nil == global.GVA_DB { + return nil, fmt.Errorf("db not init") + } + + var user system.SysUser + err = global.GVA_DB.Where("username = ?", u.Username).Preload("Authorities").Preload("Authority").First(&user).Error + if err == nil { + if ok := utils.BcryptCheck(u.Password, user.Password); !ok { + return nil, errors.New("密码错误") + } + MenuServiceApp.UserAuthorityDefaultRouter(&user) + } + return &user, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: ChangePassword +//@description: 修改用户密码 +//@param: u *model.SysUser, newPassword string +//@return: userInter *model.SysUser,err error + +func (userService *UserService) ChangePassword(u *system.SysUser, newPassword string) (userInter *system.SysUser, err error) { + var user system.SysUser + if err = global.GVA_DB.Where("id = ?", u.ID).First(&user).Error; err != nil { + return nil, err + } + if ok := utils.BcryptCheck(u.Password, user.Password); !ok { + return nil, errors.New("原密码错误") + } + user.Password = utils.BcryptHash(newPassword) + err = global.GVA_DB.Save(&user).Error + return &user, err + +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: GetUserInfoList +//@description: 分页获取数据 +//@param: info request.PageInfo +//@return: err error, list interface{}, total int64 + +func (userService *UserService) GetUserInfoList(info systemReq.GetUserList) (list interface{}, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + db := global.GVA_DB.Model(&system.SysUser{}) + var userList []system.SysUser + + if info.NickName != "" { + db = db.Where("nick_name LIKE ?", "%"+info.NickName+"%") + } + if info.Phone != "" { + db = db.Where("phone LIKE ?", "%"+info.Phone+"%") + } + if info.Username != "" { + db = db.Where("username LIKE ?", "%"+info.Username+"%") + } + if info.Email != "" { + db = db.Where("email LIKE ?", "%"+info.Email+"%") + } + + err = db.Count(&total).Error + if err != nil { + return + } + err = db.Limit(limit).Offset(offset).Preload("Authorities").Preload("Authority").Find(&userList).Error + return userList, total, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetUserAuthority +//@description: 设置一个用户的权限 +//@param: uuid uuid.UUID, authorityId string +//@return: err error + +func (userService *UserService) SetUserAuthority(id uint, authorityId uint) (err error) { + + assignErr := global.GVA_DB.Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityId).First(&system.SysUserAuthority{}).Error + if errors.Is(assignErr, gorm.ErrRecordNotFound) { + return errors.New("该用户无此角色") + } + + var authority system.SysAuthority + err = global.GVA_DB.Where("authority_id = ?", authorityId).First(&authority).Error + if err != nil { + return err + } + var authorityMenu []system.SysAuthorityMenu + var authorityMenuIDs []string + err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&authorityMenu).Error + if err != nil { + return err + } + + for i := range authorityMenu { + authorityMenuIDs = append(authorityMenuIDs, authorityMenu[i].MenuId) + } + + var authorityMenus []system.SysBaseMenu + err = global.GVA_DB.Preload("Parameters").Where("id in (?)", authorityMenuIDs).Find(&authorityMenus).Error + if err != nil { + return err + } + hasMenu := false + for i := range authorityMenus { + if authorityMenus[i].Name == authority.DefaultRouter { + hasMenu = true + break + } + } + if !hasMenu { + return errors.New("找不到默认路由,无法切换本角色") + } + + err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", id).Update("authority_id", authorityId).Error + return err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetUserAuthorities +//@description: 设置一个用户的权限 +//@param: id uint, authorityIds []string +//@return: err error + +func (userService *UserService) SetUserAuthorities(adminAuthorityID, id uint, authorityIds []uint) (err error) { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + var user system.SysUser + TxErr := tx.Where("id = ?", id).First(&user).Error + if TxErr != nil { + global.GVA_LOG.Debug(TxErr.Error()) + return errors.New("查询用户数据失败") + } + TxErr = tx.Delete(&[]system.SysUserAuthority{}, "sys_user_id = ?", id).Error + if TxErr != nil { + return TxErr + } + var useAuthority []system.SysUserAuthority + for _, v := range authorityIds { + e := AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, v) + if e != nil { + return e + } + useAuthority = append(useAuthority, system.SysUserAuthority{ + SysUserId: id, SysAuthorityAuthorityId: v, + }) + } + TxErr = tx.Create(&useAuthority).Error + if TxErr != nil { + return TxErr + } + TxErr = tx.Model(&user).Update("authority_id", authorityIds[0]).Error + if TxErr != nil { + return TxErr + } + // 返回 nil 提交事务 + return nil + }) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: DeleteUser +//@description: 删除用户 +//@param: id float64 +//@return: err error + +func (userService *UserService) DeleteUser(id int) (err error) { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + if err := tx.Where("id = ?", id).Delete(&system.SysUser{}).Error; err != nil { + return err + } + if err := tx.Delete(&[]system.SysUserAuthority{}, "sys_user_id = ?", id).Error; err != nil { + return err + } + return nil + }) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetUserInfo +//@description: 设置用户信息 +//@param: reqUser model.SysUser +//@return: err error, user model.SysUser + +func (userService *UserService) SetUserInfo(req system.SysUser) error { + return global.GVA_DB.Model(&system.SysUser{}). + Select("updated_at", "nick_name", "header_img", "phone", "email", "enable"). + Where("id=?", req.ID). + Updates(map[string]interface{}{ + "updated_at": time.Now(), + "nick_name": req.NickName, + "header_img": req.HeaderImg, + "phone": req.Phone, + "email": req.Email, + "enable": req.Enable, + }).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetSelfInfo +//@description: 设置用户信息 +//@param: reqUser model.SysUser +//@return: err error, user model.SysUser + +func (userService *UserService) SetSelfInfo(req system.SysUser) error { + return global.GVA_DB.Model(&system.SysUser{}). + Where("id=?", req.ID). + Updates(req).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: SetSelfSetting +//@description: 设置用户配置 +//@param: req datatypes.JSON, uid uint +//@return: err error + +func (userService *UserService) SetSelfSetting(req common.JSONMap, uid uint) error { + return global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", uid).Update("origin_setting", req).Error +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: GetUserInfo +//@description: 获取用户信息 +//@param: uuid uuid.UUID +//@return: err error, user system.SysUser + +func (userService *UserService) GetUserInfo(uuid uuid.UUID) (user system.SysUser, err error) { + var reqUser system.SysUser + err = global.GVA_DB.Preload("Authorities").Preload("Authority").First(&reqUser, "uuid = ?", uuid).Error + if err != nil { + return reqUser, err + } + MenuServiceApp.UserAuthorityDefaultRouter(&reqUser) + return reqUser, err +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: FindUserById +//@description: 通过id获取用户信息 +//@param: id int +//@return: err error, user *model.SysUser + +func (userService *UserService) FindUserById(id int) (user *system.SysUser, err error) { + var u system.SysUser + err = global.GVA_DB.Where("id = ?", id).First(&u).Error + return &u, err +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: FindUserByUuid +//@description: 通过uuid获取用户信息 +//@param: uuid string +//@return: err error, user *model.SysUser + +func (userService *UserService) FindUserByUuid(uuid string) (user *system.SysUser, err error) { + var u system.SysUser + if err = global.GVA_DB.Where("uuid = ?", uuid).First(&u).Error; err != nil { + return &u, errors.New("用户不存在") + } + return &u, nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: ResetPassword +//@description: 修改用户密码 +//@param: ID uint +//@return: err error + +func (userService *UserService) ResetPassword(ID uint) (err error) { + err = global.GVA_DB.Model(&system.SysUser{}).Where("id = ?", ID).Update("password", utils.BcryptHash("123456")).Error + return err +} diff --git a/source/example/file_upload_download.go b/source/example/file_upload_download.go new file mode 100644 index 0000000..6a25c9f --- /dev/null +++ b/source/example/file_upload_download.go @@ -0,0 +1,65 @@ +package example + +import ( + "context" + "git.echol.cn/loser/xiecheng_server/model/example" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderExaFile = system.InitOrderInternal + 1 + +type initExaFileMysql struct{} + +// auto run +func init() { + system.RegisterInit(initOrderExaFile, &initExaFileMysql{}) +} + +func (i *initExaFileMysql) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&example.ExaFileUploadAndDownload{}) +} + +func (i *initExaFileMysql) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&example.ExaFileUploadAndDownload{}) +} + +func (i *initExaFileMysql) InitializerName() string { + return example.ExaFileUploadAndDownload{}.TableName() +} + +func (i *initExaFileMysql) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + entities := []example.ExaFileUploadAndDownload{ + {Name: "10.png", Url: "https://qmplusimg.henrongyi.top/gvalogo.png", Tag: "png", Key: "158787308910.png"}, + {Name: "logo.png", Url: "https://qmplusimg.henrongyi.top/1576554439myAvatar.png", Tag: "png", Key: "1587973709logo.png"}, + } + if err := db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, example.ExaFileUploadAndDownload{}.TableName()+"表数据初始化失败!") + } + return ctx, nil +} + +func (i *initExaFileMysql) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + lookup := example.ExaFileUploadAndDownload{Name: "logo.png", Key: "1587973709logo.png"} + if errors.Is(db.First(&lookup, &lookup).Error, gorm.ErrRecordNotFound) { + return false + } + return true +} diff --git a/source/system/api.go b/source/system/api.go new file mode 100644 index 0000000..b29cec9 --- /dev/null +++ b/source/system/api.go @@ -0,0 +1,206 @@ +package system + +import ( + "context" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +type initApi struct{} + +const initOrderApi = system.InitOrderSystem + 1 + +// auto run +func init() { + system.RegisterInit(initOrderApi, &initApi{}) +} + +func (i *initApi) InitializerName() string { + return sysModel.SysApi{}.TableName() +} + +func (i *initApi) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysApi{}) +} + +func (i *initApi) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysApi{}) +} + +func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + entities := []sysModel.SysApi{ + {ApiGroup: "jwt", Method: "POST", Path: "/jwt/jsonInBlacklist", Description: "jwt加入黑名单(退出,必选)"}, + + {ApiGroup: "系统用户", Method: "DELETE", Path: "/user/deleteUser", Description: "删除用户"}, + {ApiGroup: "系统用户", Method: "POST", Path: "/user/admin_register", Description: "用户注册"}, + {ApiGroup: "系统用户", Method: "POST", Path: "/user/getUserList", Description: "获取用户列表"}, + {ApiGroup: "系统用户", Method: "PUT", Path: "/user/setUserInfo", Description: "设置用户信息"}, + {ApiGroup: "系统用户", Method: "PUT", Path: "/user/setSelfInfo", Description: "设置自身信息(必选)"}, + {ApiGroup: "系统用户", Method: "GET", Path: "/user/getUserInfo", Description: "获取自身信息(必选)"}, + {ApiGroup: "系统用户", Method: "POST", Path: "/user/setUserAuthorities", Description: "设置权限组"}, + {ApiGroup: "系统用户", Method: "POST", Path: "/user/changePassword", Description: "修改密码(建议选择)"}, + {ApiGroup: "系统用户", Method: "POST", Path: "/user/setUserAuthority", Description: "修改用户角色(必选)"}, + {ApiGroup: "系统用户", Method: "POST", Path: "/user/resetPassword", Description: "重置用户密码"}, + {ApiGroup: "系统用户", Method: "PUT", Path: "/user/setSelfSetting", Description: "用户界面配置"}, + + {ApiGroup: "api", Method: "POST", Path: "/api/createApi", Description: "创建api"}, + {ApiGroup: "api", Method: "POST", Path: "/api/deleteApi", Description: "删除Api"}, + {ApiGroup: "api", Method: "POST", Path: "/api/updateApi", Description: "更新Api"}, + {ApiGroup: "api", Method: "POST", Path: "/api/getApiList", Description: "获取api列表"}, + {ApiGroup: "api", Method: "POST", Path: "/api/getAllApis", Description: "获取所有api"}, + {ApiGroup: "api", Method: "POST", Path: "/api/getApiById", Description: "获取api详细信息"}, + {ApiGroup: "api", Method: "DELETE", Path: "/api/deleteApisByIds", Description: "批量删除api"}, + {ApiGroup: "api", Method: "GET", Path: "/api/syncApi", Description: "获取待同步API"}, + {ApiGroup: "api", Method: "GET", Path: "/api/getApiGroups", Description: "获取路由组"}, + {ApiGroup: "api", Method: "POST", Path: "/api/enterSyncApi", Description: "确认同步API"}, + {ApiGroup: "api", Method: "POST", Path: "/api/ignoreApi", Description: "忽略API"}, + + {ApiGroup: "角色", Method: "POST", Path: "/authority/copyAuthority", Description: "拷贝角色"}, + {ApiGroup: "角色", Method: "POST", Path: "/authority/createAuthority", Description: "创建角色"}, + {ApiGroup: "角色", Method: "POST", Path: "/authority/deleteAuthority", Description: "删除角色"}, + {ApiGroup: "角色", Method: "PUT", Path: "/authority/updateAuthority", Description: "更新角色信息"}, + {ApiGroup: "角色", Method: "POST", Path: "/authority/getAuthorityList", Description: "获取角色列表"}, + {ApiGroup: "角色", Method: "POST", Path: "/authority/setDataAuthority", Description: "设置角色资源权限"}, + + {ApiGroup: "casbin", Method: "POST", Path: "/casbin/updateCasbin", Description: "更改角色api权限"}, + {ApiGroup: "casbin", Method: "POST", Path: "/casbin/getPolicyPathByAuthorityId", Description: "获取权限列表"}, + + {ApiGroup: "菜单", Method: "POST", Path: "/menu/addBaseMenu", Description: "新增菜单"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/getMenu", Description: "获取菜单树(必选)"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/deleteBaseMenu", Description: "删除菜单"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/updateBaseMenu", Description: "更新菜单"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/getBaseMenuById", Description: "根据id获取菜单"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/getMenuList", Description: "分页获取基础menu列表"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/getBaseMenuTree", Description: "获取用户动态路由"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/getMenuAuthority", Description: "获取指定角色menu"}, + {ApiGroup: "菜单", Method: "POST", Path: "/menu/addMenuAuthority", Description: "增加menu和角色关联关系"}, + + {ApiGroup: "分片上传", Method: "GET", Path: "/fileUploadAndDownload/findFile", Description: "寻找目标文件(秒传)"}, + {ApiGroup: "分片上传", Method: "POST", Path: "/fileUploadAndDownload/breakpointContinue", Description: "断点续传"}, + {ApiGroup: "分片上传", Method: "POST", Path: "/fileUploadAndDownload/breakpointContinueFinish", Description: "断点续传完成"}, + {ApiGroup: "分片上传", Method: "POST", Path: "/fileUploadAndDownload/removeChunk", Description: "上传完成移除文件"}, + + {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/upload", Description: "文件上传(建议选择)"}, + {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/deleteFile", Description: "删除文件"}, + {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/editFileName", Description: "文件名或者备注编辑"}, + {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/getFileList", Description: "获取上传文件列表"}, + {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/importURL", Description: "导入URL"}, + + {ApiGroup: "系统服务", Method: "POST", Path: "/system/getServerInfo", Description: "获取服务器信息"}, + {ApiGroup: "系统服务", Method: "POST", Path: "/system/getSystemConfig", Description: "获取配置文件内容"}, + {ApiGroup: "系统服务", Method: "POST", Path: "/system/setSystemConfig", Description: "设置配置文件内容"}, + + {ApiGroup: "客户", Method: "PUT", Path: "/customer/customer", Description: "更新客户"}, + {ApiGroup: "客户", Method: "POST", Path: "/customer/customer", Description: "创建客户"}, + {ApiGroup: "客户", Method: "DELETE", Path: "/customer/customer", Description: "删除客户"}, + {ApiGroup: "客户", Method: "GET", Path: "/customer/customer", Description: "获取单一客户"}, + {ApiGroup: "客户", Method: "GET", Path: "/customer/customerList", Description: "获取客户列表"}, + + {ApiGroup: "代码生成器", Method: "GET", Path: "/autoCode/getDB", Description: "获取所有数据库"}, + {ApiGroup: "代码生成器", Method: "GET", Path: "/autoCode/getTables", Description: "获取数据库表"}, + {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/createTemp", Description: "自动化代码"}, + {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/preview", Description: "预览自动化代码"}, + {ApiGroup: "代码生成器", Method: "GET", Path: "/autoCode/getColumn", Description: "获取所选table的所有字段"}, + {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/installPlugin", Description: "安装插件"}, + {ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/pubPlug", Description: "打包插件"}, + + {ApiGroup: "模板配置", Method: "POST", Path: "/autoCode/createPackage", Description: "配置模板"}, + {ApiGroup: "模板配置", Method: "GET", Path: "/autoCode/getTemplates", Description: "获取模板文件"}, + {ApiGroup: "模板配置", Method: "POST", Path: "/autoCode/getPackage", Description: "获取所有模板"}, + {ApiGroup: "模板配置", Method: "POST", Path: "/autoCode/delPackage", Description: "删除模板"}, + + {ApiGroup: "代码生成器历史", Method: "POST", Path: "/autoCode/getMeta", Description: "获取meta信息"}, + {ApiGroup: "代码生成器历史", Method: "POST", Path: "/autoCode/rollback", Description: "回滚自动生成代码"}, + {ApiGroup: "代码生成器历史", Method: "POST", Path: "/autoCode/getSysHistory", Description: "查询回滚记录"}, + {ApiGroup: "代码生成器历史", Method: "POST", Path: "/autoCode/delSysHistory", Description: "删除回滚记录"}, + {ApiGroup: "代码生成器历史", Method: "POST", Path: "/autoCode/addFunc", Description: "增加模板方法"}, + + {ApiGroup: "系统字典详情", Method: "PUT", Path: "/sysDictionaryDetail/updateSysDictionaryDetail", Description: "更新字典内容"}, + {ApiGroup: "系统字典详情", Method: "POST", Path: "/sysDictionaryDetail/createSysDictionaryDetail", Description: "新增字典内容"}, + {ApiGroup: "系统字典详情", Method: "DELETE", Path: "/sysDictionaryDetail/deleteSysDictionaryDetail", Description: "删除字典内容"}, + {ApiGroup: "系统字典详情", Method: "GET", Path: "/sysDictionaryDetail/findSysDictionaryDetail", Description: "根据ID获取字典内容"}, + {ApiGroup: "系统字典详情", Method: "GET", Path: "/sysDictionaryDetail/getSysDictionaryDetailList", Description: "获取字典内容列表"}, + + {ApiGroup: "系统字典", Method: "POST", Path: "/sysDictionary/createSysDictionary", Description: "新增字典"}, + {ApiGroup: "系统字典", Method: "DELETE", Path: "/sysDictionary/deleteSysDictionary", Description: "删除字典"}, + {ApiGroup: "系统字典", Method: "PUT", Path: "/sysDictionary/updateSysDictionary", Description: "更新字典"}, + {ApiGroup: "系统字典", Method: "GET", Path: "/sysDictionary/findSysDictionary", Description: "根据ID获取字典(建议选择)"}, + {ApiGroup: "系统字典", Method: "GET", Path: "/sysDictionary/getSysDictionaryList", Description: "获取字典列表"}, + + {ApiGroup: "操作记录", Method: "POST", Path: "/sysOperationRecord/createSysOperationRecord", Description: "新增操作记录"}, + {ApiGroup: "操作记录", Method: "GET", Path: "/sysOperationRecord/findSysOperationRecord", Description: "根据ID获取操作记录"}, + {ApiGroup: "操作记录", Method: "GET", Path: "/sysOperationRecord/getSysOperationRecordList", Description: "获取操作记录列表"}, + {ApiGroup: "操作记录", Method: "DELETE", Path: "/sysOperationRecord/deleteSysOperationRecord", Description: "删除操作记录"}, + {ApiGroup: "操作记录", Method: "DELETE", Path: "/sysOperationRecord/deleteSysOperationRecordByIds", Description: "批量删除操作历史"}, + + {ApiGroup: "断点续传(插件版)", Method: "POST", Path: "/simpleUploader/upload", Description: "插件版分片上传"}, + {ApiGroup: "断点续传(插件版)", Method: "GET", Path: "/simpleUploader/checkFileMd5", Description: "文件完整度验证"}, + {ApiGroup: "断点续传(插件版)", Method: "GET", Path: "/simpleUploader/mergeFileMd5", Description: "上传完成合并文件"}, + + {ApiGroup: "email", Method: "POST", Path: "/email/emailTest", Description: "发送测试邮件"}, + {ApiGroup: "email", Method: "POST", Path: "/email/sendEmail", Description: "发送邮件"}, + + {ApiGroup: "按钮权限", Method: "POST", Path: "/authorityBtn/setAuthorityBtn", Description: "设置按钮权限"}, + {ApiGroup: "按钮权限", Method: "POST", Path: "/authorityBtn/getAuthorityBtn", Description: "获取已有按钮权限"}, + {ApiGroup: "按钮权限", Method: "POST", Path: "/authorityBtn/canRemoveAuthorityBtn", Description: "删除按钮"}, + + {ApiGroup: "导出模板", Method: "POST", Path: "/sysExportTemplate/createSysExportTemplate", Description: "新增导出模板"}, + {ApiGroup: "导出模板", Method: "DELETE", Path: "/sysExportTemplate/deleteSysExportTemplate", Description: "删除导出模板"}, + {ApiGroup: "导出模板", Method: "DELETE", Path: "/sysExportTemplate/deleteSysExportTemplateByIds", Description: "批量删除导出模板"}, + {ApiGroup: "导出模板", Method: "PUT", Path: "/sysExportTemplate/updateSysExportTemplate", Description: "更新导出模板"}, + {ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/findSysExportTemplate", Description: "根据ID获取导出模板"}, + {ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/getSysExportTemplateList", Description: "获取导出模板列表"}, + {ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/exportExcel", Description: "导出Excel"}, + {ApiGroup: "导出模板", Method: "GET", Path: "/sysExportTemplate/exportTemplate", Description: "下载模板"}, + {ApiGroup: "导出模板", Method: "POST", Path: "/sysExportTemplate/importExcel", Description: "导入Excel"}, + + {ApiGroup: "公告", Method: "POST", Path: "/info/createInfo", Description: "新建公告"}, + {ApiGroup: "公告", Method: "DELETE", Path: "/info/deleteInfo", Description: "删除公告"}, + {ApiGroup: "公告", Method: "DELETE", Path: "/info/deleteInfoByIds", Description: "批量删除公告"}, + {ApiGroup: "公告", Method: "PUT", Path: "/info/updateInfo", Description: "更新公告"}, + {ApiGroup: "公告", Method: "GET", Path: "/info/findInfo", Description: "根据ID获取公告"}, + {ApiGroup: "公告", Method: "GET", Path: "/info/getInfoList", Description: "获取公告列表"}, + + {ApiGroup: "参数管理", Method: "POST", Path: "/sysParams/createSysParams", Description: "新建参数"}, + {ApiGroup: "参数管理", Method: "DELETE", Path: "/sysParams/deleteSysParams", Description: "删除参数"}, + {ApiGroup: "参数管理", Method: "DELETE", Path: "/sysParams/deleteSysParamsByIds", Description: "批量删除参数"}, + {ApiGroup: "参数管理", Method: "PUT", Path: "/sysParams/updateSysParams", Description: "更新参数"}, + {ApiGroup: "参数管理", Method: "GET", Path: "/sysParams/findSysParams", Description: "根据ID获取参数"}, + {ApiGroup: "参数管理", Method: "GET", Path: "/sysParams/getSysParamsList", Description: "获取参数列表"}, + {ApiGroup: "参数管理", Method: "GET", Path: "/sysParams/getSysParam", Description: "获取参数列表"}, + {ApiGroup: "媒体库分类", Method: "GET", Path: "/attachmentCategory/getCategoryList", Description: "分类列表"}, + {ApiGroup: "媒体库分类", Method: "POST", Path: "/attachmentCategory/addCategory", Description: "添加/编辑分类"}, + {ApiGroup: "媒体库分类", Method: "POST", Path: "/attachmentCategory/deleteCategory", Description: "删除分类"}, + } + if err := db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, sysModel.SysApi{}.TableName()+"表数据初始化失败!") + } + next := context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initApi) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.Where("path = ? AND method = ?", "/authorityBtn/canRemoveAuthorityBtn", "POST"). + First(&sysModel.SysApi{}).Error, gorm.ErrRecordNotFound) { + return false + } + return true +} diff --git a/source/system/api_ignore.go b/source/system/api_ignore.go new file mode 100644 index 0000000..7262d43 --- /dev/null +++ b/source/system/api_ignore.go @@ -0,0 +1,77 @@ +package system + +import ( + "context" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +type initApiIgnore struct{} + +const initOrderApiIgnore = initOrderApi + 1 + +// auto run +func init() { + system.RegisterInit(initOrderApiIgnore, &initApiIgnore{}) +} + +func (i *initApiIgnore) InitializerName() string { + return sysModel.SysIgnoreApi{}.TableName() +} + +func (i *initApiIgnore) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysIgnoreApi{}) +} + +func (i *initApiIgnore) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysIgnoreApi{}) +} + +func (i *initApiIgnore) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + entities := []sysModel.SysIgnoreApi{ + {Method: "GET", Path: "/swagger/*any"}, + {Method: "GET", Path: "/api/freshCasbin"}, + {Method: "GET", Path: "/uploads/file/*filepath"}, + {Method: "GET", Path: "/health"}, + {Method: "HEAD", Path: "/uploads/file/*filepath"}, + {Method: "POST", Path: "/autoCode/llmAuto"}, + {Method: "POST", Path: "/system/reloadSystem"}, + {Method: "POST", Path: "/base/login"}, + {Method: "POST", Path: "/base/captcha"}, + {Method: "POST", Path: "/init/initdb"}, + {Method: "POST", Path: "/init/checkdb"}, + {Method: "GET", Path: "/info/getInfoDataSource"}, + {Method: "GET", Path: "/info/getInfoPublic"}, + } + if err := db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, sysModel.SysIgnoreApi{}.TableName()+"表数据初始化失败!") + } + next := context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initApiIgnore) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.Where("path = ? AND method = ?", "/swagger/*any", "GET"). + First(&sysModel.SysIgnoreApi{}).Error, gorm.ErrRecordNotFound) { + return false + } + return true +} diff --git a/source/system/authorities_menus.go b/source/system/authorities_menus.go new file mode 100644 index 0000000..937876e --- /dev/null +++ b/source/system/authorities_menus.go @@ -0,0 +1,84 @@ +package system + +import ( + "context" + + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderMenuAuthority = initOrderMenu + initOrderAuthority + +type initMenuAuthority struct{} + +// auto run +func init() { + system.RegisterInit(initOrderMenuAuthority, &initMenuAuthority{}) +} + +func (i *initMenuAuthority) MigrateTable(ctx context.Context) (context.Context, error) { + return ctx, nil // do nothing +} + +func (i *initMenuAuthority) TableCreated(ctx context.Context) bool { + return false // always replace +} + +func (i *initMenuAuthority) InitializerName() string { + return "sys_menu_authorities" +} + +func (i *initMenuAuthority) InitializeData(ctx context.Context) (next context.Context, err error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + initAuth := &initAuthority{} + authorities, ok := ctx.Value(initAuth.InitializerName()).([]sysModel.SysAuthority) + if !ok { + return ctx, errors.Wrap(system.ErrMissingDependentContext, "创建 [菜单-权限] 关联失败, 未找到权限表初始化数据") + } + menus, ok := ctx.Value(new(initMenu).InitializerName()).([]sysModel.SysBaseMenu) + if !ok { + return next, errors.Wrap(errors.New(""), "创建 [菜单-权限] 关联失败, 未找到菜单表初始化数据") + } + next = ctx + // 888 + if err = db.Model(&authorities[0]).Association("SysBaseMenus").Replace(menus); err != nil { + return next, err + } + + // 8881 + menu8881 := menus[:2] + menu8881 = append(menu8881, menus[7]) + if err = db.Model(&authorities[1]).Association("SysBaseMenus").Replace(menu8881); err != nil { + return next, err + } + + // 9528 + if err = db.Model(&authorities[2]).Association("SysBaseMenus").Replace(menus[:11]); err != nil { + return next, err + } + if err = db.Model(&authorities[2]).Association("SysBaseMenus").Append(menus[12:17]); err != nil { + return next, err + } + return next, nil +} + +func (i *initMenuAuthority) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + auth := &sysModel.SysAuthority{} + if ret := db.Model(auth). + Where("authority_id = ?", 9528).Preload("SysBaseMenus").Find(auth); ret != nil { + if ret.Error != nil { + return false + } + return len(auth.SysBaseMenus) > 0 + } + return false +} diff --git a/source/system/authority.go b/source/system/authority.go new file mode 100644 index 0000000..7e0a10d --- /dev/null +++ b/source/system/authority.go @@ -0,0 +1,88 @@ +package system + +import ( + "context" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderAuthority = initOrderCasbin + 1 + +type initAuthority struct{} + +// auto run +func init() { + system.RegisterInit(initOrderAuthority, &initAuthority{}) +} + +func (i *initAuthority) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysAuthority{}) +} + +func (i *initAuthority) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysAuthority{}) +} + +func (i *initAuthority) InitializerName() string { + return sysModel.SysAuthority{}.TableName() +} + +func (i *initAuthority) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + entities := []sysModel.SysAuthority{ + {AuthorityId: 888, AuthorityName: "普通用户", ParentId: utils.Pointer[uint](0), DefaultRouter: "dashboard"}, + {AuthorityId: 9528, AuthorityName: "测试角色", ParentId: utils.Pointer[uint](0), DefaultRouter: "dashboard"}, + {AuthorityId: 8881, AuthorityName: "普通用户子角色", ParentId: utils.Pointer[uint](888), DefaultRouter: "dashboard"}, + } + + if err := db.Create(&entities).Error; err != nil { + return ctx, errors.Wrapf(err, "%s表数据初始化失败!", sysModel.SysAuthority{}.TableName()) + } + // data authority + if err := db.Model(&entities[0]).Association("DataAuthorityId").Replace( + []*sysModel.SysAuthority{ + {AuthorityId: 888}, + {AuthorityId: 9528}, + {AuthorityId: 8881}, + }); err != nil { + return ctx, errors.Wrapf(err, "%s表数据初始化失败!", + db.Model(&entities[0]).Association("DataAuthorityId").Relationship.JoinTable.Name) + } + if err := db.Model(&entities[1]).Association("DataAuthorityId").Replace( + []*sysModel.SysAuthority{ + {AuthorityId: 9528}, + {AuthorityId: 8881}, + }); err != nil { + return ctx, errors.Wrapf(err, "%s表数据初始化失败!", + db.Model(&entities[1]).Association("DataAuthorityId").Relationship.JoinTable.Name) + } + + next := context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initAuthority) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.Where("authority_id = ?", "8881"). + First(&sysModel.SysAuthority{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据 + return false + } + return true +} diff --git a/source/system/casbin.go b/source/system/casbin.go new file mode 100644 index 0000000..c362f95 --- /dev/null +++ b/source/system/casbin.go @@ -0,0 +1,292 @@ +package system + +import ( + "context" + + "git.echol.cn/loser/xiecheng_server/service/system" + adapter "github.com/casbin/gorm-adapter/v3" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderCasbin = initOrderApiIgnore + 1 + +type initCasbin struct{} + +// auto run +func init() { + system.RegisterInit(initOrderCasbin, &initCasbin{}) +} + +func (i *initCasbin) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&adapter.CasbinRule{}) +} + +func (i *initCasbin) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&adapter.CasbinRule{}) +} + +func (i *initCasbin) InitializerName() string { + var entity adapter.CasbinRule + return entity.TableName() +} + +func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + entities := []adapter.CasbinRule{ + {Ptype: "p", V0: "888", V1: "/user/admin_register", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/api/createApi", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/getApiList", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/getApiById", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/deleteApi", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/updateApi", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/getAllApis", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/deleteApisByIds", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/api/syncApi", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/api/getApiGroups", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/api/enterSyncApi", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/api/ignoreApi", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/authority/copyAuthority", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/authority/updateAuthority", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/authority/createAuthority", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/authority/deleteAuthority", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/authority/getAuthorityList", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/authority/setDataAuthority", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/menu/getMenu", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/getMenuList", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/addBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/getBaseMenuTree", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/addMenuAuthority", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/getMenuAuthority", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/deleteBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/updateBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/menu/getBaseMenuById", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/user/getUserInfo", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/user/setUserInfo", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/user/setSelfInfo", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/user/getUserList", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/user/deleteUser", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/user/changePassword", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/user/setUserAuthority", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/user/setUserAuthorities", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/user/resetPassword", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/user/setSelfSetting", V2: "PUT"}, + + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/findFile", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/breakpointContinueFinish", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/breakpointContinue", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/removeChunk", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/upload", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/deleteFile", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/editFileName", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/getFileList", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/fileUploadAndDownload/importURL", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/casbin/updateCasbin", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/casbin/getPolicyPathByAuthorityId", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/jwt/jsonInBlacklist", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/system/getSystemConfig", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/system/setSystemConfig", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/system/getServerInfo", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/customer/customer", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/customer/customer", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/customer/customer", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/customer/customer", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/customer/customerList", V2: "GET"}, + + {Ptype: "p", V0: "888", V1: "/autoCode/getDB", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/autoCode/getMeta", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/preview", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/getTables", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/autoCode/getColumn", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/autoCode/rollback", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/createTemp", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/delSysHistory", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/getSysHistory", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/createPackage", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/getTemplates", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/autoCode/getPackage", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/delPackage", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/createPlug", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/installPlugin", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/pubPlug", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/autoCode/addFunc", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/findSysDictionaryDetail", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/updateSysDictionaryDetail", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/createSysDictionaryDetail", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/getSysDictionaryDetailList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/deleteSysDictionaryDetail", V2: "DELETE"}, + + {Ptype: "p", V0: "888", V1: "/sysDictionary/findSysDictionary", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysDictionary/updateSysDictionary", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/sysDictionary/getSysDictionaryList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysDictionary/createSysDictionary", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/sysDictionary/deleteSysDictionary", V2: "DELETE"}, + + {Ptype: "p", V0: "888", V1: "/sysOperationRecord/findSysOperationRecord", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysOperationRecord/updateSysOperationRecord", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/sysOperationRecord/createSysOperationRecord", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/sysOperationRecord/getSysOperationRecordList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysOperationRecord/deleteSysOperationRecord", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/sysOperationRecord/deleteSysOperationRecordByIds", V2: "DELETE"}, + + {Ptype: "p", V0: "888", V1: "/email/emailTest", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/email/sendEmail", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/simpleUploader/upload", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/simpleUploader/checkFileMd5", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/simpleUploader/mergeFileMd5", V2: "GET"}, + + {Ptype: "p", V0: "888", V1: "/authorityBtn/setAuthorityBtn", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/authorityBtn/getAuthorityBtn", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/authorityBtn/canRemoveAuthorityBtn", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/createSysExportTemplate", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/deleteSysExportTemplate", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/deleteSysExportTemplateByIds", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/updateSysExportTemplate", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/findSysExportTemplate", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/getSysExportTemplateList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/exportExcel", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/exportTemplate", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysExportTemplate/importExcel", V2: "POST"}, + + {Ptype: "p", V0: "888", V1: "/info/createInfo", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/info/deleteInfo", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/info/deleteInfoByIds", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/info/updateInfo", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/info/findInfo", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/info/getInfoList", V2: "GET"}, + + {Ptype: "p", V0: "888", V1: "/sysParams/createSysParams", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/sysParams/deleteSysParams", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/sysParams/deleteSysParamsByIds", V2: "DELETE"}, + {Ptype: "p", V0: "888", V1: "/sysParams/updateSysParams", V2: "PUT"}, + {Ptype: "p", V0: "888", V1: "/sysParams/findSysParams", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysParams/getSysParamsList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/sysParams/getSysParam", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/attachmentCategory/getCategoryList", V2: "GET"}, + {Ptype: "p", V0: "888", V1: "/attachmentCategory/addCategory", V2: "POST"}, + {Ptype: "p", V0: "888", V1: "/attachmentCategory/deleteCategory", V2: "POST"}, + + {Ptype: "p", V0: "8881", V1: "/user/admin_register", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/api/createApi", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/api/getApiList", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/api/getApiById", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/api/deleteApi", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/api/updateApi", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/api/getAllApis", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/authority/createAuthority", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/authority/deleteAuthority", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/authority/getAuthorityList", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/authority/setDataAuthority", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/getMenu", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/getMenuList", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/addBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/getBaseMenuTree", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/addMenuAuthority", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/getMenuAuthority", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/deleteBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/updateBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/menu/getBaseMenuById", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/user/changePassword", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/user/getUserList", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/user/setUserAuthority", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/fileUploadAndDownload/upload", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/fileUploadAndDownload/getFileList", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/fileUploadAndDownload/deleteFile", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/fileUploadAndDownload/editFileName", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/fileUploadAndDownload/importURL", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/casbin/updateCasbin", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/casbin/getPolicyPathByAuthorityId", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/jwt/jsonInBlacklist", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/system/getSystemConfig", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/system/setSystemConfig", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/customer/customer", V2: "POST"}, + {Ptype: "p", V0: "8881", V1: "/customer/customer", V2: "PUT"}, + {Ptype: "p", V0: "8881", V1: "/customer/customer", V2: "DELETE"}, + {Ptype: "p", V0: "8881", V1: "/customer/customer", V2: "GET"}, + {Ptype: "p", V0: "8881", V1: "/customer/customerList", V2: "GET"}, + {Ptype: "p", V0: "8881", V1: "/user/getUserInfo", V2: "GET"}, + + {Ptype: "p", V0: "9528", V1: "/user/admin_register", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/api/createApi", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/api/getApiList", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/api/getApiById", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/api/deleteApi", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/api/updateApi", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/api/getAllApis", V2: "POST"}, + + {Ptype: "p", V0: "9528", V1: "/authority/createAuthority", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/authority/deleteAuthority", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/authority/getAuthorityList", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/authority/setDataAuthority", V2: "POST"}, + + {Ptype: "p", V0: "9528", V1: "/menu/getMenu", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/getMenuList", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/addBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/getBaseMenuTree", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/addMenuAuthority", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/getMenuAuthority", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/deleteBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/updateBaseMenu", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/menu/getBaseMenuById", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/user/changePassword", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/user/getUserList", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/user/setUserAuthority", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/fileUploadAndDownload/upload", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/fileUploadAndDownload/getFileList", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/fileUploadAndDownload/deleteFile", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/fileUploadAndDownload/editFileName", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/fileUploadAndDownload/importURL", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/casbin/updateCasbin", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/casbin/getPolicyPathByAuthorityId", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/jwt/jsonInBlacklist", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/system/getSystemConfig", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/system/setSystemConfig", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/customer/customer", V2: "PUT"}, + {Ptype: "p", V0: "9528", V1: "/customer/customer", V2: "GET"}, + {Ptype: "p", V0: "9528", V1: "/customer/customer", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/customer/customer", V2: "DELETE"}, + {Ptype: "p", V0: "9528", V1: "/customer/customerList", V2: "GET"}, + {Ptype: "p", V0: "9528", V1: "/autoCode/createTemp", V2: "POST"}, + {Ptype: "p", V0: "9528", V1: "/user/getUserInfo", V2: "GET"}, + } + if err := db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, "Casbin 表 ("+i.InitializerName()+") 数据初始化失败!") + } + next := context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initCasbin) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.Where(adapter.CasbinRule{Ptype: "p", V0: "9528", V1: "/user/getUserInfo", V2: "GET"}). + First(&adapter.CasbinRule{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据 + return false + } + return true +} diff --git a/source/system/dictionary.go b/source/system/dictionary.go new file mode 100644 index 0000000..2c30c5f --- /dev/null +++ b/source/system/dictionary.go @@ -0,0 +1,71 @@ +package system + +import ( + "context" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderDict = initOrderCasbin + 1 + +type initDict struct{} + +// auto run +func init() { + system.RegisterInit(initOrderDict, &initDict{}) +} + +func (i *initDict) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysDictionary{}) +} + +func (i *initDict) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysDictionary{}) +} + +func (i *initDict) InitializerName() string { + return sysModel.SysDictionary{}.TableName() +} + +func (i *initDict) InitializeData(ctx context.Context) (next context.Context, err error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + True := true + entities := []sysModel.SysDictionary{ + {Name: "性别", Type: "gender", Status: &True, Desc: "性别字典"}, + {Name: "数据库int类型", Type: "int", Status: &True, Desc: "int类型对应的数据库类型"}, + {Name: "数据库时间日期类型", Type: "time.Time", Status: &True, Desc: "数据库时间日期类型"}, + {Name: "数据库浮点型", Type: "float64", Status: &True, Desc: "数据库浮点型"}, + {Name: "数据库字符串", Type: "string", Status: &True, Desc: "数据库字符串"}, + {Name: "数据库bool类型", Type: "bool", Status: &True, Desc: "数据库bool类型"}, + } + + if err = db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, sysModel.SysDictionary{}.TableName()+"表数据初始化失败!") + } + next = context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initDict) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.Where("type = ?", "bool").First(&sysModel.SysDictionary{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据 + return false + } + return true +} diff --git a/source/system/dictionary_detail.go b/source/system/dictionary_detail.go new file mode 100644 index 0000000..5312c0d --- /dev/null +++ b/source/system/dictionary_detail.go @@ -0,0 +1,121 @@ +package system + +import ( + "context" + "fmt" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderDictDetail = initOrderDict + 1 + +type initDictDetail struct{} + +// auto run +func init() { + system.RegisterInit(initOrderDictDetail, &initDictDetail{}) +} + +func (i *initDictDetail) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysDictionaryDetail{}) +} + +func (i *initDictDetail) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysDictionaryDetail{}) +} + +func (i *initDictDetail) InitializerName() string { + return sysModel.SysDictionaryDetail{}.TableName() +} + +func (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + dicts, ok := ctx.Value(new(initDict).InitializerName()).([]sysModel.SysDictionary) + if !ok { + return ctx, errors.Wrap(system.ErrMissingDependentContext, + fmt.Sprintf("未找到 %s 表初始化数据", sysModel.SysDictionary{}.TableName())) + } + True := true + dicts[0].SysDictionaryDetails = []sysModel.SysDictionaryDetail{ + {Label: "男", Value: "1", Status: &True, Sort: 1}, + {Label: "女", Value: "2", Status: &True, Sort: 2}, + } + + dicts[1].SysDictionaryDetails = []sysModel.SysDictionaryDetail{ + {Label: "smallint", Value: "1", Status: &True, Extend: "mysql", Sort: 1}, + {Label: "mediumint", Value: "2", Status: &True, Extend: "mysql", Sort: 2}, + {Label: "int", Value: "3", Status: &True, Extend: "mysql", Sort: 3}, + {Label: "bigint", Value: "4", Status: &True, Extend: "mysql", Sort: 4}, + {Label: "int2", Value: "5", Status: &True, Extend: "pgsql", Sort: 5}, + {Label: "int4", Value: "6", Status: &True, Extend: "pgsql", Sort: 6}, + {Label: "int6", Value: "7", Status: &True, Extend: "pgsql", Sort: 7}, + {Label: "int8", Value: "8", Status: &True, Extend: "pgsql", Sort: 8}, + } + + dicts[2].SysDictionaryDetails = []sysModel.SysDictionaryDetail{ + {Label: "date", Status: &True}, + {Label: "time", Value: "1", Status: &True, Extend: "mysql", Sort: 1}, + {Label: "year", Value: "2", Status: &True, Extend: "mysql", Sort: 2}, + {Label: "datetime", Value: "3", Status: &True, Extend: "mysql", Sort: 3}, + {Label: "timestamp", Value: "5", Status: &True, Extend: "mysql", Sort: 5}, + {Label: "timestamptz", Value: "6", Status: &True, Extend: "pgsql", Sort: 5}, + } + dicts[3].SysDictionaryDetails = []sysModel.SysDictionaryDetail{ + {Label: "float", Status: &True}, + {Label: "double", Value: "1", Status: &True, Extend: "mysql", Sort: 1}, + {Label: "decimal", Value: "2", Status: &True, Extend: "mysql", Sort: 2}, + {Label: "numeric", Value: "3", Status: &True, Extend: "pgsql", Sort: 3}, + {Label: "smallserial", Value: "4", Status: &True, Extend: "pgsql", Sort: 4}, + } + + dicts[4].SysDictionaryDetails = []sysModel.SysDictionaryDetail{ + {Label: "char", Status: &True}, + {Label: "varchar", Value: "1", Status: &True, Extend: "mysql", Sort: 1}, + {Label: "tinyblob", Value: "2", Status: &True, Extend: "mysql", Sort: 2}, + {Label: "tinytext", Value: "3", Status: &True, Extend: "mysql", Sort: 3}, + {Label: "text", Value: "4", Status: &True, Extend: "mysql", Sort: 4}, + {Label: "blob", Value: "5", Status: &True, Extend: "mysql", Sort: 5}, + {Label: "mediumblob", Value: "6", Status: &True, Extend: "mysql", Sort: 6}, + {Label: "mediumtext", Value: "7", Status: &True, Extend: "mysql", Sort: 7}, + {Label: "longblob", Value: "8", Status: &True, Extend: "mysql", Sort: 8}, + {Label: "longtext", Value: "9", Status: &True, Extend: "mysql", Sort: 9}, + } + + dicts[5].SysDictionaryDetails = []sysModel.SysDictionaryDetail{ + {Label: "tinyint", Value: "1", Extend: "mysql", Status: &True}, + {Label: "bool", Value: "2", Extend: "pgsql", Status: &True}, + } + for _, dict := range dicts { + if err := db.Model(&dict).Association("SysDictionaryDetails"). + Replace(dict.SysDictionaryDetails); err != nil { + return ctx, errors.Wrap(err, sysModel.SysDictionaryDetail{}.TableName()+"表数据初始化失败!") + } + } + return ctx, nil +} + +func (i *initDictDetail) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + var dict sysModel.SysDictionary + if err := db.Preload("SysDictionaryDetails"). + First(&dict, &sysModel.SysDictionary{Name: "数据库bool类型"}).Error; err != nil { + return false + } + return len(dict.SysDictionaryDetails) > 0 && dict.SysDictionaryDetails[0].Label == "tinyint" +} diff --git a/source/system/excel_template.go b/source/system/excel_template.go new file mode 100644 index 0000000..6a6c020 --- /dev/null +++ b/source/system/excel_template.go @@ -0,0 +1,75 @@ +package system + +import ( + "context" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +type initExcelTemplate struct{} + +const initOrderExcelTemplate = initOrderDictDetail + 1 + +// auto run +func init() { + system.RegisterInit(initOrderExcelTemplate, &initExcelTemplate{}) +} + +func (i *initExcelTemplate) InitializerName() string { + return "sys_export_templates" +} + +func (i *initExcelTemplate) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysExportTemplate{}) +} + +func (i *initExcelTemplate) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysExportTemplate{}) +} + +func (i *initExcelTemplate) InitializeData(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + + entities := []sysModel.SysExportTemplate{ + { + Name: "api", + TableName: "sys_apis", + TemplateID: "api", + TemplateInfo: `{ +"path":"路径", +"method":"方法(大写)", +"description":"方法介绍", +"api_group":"方法分组" +}`, + }, + } + if err := db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, "sys_export_templates"+"表数据初始化失败!") + } + next := context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initExcelTemplate) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.First(&sysModel.SysExportTemplate{}).Error, gorm.ErrRecordNotFound) { + return false + } + return true +} diff --git a/source/system/menu.go b/source/system/menu.go new file mode 100644 index 0000000..44b2114 --- /dev/null +++ b/source/system/menu.go @@ -0,0 +1,102 @@ +package system + +import ( + "context" + + . "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderMenu = initOrderAuthority + 1 + +type initMenu struct{} + +// auto run +func init() { + system.RegisterInit(initOrderMenu, &initMenu{}) +} + +func (i *initMenu) InitializerName() string { + return SysBaseMenu{}.TableName() +} + +func (i *initMenu) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate( + &SysBaseMenu{}, + &SysBaseMenuParameter{}, + &SysBaseMenuBtn{}, + ) +} + +func (i *initMenu) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + m := db.Migrator() + return m.HasTable(&SysBaseMenu{}) && + m.HasTable(&SysBaseMenuParameter{}) && + m.HasTable(&SysBaseMenuBtn{}) +} + +func (i *initMenu) InitializeData(ctx context.Context) (next context.Context, err error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + entities := []SysBaseMenu{ + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Sort: 1, Meta: Meta{Title: "仪表盘", Icon: "odometer"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "about", Name: "about", Component: "view/about/index.vue", Sort: 9, Meta: Meta{Title: "关于我们", Icon: "info-filled"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "admin", Name: "superAdmin", Component: "view/superAdmin/index.vue", Sort: 3, Meta: Meta{Title: "超级管理员", Icon: "user"}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "authority", Name: "authority", Component: "view/superAdmin/authority/authority.vue", Sort: 1, Meta: Meta{Title: "角色管理", Icon: "avatar"}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "menu", Name: "menu", Component: "view/superAdmin/menu/menu.vue", Sort: 2, Meta: Meta{Title: "菜单管理", Icon: "tickets", KeepAlive: true}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "api", Name: "api", Component: "view/superAdmin/api/api.vue", Sort: 3, Meta: Meta{Title: "api管理", Icon: "platform", KeepAlive: true}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "user", Name: "user", Component: "view/superAdmin/user/user.vue", Sort: 4, Meta: Meta{Title: "用户管理", Icon: "coordinate"}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "dictionary", Name: "dictionary", Component: "view/superAdmin/dictionary/sysDictionary.vue", Sort: 5, Meta: Meta{Title: "字典管理", Icon: "notebook"}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "operation", Name: "operation", Component: "view/superAdmin/operation/sysOperationRecord.vue", Sort: 6, Meta: Meta{Title: "操作历史", Icon: "pie-chart"}}, + {MenuLevel: 0, Hidden: true, ParentId: 0, Path: "person", Name: "person", Component: "view/person/person.vue", Sort: 4, Meta: Meta{Title: "个人信息", Icon: "message"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "example", Name: "example", Component: "view/example/index.vue", Sort: 7, Meta: Meta{Title: "示例文件", Icon: "management"}}, + {MenuLevel: 0, Hidden: false, ParentId: 11, Path: "upload", Name: "upload", Component: "view/example/upload/upload.vue", Sort: 5, Meta: Meta{Title: "媒体库(上传下载)", Icon: "upload"}}, + {MenuLevel: 0, Hidden: false, ParentId: 11, Path: "breakpoint", Name: "breakpoint", Component: "view/example/breakpoint/breakpoint.vue", Sort: 6, Meta: Meta{Title: "断点续传", Icon: "upload-filled"}}, + {MenuLevel: 0, Hidden: false, ParentId: 11, Path: "customer", Name: "customer", Component: "view/example/customer/customer.vue", Sort: 7, Meta: Meta{Title: "客户列表(资源示例)", Icon: "avatar"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "systemTools", Name: "systemTools", Component: "view/systemTools/index.vue", Sort: 5, Meta: Meta{Title: "系统工具", Icon: "tools"}}, + {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "autoCode", Name: "autoCode", Component: "view/systemTools/autoCode/index.vue", Sort: 1, Meta: Meta{Title: "代码生成器", Icon: "cpu", KeepAlive: true}}, + {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "formCreate", Name: "formCreate", Component: "view/systemTools/formCreate/index.vue", Sort: 3, Meta: Meta{Title: "表单生成器", Icon: "magic-stick", KeepAlive: true}}, + {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "system", Name: "system", Component: "view/systemTools/system/system.vue", Sort: 4, Meta: Meta{Title: "系统配置", Icon: "operation"}}, + {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "autoCodeAdmin", Name: "autoCodeAdmin", Component: "view/systemTools/autoCodeAdmin/index.vue", Sort: 2, Meta: Meta{Title: "自动化代码管理", Icon: "magic-stick"}}, + {MenuLevel: 0, Hidden: true, ParentId: 15, Path: "autoCodeEdit/:id", Name: "autoCodeEdit", Component: "view/systemTools/autoCode/index.vue", Sort: 0, Meta: Meta{Title: "自动化代码-${id}", Icon: "magic-stick"}}, + {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "autoPkg", Name: "autoPkg", Component: "view/systemTools/autoPkg/autoPkg.vue", Sort: 0, Meta: Meta{Title: "模板配置", Icon: "folder"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "https://www.gin-vue-admin.com", Name: "https://www.gin-vue-admin.com", Component: "/", Sort: 0, Meta: Meta{Title: "官方网站", Icon: "customer-gva"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "state", Name: "state", Component: "view/system/state.vue", Sort: 8, Meta: Meta{Title: "服务器状态", Icon: "cloudy"}}, + {MenuLevel: 0, Hidden: false, ParentId: 0, Path: "plugin", Name: "plugin", Component: "view/routerHolder.vue", Sort: 6, Meta: Meta{Title: "插件系统", Icon: "cherry"}}, + {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "https://plugin.gin-vue-admin.com/", Name: "https://plugin.gin-vue-admin.com/", Component: "https://plugin.gin-vue-admin.com/", Sort: 0, Meta: Meta{Title: "插件市场", Icon: "shop"}}, + {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "installPlugin", Name: "installPlugin", Component: "view/systemTools/installPlugin/index.vue", Sort: 1, Meta: Meta{Title: "插件安装", Icon: "box"}}, + {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "pubPlug", Name: "pubPlug", Component: "view/systemTools/pubPlug/pubPlug.vue", Sort: 3, Meta: Meta{Title: "打包插件", Icon: "files"}}, + {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "plugin-email", Name: "plugin-email", Component: "plugin/email/view/index.vue", Sort: 4, Meta: Meta{Title: "邮件插件", Icon: "message"}}, + {MenuLevel: 0, Hidden: false, ParentId: 15, Path: "exportTemplate", Name: "exportTemplate", Component: "view/systemTools/exportTemplate/exportTemplate.vue", Sort: 5, Meta: Meta{Title: "导出模板", Icon: "reading"}}, + {MenuLevel: 0, Hidden: false, ParentId: 24, Path: "anInfo", Name: "anInfo", Component: "plugin/announcement/view/info.vue", Sort: 5, Meta: Meta{Title: "公告管理[示例]", Icon: "scaleToOriginal"}}, + {MenuLevel: 0, Hidden: false, ParentId: 3, Path: "sysParams", Name: "sysParams", Component: "view/superAdmin/params/sysParams.vue", Sort: 7, Meta: Meta{Title: "参数管理", Icon: "compass"}}, + } + if err = db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+"表数据初始化失败!") + } + next = context.WithValue(ctx, i.InitializerName(), entities) + return next, nil +} + +func (i *initMenu) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + if errors.Is(db.Where("path = ?", "autoPkg").First(&SysBaseMenu{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据 + return false + } + return true +} diff --git a/source/system/user.go b/source/system/user.go new file mode 100644 index 0000000..6c6ab57 --- /dev/null +++ b/source/system/user.go @@ -0,0 +1,106 @@ +package system + +import ( + "context" + sysModel "git.echol.cn/loser/xiecheng_server/model/system" + "git.echol.cn/loser/xiecheng_server/service/system" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/google/uuid" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +const initOrderUser = initOrderAuthority + 1 + +type initUser struct{} + +// auto run +func init() { + system.RegisterInit(initOrderUser, &initUser{}) +} + +func (i *initUser) MigrateTable(ctx context.Context) (context.Context, error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + return ctx, db.AutoMigrate(&sysModel.SysUser{}) +} + +func (i *initUser) TableCreated(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + return db.Migrator().HasTable(&sysModel.SysUser{}) +} + +func (i *initUser) InitializerName() string { + return sysModel.SysUser{}.TableName() +} + +func (i *initUser) InitializeData(ctx context.Context) (next context.Context, err error) { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return ctx, system.ErrMissingDBContext + } + + ap := ctx.Value("adminPassword") + apStr, ok := ap.(string) + if !ok { + apStr = "123456" + } + + password := utils.BcryptHash(apStr) + adminPassword := utils.BcryptHash(apStr) + + entities := []sysModel.SysUser{ + { + UUID: uuid.New(), + Username: "admin", + Password: adminPassword, + NickName: "Mr.奇淼", + HeaderImg: "https://qmplusimg.henrongyi.top/gva_header.jpg", + AuthorityId: 888, + Phone: "17611111111", + Email: "333333333@qq.com", + }, + { + UUID: uuid.New(), + Username: "a303176530", + Password: password, + NickName: "用户1", + HeaderImg: "https:///qmplusimg.henrongyi.top/1572075907logo.png", + AuthorityId: 9528, + Phone: "17611111111", + Email: "333333333@qq.com"}, + } + if err = db.Create(&entities).Error; err != nil { + return ctx, errors.Wrap(err, sysModel.SysUser{}.TableName()+"表数据初始化失败!") + } + next = context.WithValue(ctx, i.InitializerName(), entities) + authorityEntities, ok := ctx.Value(new(initAuthority).InitializerName()).([]sysModel.SysAuthority) + if !ok { + return next, errors.Wrap(system.ErrMissingDependentContext, "创建 [用户-权限] 关联失败, 未找到权限表初始化数据") + } + if err = db.Model(&entities[0]).Association("Authorities").Replace(authorityEntities); err != nil { + return next, err + } + if err = db.Model(&entities[1]).Association("Authorities").Replace(authorityEntities[:1]); err != nil { + return next, err + } + return next, err +} + +func (i *initUser) DataInserted(ctx context.Context) bool { + db, ok := ctx.Value("db").(*gorm.DB) + if !ok { + return false + } + var record sysModel.SysUser + if errors.Is(db.Where("username = ?", "a303176530"). + Preload("Authorities").First(&record).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据 + return false + } + return len(record.Authorities) > 0 && record.Authorities[0].AuthorityId == 888 +} diff --git a/task/clearTable.go b/task/clearTable.go new file mode 100644 index 0000000..09a9e12 --- /dev/null +++ b/task/clearTable.go @@ -0,0 +1,51 @@ +package task + +import ( + "errors" + "fmt" + "git.echol.cn/loser/xiecheng_server/model/common" + "time" + + "gorm.io/gorm" +) + +//@author: [songzhibin97](https://github.com/songzhibin97) +//@function: ClearTable +//@description: 清理数据库表数据 +//@param: db(数据库对象) *gorm.DB, tableName(表名) string, compareField(比较字段) string, interval(间隔) string +//@return: error + +func ClearTable(db *gorm.DB) error { + var ClearTableDetail []common.ClearDB + + ClearTableDetail = append(ClearTableDetail, common.ClearDB{ + TableName: "sys_operation_records", + CompareField: "created_at", + Interval: "2160h", + }) + + ClearTableDetail = append(ClearTableDetail, common.ClearDB{ + TableName: "jwt_blacklists", + CompareField: "created_at", + Interval: "168h", + }) + + if db == nil { + return errors.New("db Cannot be empty") + } + + for _, detail := range ClearTableDetail { + duration, err := time.ParseDuration(detail.Interval) + if err != nil { + return err + } + if duration < 0 { + return errors.New("parse duration < 0") + } + err = db.Debug().Exec(fmt.Sprintf("DELETE FROM %s WHERE %s < ?", detail.TableName, detail.CompareField), time.Now().Add(-duration)).Error + if err != nil { + return err + } + } + return nil +} diff --git a/utils/ast/ast.go b/utils/ast/ast.go new file mode 100644 index 0000000..0dc3771 --- /dev/null +++ b/utils/ast/ast.go @@ -0,0 +1,231 @@ +package ast + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/model/system" + "go/ast" + "go/parser" + "go/token" + "log" +) + +// AddImport 增加 import 方法 +func AddImport(astNode ast.Node, imp string) { + impStr := fmt.Sprintf("\"%s\"", imp) + ast.Inspect(astNode, func(node ast.Node) bool { + if genDecl, ok := node.(*ast.GenDecl); ok { + if genDecl.Tok == token.IMPORT { + for i := range genDecl.Specs { + if impNode, ok := genDecl.Specs[i].(*ast.ImportSpec); ok { + if impNode.Path.Value == impStr { + return false + } + } + } + genDecl.Specs = append(genDecl.Specs, &ast.ImportSpec{ + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: impStr, + }, + }) + } + } + return true + }) +} + +// FindFunction 查询特定function方法 +func FindFunction(astNode ast.Node, FunctionName string) *ast.FuncDecl { + var funcDeclP *ast.FuncDecl + ast.Inspect(astNode, func(node ast.Node) bool { + if funcDecl, ok := node.(*ast.FuncDecl); ok { + if funcDecl.Name.String() == FunctionName { + funcDeclP = funcDecl + return false + } + } + return true + }) + return funcDeclP +} + +// FindArray 查询特定数组方法 +func FindArray(astNode ast.Node, identName, selectorExprName string) *ast.CompositeLit { + var assignStmt *ast.CompositeLit + ast.Inspect(astNode, func(n ast.Node) bool { + switch node := n.(type) { + case *ast.AssignStmt: + for _, expr := range node.Rhs { + if exprType, ok := expr.(*ast.CompositeLit); ok { + if arrayType, ok := exprType.Type.(*ast.ArrayType); ok { + sel, ok1 := arrayType.Elt.(*ast.SelectorExpr) + x, ok2 := sel.X.(*ast.Ident) + if ok1 && ok2 && x.Name == identName && sel.Sel.Name == selectorExprName { + assignStmt = exprType + return false + } + } + } + } + } + return true + }) + return assignStmt +} + +func CreateMenuStructAst(menus []system.SysBaseMenu) *[]ast.Expr { + var menuElts []ast.Expr + for i := range menus { + elts := []ast.Expr{ // 结构体的字段 + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "ParentId"}, + Value: &ast.BasicLit{Kind: token.INT, Value: "0"}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Path"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", menus[i].Path)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Name"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", menus[i].Name)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Hidden"}, + Value: &ast.Ident{Name: "false"}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Component"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", menus[i].Component)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Sort"}, + Value: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", menus[i].Sort)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Meta"}, + Value: &ast.CompositeLit{ + Type: &ast.SelectorExpr{ + X: &ast.Ident{Name: "model"}, + Sel: &ast.Ident{Name: "Meta"}, + }, + Elts: []ast.Expr{ + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Title"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", menus[i].Title)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Icon"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", menus[i].Icon)}, + }, + }, + }, + }, + } + menuElts = append(menuElts, &ast.CompositeLit{ + Type: nil, + Elts: elts, + }) + } + return &menuElts +} + +func CreateApiStructAst(apis []system.SysApi) *[]ast.Expr { + var apiElts []ast.Expr + for i := range apis { + elts := []ast.Expr{ // 结构体的字段 + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Path"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", apis[i].Path)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Description"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", apis[i].Description)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "ApiGroup"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", apis[i].ApiGroup)}, + }, + &ast.KeyValueExpr{ + Key: &ast.Ident{Name: "Method"}, + Value: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"%s\"", apis[i].Method)}, + }, + } + apiElts = append(apiElts, &ast.CompositeLit{ + Type: nil, + Elts: elts, + }) + } + return &apiElts +} + +// CheckImport 检查是否存在Import +func CheckImport(file *ast.File, importPath string) bool { + for _, imp := range file.Imports { + // Remove quotes around the import path + path := imp.Path.Value[1 : len(imp.Path.Value)-1] + + if path == importPath { + return true + } + } + + return false +} + +func clearPosition(astNode ast.Node) { + ast.Inspect(astNode, func(n ast.Node) bool { + switch node := n.(type) { + case *ast.Ident: + // 清除位置信息 + node.NamePos = token.NoPos + case *ast.CallExpr: + // 清除位置信息 + node.Lparen = token.NoPos + node.Rparen = token.NoPos + case *ast.BasicLit: + // 清除位置信息 + node.ValuePos = token.NoPos + case *ast.SelectorExpr: + // 清除位置信息 + node.Sel.NamePos = token.NoPos + case *ast.BinaryExpr: + node.OpPos = token.NoPos + case *ast.UnaryExpr: + node.OpPos = token.NoPos + case *ast.StarExpr: + node.Star = token.NoPos + } + return true + }) +} + +func CreateStmt(statement string) *ast.ExprStmt { + expr, err := parser.ParseExpr(statement) + if err != nil { + log.Fatal(err) + } + clearPosition(expr) + return &ast.ExprStmt{X: expr} +} + +func IsBlockStmt(node ast.Node) bool { + _, ok := node.(*ast.BlockStmt) + return ok +} + +func VariableExistsInBlock(block *ast.BlockStmt, varName string) bool { + exists := false + ast.Inspect(block, func(n ast.Node) bool { + switch node := n.(type) { + case *ast.AssignStmt: + for _, expr := range node.Lhs { + if ident, ok := expr.(*ast.Ident); ok && ident.Name == varName { + exists = true + return false + } + } + } + return true + }) + return exists +} diff --git a/utils/ast/ast_auto_enter.go b/utils/ast/ast_auto_enter.go new file mode 100644 index 0000000..382f554 --- /dev/null +++ b/utils/ast/ast_auto_enter.go @@ -0,0 +1,47 @@ +package ast + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" +) + +func ImportForAutoEnter(path string, funcName string, code string) { + src, err := os.ReadFile(path) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + ast.Inspect(astFile, func(node ast.Node) bool { + if typeSpec, ok := node.(*ast.TypeSpec); ok { + if typeSpec.Name.Name == funcName { + if st, ok := typeSpec.Type.(*ast.StructType); ok { + for i := range st.Fields.List { + if t, ok := st.Fields.List[i].Type.(*ast.Ident); ok { + if t.Name == code { + return false + } + } + } + sn := &ast.Field{ + Type: &ast.Ident{Name: code}, + } + st.Fields.List = append(st.Fields.List, sn) + } + } + } + return true + }) + var out []byte + bf := bytes.NewBuffer(out) + err = printer.Fprint(bf, fileSet, astFile) + if err != nil { + return + } + _ = os.WriteFile(path, bf.Bytes(), 0666) +} diff --git a/utils/ast/ast_enter.go b/utils/ast/ast_enter.go new file mode 100644 index 0000000..7a5c727 --- /dev/null +++ b/utils/ast/ast_enter.go @@ -0,0 +1,181 @@ +package ast + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "log" + "os" + "strconv" + "strings" +) + +type Visitor struct { + ImportCode string + StructName string + PackageName string + GroupName string +} + +func (vi *Visitor) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.GenDecl: + // 查找有没有import context包 + // Notice:没有考虑没有import任何包的情况 + if n.Tok == token.IMPORT && vi.ImportCode != "" { + vi.addImport(n) + // 不需要再遍历子树 + return nil + } + if n.Tok == token.TYPE && vi.StructName != "" && vi.PackageName != "" && vi.GroupName != "" { + vi.addStruct(n) + return nil + } + case *ast.FuncDecl: + if n.Name.Name == "Routers" { + vi.addFuncBodyVar(n) + return nil + } + + } + return vi +} + +func (vi *Visitor) addStruct(genDecl *ast.GenDecl) ast.Visitor { + for i := range genDecl.Specs { + switch n := genDecl.Specs[i].(type) { + case *ast.TypeSpec: + if strings.Index(n.Name.Name, "Group") > -1 { + switch t := n.Type.(type) { + case *ast.StructType: + f := &ast.Field{ + Names: []*ast.Ident{ + { + Name: vi.StructName, + Obj: &ast.Object{ + Kind: ast.Var, + Name: vi.StructName, + }, + }, + }, + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: vi.PackageName, + }, + Sel: &ast.Ident{ + Name: vi.GroupName, + }, + }, + } + t.Fields.List = append(t.Fields.List, f) + } + } + } + } + return vi +} + +func (vi *Visitor) addImport(genDecl *ast.GenDecl) ast.Visitor { + // 是否已经import + hasImported := false + for _, v := range genDecl.Specs { + importSpec := v.(*ast.ImportSpec) + // 如果已经包含 + if importSpec.Path.Value == strconv.Quote(vi.ImportCode) { + hasImported = true + } + } + if !hasImported { + genDecl.Specs = append(genDecl.Specs, &ast.ImportSpec{ + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(vi.ImportCode), + }, + }) + } + return vi +} + +func (vi *Visitor) addFuncBodyVar(funDecl *ast.FuncDecl) ast.Visitor { + hasVar := false + for _, v := range funDecl.Body.List { + switch varSpec := v.(type) { + case *ast.AssignStmt: + for i := range varSpec.Lhs { + switch nn := varSpec.Lhs[i].(type) { + case *ast.Ident: + if nn.Name == vi.PackageName+"Router" { + hasVar = true + } + } + } + } + } + if !hasVar { + assignStmt := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: vi.PackageName + "Router", + Obj: &ast.Object{ + Kind: ast.Var, + Name: vi.PackageName + "Router", + }, + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "router", + }, + Sel: &ast.Ident{ + Name: "RouterGroupApp", + }, + }, + Sel: &ast.Ident{ + Name: cases.Title(language.English).String(vi.PackageName), + }, + }, + }, + } + funDecl.Body.List = append(funDecl.Body.List, funDecl.Body.List[1]) + index := 1 + copy(funDecl.Body.List[index+1:], funDecl.Body.List[index:]) + funDecl.Body.List[index] = assignStmt + } + return vi +} + +func ImportReference(filepath, importCode, structName, packageName, groupName string) error { + fSet := token.NewFileSet() + fParser, err := parser.ParseFile(fSet, filepath, nil, parser.ParseComments) + if err != nil { + return err + } + importCode = strings.TrimSpace(importCode) + v := &Visitor{ + ImportCode: importCode, + StructName: structName, + PackageName: packageName, + GroupName: groupName, + } + if importCode == "" { + ast.Print(fSet, fParser) + } + + ast.Walk(v, fParser) + + var output []byte + buffer := bytes.NewBuffer(output) + err = format.Node(buffer, fSet, fParser) + if err != nil { + log.Fatal(err) + } + // 写回数据 + return os.WriteFile(filepath, buffer.Bytes(), 0o600) +} diff --git a/utils/ast/ast_gorm.go b/utils/ast/ast_gorm.go new file mode 100644 index 0000000..dea68f0 --- /dev/null +++ b/utils/ast/ast_gorm.go @@ -0,0 +1,166 @@ +package ast + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" +) + +// AddRegisterTablesAst 自动为 gorm.go 注册一个自动迁移 +func AddRegisterTablesAst(path, funcName, pk, varName, dbName, model string) { + modelPk := fmt.Sprintf("git.echol.cn/loser/xiecheng_server/model/%s", pk) + src, err := os.ReadFile(path) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + if err != nil { + fmt.Println(err) + } + AddImport(astFile, modelPk) + FuncNode := FindFunction(astFile, funcName) + if FuncNode != nil { + ast.Print(fileSet, FuncNode) + } + addDBVar(FuncNode.Body, varName, dbName) + addAutoMigrate(FuncNode.Body, varName, pk, model) + var out []byte + bf := bytes.NewBuffer(out) + printer.Fprint(bf, fileSet, astFile) + + os.WriteFile(path, bf.Bytes(), 0666) +} + +// 增加一个 db库变量 +func addDBVar(astBody *ast.BlockStmt, varName, dbName string) { + if dbName == "" { + return + } + dbStr := fmt.Sprintf("\"%s\"", dbName) + + for i := range astBody.List { + if assignStmt, ok := astBody.List[i].(*ast.AssignStmt); ok { + if ident, ok := assignStmt.Lhs[0].(*ast.Ident); ok { + if ident.Name == varName { + return + } + } + } + } + assignNode := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: varName, + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "global", + }, + Sel: &ast.Ident{ + Name: "GetGlobalDBByDBName", + }, + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: dbStr, + }, + }, + }, + }, + } + astBody.List = append([]ast.Stmt{assignNode}, astBody.List...) +} + +// 为db库变量增加 AutoMigrate 方法 +func addAutoMigrate(astBody *ast.BlockStmt, dbname string, pk string, model string) { + if dbname == "" { + dbname = "db" + } + flag := true + ast.Inspect(astBody, func(node ast.Node) bool { + // 首先判断需要加入的方法调用语句是否存在 不存在则直接走到下方逻辑 + switch n := node.(type) { + case *ast.CallExpr: + // 判断是否找到了AutoMigrate语句 + if s, ok := n.Fun.(*ast.SelectorExpr); ok { + if x, ok := s.X.(*ast.Ident); ok { + if s.Sel.Name == "AutoMigrate" && x.Name == dbname { + flag = false + if !NeedAppendModel(n, pk, model) { + return false + } + // 判断已经找到了AutoMigrate语句 + n.Args = append(n.Args, &ast.CompositeLit{ + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: pk, + }, + Sel: &ast.Ident{ + Name: model, + }, + }, + }) + return false + } + } + } + } + return true + //然后判断 pk.model是否存在 如果存在直接跳出 如果不存在 则向已经找到的方法调用语句的node里面push一条 + }) + + if flag { + exprStmt := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: dbname, + }, + Sel: &ast.Ident{ + Name: "AutoMigrate", + }, + }, + Args: []ast.Expr{ + &ast.CompositeLit{ + Type: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: pk, + }, + Sel: &ast.Ident{ + Name: model, + }, + }, + }, + }, + }} + astBody.List = append(astBody.List, exprStmt) + } +} + +// NeedAppendModel 为automigrate增加实参 +func NeedAppendModel(callNode ast.Node, pk string, model string) bool { + flag := true + ast.Inspect(callNode, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.SelectorExpr: + if x, ok := n.X.(*ast.Ident); ok { + if n.Sel.Name == model && x.Name == pk { + flag = false + return false + } + } + } + return true + }) + return flag +} diff --git a/utils/ast/ast_init_test.go b/utils/ast/ast_init_test.go new file mode 100644 index 0000000..8c0c581 --- /dev/null +++ b/utils/ast/ast_init_test.go @@ -0,0 +1,11 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" +) + +func init() { + global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("../../../") + global.GVA_CONFIG.AutoCode.Server = "server" +} diff --git a/utils/ast/ast_rollback.go b/utils/ast/ast_rollback.go new file mode 100644 index 0000000..6e84118 --- /dev/null +++ b/utils/ast/ast_rollback.go @@ -0,0 +1,173 @@ +package ast + +import ( + "bytes" + "fmt" + "git.echol.cn/loser/xiecheng_server/global" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" + "path/filepath" +) + +func RollBackAst(pk, model string) { + RollGormBack(pk, model) + RollRouterBack(pk, model) +} + +func RollGormBack(pk, model string) { + + // 首先分析存在多少个ttt作为调用方的node块 + // 如果多个 仅仅删除对应块即可 + // 如果单个 那么还需要剔除import + path := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go") + src, err := os.ReadFile(path) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + if err != nil { + fmt.Println(err) + } + var n *ast.CallExpr + var k int = -1 + var pkNum = 0 + ast.Inspect(astFile, func(node ast.Node) bool { + if node, ok := node.(*ast.CallExpr); ok { + for i := range node.Args { + pkOK := false + modelOK := false + ast.Inspect(node.Args[i], func(item ast.Node) bool { + if ii, ok := item.(*ast.Ident); ok { + if ii.Name == pk { + pkOK = true + pkNum++ + } + if ii.Name == model { + modelOK = true + } + } + if pkOK && modelOK { + n = node + k = i + } + return true + }) + } + } + return true + }) + if k > -1 { + n.Args = append(append([]ast.Expr{}, n.Args[:k]...), n.Args[k+1:]...) + } + if pkNum == 1 { + var imI int = -1 + var gp *ast.GenDecl + ast.Inspect(astFile, func(node ast.Node) bool { + if gen, ok := node.(*ast.GenDecl); ok { + for i := range gen.Specs { + if imspec, ok := gen.Specs[i].(*ast.ImportSpec); ok { + if imspec.Path.Value == "\"git.echol.cn/loser/xiecheng_server/model/"+pk+"\"" { + gp = gen + imI = i + return false + } + } + } + } + return true + }) + + if imI > -1 { + gp.Specs = append(append([]ast.Spec{}, gp.Specs[:imI]...), gp.Specs[imI+1:]...) + } + } + + var out []byte + bf := bytes.NewBuffer(out) + printer.Fprint(bf, fileSet, astFile) + os.Remove(path) + os.WriteFile(path, bf.Bytes(), 0666) + +} + +func RollRouterBack(pk, model string) { + + // 首先抓到所有的代码块结构 {} + // 分析结构中是否存在一个变量叫做 pk+Router + // 然后获取到代码块指针 对内部需要回滚的代码进行剔除 + path := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go") + src, err := os.ReadFile(path) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, 0) + if err != nil { + fmt.Println(err) + } + + var block *ast.BlockStmt + var routerStmt *ast.FuncDecl + + ast.Inspect(astFile, func(node ast.Node) bool { + if n, ok := node.(*ast.FuncDecl); ok { + if n.Name.Name == "initBizRouter" { + routerStmt = n + } + } + + if n, ok := node.(*ast.BlockStmt); ok { + ast.Inspect(n, func(bNode ast.Node) bool { + if in, ok := bNode.(*ast.Ident); ok { + if in.Name == pk+"Router" { + block = n + return false + } + } + return true + }) + return true + } + return true + }) + var k int + for i := range block.List { + if stmtNode, ok := block.List[i].(*ast.ExprStmt); ok { + ast.Inspect(stmtNode, func(node ast.Node) bool { + if n, ok := node.(*ast.Ident); ok { + if n.Name == "Init"+model+"Router" { + k = i + return false + } + } + return true + }) + } + } + + block.List = append(append([]ast.Stmt{}, block.List[:k]...), block.List[k+1:]...) + + if len(block.List) == 1 { + // 说明这个块就没任何意义了 + block.List = nil + } + + for i, n := range routerStmt.Body.List { + if n, ok := n.(*ast.BlockStmt); ok { + if n.List == nil { + routerStmt.Body.List = append(append([]ast.Stmt{}, routerStmt.Body.List[:i]...), routerStmt.Body.List[i+1:]...) + i-- + } + } + } + + var out []byte + bf := bytes.NewBuffer(out) + printer.Fprint(bf, fileSet, astFile) + os.Remove(path) + os.WriteFile(path, bf.Bytes(), 0666) +} diff --git a/utils/ast/ast_router.go b/utils/ast/ast_router.go new file mode 100644 index 0000000..86356b8 --- /dev/null +++ b/utils/ast/ast_router.go @@ -0,0 +1,135 @@ +package ast + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" + "strings" +) + +func AppendNodeToList(stmts []ast.Stmt, stmt ast.Stmt, index int) []ast.Stmt { + return append(stmts[:index], append([]ast.Stmt{stmt}, stmts[index:]...)...) +} + +func AddRouterCode(path, funcName, pk, model string) { + src, err := os.ReadFile(path) + if err != nil { + fmt.Println(err) + } + fileSet := token.NewFileSet() + astFile, err := parser.ParseFile(fileSet, "", src, parser.ParseComments) + + if err != nil { + fmt.Println(err) + } + + FuncNode := FindFunction(astFile, funcName) + + pkName := strings.ToUpper(pk[:1]) + pk[1:] + routerName := fmt.Sprintf("%sRouter", pk) + modelName := fmt.Sprintf("Init%sRouter", model) + var bloctPre *ast.BlockStmt + for i := len(FuncNode.Body.List) - 1; i >= 0; i-- { + if block, ok := FuncNode.Body.List[i].(*ast.BlockStmt); ok { + bloctPre = block + } + } + ast.Print(fileSet, FuncNode) + if ok, b := needAppendRouter(FuncNode, pk); ok { + routerNode := + &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{Name: routerName}, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: "router"}, + Sel: &ast.Ident{Name: "RouterGroupApp"}, + }, + Sel: &ast.Ident{Name: pkName}, + }, + }, + }, + }, + } + + FuncNode.Body.List = AppendNodeToList(FuncNode.Body.List, routerNode, len(FuncNode.Body.List)-1) + bloctPre = routerNode + } else { + bloctPre = b + } + + if needAppendInit(FuncNode, routerName, modelName) { + bloctPre.List = append(bloctPre.List, + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: routerName}, + Sel: &ast.Ident{Name: modelName}, + }, + Args: []ast.Expr{ + &ast.Ident{ + Name: "privateGroup", + }, + &ast.Ident{ + Name: "publicGroup", + }, + }, + }, + }) + } + var out []byte + bf := bytes.NewBuffer(out) + printer.Fprint(bf, fileSet, astFile) + os.WriteFile(path, bf.Bytes(), 0666) +} + +func needAppendRouter(funcNode ast.Node, pk string) (bool, *ast.BlockStmt) { + flag := true + var block *ast.BlockStmt + ast.Inspect(funcNode, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.BlockStmt: + for i := range n.List { + if assignNode, ok := n.List[i].(*ast.AssignStmt); ok { + if identNode, ok := assignNode.Lhs[0].(*ast.Ident); ok { + if identNode.Name == fmt.Sprintf("%sRouter", pk) { + flag = false + block = n + return false + } + } + } + } + + } + return true + }) + return flag, block +} + +func needAppendInit(funcNode ast.Node, routerName string, modelName string) bool { + flag := true + ast.Inspect(funcNode, func(node ast.Node) bool { + switch n := funcNode.(type) { + case *ast.CallExpr: + if selectNode, ok := n.Fun.(*ast.SelectorExpr); ok { + x, xok := selectNode.X.(*ast.Ident) + if xok && x.Name == routerName && selectNode.Sel.Name == modelName { + flag = false + return false + } + } + } + return true + }) + return flag +} diff --git a/utils/ast/ast_test.go b/utils/ast/ast_test.go new file mode 100644 index 0000000..a319a60 --- /dev/null +++ b/utils/ast/ast_test.go @@ -0,0 +1,32 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" + "path/filepath" + "testing" +) + +func TestAst(t *testing.T) { + filename := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "plugin.go") + fileSet := token.NewFileSet() + file, err := parser.ParseFile(fileSet, filename, nil, parser.ParseComments) + if err != nil { + t.Error(err) + return + } + err = ast.Print(fileSet, file) + if err != nil { + t.Error(err) + return + } + err = printer.Fprint(os.Stdout, token.NewFileSet(), file) + if err != nil { + panic(err) + } + +} diff --git a/utils/ast/ast_type.go b/utils/ast/ast_type.go new file mode 100644 index 0000000..c4e905e --- /dev/null +++ b/utils/ast/ast_type.go @@ -0,0 +1,53 @@ +package ast + +type Type string + +func (r Type) String() string { + return string(r) +} + +func (r Type) Group() string { + switch r { + case TypePackageApiEnter: + return "ApiGroup" + case TypePackageRouterEnter: + return "RouterGroup" + case TypePackageServiceEnter: + return "ServiceGroup" + case TypePackageApiModuleEnter: + return "ApiGroup" + case TypePackageRouterModuleEnter: + return "RouterGroup" + case TypePackageServiceModuleEnter: + return "ServiceGroup" + case TypePluginApiEnter: + return "api" + case TypePluginRouterEnter: + return "router" + case TypePluginServiceEnter: + return "service" + default: + return "" + } +} + +const ( + TypePackageApiEnter = "PackageApiEnter" // server/api/v1/enter.go + TypePackageRouterEnter = "PackageRouterEnter" // server/router/enter.go + TypePackageServiceEnter = "PackageServiceEnter" // server/service/enter.go + TypePackageApiModuleEnter = "PackageApiModuleEnter" // server/api/v1/{package}/enter.go + TypePackageRouterModuleEnter = "PackageRouterModuleEnter" // server/router/{package}/enter.go + TypePackageServiceModuleEnter = "PackageServiceModuleEnter" // server/service/{package}/enter.go + TypePackageInitializeGorm = "PackageInitializeGorm" // server/initialize/gorm_biz.go + TypePackageInitializeRouter = "PackageInitializeRouter" // server/initialize/router_biz.go + TypePluginGen = "PluginGen" // server/plugin/{package}/gen/main.go + TypePluginApiEnter = "PluginApiEnter" // server/plugin/{package}/enter.go + TypePluginInitializeV1 = "PluginInitializeV1" // server/initialize/plugin_biz_v1.go + TypePluginInitializeV2 = "PluginInitializeV2" // server/initialize/plugin_biz_v2.go + TypePluginRouterEnter = "PluginRouterEnter" // server/plugin/{package}/enter.go + TypePluginServiceEnter = "PluginServiceEnter" // server/plugin/{package}/enter.go + TypePluginInitializeApi = "PluginInitializeApi" // server/plugin/{package}/initialize/api.go + TypePluginInitializeGorm = "PluginInitializeGorm" // server/plugin/{package}/initialize/gorm.go + TypePluginInitializeMenu = "PluginInitializeMenu" // server/plugin/{package}/initialize/menu.go + TypePluginInitializeRouter = "PluginInitializeRouter" // server/plugin/{package}/initialize/router.go +) diff --git a/utils/ast/import.go b/utils/ast/import.go new file mode 100644 index 0000000..5de18a3 --- /dev/null +++ b/utils/ast/import.go @@ -0,0 +1,94 @@ +package ast + +import ( + "go/ast" + "go/token" + "io" + "strings" +) + +type Import struct { + Base + ImportPath string // 导包路径 +} + +func NewImport(importPath string) *Import { + return &Import{ImportPath: importPath} +} + +func (a *Import) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + return a.Base.Parse(filename, writer) +} + +func (a *Import) Rollback(file *ast.File) error { + if a.ImportPath == "" { + return nil + } + for i := 0; i < len(file.Decls); i++ { + v1, o1 := file.Decls[i].(*ast.GenDecl) + if o1 { + if v1.Tok != token.IMPORT { + break + } + for j := 0; j < len(v1.Specs); j++ { + v2, o2 := v1.Specs[j].(*ast.ImportSpec) + if o2 && strings.HasSuffix(a.ImportPath, v2.Path.Value) { + v1.Specs = append(v1.Specs[:j], v1.Specs[j+1:]...) + if len(v1.Specs) == 0 { + file.Decls = append(file.Decls[:i], file.Decls[i+1:]...) + } // 如果没有import声明,就删除, 如果不删除则会出现import() + break + } + } + } + } + return nil +} + +func (a *Import) Injection(file *ast.File) error { + if a.ImportPath == "" { + return nil + } + var has bool + for i := 0; i < len(file.Decls); i++ { + v1, o1 := file.Decls[i].(*ast.GenDecl) + if o1 { + if v1.Tok != token.IMPORT { + break + } + for j := 0; j < len(v1.Specs); j++ { + v2, o2 := v1.Specs[j].(*ast.ImportSpec) + if o2 && strings.HasSuffix(a.ImportPath, v2.Path.Value) { + has = true + break + } + } + if !has { + spec := &ast.ImportSpec{ + Path: &ast.BasicLit{Kind: token.STRING, Value: a.ImportPath}, + } + v1.Specs = append(v1.Specs, spec) + return nil + } + } + } + if !has { + decls := file.Decls + file.Decls = make([]ast.Decl, 0, len(file.Decls)+1) + decl := &ast.GenDecl{ + Tok: token.IMPORT, + Specs: []ast.Spec{ + &ast.ImportSpec{ + Path: &ast.BasicLit{Kind: token.STRING, Value: a.ImportPath}, + }, + }, + } + file.Decls = append(file.Decls, decl) + file.Decls = append(file.Decls, decls...) + } // 如果没有import声明,就创建一个, 主要要放在第一个 + return nil +} + +func (a *Import) Format(filename string, writer io.Writer, file *ast.File) error { + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/interfaces.go b/utils/ast/interfaces.go new file mode 100644 index 0000000..33ecc47 --- /dev/null +++ b/utils/ast/interfaces.go @@ -0,0 +1,17 @@ +package ast + +import ( + "go/ast" + "io" +) + +type Ast interface { + // Parse 解析文件/代码 + Parse(filename string, writer io.Writer) (file *ast.File, err error) + // Rollback 回滚 + Rollback(file *ast.File) error + // Injection 注入 + Injection(file *ast.File) error + // Format 格式化输出 + Format(filename string, writer io.Writer, file *ast.File) error +} diff --git a/utils/ast/interfaces_base.go b/utils/ast/interfaces_base.go new file mode 100644 index 0000000..ad26946 --- /dev/null +++ b/utils/ast/interfaces_base.go @@ -0,0 +1,76 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "github.com/pkg/errors" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io" + "os" + "path" + "path/filepath" + "strings" +) + +type Base struct{} + +func (a *Base) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + fileSet := token.NewFileSet() + if writer != nil { + file, err = parser.ParseFile(fileSet, filename, nil, parser.ParseComments) + } else { + file, err = parser.ParseFile(fileSet, filename, writer, parser.ParseComments) + } + if err != nil { + return nil, errors.Wrapf(err, "[filepath:%s]打开/解析文件失败!", filename) + } + return file, nil +} + +func (a *Base) Rollback(file *ast.File) error { + return nil +} + +func (a *Base) Injection(file *ast.File) error { + return nil +} + +func (a *Base) Format(filename string, writer io.Writer, file *ast.File) error { + fileSet := token.NewFileSet() + if writer == nil { + open, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0666) + defer open.Close() + if err != nil { + return errors.Wrapf(err, "[filepath:%s]打开文件失败!", filename) + } + writer = open + } + err := format.Node(writer, fileSet, file) + if err != nil { + return errors.Wrapf(err, "[filepath:%s]注入失败!", filename) + } + return nil +} + +// RelativePath 绝对路径转相对路径 +func (a *Base) RelativePath(filePath string) string { + server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server) + hasServer := strings.Index(filePath, server) + if hasServer != -1 { + filePath = strings.TrimPrefix(filePath, server) + keys := strings.Split(filePath, string(filepath.Separator)) + filePath = path.Join(keys...) + } + return filePath +} + +// AbsolutePath 相对路径转绝对路径 +func (a *Base) AbsolutePath(filePath string) string { + server := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server) + keys := strings.Split(filePath, "/") + filePath = filepath.Join(keys...) + filePath = filepath.Join(server, filePath) + return filePath +} diff --git a/utils/ast/package_enter.go b/utils/ast/package_enter.go new file mode 100644 index 0000000..f4b6305 --- /dev/null +++ b/utils/ast/package_enter.go @@ -0,0 +1,85 @@ +package ast + +import ( + "go/ast" + "go/token" + "io" +) + +// PackageEnter 模块化入口 +type PackageEnter struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + StructName string // 结构体名称 + PackageName string // 包名 + RelativePath string // 相对路径 + PackageStructName string // 包结构体名称 +} + +func (a *PackageEnter) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PackageEnter) Rollback(file *ast.File) error { + // 无需回滚 + return nil +} + +func (a *PackageEnter) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + ast.Inspect(file, func(n ast.Node) bool { + genDecl, ok := n.(*ast.GenDecl) + if !ok || genDecl.Tok != token.TYPE { + return true + } + + for _, spec := range genDecl.Specs { + typeSpec, specok := spec.(*ast.TypeSpec) + if !specok || typeSpec.Name.Name != a.Type.Group() { + continue + } + + structType, structTypeOK := typeSpec.Type.(*ast.StructType) + if !structTypeOK { + continue + } + + for _, field := range structType.Fields.List { + if len(field.Names) == 1 && field.Names[0].Name == a.StructName { + return true + } + } + + field := &ast.Field{ + Names: []*ast.Ident{{Name: a.StructName}}, + Type: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.PackageStructName}, + }, + } + structType.Fields.List = append(structType.Fields.List, field) + return false + } + + return true + }) + return nil +} + +func (a *PackageEnter) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/package_enter_test.go b/utils/ast/package_enter_test.go new file mode 100644 index 0000000..392bec8 --- /dev/null +++ b/utils/ast/package_enter_test.go @@ -0,0 +1,154 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPackageEnter_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + PackageName string + PackageStructName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试ExampleApiGroup回滚", + fields: fields{ + Type: TypePackageApiEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/api/v1/example"`, + StructName: "ExampleApiGroup", + PackageName: "example", + PackageStructName: "ApiGroup", + }, + wantErr: false, + }, + { + name: "测试ExampleRouterGroup回滚", + fields: fields{ + Type: TypePackageRouterEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/router/example"`, + StructName: "Example", + PackageName: "example", + PackageStructName: "RouterGroup", + }, + wantErr: false, + }, + { + name: "测试ExampleServiceGroup回滚", + fields: fields{ + Type: TypePackageServiceEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/service/example"`, + StructName: "ExampleServiceGroup", + PackageName: "example", + PackageStructName: "ServiceGroup", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageEnter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + PackageName: tt.fields.PackageName, + PackageStructName: tt.fields.PackageStructName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPackageEnter_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + PackageName string + PackageStructName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试ExampleApiGroup注入", + fields: fields{ + Type: TypePackageApiEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/api/v1/example"`, + StructName: "ExampleApiGroup", + PackageName: "example", + PackageStructName: "ApiGroup", + }, + }, + { + name: "测试ExampleRouterGroup注入", + fields: fields{ + Type: TypePackageRouterEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/router/example"`, + StructName: "Example", + PackageName: "example", + PackageStructName: "RouterGroup", + }, + wantErr: false, + }, + { + name: "测试ExampleServiceGroup注入", + fields: fields{ + Type: TypePackageServiceEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/service/example"`, + StructName: "ExampleServiceGroup", + PackageName: "example", + PackageStructName: "ServiceGroup", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageEnter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + PackageName: tt.fields.PackageName, + PackageStructName: tt.fields.PackageStructName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Format() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/package_initialize_gorm.go b/utils/ast/package_initialize_gorm.go new file mode 100644 index 0000000..594f714 --- /dev/null +++ b/utils/ast/package_initialize_gorm.go @@ -0,0 +1,196 @@ +package ast + +import ( + "fmt" + "go/ast" + "go/token" + "io" +) + +// PackageInitializeGorm 包初始化gorm +type PackageInitializeGorm struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + Business string // 业务库 gva => gva, 不要传"gva" + StructName string // 结构体名称 + PackageName string // 包名 + RelativePath string // 相对路径 + IsNew bool // 是否使用new关键字 true: new(PackageName.StructName) false: &PackageName.StructName{} +} + +func (a *PackageInitializeGorm) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PackageInitializeGorm) Rollback(file *ast.File) error { + packageNameNum := 0 + // 寻找目标结构 + ast.Inspect(file, func(n ast.Node) bool { + // 总调用的db变量根据business来决定 + varDB := a.Business + "Db" + + if a.Business == "" { + varDB = "db" + } + + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + // 检查是不是 db.AutoMigrate() 方法 + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok || selExpr.Sel.Name != "AutoMigrate" { + return true + } + + // 检查调用方是不是 db + ident, ok := selExpr.X.(*ast.Ident) + if !ok || ident.Name != varDB { + return true + } + + // 删除结构体参数 + for i := 0; i < len(callExpr.Args); i++ { + if com, comok := callExpr.Args[i].(*ast.CompositeLit); comok { + if selector, exprok := com.Type.(*ast.SelectorExpr); exprok { + if x, identok := selector.X.(*ast.Ident); identok { + if x.Name == a.PackageName { + packageNameNum++ + if selector.Sel.Name == a.StructName { + callExpr.Args = append(callExpr.Args[:i], callExpr.Args[i+1:]...) + i-- + } + } + } + } + } + } + return true + }) + + if packageNameNum == 1 { + _ = NewImport(a.ImportPath).Rollback(file) + } + return nil +} + +func (a *PackageInitializeGorm) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + bizModelDecl := FindFunction(file, "bizModel") + if bizModelDecl != nil { + a.addDbVar(bizModelDecl.Body) + } + // 寻找目标结构 + ast.Inspect(file, func(n ast.Node) bool { + // 总调用的db变量根据business来决定 + varDB := a.Business + "Db" + + if a.Business == "" { + varDB = "db" + } + + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + // 检查是不是 db.AutoMigrate() 方法 + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok || selExpr.Sel.Name != "AutoMigrate" { + return true + } + + // 检查调用方是不是 db + ident, ok := selExpr.X.(*ast.Ident) + if !ok || ident.Name != varDB { + return true + } + + // 添加结构体参数 + callExpr.Args = append(callExpr.Args, &ast.CompositeLit{ + Type: &ast.SelectorExpr{ + X: ast.NewIdent(a.PackageName), + Sel: ast.NewIdent(a.StructName), + }, + }) + return true + }) + return nil +} + +func (a *PackageInitializeGorm) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} + +// 创建businessDB变量 +func (a *PackageInitializeGorm) addDbVar(astBody *ast.BlockStmt) { + for i := range astBody.List { + if assignStmt, ok := astBody.List[i].(*ast.AssignStmt); ok { + if ident, ok := assignStmt.Lhs[0].(*ast.Ident); ok { + if (a.Business == "" && ident.Name == "db") || ident.Name == a.Business+"Db" { + return + } + } + } + } + + // 添加 businessDb := global.GetGlobalDBByDBName("business") 变量 + assignNode := &ast.AssignStmt{ + Lhs: []ast.Expr{ + &ast.Ident{ + Name: a.Business + "Db", + }, + }, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: "global", + }, + Sel: &ast.Ident{ + Name: "GetGlobalDBByDBName", + }, + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.STRING, + Value: fmt.Sprintf("\"%s\"", a.Business), + }, + }, + }, + }, + } + + // 添加 businessDb.AutoMigrate() 方法 + autoMigrateCall := &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{ + Name: a.Business + "Db", + }, + Sel: &ast.Ident{ + Name: "AutoMigrate", + }, + }, + }, + } + + returnNode := astBody.List[len(astBody.List)-1] + astBody.List = append(astBody.List[:len(astBody.List)-1], assignNode, autoMigrateCall, returnNode) +} diff --git a/utils/ast/package_initialize_gorm_test.go b/utils/ast/package_initialize_gorm_test.go new file mode 100644 index 0000000..1b2aea0 --- /dev/null +++ b/utils/ast/package_initialize_gorm_test.go @@ -0,0 +1,171 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPackageInitializeGorm_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + PackageName string + IsNew bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 &example.ExaFileUploadAndDownload{} 注入", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaFileUploadAndDownload", + PackageName: "example", + IsNew: false, + }, + }, + { + name: "测试 &example.ExaCustomer{} 注入", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaCustomer", + PackageName: "example", + IsNew: false, + }, + }, + { + name: "测试 new(example.ExaFileUploadAndDownload) 注入", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaFileUploadAndDownload", + PackageName: "example", + IsNew: true, + }, + }, + { + name: "测试 new(example.ExaCustomer) 注入", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaCustomer", + PackageName: "example", + IsNew: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageInitializeGorm{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + PackageName: tt.fields.PackageName, + IsNew: tt.fields.IsNew, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPackageInitializeGorm_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + PackageName string + IsNew bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 &example.ExaFileUploadAndDownload{} 回滚", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaFileUploadAndDownload", + PackageName: "example", + IsNew: false, + }, + }, + { + name: "测试 &example.ExaCustomer{} 回滚", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaCustomer", + PackageName: "example", + IsNew: false, + }, + }, + { + name: "测试 new(example.ExaFileUploadAndDownload) 回滚", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaFileUploadAndDownload", + PackageName: "example", + IsNew: true, + }, + }, + { + name: "测试 new(example.ExaCustomer) 回滚", + fields: fields{ + Type: TypePackageInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "gorm_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/model/example"`, + StructName: "ExaCustomer", + PackageName: "example", + IsNew: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageInitializeGorm{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + PackageName: tt.fields.PackageName, + IsNew: tt.fields.IsNew, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/package_initialize_router.go b/utils/ast/package_initialize_router.go new file mode 100644 index 0000000..9fe4429 --- /dev/null +++ b/utils/ast/package_initialize_router.go @@ -0,0 +1,150 @@ +package ast + +import ( + "fmt" + "go/ast" + "go/token" + "io" +) + +// PackageInitializeRouter 包初始化路由 +// ModuleName := PackageName.AppName.GroupName +// ModuleName.FunctionName(RouterGroupName) +type PackageInitializeRouter struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + RelativePath string // 相对路径 + AppName string // 应用名称 + GroupName string // 分组名称 + ModuleName string // 模块名称 + PackageName string // 包名 + FunctionName string // 函数名 + RouterGroupName string // 路由分组名称 + LeftRouterGroupName string // 左路由分组名称 + RightRouterGroupName string // 右路由分组名称 +} + +func (a *PackageInitializeRouter) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PackageInitializeRouter) Rollback(file *ast.File) error { + funcDecl := FindFunction(file, "initBizRouter") + exprNum := 0 + for i := range funcDecl.Body.List { + if IsBlockStmt(funcDecl.Body.List[i]) { + if VariableExistsInBlock(funcDecl.Body.List[i].(*ast.BlockStmt), a.ModuleName) { + for ii, stmt := range funcDecl.Body.List[i].(*ast.BlockStmt).List { + // 检查语句是否为 *ast.ExprStmt + exprStmt, ok := stmt.(*ast.ExprStmt) + if !ok { + continue + } + // 检查表达式是否为 *ast.CallExpr + callExpr, ok := exprStmt.X.(*ast.CallExpr) + if !ok { + continue + } + // 检查是否调用了我们正在寻找的函数 + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + // 检查调用的函数是否为 systemRouter.InitApiRouter + ident, ok := selExpr.X.(*ast.Ident) + //只要存在调用则+1 + if ok && ident.Name == a.ModuleName { + exprNum++ + } + //判断是否为目标结构 + if !ok || ident.Name != a.ModuleName || selExpr.Sel.Name != a.FunctionName { + continue + } + exprNum-- + // 从语句列表中移除。 + funcDecl.Body.List[i].(*ast.BlockStmt).List = append(funcDecl.Body.List[i].(*ast.BlockStmt).List[:ii], funcDecl.Body.List[i].(*ast.BlockStmt).List[ii+1:]...) + // 如果不再存在任何调用,则删除导入和变量。 + if exprNum == 0 { + funcDecl.Body.List = append(funcDecl.Body.List[:i], funcDecl.Body.List[i+1:]...) + } + break + } + break + } + } + } + + return nil +} + +func (a *PackageInitializeRouter) Injection(file *ast.File) error { + funcDecl := FindFunction(file, "initBizRouter") + hasRouter := false + var varBlock *ast.BlockStmt + for i := range funcDecl.Body.List { + if IsBlockStmt(funcDecl.Body.List[i]) { + if VariableExistsInBlock(funcDecl.Body.List[i].(*ast.BlockStmt), a.ModuleName) { + hasRouter = true + varBlock = funcDecl.Body.List[i].(*ast.BlockStmt) + break + } + } + } + if !hasRouter { + stmt := a.CreateAssignStmt() + varBlock = &ast.BlockStmt{ + List: []ast.Stmt{ + stmt, + }, + } + } + routerStmt := CreateStmt(fmt.Sprintf("%s.%s(%s,%s)", a.ModuleName, a.FunctionName, a.LeftRouterGroupName, a.RightRouterGroupName)) + varBlock.List = append(varBlock.List, routerStmt) + if !hasRouter { + funcDecl.Body.List = append(funcDecl.Body.List, varBlock) + } + return nil +} + +func (a *PackageInitializeRouter) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} + +func (a *PackageInitializeRouter) CreateAssignStmt() *ast.AssignStmt { + //创建左侧变量 + ident := &ast.Ident{ + Name: a.ModuleName, + } + + //创建右侧的赋值语句 + selector := &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.AppName}, + }, + Sel: &ast.Ident{Name: a.GroupName}, + } + + // 创建一个组合的赋值语句 + stmt := &ast.AssignStmt{ + Lhs: []ast.Expr{ident}, + Tok: token.DEFINE, + Rhs: []ast.Expr{selector}, + } + + return stmt +} diff --git a/utils/ast/package_initialize_router_test.go b/utils/ast/package_initialize_router_test.go new file mode 100644 index 0000000..be032d0 --- /dev/null +++ b/utils/ast/package_initialize_router_test.go @@ -0,0 +1,158 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPackageInitializeRouter_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + AppName string + GroupName string + ModuleName string + PackageName string + FunctionName string + RouterGroupName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 InitCustomerRouter 注入", + fields: fields{ + Type: TypePackageInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/router"`, + AppName: "RouterGroupApp", + GroupName: "Example", + ModuleName: "exampleRouter", + PackageName: "router", + FunctionName: "InitCustomerRouter", + RouterGroupName: "privateGroup", + }, + wantErr: false, + }, + { + name: "测试 InitFileUploadAndDownloadRouter 注入", + fields: fields{ + Type: TypePackageInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/router"`, + AppName: "RouterGroupApp", + GroupName: "Example", + ModuleName: "exampleRouter", + PackageName: "router", + FunctionName: "InitFileUploadAndDownloadRouter", + RouterGroupName: "privateGroup", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageInitializeRouter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + AppName: tt.fields.AppName, + GroupName: tt.fields.GroupName, + ModuleName: tt.fields.ModuleName, + PackageName: tt.fields.PackageName, + FunctionName: tt.fields.FunctionName, + RouterGroupName: tt.fields.RouterGroupName, + LeftRouterGroupName: "privateGroup", + RightRouterGroupName: "publicGroup", + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPackageInitializeRouter_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + AppName string + GroupName string + ModuleName string + PackageName string + FunctionName string + RouterGroupName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + + { + name: "测试 InitCustomerRouter 回滚", + fields: fields{ + Type: TypePackageInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/router"`, + AppName: "RouterGroupApp", + GroupName: "Example", + ModuleName: "exampleRouter", + PackageName: "router", + FunctionName: "InitCustomerRouter", + RouterGroupName: "privateGroup", + }, + wantErr: false, + }, + { + name: "测试 InitFileUploadAndDownloadRouter 回滚", + fields: fields{ + Type: TypePackageInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "router_biz.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/router"`, + AppName: "RouterGroupApp", + GroupName: "Example", + ModuleName: "exampleRouter", + PackageName: "router", + FunctionName: "InitFileUploadAndDownloadRouter", + RouterGroupName: "privateGroup", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageInitializeRouter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + AppName: tt.fields.AppName, + GroupName: tt.fields.GroupName, + ModuleName: tt.fields.ModuleName, + PackageName: tt.fields.PackageName, + FunctionName: tt.fields.FunctionName, + RouterGroupName: tt.fields.RouterGroupName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/package_module_enter.go b/utils/ast/package_module_enter.go new file mode 100644 index 0000000..881fb3f --- /dev/null +++ b/utils/ast/package_module_enter.go @@ -0,0 +1,180 @@ +package ast + +import ( + "go/ast" + "go/token" + "io" +) + +// PackageModuleEnter 模块化入口 +// ModuleName := PackageName.AppName.GroupName.ServiceName +type PackageModuleEnter struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + RelativePath string // 相对路径 + StructName string // 结构体名称 + AppName string // 应用名称 + GroupName string // 分组名称 + ModuleName string // 模块名称 + PackageName string // 包名 + ServiceName string // 服务名称 +} + +func (a *PackageModuleEnter) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PackageModuleEnter) Rollback(file *ast.File) error { + for i := 0; i < len(file.Decls); i++ { + v1, o1 := file.Decls[i].(*ast.GenDecl) + if o1 { + for j := 0; j < len(v1.Specs); j++ { + v2, o2 := v1.Specs[j].(*ast.TypeSpec) + if o2 { + if v2.Name.Name != a.Type.Group() { + continue + } + v3, o3 := v2.Type.(*ast.StructType) + if o3 { + for k := 0; k < len(v3.Fields.List); k++ { + v4, o4 := v3.Fields.List[k].Type.(*ast.Ident) + if o4 && v4.Name == a.StructName { + v3.Fields.List = append(v3.Fields.List[:k], v3.Fields.List[k+1:]...) + } + } + } + continue + } + if a.Type == TypePackageServiceModuleEnter { + continue + } + v3, o3 := v1.Specs[j].(*ast.ValueSpec) + if o3 { + if len(v3.Names) == 1 && v3.Names[0].Name == a.ModuleName { + v1.Specs = append(v1.Specs[:j], v1.Specs[j+1:]...) + } + } + if v1.Tok == token.VAR && len(v1.Specs) == 0 { + _ = NewImport(a.ImportPath).Rollback(file) + if i == len(file.Decls) { + file.Decls = append(file.Decls[:i-1]) + break + } // 空的var(), 如果不删除则会影响的注入变量, 因为识别不到*ast.ValueSpec + file.Decls = append(file.Decls[:i], file.Decls[i+1:]...) + } + } + } + } + return nil +} + +func (a *PackageModuleEnter) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + var hasValue bool + var hasVariables bool + for i := 0; i < len(file.Decls); i++ { + v1, o1 := file.Decls[i].(*ast.GenDecl) + if o1 { + if v1.Tok == token.VAR { + hasVariables = true + } + for j := 0; j < len(v1.Specs); j++ { + if a.Type == TypePackageServiceModuleEnter { + hasValue = true + } + v2, o2 := v1.Specs[j].(*ast.TypeSpec) + if o2 { + if v2.Name.Name != a.Type.Group() { + continue + } + v3, o3 := v2.Type.(*ast.StructType) + if o3 { + var hasStruct bool + for k := 0; k < len(v3.Fields.List); k++ { + v4, o4 := v3.Fields.List[k].Type.(*ast.Ident) + if o4 && v4.Name == a.StructName { + hasStruct = true + } + } + if !hasStruct { + field := &ast.Field{Type: &ast.Ident{Name: a.StructName}} + v3.Fields.List = append(v3.Fields.List, field) + } + } + continue + } + v3, o3 := v1.Specs[j].(*ast.ValueSpec) + if o3 { + hasVariables = true + if len(v3.Names) == 1 && v3.Names[0].Name == a.ModuleName { + hasValue = true + } + } + if v1.Tok == token.VAR && len(v1.Specs) == 0 { + hasVariables = false + } // 说明是空var() + if hasVariables && !hasValue { + spec := &ast.ValueSpec{ + Names: []*ast.Ident{{Name: a.ModuleName}}, + Values: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.AppName}, + }, + Sel: &ast.Ident{Name: a.GroupName}, + }, + Sel: &ast.Ident{Name: a.ServiceName}, + }, + }, + } + v1.Specs = append(v1.Specs, spec) + hasValue = true + } + } + } + } + if !hasValue && !hasVariables { + decl := &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: a.ModuleName}}, + Values: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.AppName}, + }, + Sel: &ast.Ident{Name: a.GroupName}, + }, + Sel: &ast.Ident{Name: a.ServiceName}, + }, + }, + }, + }, + } + file.Decls = append(file.Decls, decl) + } + return nil +} + +func (a *PackageModuleEnter) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/package_module_enter_test.go b/utils/ast/package_module_enter_test.go new file mode 100644 index 0000000..ae0f485 --- /dev/null +++ b/utils/ast/package_module_enter_test.go @@ -0,0 +1,185 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPackageModuleEnter_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + AppName string + GroupName string + ModuleName string + PackageName string + ServiceName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 FileUploadAndDownloadRouter 回滚", + fields: fields{ + Type: TypePackageRouterModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", "example", "enter.go"), + ImportPath: `api "git.echol.cn/loser/xiecheng_server/api/v1"`, + StructName: "FileUploadAndDownloadRouter", + AppName: "ApiGroupApp", + GroupName: "ExampleApiGroup", + ModuleName: "exaFileUploadAndDownloadApi", + PackageName: "api", + ServiceName: "FileUploadAndDownloadApi", + }, + wantErr: false, + }, + { + name: "测试 FileUploadAndDownloadApi 回滚", + fields: fields{ + Type: TypePackageApiModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", "example", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/service"`, + StructName: "FileUploadAndDownloadApi", + AppName: "ServiceGroupApp", + GroupName: "ExampleServiceGroup", + ModuleName: "fileUploadAndDownloadService", + PackageName: "service", + ServiceName: "FileUploadAndDownloadService", + }, + wantErr: false, + }, + { + name: "测试 FileUploadAndDownloadService 回滚", + fields: fields{ + Type: TypePackageServiceModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", "example", "enter.go"), + ImportPath: ``, + StructName: "FileUploadAndDownloadService", + AppName: "", + GroupName: "", + ModuleName: "", + PackageName: "", + ServiceName: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageModuleEnter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + AppName: tt.fields.AppName, + GroupName: tt.fields.GroupName, + ModuleName: tt.fields.ModuleName, + PackageName: tt.fields.PackageName, + ServiceName: tt.fields.ServiceName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPackageModuleEnter_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + AppName string + GroupName string + ModuleName string + PackageName string + ServiceName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 FileUploadAndDownloadRouter 注入", + fields: fields{ + Type: TypePackageRouterModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "router", "example", "enter.go"), + ImportPath: `api "git.echol.cn/loser/xiecheng_server/api/v1"`, + StructName: "FileUploadAndDownloadRouter", + AppName: "ApiGroupApp", + GroupName: "ExampleApiGroup", + ModuleName: "exaFileUploadAndDownloadApi", + PackageName: "api", + ServiceName: "FileUploadAndDownloadApi", + }, + wantErr: false, + }, + { + name: "测试 FileUploadAndDownloadApi 注入", + fields: fields{ + Type: TypePackageApiModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "api", "v1", "example", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/service"`, + StructName: "FileUploadAndDownloadApi", + AppName: "ServiceGroupApp", + GroupName: "ExampleServiceGroup", + ModuleName: "fileUploadAndDownloadService", + PackageName: "service", + ServiceName: "FileUploadAndDownloadService", + }, + wantErr: false, + }, + { + name: "测试 FileUploadAndDownloadService 注入", + fields: fields{ + Type: TypePackageServiceModuleEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "service", "example", "enter.go"), + ImportPath: ``, + StructName: "FileUploadAndDownloadService", + AppName: "", + GroupName: "", + ModuleName: "", + PackageName: "", + ServiceName: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PackageModuleEnter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + AppName: tt.fields.AppName, + GroupName: tt.fields.GroupName, + ModuleName: tt.fields.ModuleName, + PackageName: tt.fields.PackageName, + ServiceName: tt.fields.ServiceName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/plugin_enter.go b/utils/ast/plugin_enter.go new file mode 100644 index 0000000..df5bba4 --- /dev/null +++ b/utils/ast/plugin_enter.go @@ -0,0 +1,167 @@ +package ast + +import ( + "go/ast" + "go/token" + "io" +) + +// PluginEnter 插件化入口 +// ModuleName := PackageName.GroupName.ServiceName +type PluginEnter struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + RelativePath string // 相对路径 + StructName string // 结构体名称 + StructCamelName string // 结构体小驼峰名称 + ModuleName string // 模块名称 + GroupName string // 分组名称 + PackageName string // 包名 + ServiceName string // 服务名称 +} + +func (a *PluginEnter) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PluginEnter) Rollback(file *ast.File) error { + //回滚结构体内内容 + var structType *ast.StructType + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.TypeSpec: + if s, ok := x.Type.(*ast.StructType); ok { + structType = s + for i, field := range x.Type.(*ast.StructType).Fields.List { + if len(field.Names) > 0 && field.Names[0].Name == a.StructName { + s.Fields.List = append(s.Fields.List[:i], s.Fields.List[i+1:]...) + return false + } + } + } + } + return true + }) + + if len(structType.Fields.List) == 0 { + _ = NewImport(a.ImportPath).Rollback(file) + } + + if a.Type == TypePluginServiceEnter { + return nil + } + + //回滚变量内容 + ast.Inspect(file, func(n ast.Node) bool { + genDecl, ok := n.(*ast.GenDecl) + if ok && genDecl.Tok == token.VAR { + for i, spec := range genDecl.Specs { + valueSpec, vsok := spec.(*ast.ValueSpec) + if vsok { + for _, name := range valueSpec.Names { + if name.Name == a.ModuleName { + genDecl.Specs = append(genDecl.Specs[:i], genDecl.Specs[i+1:]...) + return false + } + } + } + } + } + return true + }) + + return nil +} + +func (a *PluginEnter) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + + has := false + hasVar := false + var firstStruct *ast.StructType + var varSpec *ast.GenDecl + //寻找是否存在结构且定位 + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.TypeSpec: + if s, ok := x.Type.(*ast.StructType); ok { + firstStruct = s + for _, field := range x.Type.(*ast.StructType).Fields.List { + if len(field.Names) > 0 && field.Names[0].Name == a.StructName { + has = true + return false + } + } + } + } + return true + }) + + if !has { + field := &ast.Field{ + Names: []*ast.Ident{{Name: a.StructName}}, + Type: &ast.Ident{Name: a.StructCamelName}, + } + firstStruct.Fields.List = append(firstStruct.Fields.List, field) + } + + if a.Type == TypePluginServiceEnter { + return nil + } + + //寻找是否存在变量且定位 + ast.Inspect(file, func(n ast.Node) bool { + genDecl, ok := n.(*ast.GenDecl) + if ok && genDecl.Tok == token.VAR { + for _, spec := range genDecl.Specs { + valueSpec, vsok := spec.(*ast.ValueSpec) + if vsok { + varSpec = genDecl + for _, name := range valueSpec.Names { + if name.Name == a.ModuleName { + hasVar = true + return false + } + } + } + } + } + return true + }) + + if !hasVar { + spec := &ast.ValueSpec{ + Names: []*ast.Ident{{Name: a.ModuleName}}, + Values: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.GroupName}, + }, + Sel: &ast.Ident{Name: a.ServiceName}, + }, + }, + } + varSpec.Specs = append(varSpec.Specs, spec) + } + + return nil +} + +func (a *PluginEnter) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/plugin_enter_test.go b/utils/ast/plugin_enter_test.go new file mode 100644 index 0000000..e4c50f8 --- /dev/null +++ b/utils/ast/plugin_enter_test.go @@ -0,0 +1,200 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPluginEnter_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + StructCamelName string + ModuleName string + GroupName string + PackageName string + ServiceName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 Gva插件UserApi 注入", + fields: fields{ + Type: TypePluginApiEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "api", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/service"`, + StructName: "User", + StructCamelName: "user", + ModuleName: "serviceUser", + GroupName: "Service", + PackageName: "service", + ServiceName: "User", + }, + wantErr: false, + }, + { + name: "测试 Gva插件UserRouter 注入", + fields: fields{ + Type: TypePluginRouterEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "router", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/api"`, + StructName: "User", + StructCamelName: "user", + ModuleName: "userApi", + GroupName: "Api", + PackageName: "api", + ServiceName: "User", + }, + wantErr: false, + }, + { + name: "测试 Gva插件UserService 注入", + fields: fields{ + Type: TypePluginServiceEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "service", "enter.go"), + ImportPath: "", + StructName: "User", + StructCamelName: "user", + ModuleName: "", + GroupName: "", + PackageName: "", + ServiceName: "", + }, + wantErr: false, + }, + { + name: "测试 gva的User 注入", + fields: fields{ + Type: TypePluginServiceEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "service", "enter.go"), + ImportPath: "", + StructName: "User", + StructCamelName: "user", + ModuleName: "", + GroupName: "", + PackageName: "", + ServiceName: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginEnter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + StructCamelName: tt.fields.StructCamelName, + ModuleName: tt.fields.ModuleName, + GroupName: tt.fields.GroupName, + PackageName: tt.fields.PackageName, + ServiceName: tt.fields.ServiceName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPluginEnter_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + StructCamelName string + ModuleName string + GroupName string + PackageName string + ServiceName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 Gva插件UserRouter 回滚", + fields: fields{ + Type: TypePluginRouterEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "router", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/api"`, + StructName: "User", + StructCamelName: "user", + ModuleName: "userApi", + GroupName: "Api", + PackageName: "api", + ServiceName: "User", + }, + wantErr: false, + }, + { + name: "测试 Gva插件UserApi 回滚", + fields: fields{ + Type: TypePluginApiEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "api", "enter.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/service"`, + StructName: "User", + StructCamelName: "user", + ModuleName: "serviceUser", + GroupName: "Service", + PackageName: "service", + ServiceName: "User", + }, + wantErr: false, + }, + { + name: "测试 Gva插件UserService 回滚", + fields: fields{ + Type: TypePluginServiceEnter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "service", "enter.go"), + ImportPath: "", + StructName: "User", + StructCamelName: "user", + ModuleName: "", + GroupName: "", + PackageName: "", + ServiceName: "", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginEnter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + StructCamelName: tt.fields.StructCamelName, + ModuleName: tt.fields.ModuleName, + GroupName: tt.fields.GroupName, + PackageName: tt.fields.PackageName, + ServiceName: tt.fields.ServiceName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/plugin_gen.go b/utils/ast/plugin_gen.go new file mode 100644 index 0000000..ed7d04f --- /dev/null +++ b/utils/ast/plugin_gen.go @@ -0,0 +1,189 @@ +package ast + +import ( + "go/ast" + "go/token" + "io" +) + +type PluginGen struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + RelativePath string // 相对路径 + StructName string // 结构体名称 + PackageName string // 包名 + IsNew bool // 是否使用new关键字 +} + +func (a *PluginGen) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} +func (a *PluginGen) Rollback(file *ast.File) error { + for i := 0; i < len(file.Decls); i++ { + v1, o1 := file.Decls[i].(*ast.FuncDecl) + if o1 { + for j := 0; j < len(v1.Body.List); j++ { + v2, o2 := v1.Body.List[j].(*ast.ExprStmt) + if o2 { + v3, o3 := v2.X.(*ast.CallExpr) + if o3 { + v4, o4 := v3.Fun.(*ast.SelectorExpr) + if o4 { + if v4.Sel.Name != "ApplyBasic" { + continue + } + for k := 0; k < len(v3.Args); k++ { + v5, o5 := v3.Args[k].(*ast.CallExpr) + if o5 { + v6, o6 := v5.Fun.(*ast.Ident) + if o6 { + if v6.Name != "new" { + continue + } + for l := 0; l < len(v5.Args); l++ { + v7, o7 := v5.Args[l].(*ast.SelectorExpr) + if o7 { + v8, o8 := v7.X.(*ast.Ident) + if o8 { + if v8.Name == a.PackageName && v7.Sel.Name == a.StructName { + v3.Args = append(v3.Args[:k], v3.Args[k+1:]...) + continue + } + } + } + } + } + } + if k >= len(v3.Args) { + break + } + v6, o6 := v3.Args[k].(*ast.CompositeLit) + if o6 { + v7, o7 := v6.Type.(*ast.SelectorExpr) + if o7 { + v8, o8 := v7.X.(*ast.Ident) + if o8 { + if v8.Name == a.PackageName && v7.Sel.Name == a.StructName { + v3.Args = append(v3.Args[:k], v3.Args[k+1:]...) + continue + } + } + } + } + } + if len(v3.Args) == 0 { + _ = NewImport(a.ImportPath).Rollback(file) + } + } + } + } + } + } + } + return nil +} + +func (a *PluginGen) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + for i := 0; i < len(file.Decls); i++ { + v1, o1 := file.Decls[i].(*ast.FuncDecl) + if o1 { + for j := 0; j < len(v1.Body.List); j++ { + v2, o2 := v1.Body.List[j].(*ast.ExprStmt) + if o2 { + v3, o3 := v2.X.(*ast.CallExpr) + if o3 { + v4, o4 := v3.Fun.(*ast.SelectorExpr) + if o4 { + if v4.Sel.Name != "ApplyBasic" { + continue + } + var has bool + for k := 0; k < len(v3.Args); k++ { + v5, o5 := v3.Args[k].(*ast.CallExpr) + if o5 { + v6, o6 := v5.Fun.(*ast.Ident) + if o6 { + if v6.Name != "new" { + continue + } + for l := 0; l < len(v5.Args); l++ { + v7, o7 := v5.Args[l].(*ast.SelectorExpr) + if o7 { + v8, o8 := v7.X.(*ast.Ident) + if o8 { + if v8.Name == a.PackageName && v7.Sel.Name == a.StructName { + has = true + break + } + } + } + } + } + } + v6, o6 := v3.Args[k].(*ast.CompositeLit) + if o6 { + v7, o7 := v6.Type.(*ast.SelectorExpr) + if o7 { + v8, o8 := v7.X.(*ast.Ident) + if o8 { + if v8.Name == a.PackageName && v7.Sel.Name == a.StructName { + has = true + break + } + } + } + } + } + if !has { + if a.IsNew { + arg := &ast.CallExpr{ + Fun: &ast.Ident{Name: "\n\t\tnew"}, + Args: []ast.Expr{ + &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.StructName}, + }, + }, + } + v3.Args = append(v3.Args, arg) + v3.Args = append(v3.Args, &ast.BasicLit{ + Kind: token.STRING, + Value: "\n", + }) + break + } + arg := &ast.CompositeLit{ + Type: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.StructName}, + }, + } + v3.Args = append(v3.Args, arg) + } + } + } + } + } + } + } + return nil +} + +func (a *PluginGen) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/plugin_gen_test.go b/utils/ast/plugin_gen_test.go new file mode 100644 index 0000000..17e1c11 --- /dev/null +++ b/utils/ast/plugin_gen_test.go @@ -0,0 +1,127 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPluginGenModel_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + PackageName string + StructName string + IsNew bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 GvaUser 结构体注入", + fields: fields{ + Type: TypePluginGen, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "gen", "main.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + PackageName: "model", + StructName: "User", + IsNew: false, + }, + }, + { + name: "测试 GvaUser 结构体注入", + fields: fields{ + Type: TypePluginGen, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "gen", "main.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + PackageName: "model", + StructName: "User", + IsNew: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginGen{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + PackageName: tt.fields.PackageName, + StructName: tt.fields.StructName, + IsNew: tt.fields.IsNew, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPluginGenModel_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + PackageName string + StructName string + IsNew bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 GvaUser 回滚", + fields: fields{ + Type: TypePluginGen, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "gen", "main.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + PackageName: "model", + StructName: "User", + IsNew: false, + }, + }, + { + name: "测试 GvaUser 回滚", + fields: fields{ + Type: TypePluginGen, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "gen", "main.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + PackageName: "model", + StructName: "User", + IsNew: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginGen{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + PackageName: tt.fields.PackageName, + StructName: tt.fields.StructName, + IsNew: tt.fields.IsNew, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/plugin_initialize_gorm.go b/utils/ast/plugin_initialize_gorm.go new file mode 100644 index 0000000..e342251 --- /dev/null +++ b/utils/ast/plugin_initialize_gorm.go @@ -0,0 +1,111 @@ +package ast + +import ( + "go/ast" + "io" +) + +type PluginInitializeGorm struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + RelativePath string // 相对路径 + StructName string // 结构体名称 + PackageName string // 包名 + IsNew bool // 是否使用new关键字 true: new(PackageName.StructName) false: &PackageName.StructName{} +} + +func (a *PluginInitializeGorm) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PluginInitializeGorm) Rollback(file *ast.File) error { + var needRollBackImport bool + ast.Inspect(file, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + selExpr, seok := callExpr.Fun.(*ast.SelectorExpr) + if !seok || selExpr.Sel.Name != "AutoMigrate" { + return true + } + if len(callExpr.Args) <= 1 { + needRollBackImport = true + } + // 删除指定的参数 + for i, arg := range callExpr.Args { + compLit, cok := arg.(*ast.CompositeLit) + if !cok { + continue + } + + cselExpr, sok := compLit.Type.(*ast.SelectorExpr) + if !sok { + continue + } + + ident, idok := cselExpr.X.(*ast.Ident) + if idok && ident.Name == a.PackageName && cselExpr.Sel.Name == a.StructName { + // 删除参数 + callExpr.Args = append(callExpr.Args[:i], callExpr.Args[i+1:]...) + break + } + } + + return true + }) + + if needRollBackImport { + _ = NewImport(a.ImportPath).Rollback(file) + } + + return nil +} + +func (a *PluginInitializeGorm) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + var call *ast.CallExpr + ast.Inspect(file, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if ok && selExpr.Sel.Name == "AutoMigrate" { + call = callExpr + return false + } + + return true + }) + + arg := &ast.CompositeLit{ + Type: &ast.SelectorExpr{ + X: &ast.Ident{Name: a.PackageName}, + Sel: &ast.Ident{Name: a.StructName}, + }, + } + + call.Args = append(call.Args, arg) + return nil +} + +func (a *PluginInitializeGorm) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/plugin_initialize_gorm_test.go b/utils/ast/plugin_initialize_gorm_test.go new file mode 100644 index 0000000..66c8440 --- /dev/null +++ b/utils/ast/plugin_initialize_gorm_test.go @@ -0,0 +1,138 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPluginInitializeGorm_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + PackageName string + IsNew bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 &model.User{} 注入", + fields: fields{ + Type: TypePluginInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "gorm.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + StructName: "User", + PackageName: "model", + IsNew: false, + }, + }, + { + name: "测试 new(model.ExaCustomer) 注入", + fields: fields{ + Type: TypePluginInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "gorm.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + StructName: "User", + PackageName: "model", + IsNew: true, + }, + }, + { + name: "测试 new(model.SysUsers) 注入", + fields: fields{ + Type: TypePluginInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "gorm.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + StructName: "SysUser", + PackageName: "model", + IsNew: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginInitializeGorm{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + PackageName: tt.fields.PackageName, + IsNew: tt.fields.IsNew, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPluginInitializeGorm_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + StructName string + PackageName string + IsNew bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 &model.User{} 回滚", + fields: fields{ + Type: TypePluginInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "gorm.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + StructName: "User", + PackageName: "model", + IsNew: false, + }, + }, + { + name: "测试 new(model.ExaCustomer) 回滚", + fields: fields{ + Type: TypePluginInitializeGorm, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "gorm.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/model"`, + StructName: "User", + PackageName: "model", + IsNew: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginInitializeGorm{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + StructName: tt.fields.StructName, + PackageName: tt.fields.PackageName, + IsNew: tt.fields.IsNew, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/plugin_initialize_router.go b/utils/ast/plugin_initialize_router.go new file mode 100644 index 0000000..6550789 --- /dev/null +++ b/utils/ast/plugin_initialize_router.go @@ -0,0 +1,124 @@ +package ast + +import ( + "fmt" + "go/ast" + "io" +) + +// PluginInitializeRouter 插件初始化路由 +// PackageName.AppName.GroupName.FunctionName() +type PluginInitializeRouter struct { + Base + Type Type // 类型 + Path string // 文件路径 + ImportPath string // 导包路径 + ImportGlobalPath string // 导包全局变量路径 + ImportMiddlewarePath string // 导包中间件路径 + RelativePath string // 相对路径 + AppName string // 应用名称 + GroupName string // 分组名称 + PackageName string // 包名 + FunctionName string // 函数名 + LeftRouterGroupName string // 左路由分组名称 + RightRouterGroupName string // 右路由分组名称 +} + +func (a *PluginInitializeRouter) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.Path + a.RelativePath = a.Base.RelativePath(a.Path) + return a.Base.Parse(filename, writer) + } + a.Path = a.Base.AbsolutePath(a.RelativePath) + filename = a.Path + } + return a.Base.Parse(filename, writer) +} + +func (a *PluginInitializeRouter) Rollback(file *ast.File) error { + funcDecl := FindFunction(file, "Router") + delI := 0 + routerNum := 0 + for i := len(funcDecl.Body.List) - 1; i >= 0; i-- { + stmt, ok := funcDecl.Body.List[i].(*ast.ExprStmt) + if !ok { + continue + } + + callExpr, ok := stmt.X.(*ast.CallExpr) + if !ok { + continue + } + + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + + ident, ok := selExpr.X.(*ast.SelectorExpr) + + if ok { + if iExpr, ieok := ident.X.(*ast.SelectorExpr); ieok { + if iden, idok := iExpr.X.(*ast.Ident); idok { + if iden.Name == "router" { + routerNum++ + } + } + } + if ident.Sel.Name == a.GroupName && selExpr.Sel.Name == a.FunctionName { + // 删除语句 + delI = i + } + } + } + + funcDecl.Body.List = append(funcDecl.Body.List[:delI], funcDecl.Body.List[delI+1:]...) + + if routerNum <= 1 { + _ = NewImport(a.ImportPath).Rollback(file) + } + + return nil +} + +func (a *PluginInitializeRouter) Injection(file *ast.File) error { + _ = NewImport(a.ImportPath).Injection(file) + funcDecl := FindFunction(file, "Router") + + var exists bool + + ast.Inspect(funcDecl, func(n ast.Node) bool { + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + + ident, ok := selExpr.X.(*ast.SelectorExpr) + if ok && ident.Sel.Name == a.GroupName && selExpr.Sel.Name == a.FunctionName { + exists = true + return false + } + return true + }) + + if !exists { + stmtStr := fmt.Sprintf("%s.%s.%s.%s(%s, %s)", a.PackageName, a.AppName, a.GroupName, a.FunctionName, a.LeftRouterGroupName, a.RightRouterGroupName) + stmt := CreateStmt(stmtStr) + funcDecl.Body.List = append(funcDecl.Body.List, stmt) + } + return nil +} + +func (a *PluginInitializeRouter) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.Path + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/plugin_initialize_router_test.go b/utils/ast/plugin_initialize_router_test.go new file mode 100644 index 0000000..feb83bd --- /dev/null +++ b/utils/ast/plugin_initialize_router_test.go @@ -0,0 +1,155 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPluginInitializeRouter_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + AppName string + GroupName string + PackageName string + FunctionName string + LeftRouterGroupName string + RightRouterGroupName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 Gva插件User 注入", + fields: fields{ + Type: TypePluginInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "router.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/router"`, + AppName: "Router", + GroupName: "User", + PackageName: "router", + FunctionName: "Init", + LeftRouterGroupName: "public", + RightRouterGroupName: "private", + }, + wantErr: false, + }, + { + name: "测试 中文 注入", + fields: fields{ + Type: TypePluginInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "router.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/router"`, + AppName: "Router", + GroupName: "U中文", + PackageName: "router", + FunctionName: "Init", + LeftRouterGroupName: "public", + RightRouterGroupName: "private", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginInitializeRouter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + AppName: tt.fields.AppName, + GroupName: tt.fields.GroupName, + PackageName: tt.fields.PackageName, + FunctionName: tt.fields.FunctionName, + LeftRouterGroupName: tt.fields.LeftRouterGroupName, + RightRouterGroupName: tt.fields.RightRouterGroupName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPluginInitializeRouter_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + ImportPath string + AppName string + GroupName string + PackageName string + FunctionName string + LeftRouterGroupName string + RightRouterGroupName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 Gva插件User 回滚", + fields: fields{ + Type: TypePluginInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "router.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/router"`, + AppName: "Router", + GroupName: "User", + PackageName: "router", + FunctionName: "Init", + LeftRouterGroupName: "public", + RightRouterGroupName: "private", + }, + wantErr: false, + }, + { + name: "测试 中文 注入", + fields: fields{ + Type: TypePluginInitializeRouter, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "initialize", "router.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva/router"`, + AppName: "Router", + GroupName: "U中文", + PackageName: "router", + FunctionName: "Init", + LeftRouterGroupName: "public", + RightRouterGroupName: "private", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &PluginInitializeRouter{ + Type: tt.fields.Type, + Path: tt.fields.Path, + ImportPath: tt.fields.ImportPath, + AppName: tt.fields.AppName, + GroupName: tt.fields.GroupName, + PackageName: tt.fields.PackageName, + FunctionName: tt.fields.FunctionName, + LeftRouterGroupName: tt.fields.LeftRouterGroupName, + RightRouterGroupName: tt.fields.RightRouterGroupName, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/ast/plugin_initialize_v2.go b/utils/ast/plugin_initialize_v2.go new file mode 100644 index 0000000..1befdc6 --- /dev/null +++ b/utils/ast/plugin_initialize_v2.go @@ -0,0 +1,52 @@ +package ast + +import ( + "fmt" + "go/ast" + "io" +) + +type PluginInitializeV2 struct { + Base + Type Type // 类型 + Path string // 文件路径 + PluginPath string // 插件路径 + RelativePath string // 相对路径 + ImportPath string // 导包路径 + StructName string // 结构体名称 + PackageName string // 包名 +} + +func (a *PluginInitializeV2) Parse(filename string, writer io.Writer) (file *ast.File, err error) { + if filename == "" { + if a.RelativePath == "" { + filename = a.PluginPath + a.RelativePath = a.Base.RelativePath(a.PluginPath) + return a.Base.Parse(filename, writer) + } + a.PluginPath = a.Base.AbsolutePath(a.RelativePath) + filename = a.PluginPath + } + return a.Base.Parse(filename, writer) +} + +func (a *PluginInitializeV2) Injection(file *ast.File) error { + if !CheckImport(file, a.ImportPath) { + NewImport(a.ImportPath).Injection(file) + funcDecl := FindFunction(file, "bizPluginV2") + stmt := CreateStmt(fmt.Sprintf("PluginInitV2(engine, %s.Plugin)", a.PackageName)) + funcDecl.Body.List = append(funcDecl.Body.List, stmt) + } + return nil +} + +func (a *PluginInitializeV2) Rollback(file *ast.File) error { + return nil +} + +func (a *PluginInitializeV2) Format(filename string, writer io.Writer, file *ast.File) error { + if filename == "" { + filename = a.PluginPath + } + return a.Base.Format(filename, writer, file) +} diff --git a/utils/ast/plugin_initialize_v2_test.go b/utils/ast/plugin_initialize_v2_test.go new file mode 100644 index 0000000..378871b --- /dev/null +++ b/utils/ast/plugin_initialize_v2_test.go @@ -0,0 +1,100 @@ +package ast + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "path/filepath" + "testing" +) + +func TestPluginInitialize_Injection(t *testing.T) { + type fields struct { + Type Type + Path string + PluginPath string + ImportPath string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 Gva插件 注册注入", + fields: fields{ + Type: TypePluginInitializeV2, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "plugin_biz_v2.go"), + PluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "plugin.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva"`, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := PluginInitializeV2{ + Type: tt.fields.Type, + Path: tt.fields.Path, + PluginPath: tt.fields.PluginPath, + ImportPath: tt.fields.ImportPath, + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Injection(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Injection() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPluginInitialize_Rollback(t *testing.T) { + type fields struct { + Type Type + Path string + PluginPath string + ImportPath string + PluginName string + StructName string + PackageName string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "测试 Gva插件 回滚", + fields: fields{ + Type: TypePluginInitializeV2, + Path: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "initialize", "plugin_biz_v2.go"), + PluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin", "gva", "plugin.go"), + ImportPath: `"git.echol.cn/loser/xiecheng_server/plugin/gva"`, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := PluginInitializeV2{ + Type: tt.fields.Type, + Path: tt.fields.Path, + PluginPath: tt.fields.PluginPath, + ImportPath: tt.fields.ImportPath, + StructName: "Plugin", + PackageName: "gva", + } + file, err := a.Parse(a.Path, nil) + if err != nil { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + } + a.Rollback(file) + err = a.Format(a.Path, nil, file) + if (err != nil) != tt.wantErr { + t.Errorf("Rollback() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/utils/breakpoint_continue.go b/utils/breakpoint_continue.go new file mode 100644 index 0000000..bce6686 --- /dev/null +++ b/utils/breakpoint_continue.go @@ -0,0 +1,112 @@ +package utils + +import ( + "errors" + "os" + "strconv" + "strings" +) + +// 前端传来文件片与当前片为什么文件的第几片 +// 后端拿到以后比较次分片是否上传 或者是否为不完全片 +// 前端发送每片多大 +// 前端告知是否为最后一片且是否完成 + +const ( + breakpointDir = "./breakpointDir/" + finishDir = "./fileDir/" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: BreakPointContinue +//@description: 断点续传 +//@param: content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string +//@return: error, string + +func BreakPointContinue(content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string) (string, error) { + path := breakpointDir + fileMd5 + "/" + err := os.MkdirAll(path, os.ModePerm) + if err != nil { + return path, err + } + pathC, err := makeFileContent(content, fileName, path, contentNumber) + return pathC, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CheckMd5 +//@description: 检查Md5 +//@param: content []byte, chunkMd5 string +//@return: CanUpload bool + +func CheckMd5(content []byte, chunkMd5 string) (CanUpload bool) { + fileMd5 := MD5V(content) + if fileMd5 == chunkMd5 { + return true // 可以继续上传 + } else { + return false // 切片不完整,废弃 + } +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: makeFileContent +//@description: 创建切片内容 +//@param: content []byte, fileName string, FileDir string, contentNumber int +//@return: string, error + +func makeFileContent(content []byte, fileName string, FileDir string, contentNumber int) (string, error) { + if strings.Contains(fileName, "..") || strings.Contains(FileDir, "..") { + return "", errors.New("文件名或路径不合法") + } + path := FileDir + fileName + "_" + strconv.Itoa(contentNumber) + f, err := os.Create(path) + if err != nil { + return path, err + } else { + _, err = f.Write(content) + if err != nil { + return path, err + } + } + defer f.Close() + return path, nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: makeFileContent +//@description: 创建切片文件 +//@param: fileName string, FileMd5 string +//@return: error, string + +func MakeFile(fileName string, FileMd5 string) (string, error) { + rd, err := os.ReadDir(breakpointDir + FileMd5) + if err != nil { + return finishDir + fileName, err + } + _ = os.MkdirAll(finishDir, os.ModePerm) + fd, err := os.OpenFile(finishDir+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644) + if err != nil { + return finishDir + fileName, err + } + defer fd.Close() + for k := range rd { + content, _ := os.ReadFile(breakpointDir + FileMd5 + "/" + fileName + "_" + strconv.Itoa(k)) + _, err = fd.Write(content) + if err != nil { + _ = os.Remove(finishDir + fileName) + return finishDir + fileName, err + } + } + return finishDir + fileName, nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: RemoveChunk +//@description: 移除切片 +//@param: FileMd5 string +//@return: error + +func RemoveChunk(FileMd5 string) error { + err := os.RemoveAll(breakpointDir + FileMd5) + return err +} diff --git a/utils/captcha/redis.go b/utils/captcha/redis.go new file mode 100644 index 0000000..2872f92 --- /dev/null +++ b/utils/captcha/redis.go @@ -0,0 +1,60 @@ +package captcha + +import ( + "context" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "github.com/mojocn/base64Captcha" + "go.uber.org/zap" +) + +func NewDefaultRedisStore() *RedisStore { + return &RedisStore{ + Expiration: time.Second * 180, + PreKey: "CAPTCHA_", + Context: context.TODO(), + } +} + +type RedisStore struct { + Expiration time.Duration + PreKey string + Context context.Context +} + +func (rs *RedisStore) UseWithCtx(ctx context.Context) base64Captcha.Store { + rs.Context = ctx + return rs +} + +func (rs *RedisStore) Set(id string, value string) error { + err := global.GVA_REDIS.Set(rs.Context, rs.PreKey+id, value, rs.Expiration).Err() + if err != nil { + global.GVA_LOG.Error("RedisStoreSetError!", zap.Error(err)) + return err + } + return nil +} + +func (rs *RedisStore) Get(key string, clear bool) string { + val, err := global.GVA_REDIS.Get(rs.Context, key).Result() + if err != nil { + global.GVA_LOG.Error("RedisStoreGetError!", zap.Error(err)) + return "" + } + if clear { + err := global.GVA_REDIS.Del(rs.Context, key).Err() + if err != nil { + global.GVA_LOG.Error("RedisStoreClearError!", zap.Error(err)) + return "" + } + } + return val +} + +func (rs *RedisStore) Verify(id, answer string, clear bool) bool { + key := rs.PreKey + id + v := rs.Get(key, clear) + return v == answer +} diff --git a/utils/claims.go b/utils/claims.go new file mode 100644 index 0000000..69b0822 --- /dev/null +++ b/utils/claims.go @@ -0,0 +1,148 @@ +package utils + +import ( + "net" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system" + systemReq "git.echol.cn/loser/xiecheng_server/model/system/request" + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func ClearToken(c *gin.Context) { + // 增加cookie x-token 向来源的web添加 + host, _, err := net.SplitHostPort(c.Request.Host) + if err != nil { + host = c.Request.Host + } + + if net.ParseIP(host) != nil { + c.SetCookie("x-token", "", -1, "/", "", false, false) + } else { + c.SetCookie("x-token", "", -1, "/", host, false, false) + } +} + +func SetToken(c *gin.Context, token string, maxAge int) { + // 增加cookie x-token 向来源的web添加 + host, _, err := net.SplitHostPort(c.Request.Host) + if err != nil { + host = c.Request.Host + } + + if net.ParseIP(host) != nil { + c.SetCookie("x-token", token, maxAge, "/", "", false, false) + } else { + c.SetCookie("x-token", token, maxAge, "/", host, false, false) + } +} + +func GetToken(c *gin.Context) string { + token := c.Request.Header.Get("x-token") + if token == "" { + j := NewJWT() + token, _ = c.Cookie("x-token") + claims, err := j.ParseToken(token) + if err != nil { + global.GVA_LOG.Error("重新写入cookie token失败,未能成功解析token,请检查请求头是否存在x-token且claims是否为规定结构") + return token + } + SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60)) + } + return token +} + +func GetClaims(c *gin.Context) (*systemReq.CustomClaims, error) { + token := GetToken(c) + j := NewJWT() + claims, err := j.ParseToken(token) + if err != nil { + global.GVA_LOG.Error("从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在x-token且claims是否为规定结构") + } + return claims, err +} + +// GetUserID 从Gin的Context中获取从jwt解析出来的用户ID +func GetUserID(c *gin.Context) uint { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return 0 + } else { + return cl.BaseClaims.ID + } + } else { + waitUse := claims.(*systemReq.CustomClaims) + return waitUse.BaseClaims.ID + } +} + +// GetUserUuid 从Gin的Context中获取从jwt解析出来的用户UUID +func GetUserUuid(c *gin.Context) uuid.UUID { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return uuid.UUID{} + } else { + return cl.UUID + } + } else { + waitUse := claims.(*systemReq.CustomClaims) + return waitUse.UUID + } +} + +// GetUserAuthorityId 从Gin的Context中获取从jwt解析出来的用户角色id +func GetUserAuthorityId(c *gin.Context) uint { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return 0 + } else { + return cl.AuthorityId + } + } else { + waitUse := claims.(*systemReq.CustomClaims) + return waitUse.AuthorityId + } +} + +// GetUserInfo 从Gin的Context中获取从jwt解析出来的用户角色id +func GetUserInfo(c *gin.Context) *systemReq.CustomClaims { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return nil + } else { + return cl + } + } else { + waitUse := claims.(*systemReq.CustomClaims) + return waitUse + } +} + +// GetUserName 从Gin的Context中获取从jwt解析出来的用户名 +func GetUserName(c *gin.Context) string { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return "" + } else { + return cl.Username + } + } else { + waitUse := claims.(*systemReq.CustomClaims) + return waitUse.Username + } +} + +func LoginToken(user system.Login) (token string, claims systemReq.CustomClaims, err error) { + j := NewJWT() + claims = j.CreateClaims(systemReq.BaseClaims{ + UUID: user.GetUUID(), + ID: user.GetUserId(), + NickName: user.GetNickname(), + Username: user.GetUsername(), + AuthorityId: user.GetAuthorityId(), + }) + token, err = j.CreateToken(claims) + return +} diff --git a/utils/directory.go b/utils/directory.go new file mode 100644 index 0000000..18e72ab --- /dev/null +++ b/utils/directory.go @@ -0,0 +1,124 @@ +package utils + +import ( + "errors" + "os" + "path/filepath" + "reflect" + "strings" + + "git.echol.cn/loser/xiecheng_server/global" + "go.uber.org/zap" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: PathExists +//@description: 文件目录是否存在 +//@param: path string +//@return: bool, error + +func PathExists(path string) (bool, error) { + fi, err := os.Stat(path) + if err == nil { + if fi.IsDir() { + return true, nil + } + return false, errors.New("存在同名文件") + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: CreateDir +//@description: 批量创建文件夹 +//@param: dirs ...string +//@return: err error + +func CreateDir(dirs ...string) (err error) { + for _, v := range dirs { + exist, err := PathExists(v) + if err != nil { + return err + } + if !exist { + global.GVA_LOG.Debug("create directory" + v) + if err := os.MkdirAll(v, os.ModePerm); err != nil { + global.GVA_LOG.Error("create directory"+v, zap.Any(" error:", err)) + return err + } + } + } + return err +} + +//@author: [songzhibin97](https://github.com/songzhibin97) +//@function: FileMove +//@description: 文件移动供外部调用 +//@param: src string, dst string(src: 源位置,绝对路径or相对路径, dst: 目标位置,绝对路径or相对路径,必须为文件夹) +//@return: err error + +func FileMove(src string, dst string) (err error) { + if dst == "" { + return nil + } + src, err = filepath.Abs(src) + if err != nil { + return err + } + dst, err = filepath.Abs(dst) + if err != nil { + return err + } + revoke := false + dir := filepath.Dir(dst) +Redirect: + _, err = os.Stat(dir) + if err != nil { + err = os.MkdirAll(dir, 0o755) + if err != nil { + return err + } + if !revoke { + revoke = true + goto Redirect + } + } + return os.Rename(src, dst) +} + +func DeLFile(filePath string) error { + return os.RemoveAll(filePath) +} + +//@author: [songzhibin97](https://github.com/songzhibin97) +//@function: TrimSpace +//@description: 去除结构体空格 +//@param: target interface (target: 目标结构体,传入必须是指针类型) +//@return: null + +func TrimSpace(target interface{}) { + t := reflect.TypeOf(target) + if t.Kind() != reflect.Ptr { + return + } + t = t.Elem() + v := reflect.ValueOf(target).Elem() + for i := 0; i < t.NumField(); i++ { + switch v.Field(i).Kind() { + case reflect.String: + v.Field(i).SetString(strings.TrimSpace(v.Field(i).String())) + } + } +} + +// FileExist 判断文件是否存在 +func FileExist(path string) bool { + fi, err := os.Lstat(path) + if err == nil { + return !fi.IsDir() + } + return !os.IsNotExist(err) +} diff --git a/utils/fmt_plus.go b/utils/fmt_plus.go new file mode 100644 index 0000000..5c7903f --- /dev/null +++ b/utils/fmt_plus.go @@ -0,0 +1,108 @@ +package utils + +import ( + "fmt" + "git.echol.cn/loser/xiecheng_server/model/common" + "math/rand" + "reflect" + "strings" +) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: StructToMap +//@description: 利用反射将结构体转化为map +//@param: obj interface{} +//@return: map[string]interface{} + +func StructToMap(obj interface{}) map[string]interface{} { + obj1 := reflect.TypeOf(obj) + obj2 := reflect.ValueOf(obj) + + data := make(map[string]interface{}) + for i := 0; i < obj1.NumField(); i++ { + if obj1.Field(i).Tag.Get("mapstructure") != "" { + data[obj1.Field(i).Tag.Get("mapstructure")] = obj2.Field(i).Interface() + } else { + data[obj1.Field(i).Name] = obj2.Field(i).Interface() + } + } + return data +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: ArrayToString +//@description: 将数组格式化为字符串 +//@param: array []interface{} +//@return: string + +func ArrayToString(array []interface{}) string { + return strings.Replace(strings.Trim(fmt.Sprint(array), "[]"), " ", ",", -1) +} + +func Pointer[T any](in T) (out *T) { + return &in +} + +func FirstUpper(s string) string { + if s == "" { + return "" + } + return strings.ToUpper(s[:1]) + s[1:] +} + +func FirstLower(s string) string { + if s == "" { + return "" + } + return strings.ToLower(s[:1]) + s[1:] +} + +// MaheHump 将字符串转换为驼峰命名 +func MaheHump(s string) string { + words := strings.Split(s, "-") + + for i := 1; i < len(words); i++ { + words[i] = strings.Title(words[i]) + } + + return strings.Join(words, "") +} + +// RandomString 随机字符串 +func RandomString(n int) string { + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b := make([]rune, n) + for i := range b { + b[i] = letters[RandomInt(0, len(letters))] + } + return string(b) +} + +func RandomInt(min, max int) int { + return min + rand.Intn(max-min) +} + +// BuildTree 用于构建一个树形结构 +func BuildTree[T common.TreeNode[T]](nodes []T) []T { + nodeMap := make(map[int]T) + // 创建一个基本map + for i := range nodes { + nodeMap[nodes[i].GetID()] = nodes[i] + } + + for i := range nodes { + if nodes[i].GetParentID() != 0 { + parent := nodeMap[nodes[i].GetParentID()] + parent.SetChildren(nodes[i]) + } + } + + var rootNodes []T + + for i := range nodeMap { + if nodeMap[i].GetParentID() == 0 { + rootNodes = append(rootNodes, nodeMap[i]) + } + } + return rootNodes +} diff --git a/utils/hash.go b/utils/hash.go new file mode 100644 index 0000000..9c3564b --- /dev/null +++ b/utils/hash.go @@ -0,0 +1,31 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" + "golang.org/x/crypto/bcrypt" +) + +// BcryptHash 使用 bcrypt 对密码进行加密 +func BcryptHash(password string) string { + bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(bytes) +} + +// BcryptCheck 对比明文密码和数据库的哈希值 +func BcryptCheck(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: MD5V +//@description: md5加密 +//@param: str []byte +//@return: string + +func MD5V(str []byte, b ...byte) string { + h := md5.New() + h.Write(str) + return hex.EncodeToString(h.Sum(b)) +} diff --git a/utils/human_duration.go b/utils/human_duration.go new file mode 100644 index 0000000..0cdb055 --- /dev/null +++ b/utils/human_duration.go @@ -0,0 +1,29 @@ +package utils + +import ( + "strconv" + "strings" + "time" +) + +func ParseDuration(d string) (time.Duration, error) { + d = strings.TrimSpace(d) + dr, err := time.ParseDuration(d) + if err == nil { + return dr, nil + } + if strings.Contains(d, "d") { + index := strings.Index(d, "d") + + hour, _ := strconv.Atoi(d[:index]) + dr = time.Hour * 24 * time.Duration(hour) + ndr, err := time.ParseDuration(d[index+1:]) + if err != nil { + return dr, nil + } + return dr + ndr, nil + } + + dv, err := strconv.ParseInt(d, 10, 64) + return time.Duration(dv), err +} diff --git a/utils/human_duration_test.go b/utils/human_duration_test.go new file mode 100644 index 0000000..8a5294b --- /dev/null +++ b/utils/human_duration_test.go @@ -0,0 +1,49 @@ +package utils + +import ( + "testing" + "time" +) + +func TestParseDuration(t *testing.T) { + type args struct { + d string + } + tests := []struct { + name string + args args + want time.Duration + wantErr bool + }{ + { + name: "5h20m", + args: args{"5h20m"}, + want: time.Hour*5 + 20*time.Minute, + wantErr: false, + }, + { + name: "1d5h20m", + args: args{"1d5h20m"}, + want: 24*time.Hour + time.Hour*5 + 20*time.Minute, + wantErr: false, + }, + { + name: "1d", + args: args{"1d"}, + want: 24 * time.Hour, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseDuration(tt.args.d) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDuration() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ParseDuration() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/utils/json.go b/utils/json.go new file mode 100644 index 0000000..8c4118c --- /dev/null +++ b/utils/json.go @@ -0,0 +1,34 @@ +package utils + +import ( + "encoding/json" + "strings" +) + +func GetJSONKeys(jsonStr string) (keys []string, err error) { + // 使用json.Decoder,以便在解析过程中记录键的顺序 + dec := json.NewDecoder(strings.NewReader(jsonStr)) + t, err := dec.Token() + if err != nil { + return nil, err + } + // 确保数据是一个对象 + if t != json.Delim('{') { + return nil, err + } + for dec.More() { + t, err = dec.Token() + if err != nil { + return nil, err + } + keys = append(keys, t.(string)) + + // 解析值 + var value interface{} + err = dec.Decode(&value) + if err != nil { + return nil, err + } + } + return keys, nil +} diff --git a/utils/json_test.go b/utils/json_test.go new file mode 100644 index 0000000..f21a679 --- /dev/null +++ b/utils/json_test.go @@ -0,0 +1,53 @@ +package utils + +import ( + "fmt" + "testing" +) + +func TestGetJSONKeys(t *testing.T) { + var jsonStr = ` + { + "Name": "test", + "TableName": "test", + "TemplateID": "test", + "TemplateInfo": "test", + "Limit": 0 +}` + keys, err := GetJSONKeys(jsonStr) + if err != nil { + t.Errorf("GetJSONKeys failed" + err.Error()) + return + } + if len(keys) != 5 { + t.Errorf("GetJSONKeys failed" + err.Error()) + return + } + if keys[0] != "Name" { + t.Errorf("GetJSONKeys failed" + err.Error()) + + return + } + if keys[1] != "TableName" { + t.Errorf("GetJSONKeys failed" + err.Error()) + + return + } + if keys[2] != "TemplateID" { + t.Errorf("GetJSONKeys failed" + err.Error()) + + return + } + if keys[3] != "TemplateInfo" { + t.Errorf("GetJSONKeys failed" + err.Error()) + + return + } + if keys[4] != "Limit" { + t.Errorf("GetJSONKeys failed" + err.Error()) + + return + } + + fmt.Println(keys) +} diff --git a/utils/jwt.go b/utils/jwt.go new file mode 100644 index 0000000..c875378 --- /dev/null +++ b/utils/jwt.go @@ -0,0 +1,87 @@ +package utils + +import ( + "errors" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/model/system/request" + jwt "github.com/golang-jwt/jwt/v5" +) + +type JWT struct { + SigningKey []byte +} + +var ( + TokenValid = errors.New("未知错误") + TokenExpired = errors.New("token已过期") + TokenNotValidYet = errors.New("token尚未激活") + TokenMalformed = errors.New("这不是一个token") + TokenSignatureInvalid = errors.New("无效签名") + TokenInvalid = errors.New("无法处理此token") +) + +func NewJWT() *JWT { + return &JWT{ + []byte(global.GVA_CONFIG.JWT.SigningKey), + } +} + +func (j *JWT) CreateClaims(baseClaims request.BaseClaims) request.CustomClaims { + bf, _ := ParseDuration(global.GVA_CONFIG.JWT.BufferTime) + ep, _ := ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + claims := request.CustomClaims{ + BaseClaims: baseClaims, + BufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失 + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"GVA"}, // 受众 + NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间 + ExpiresAt: jwt.NewNumericDate(time.Now().Add(ep)), // 过期时间 7天 配置文件 + Issuer: global.GVA_CONFIG.JWT.Issuer, // 签名的发行者 + }, + } + return claims +} + +// CreateToken 创建一个token +func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(j.SigningKey) +} + +// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题 +func (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) { + v, err, _ := global.GVA_Concurrency_Control.Do("JWT:"+oldToken, func() (interface{}, error) { + return j.CreateToken(claims) + }) + return v.(string), err +} + +// ParseToken 解析 token +func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) { + token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) { + return j.SigningKey, nil + }) + + if err != nil { + switch { + case errors.Is(err, jwt.ErrTokenExpired): + return nil, TokenExpired + case errors.Is(err, jwt.ErrTokenMalformed): + return nil, TokenMalformed + case errors.Is(err, jwt.ErrTokenSignatureInvalid): + return nil, TokenSignatureInvalid + case errors.Is(err, jwt.ErrTokenNotValidYet): + return nil, TokenNotValidYet + default: + return nil, TokenInvalid + } + } + if token != nil { + if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid { + return claims, nil + } + } + return nil, TokenValid +} diff --git a/utils/plugin/plugin.go b/utils/plugin/plugin.go new file mode 100644 index 0000000..a59d5b5 --- /dev/null +++ b/utils/plugin/plugin.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "github.com/gin-gonic/gin" +) + +const ( + OnlyFuncName = "Plugin" +) + +// Plugin 插件模式接口化 +type Plugin interface { + // Register 注册路由 + Register(group *gin.RouterGroup) + + // RouterPath 用户返回注册路由 + RouterPath() string +} diff --git a/utils/plugin/v2/plugin.go b/utils/plugin/v2/plugin.go new file mode 100644 index 0000000..4dac0ab --- /dev/null +++ b/utils/plugin/v2/plugin.go @@ -0,0 +1,11 @@ +package plugin + +import ( + "github.com/gin-gonic/gin" +) + +// Plugin 插件模式接口化v2 +type Plugin interface { + // Register 注册路由 + Register(group *gin.Engine) +} diff --git a/utils/reload.go b/utils/reload.go new file mode 100644 index 0000000..de5499b --- /dev/null +++ b/utils/reload.go @@ -0,0 +1,18 @@ +package utils + +import ( + "errors" + "os" + "os/exec" + "runtime" + "strconv" +) + +func Reload() error { + if runtime.GOOS == "windows" { + return errors.New("系统不支持") + } + pid := os.Getpid() + cmd := exec.Command("kill", "-1", strconv.Itoa(pid)) + return cmd.Run() +} diff --git a/utils/request/http.go b/utils/request/http.go new file mode 100644 index 0000000..86d0d15 --- /dev/null +++ b/utils/request/http.go @@ -0,0 +1,62 @@ +package request + +import ( + "bytes" + "encoding/json" + "net/http" + "net/url" +) + +func HttpRequest( + urlStr string, + method string, + headers map[string]string, + params map[string]string, + data any) (*http.Response, error) { + // 创建URL + u, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + // 添加查询参数 + query := u.Query() + for k, v := range params { + query.Set(k, v) + } + u.RawQuery = query.Encode() + + // 将数据编码为JSON + buf := new(bytes.Buffer) + if data != nil { + b, err := json.Marshal(data) + if err != nil { + return nil, err + } + buf = bytes.NewBuffer(b) + } + + // 创建请求 + req, err := http.NewRequest(method, u.String(), buf) + + if err != nil { + return nil, err + } + + for k, v := range headers { + req.Header.Set(k, v) + } + + if data != nil { + req.Header.Set("Content-Type", "application/json") + } + + // 发送请求 + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + // 返回响应,让调用者处理 + return resp, nil +} diff --git a/utils/server.go b/utils/server.go new file mode 100644 index 0000000..4463453 --- /dev/null +++ b/utils/server.go @@ -0,0 +1,126 @@ +package utils + +import ( + "git.echol.cn/loser/xiecheng_server/global" + "runtime" + "time" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/mem" +) + +const ( + B = 1 + KB = 1024 * B + MB = 1024 * KB + GB = 1024 * MB +) + +type Server struct { + Os Os `json:"os"` + Cpu Cpu `json:"cpu"` + Ram Ram `json:"ram"` + Disk []Disk `json:"disk"` +} + +type Os struct { + GOOS string `json:"goos"` + NumCPU int `json:"numCpu"` + Compiler string `json:"compiler"` + GoVersion string `json:"goVersion"` + NumGoroutine int `json:"numGoroutine"` +} + +type Cpu struct { + Cpus []float64 `json:"cpus"` + Cores int `json:"cores"` +} + +type Ram struct { + UsedMB int `json:"usedMb"` + TotalMB int `json:"totalMb"` + UsedPercent int `json:"usedPercent"` +} + +type Disk struct { + MountPoint string `json:"mountPoint"` + UsedMB int `json:"usedMb"` + UsedGB int `json:"usedGb"` + TotalMB int `json:"totalMb"` + TotalGB int `json:"totalGb"` + UsedPercent int `json:"usedPercent"` +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: InitCPU +//@description: OS信息 +//@return: o Os, err error + +func InitOS() (o Os) { + o.GOOS = runtime.GOOS + o.NumCPU = runtime.NumCPU() + o.Compiler = runtime.Compiler + o.GoVersion = runtime.Version() + o.NumGoroutine = runtime.NumGoroutine() + return o +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: InitCPU +//@description: CPU信息 +//@return: c Cpu, err error + +func InitCPU() (c Cpu, err error) { + if cores, err := cpu.Counts(false); err != nil { + return c, err + } else { + c.Cores = cores + } + if cpus, err := cpu.Percent(time.Duration(200)*time.Millisecond, true); err != nil { + return c, err + } else { + c.Cpus = cpus + } + return c, nil +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: InitRAM +//@description: RAM信息 +//@return: r Ram, err error + +func InitRAM() (r Ram, err error) { + if u, err := mem.VirtualMemory(); err != nil { + return r, err + } else { + r.UsedMB = int(u.Used) / MB + r.TotalMB = int(u.Total) / MB + r.UsedPercent = int(u.UsedPercent) + } + return r, nil +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@function: InitDisk +//@description: 硬盘信息 +//@return: d Disk, err error + +func InitDisk() (d []Disk, err error) { + for i := range global.GVA_CONFIG.DiskList { + mp := global.GVA_CONFIG.DiskList[i].MountPoint + if u, err := disk.Usage(mp); err != nil { + return d, err + } else { + d = append(d, Disk{ + MountPoint: mp, + UsedMB: int(u.Used) / MB, + UsedGB: int(u.Used) / GB, + TotalMB: int(u.Total) / MB, + TotalGB: int(u.Total) / GB, + UsedPercent: int(u.UsedPercent), + }) + } + } + return d, nil +} diff --git a/utils/timer/timed_task.go b/utils/timer/timed_task.go new file mode 100644 index 0000000..06865d2 --- /dev/null +++ b/utils/timer/timed_task.go @@ -0,0 +1,229 @@ +package timer + +import ( + "github.com/robfig/cron/v3" + "sync" +) + +type Timer interface { + // 寻找所有Cron + FindCronList() map[string]*taskManager + // 添加Task 方法形式以秒的形式加入 + AddTaskByFuncWithSecond(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) // 添加Task Func以秒的形式加入 + // 添加Task 接口形式以秒的形式加入 + AddTaskByJobWithSeconds(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error) + // 通过函数的方法添加任务 + AddTaskByFunc(cronName string, spec string, task func(), taskName string, option ...cron.Option) (cron.EntryID, error) + // 通过接口的方法添加任务 要实现一个带有 Run方法的接口触发 + AddTaskByJob(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error) + // 获取对应taskName的cron 可能会为空 + FindCron(cronName string) (*taskManager, bool) + // 指定cron开始执行 + StartCron(cronName string) + // 指定cron停止执行 + StopCron(cronName string) + // 查找指定cron下的指定task + FindTask(cronName string, taskName string) (*task, bool) + // 根据id删除指定cron下的指定task + RemoveTask(cronName string, id int) + // 根据taskName删除指定cron下的指定task + RemoveTaskByName(cronName string, taskName string) + // 清理掉指定cronName + Clear(cronName string) + // 停止所有的cron + Close() +} + +type task struct { + EntryID cron.EntryID + Spec string + TaskName string +} + +type taskManager struct { + corn *cron.Cron + tasks map[cron.EntryID]*task +} + +// timer 定时任务管理 +type timer struct { + cronList map[string]*taskManager + sync.Mutex +} + +// AddTaskByFunc 通过函数的方法添加任务 +func (t *timer) AddTaskByFunc(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) { + t.Lock() + defer t.Unlock() + if _, ok := t.cronList[cronName]; !ok { + tasks := make(map[cron.EntryID]*task) + t.cronList[cronName] = &taskManager{ + corn: cron.New(option...), + tasks: tasks, + } + } + id, err := t.cronList[cronName].corn.AddFunc(spec, fun) + t.cronList[cronName].corn.Start() + t.cronList[cronName].tasks[id] = &task{ + EntryID: id, + Spec: spec, + TaskName: taskName, + } + return id, err +} + +// AddTaskByFuncWithSecond 通过函数的方法使用WithSeconds添加任务 +func (t *timer) AddTaskByFuncWithSecond(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) { + t.Lock() + defer t.Unlock() + option = append(option, cron.WithSeconds()) + if _, ok := t.cronList[cronName]; !ok { + tasks := make(map[cron.EntryID]*task) + t.cronList[cronName] = &taskManager{ + corn: cron.New(option...), + tasks: tasks, + } + } + id, err := t.cronList[cronName].corn.AddFunc(spec, fun) + t.cronList[cronName].corn.Start() + t.cronList[cronName].tasks[id] = &task{ + EntryID: id, + Spec: spec, + TaskName: taskName, + } + return id, err +} + +// AddTaskByJob 通过接口的方法添加任务 +func (t *timer) AddTaskByJob(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error) { + t.Lock() + defer t.Unlock() + if _, ok := t.cronList[cronName]; !ok { + tasks := make(map[cron.EntryID]*task) + t.cronList[cronName] = &taskManager{ + corn: cron.New(option...), + tasks: tasks, + } + } + id, err := t.cronList[cronName].corn.AddJob(spec, job) + t.cronList[cronName].corn.Start() + t.cronList[cronName].tasks[id] = &task{ + EntryID: id, + Spec: spec, + TaskName: taskName, + } + return id, err +} + +// AddTaskByJobWithSeconds 通过接口的方法添加任务 +func (t *timer) AddTaskByJobWithSeconds(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error) { + t.Lock() + defer t.Unlock() + option = append(option, cron.WithSeconds()) + if _, ok := t.cronList[cronName]; !ok { + tasks := make(map[cron.EntryID]*task) + t.cronList[cronName] = &taskManager{ + corn: cron.New(option...), + tasks: tasks, + } + } + id, err := t.cronList[cronName].corn.AddJob(spec, job) + t.cronList[cronName].corn.Start() + t.cronList[cronName].tasks[id] = &task{ + EntryID: id, + Spec: spec, + TaskName: taskName, + } + return id, err +} + +// FindCron 获取对应cronName的cron 可能会为空 +func (t *timer) FindCron(cronName string) (*taskManager, bool) { + t.Lock() + defer t.Unlock() + v, ok := t.cronList[cronName] + return v, ok +} + +// FindTask 获取对应cronName的cron 可能会为空 +func (t *timer) FindTask(cronName string, taskName string) (*task, bool) { + t.Lock() + defer t.Unlock() + v, ok := t.cronList[cronName] + if !ok { + return nil, ok + } + for _, t2 := range v.tasks { + if t2.TaskName == taskName { + return t2, true + } + } + return nil, false +} + +// FindCronList 获取所有的任务列表 +func (t *timer) FindCronList() map[string]*taskManager { + t.Lock() + defer t.Unlock() + return t.cronList +} + +// StartCron 开始任务 +func (t *timer) StartCron(cronName string) { + t.Lock() + defer t.Unlock() + if v, ok := t.cronList[cronName]; ok { + v.corn.Start() + } +} + +// StopCron 停止任务 +func (t *timer) StopCron(cronName string) { + t.Lock() + defer t.Unlock() + if v, ok := t.cronList[cronName]; ok { + v.corn.Stop() + } +} + +// RemoveTask 从cronName 删除指定任务 +func (t *timer) RemoveTask(cronName string, id int) { + t.Lock() + defer t.Unlock() + if v, ok := t.cronList[cronName]; ok { + v.corn.Remove(cron.EntryID(id)) + delete(v.tasks, cron.EntryID(id)) + } +} + +// RemoveTaskByName 从cronName 使用taskName 删除指定任务 +func (t *timer) RemoveTaskByName(cronName string, taskName string) { + fTask, ok := t.FindTask(cronName, taskName) + if !ok { + return + } + t.RemoveTask(cronName, int(fTask.EntryID)) +} + +// Clear 清除任务 +func (t *timer) Clear(cronName string) { + t.Lock() + defer t.Unlock() + if v, ok := t.cronList[cronName]; ok { + v.corn.Stop() + delete(t.cronList, cronName) + } +} + +// Close 释放资源 +func (t *timer) Close() { + t.Lock() + defer t.Unlock() + for _, v := range t.cronList { + v.corn.Stop() + } +} + +func NewTimerTask() Timer { + return &timer{cronList: make(map[string]*taskManager)} +} diff --git a/utils/timer/timed_task_test.go b/utils/timer/timed_task_test.go new file mode 100644 index 0000000..9f2c02c --- /dev/null +++ b/utils/timer/timed_task_test.go @@ -0,0 +1,72 @@ +package timer + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +var job = mockJob{} + +type mockJob struct{} + +func (job mockJob) Run() { + mockFunc() +} + +func mockFunc() { + time.Sleep(time.Second) + fmt.Println("1s...") +} + +func TestNewTimerTask(t *testing.T) { + tm := NewTimerTask() + _tm := tm.(*timer) + + { + _, err := tm.AddTaskByFunc("func", "@every 1s", mockFunc, "测试mockfunc") + assert.Nil(t, err) + _, ok := _tm.cronList["func"] + if !ok { + t.Error("no find func") + } + } + + { + _, err := tm.AddTaskByJob("job", "@every 1s", job, "测试job mockfunc") + assert.Nil(t, err) + _, ok := _tm.cronList["job"] + if !ok { + t.Error("no find job") + } + } + + { + _, ok := tm.FindCron("func") + if !ok { + t.Error("no find func") + } + _, ok = tm.FindCron("job") + if !ok { + t.Error("no find job") + } + _, ok = tm.FindCron("none") + if ok { + t.Error("find none") + } + } + { + tm.Clear("func") + _, ok := tm.FindCron("func") + if ok { + t.Error("find func") + } + } + { + a := tm.FindCronList() + b, c := tm.FindCron("job") + fmt.Println(a, b, c) + } +} diff --git a/utils/upload/aliyun_oss.go b/utils/upload/aliyun_oss.go new file mode 100644 index 0000000..02154da --- /dev/null +++ b/utils/upload/aliyun_oss.go @@ -0,0 +1,75 @@ +package upload + +import ( + "errors" + "mime/multipart" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "go.uber.org/zap" +) + +type AliyunOSS struct{} + +func (*AliyunOSS) UploadFile(file *multipart.FileHeader) (string, string, error) { + bucket, err := NewBucket() + if err != nil { + global.GVA_LOG.Error("function AliyunOSS.NewBucket() Failed", zap.Any("err", err.Error())) + return "", "", errors.New("function AliyunOSS.NewBucket() Failed, err:" + err.Error()) + } + + // 读取本地文件。 + f, openError := file.Open() + if openError != nil { + global.GVA_LOG.Error("function file.Open() Failed", zap.Any("err", openError.Error())) + return "", "", errors.New("function file.Open() Failed, err:" + openError.Error()) + } + defer f.Close() // 创建文件 defer 关闭 + // 上传阿里云路径 文件名格式 自己可以改 建议保证唯一性 + // yunFileTmpPath := filepath.Join("uploads", time.Now().Format("2006-01-02")) + "/" + file.Filename + yunFileTmpPath := global.GVA_CONFIG.AliyunOSS.BasePath + "/" + "uploads" + "/" + time.Now().Format("2006-01-02") + "/" + file.Filename + + // 上传文件流。 + err = bucket.PutObject(yunFileTmpPath, f) + if err != nil { + global.GVA_LOG.Error("function formUploader.Put() Failed", zap.Any("err", err.Error())) + return "", "", errors.New("function formUploader.Put() Failed, err:" + err.Error()) + } + + return global.GVA_CONFIG.AliyunOSS.BucketUrl + "/" + yunFileTmpPath, yunFileTmpPath, nil +} + +func (*AliyunOSS) DeleteFile(key string) error { + bucket, err := NewBucket() + if err != nil { + global.GVA_LOG.Error("function AliyunOSS.NewBucket() Failed", zap.Any("err", err.Error())) + return errors.New("function AliyunOSS.NewBucket() Failed, err:" + err.Error()) + } + + // 删除单个文件。objectName表示删除OSS文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 + // 如需删除文件夹,请将objectName设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有object删除后才能删除该文件夹。 + err = bucket.DeleteObject(key) + if err != nil { + global.GVA_LOG.Error("function bucketManager.Delete() failed", zap.Any("err", err.Error())) + return errors.New("function bucketManager.Delete() failed, err:" + err.Error()) + } + + return nil +} + +func NewBucket() (*oss.Bucket, error) { + // 创建OSSClient实例。 + client, err := oss.New(global.GVA_CONFIG.AliyunOSS.Endpoint, global.GVA_CONFIG.AliyunOSS.AccessKeyId, global.GVA_CONFIG.AliyunOSS.AccessKeySecret) + if err != nil { + return nil, err + } + + // 获取存储空间。 + bucket, err := client.Bucket(global.GVA_CONFIG.AliyunOSS.BucketName) + if err != nil { + return nil, err + } + + return bucket, nil +} diff --git a/utils/upload/aws_s3.go b/utils/upload/aws_s3.go new file mode 100644 index 0000000..4bf6f32 --- /dev/null +++ b/utils/upload/aws_s3.go @@ -0,0 +1,97 @@ +package upload + +import ( + "errors" + "fmt" + "mime/multipart" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "go.uber.org/zap" +) + +type AwsS3 struct{} + +//@author: [WqyJh](https://github.com/WqyJh) +//@object: *AwsS3 +//@function: UploadFile +//@description: Upload file to Aws S3 using aws-sdk-go. See https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/s3-example-basic-bucket-operations.html#s3-examples-bucket-ops-upload-file-to-bucket +//@param: file *multipart.FileHeader +//@return: string, string, error + +func (*AwsS3) UploadFile(file *multipart.FileHeader) (string, string, error) { + session := newSession() + uploader := s3manager.NewUploader(session) + + fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename) + filename := global.GVA_CONFIG.AwsS3.PathPrefix + "/" + fileKey + f, openError := file.Open() + if openError != nil { + global.GVA_LOG.Error("function file.Open() failed", zap.Any("err", openError.Error())) + return "", "", errors.New("function file.Open() failed, err:" + openError.Error()) + } + defer f.Close() // 创建文件 defer 关闭 + + _, err := uploader.Upload(&s3manager.UploadInput{ + Bucket: aws.String(global.GVA_CONFIG.AwsS3.Bucket), + Key: aws.String(filename), + Body: f, + }) + if err != nil { + global.GVA_LOG.Error("function uploader.Upload() failed", zap.Any("err", err.Error())) + return "", "", err + } + + return global.GVA_CONFIG.AwsS3.BaseURL + "/" + filename, fileKey, nil +} + +//@author: [WqyJh](https://github.com/WqyJh) +//@object: *AwsS3 +//@function: DeleteFile +//@description: Delete file from Aws S3 using aws-sdk-go. See https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/s3-example-basic-bucket-operations.html#s3-examples-bucket-ops-delete-bucket-item +//@param: file *multipart.FileHeader +//@return: string, string, error + +func (*AwsS3) DeleteFile(key string) error { + session := newSession() + svc := s3.New(session) + filename := global.GVA_CONFIG.AwsS3.PathPrefix + "/" + key + bucket := global.GVA_CONFIG.AwsS3.Bucket + + _, err := svc.DeleteObject(&s3.DeleteObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(filename), + }) + if err != nil { + global.GVA_LOG.Error("function svc.DeleteObject() failed", zap.Any("err", err.Error())) + return errors.New("function svc.DeleteObject() failed, err:" + err.Error()) + } + + _ = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(filename), + }) + return nil +} + +// newSession Create S3 session +func newSession() *session.Session { + sess, _ := session.NewSession(&aws.Config{ + Region: aws.String(global.GVA_CONFIG.AwsS3.Region), + Endpoint: aws.String(global.GVA_CONFIG.AwsS3.Endpoint), //minio在这里设置地址,可以兼容 + S3ForcePathStyle: aws.Bool(global.GVA_CONFIG.AwsS3.S3ForcePathStyle), + DisableSSL: aws.Bool(global.GVA_CONFIG.AwsS3.DisableSSL), + Credentials: credentials.NewStaticCredentials( + global.GVA_CONFIG.AwsS3.SecretID, + global.GVA_CONFIG.AwsS3.SecretKey, + "", + ), + }) + return sess +} diff --git a/utils/upload/cloudflare_r2.go b/utils/upload/cloudflare_r2.go new file mode 100644 index 0000000..3f28639 --- /dev/null +++ b/utils/upload/cloudflare_r2.go @@ -0,0 +1,85 @@ +package upload + +import ( + "errors" + "fmt" + "mime/multipart" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "go.uber.org/zap" +) + +type CloudflareR2 struct{} + +func (c *CloudflareR2) UploadFile(file *multipart.FileHeader) (fileUrl string, fileName string, err error) { + session := c.newSession() + client := s3manager.NewUploader(session) + + fileKey := fmt.Sprintf("%d_%s", time.Now().Unix(), file.Filename) + fileName = fmt.Sprintf("%s/%s", global.GVA_CONFIG.CloudflareR2.Path, fileKey) + f, openError := file.Open() + if openError != nil { + global.GVA_LOG.Error("function file.Open() failed", zap.Any("err", openError.Error())) + return "", "", errors.New("function file.Open() failed, err:" + openError.Error()) + } + defer f.Close() // 创建文件 defer 关闭 + + input := &s3manager.UploadInput{ + Bucket: aws.String(global.GVA_CONFIG.CloudflareR2.Bucket), + Key: aws.String(fileName), + Body: f, + } + + _, err = client.Upload(input) + if err != nil { + global.GVA_LOG.Error("function uploader.Upload() failed", zap.Any("err", err.Error())) + return "", "", err + } + + return fmt.Sprintf("%s/%s", global.GVA_CONFIG.CloudflareR2.BaseURL, + fileName), + fileKey, + nil +} + +func (c *CloudflareR2) DeleteFile(key string) error { + session := newSession() + svc := s3.New(session) + filename := global.GVA_CONFIG.CloudflareR2.Path + "/" + key + bucket := global.GVA_CONFIG.CloudflareR2.Bucket + + _, err := svc.DeleteObject(&s3.DeleteObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(filename), + }) + if err != nil { + global.GVA_LOG.Error("function svc.DeleteObject() failed", zap.Any("err", err.Error())) + return errors.New("function svc.DeleteObject() failed, err:" + err.Error()) + } + + _ = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(filename), + }) + return nil +} + +func (*CloudflareR2) newSession() *session.Session { + endpoint := fmt.Sprintf("%s.r2.cloudflarestorage.com", global.GVA_CONFIG.CloudflareR2.AccountID) + + return session.Must(session.NewSession(&aws.Config{ + Region: aws.String("auto"), + Endpoint: aws.String(endpoint), + Credentials: credentials.NewStaticCredentials( + global.GVA_CONFIG.CloudflareR2.AccessKeyID, + global.GVA_CONFIG.CloudflareR2.SecretAccessKey, + "", + ), + })) +} diff --git a/utils/upload/local.go b/utils/upload/local.go new file mode 100644 index 0000000..8bdc33d --- /dev/null +++ b/utils/upload/local.go @@ -0,0 +1,109 @@ +package upload + +import ( + "errors" + "io" + "mime/multipart" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/utils" + "go.uber.org/zap" +) + +var mu sync.Mutex + +type Local struct{} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [ccfish86](https://github.com/ccfish86) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@object: *Local +//@function: UploadFile +//@description: 上传文件 +//@param: file *multipart.FileHeader +//@return: string, string, error + +func (*Local) UploadFile(file *multipart.FileHeader) (string, string, error) { + // 读取文件后缀 + ext := filepath.Ext(file.Filename) + // 读取文件名并加密 + name := strings.TrimSuffix(file.Filename, ext) + name = utils.MD5V([]byte(name)) + // 拼接新文件名 + filename := name + "_" + time.Now().Format("20060102150405") + ext + // 尝试创建此路径 + mkdirErr := os.MkdirAll(global.GVA_CONFIG.Local.StorePath, os.ModePerm) + if mkdirErr != nil { + global.GVA_LOG.Error("function os.MkdirAll() failed", zap.Any("err", mkdirErr.Error())) + return "", "", errors.New("function os.MkdirAll() failed, err:" + mkdirErr.Error()) + } + // 拼接路径和文件名 + p := global.GVA_CONFIG.Local.StorePath + "/" + filename + filepath := global.GVA_CONFIG.Local.Path + "/" + filename + + f, openError := file.Open() // 读取文件 + if openError != nil { + global.GVA_LOG.Error("function file.Open() failed", zap.Any("err", openError.Error())) + return "", "", errors.New("function file.Open() failed, err:" + openError.Error()) + } + defer f.Close() // 创建文件 defer 关闭 + + out, createErr := os.Create(p) + if createErr != nil { + global.GVA_LOG.Error("function os.Create() failed", zap.Any("err", createErr.Error())) + + return "", "", errors.New("function os.Create() failed, err:" + createErr.Error()) + } + defer out.Close() // 创建文件 defer 关闭 + + _, copyErr := io.Copy(out, f) // 传输(拷贝)文件 + if copyErr != nil { + global.GVA_LOG.Error("function io.Copy() failed", zap.Any("err", copyErr.Error())) + return "", "", errors.New("function io.Copy() failed, err:" + copyErr.Error()) + } + return filepath, filename, nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [ccfish86](https://github.com/ccfish86) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@object: *Local +//@function: DeleteFile +//@description: 删除文件 +//@param: key string +//@return: error + +func (*Local) DeleteFile(key string) error { + // 检查 key 是否为空 + if key == "" { + return errors.New("key不能为空") + } + + // 验证 key 是否包含非法字符或尝试访问存储路径之外的文件 + if strings.Contains(key, "..") || strings.ContainsAny(key, `\/:*?"<>|`) { + return errors.New("非法的key") + } + + p := filepath.Join(global.GVA_CONFIG.Local.StorePath, key) + + // 检查文件是否存在 + if _, err := os.Stat(p); os.IsNotExist(err) { + return errors.New("文件不存在") + } + + // 使用文件锁防止并发删除 + mu.Lock() + defer mu.Unlock() + + err := os.Remove(p) + if err != nil { + return errors.New("文件删除失败: " + err.Error()) + } + + return nil +} diff --git a/utils/upload/minio_oss.go b/utils/upload/minio_oss.go new file mode 100644 index 0000000..ff27d0b --- /dev/null +++ b/utils/upload/minio_oss.go @@ -0,0 +1,99 @@ +package upload + +import ( + "bytes" + "context" + "errors" + "io" + "mime/multipart" + "path/filepath" + "strings" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "git.echol.cn/loser/xiecheng_server/utils" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "go.uber.org/zap" +) + +var MinioClient *Minio // 优化性能,但是不支持动态配置 + +type Minio struct { + Client *minio.Client + bucket string +} + +func GetMinio(endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (*Minio, error) { + if MinioClient != nil { + return MinioClient, nil + } + // Initialize minio client object. + minioClient, err := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: useSSL, // Set to true if using https + }) + if err != nil { + return nil, err + } + // 尝试创建bucket + err = minioClient.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{}) + if err != nil { + // Check to see if we already own this bucket (which happens if you run this twice) + exists, errBucketExists := minioClient.BucketExists(context.Background(), bucketName) + if errBucketExists == nil && exists { + // log.Printf("We already own %s\n", bucketName) + } else { + return nil, err + } + } + MinioClient = &Minio{Client: minioClient, bucket: bucketName} + return MinioClient, nil +} + +func (m *Minio) UploadFile(file *multipart.FileHeader) (filePathres, key string, uploadErr error) { + f, openError := file.Open() + // mutipart.File to os.File + if openError != nil { + global.GVA_LOG.Error("function file.Open() Failed", zap.Any("err", openError.Error())) + return "", "", errors.New("function file.Open() Failed, err:" + openError.Error()) + } + + filecontent := bytes.Buffer{} + _, err := io.Copy(&filecontent, f) + if err != nil { + global.GVA_LOG.Error("读取文件失败", zap.Any("err", err.Error())) + return "", "", errors.New("读取文件失败, err:" + err.Error()) + } + f.Close() // 创建文件 defer 关闭 + + // 对文件名进行加密存储 + ext := filepath.Ext(file.Filename) + filename := utils.MD5V([]byte(strings.TrimSuffix(file.Filename, ext))) + ext + if global.GVA_CONFIG.Minio.BasePath == "" { + filePathres = "uploads" + "/" + time.Now().Format("2006-01-02") + "/" + filename + } else { + filePathres = global.GVA_CONFIG.Minio.BasePath + "/" + time.Now().Format("2006-01-02") + "/" + filename + } + + // 设置超时10分钟 + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) + defer cancel() + + // Upload the file with PutObject 大文件自动切换为分片上传 + info, err := m.Client.PutObject(ctx, global.GVA_CONFIG.Minio.BucketName, filePathres, &filecontent, file.Size, minio.PutObjectOptions{ContentType: "application/octet-stream"}) + if err != nil { + global.GVA_LOG.Error("上传文件到minio失败", zap.Any("err", err.Error())) + return "", "", errors.New("上传文件到minio失败, err:" + err.Error()) + } + return global.GVA_CONFIG.Minio.BucketUrl + "/" + info.Key, filePathres, nil +} + +func (m *Minio) DeleteFile(key string) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + // Delete the object from MinIO + err := m.Client.RemoveObject(ctx, m.bucket, key, minio.RemoveObjectOptions{}) + return err +} diff --git a/utils/upload/obs.go b/utils/upload/obs.go new file mode 100644 index 0000000..ba8312d --- /dev/null +++ b/utils/upload/obs.go @@ -0,0 +1,69 @@ +package upload + +import ( + "mime/multipart" + + "git.echol.cn/loser/xiecheng_server/global" + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "github.com/pkg/errors" +) + +var HuaWeiObs = new(Obs) + +type Obs struct{} + +func NewHuaWeiObsClient() (client *obs.ObsClient, err error) { + return obs.New(global.GVA_CONFIG.HuaWeiObs.AccessKey, global.GVA_CONFIG.HuaWeiObs.SecretKey, global.GVA_CONFIG.HuaWeiObs.Endpoint) +} + +func (o *Obs) UploadFile(file *multipart.FileHeader) (string, string, error) { + // var open multipart.File + open, err := file.Open() + if err != nil { + return "", "", err + } + defer open.Close() + filename := file.Filename + input := &obs.PutObjectInput{ + PutObjectBasicInput: obs.PutObjectBasicInput{ + ObjectOperationInput: obs.ObjectOperationInput{ + Bucket: global.GVA_CONFIG.HuaWeiObs.Bucket, + Key: filename, + }, + HttpHeader: obs.HttpHeader{ + ContentType: file.Header.Get("content-type"), + }, + }, + Body: open, + } + + var client *obs.ObsClient + client, err = NewHuaWeiObsClient() + if err != nil { + return "", "", errors.Wrap(err, "获取华为对象存储对象失败!") + } + + _, err = client.PutObject(input) + if err != nil { + return "", "", errors.Wrap(err, "文件上传失败!") + } + filepath := global.GVA_CONFIG.HuaWeiObs.Path + "/" + filename + return filepath, filename, err +} + +func (o *Obs) DeleteFile(key string) error { + client, err := NewHuaWeiObsClient() + if err != nil { + return errors.Wrap(err, "获取华为对象存储对象失败!") + } + input := &obs.DeleteObjectInput{ + Bucket: global.GVA_CONFIG.HuaWeiObs.Bucket, + Key: key, + } + var output *obs.DeleteObjectOutput + output, err = client.DeleteObject(input) + if err != nil { + return errors.Wrapf(err, "删除对象(%s)失败!, output: %v", key, output) + } + return nil +} diff --git a/utils/upload/qiniu.go b/utils/upload/qiniu.go new file mode 100644 index 0000000..f9d79a3 --- /dev/null +++ b/utils/upload/qiniu.go @@ -0,0 +1,96 @@ +package upload + +import ( + "context" + "errors" + "fmt" + "mime/multipart" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + "github.com/qiniu/go-sdk/v7/auth/qbox" + "github.com/qiniu/go-sdk/v7/storage" + "go.uber.org/zap" +) + +type Qiniu struct{} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [ccfish86](https://github.com/ccfish86) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@object: *Qiniu +//@function: UploadFile +//@description: 上传文件 +//@param: file *multipart.FileHeader +//@return: string, string, error + +func (*Qiniu) UploadFile(file *multipart.FileHeader) (string, string, error) { + putPolicy := storage.PutPolicy{Scope: global.GVA_CONFIG.Qiniu.Bucket} + mac := qbox.NewMac(global.GVA_CONFIG.Qiniu.AccessKey, global.GVA_CONFIG.Qiniu.SecretKey) + upToken := putPolicy.UploadToken(mac) + cfg := qiniuConfig() + formUploader := storage.NewFormUploader(cfg) + ret := storage.PutRet{} + putExtra := storage.PutExtra{Params: map[string]string{"x:name": "github logo"}} + + f, openError := file.Open() + if openError != nil { + global.GVA_LOG.Error("function file.Open() failed", zap.Any("err", openError.Error())) + + return "", "", errors.New("function file.Open() failed, err:" + openError.Error()) + } + defer f.Close() // 创建文件 defer 关闭 + fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename) // 文件名格式 自己可以改 建议保证唯一性 + putErr := formUploader.Put(context.Background(), &ret, upToken, fileKey, f, file.Size, &putExtra) + if putErr != nil { + global.GVA_LOG.Error("function formUploader.Put() failed", zap.Any("err", putErr.Error())) + return "", "", errors.New("function formUploader.Put() failed, err:" + putErr.Error()) + } + return global.GVA_CONFIG.Qiniu.ImgPath + "/" + ret.Key, ret.Key, nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@author: [ccfish86](https://github.com/ccfish86) +//@author: [SliverHorn](https://github.com/SliverHorn) +//@object: *Qiniu +//@function: DeleteFile +//@description: 删除文件 +//@param: key string +//@return: error + +func (*Qiniu) DeleteFile(key string) error { + mac := qbox.NewMac(global.GVA_CONFIG.Qiniu.AccessKey, global.GVA_CONFIG.Qiniu.SecretKey) + cfg := qiniuConfig() + bucketManager := storage.NewBucketManager(mac, cfg) + if err := bucketManager.Delete(global.GVA_CONFIG.Qiniu.Bucket, key); err != nil { + global.GVA_LOG.Error("function bucketManager.Delete() failed", zap.Any("err", err.Error())) + return errors.New("function bucketManager.Delete() failed, err:" + err.Error()) + } + return nil +} + +//@author: [SliverHorn](https://github.com/SliverHorn) +//@object: *Qiniu +//@function: qiniuConfig +//@description: 根据配置文件进行返回七牛云的配置 +//@return: *storage.Config + +func qiniuConfig() *storage.Config { + cfg := storage.Config{ + UseHTTPS: global.GVA_CONFIG.Qiniu.UseHTTPS, + UseCdnDomains: global.GVA_CONFIG.Qiniu.UseCdnDomains, + } + switch global.GVA_CONFIG.Qiniu.Zone { // 根据配置文件进行初始化空间对应的机房 + case "ZoneHuadong": + cfg.Zone = &storage.ZoneHuadong + case "ZoneHuabei": + cfg.Zone = &storage.ZoneHuabei + case "ZoneHuanan": + cfg.Zone = &storage.ZoneHuanan + case "ZoneBeimei": + cfg.Zone = &storage.ZoneBeimei + case "ZoneXinjiapo": + cfg.Zone = &storage.ZoneXinjiapo + } + return &cfg +} diff --git a/utils/upload/tencent_cos.go b/utils/upload/tencent_cos.go new file mode 100644 index 0000000..3244985 --- /dev/null +++ b/utils/upload/tencent_cos.go @@ -0,0 +1,61 @@ +package upload + +import ( + "context" + "errors" + "fmt" + "mime/multipart" + "net/http" + "net/url" + "time" + + "git.echol.cn/loser/xiecheng_server/global" + + "github.com/tencentyun/cos-go-sdk-v5" + "go.uber.org/zap" +) + +type TencentCOS struct{} + +// UploadFile upload file to COS +func (*TencentCOS) UploadFile(file *multipart.FileHeader) (string, string, error) { + client := NewClient() + f, openError := file.Open() + if openError != nil { + global.GVA_LOG.Error("function file.Open() failed", zap.Any("err", openError.Error())) + return "", "", errors.New("function file.Open() failed, err:" + openError.Error()) + } + defer f.Close() // 创建文件 defer 关闭 + fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename) + + _, err := client.Object.Put(context.Background(), global.GVA_CONFIG.TencentCOS.PathPrefix+"/"+fileKey, f, nil) + if err != nil { + panic(err) + } + return global.GVA_CONFIG.TencentCOS.BaseURL + "/" + global.GVA_CONFIG.TencentCOS.PathPrefix + "/" + fileKey, fileKey, nil +} + +// DeleteFile delete file form COS +func (*TencentCOS) DeleteFile(key string) error { + client := NewClient() + name := global.GVA_CONFIG.TencentCOS.PathPrefix + "/" + key + _, err := client.Object.Delete(context.Background(), name) + if err != nil { + global.GVA_LOG.Error("function bucketManager.Delete() failed", zap.Any("err", err.Error())) + return errors.New("function bucketManager.Delete() failed, err:" + err.Error()) + } + return nil +} + +// NewClient init COS client +func NewClient() *cos.Client { + urlStr, _ := url.Parse("https://" + global.GVA_CONFIG.TencentCOS.Bucket + ".cos." + global.GVA_CONFIG.TencentCOS.Region + ".myqcloud.com") + baseURL := &cos.BaseURL{BucketURL: urlStr} + client := cos.NewClient(baseURL, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: global.GVA_CONFIG.TencentCOS.SecretID, + SecretKey: global.GVA_CONFIG.TencentCOS.SecretKey, + }, + }) + return client +} diff --git a/utils/upload/upload.go b/utils/upload/upload.go new file mode 100644 index 0000000..9570b18 --- /dev/null +++ b/utils/upload/upload.go @@ -0,0 +1,46 @@ +package upload + +import ( + "mime/multipart" + + "git.echol.cn/loser/xiecheng_server/global" +) + +// OSS 对象存储接口 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [ccfish86](https://github.com/ccfish86) +type OSS interface { + UploadFile(file *multipart.FileHeader) (string, string, error) + DeleteFile(key string) error +} + +// NewOss OSS的实例化方法 +// Author [SliverHorn](https://github.com/SliverHorn) +// Author [ccfish86](https://github.com/ccfish86) +func NewOss() OSS { + switch global.GVA_CONFIG.System.OssType { + case "local": + return &Local{} + case "qiniu": + return &Qiniu{} + case "tencent-cos": + return &TencentCOS{} + case "aliyun-oss": + return &AliyunOSS{} + case "huawei-obs": + return HuaWeiObs + case "aws-s3": + return &AwsS3{} + case "cloudflare-r2": + return &CloudflareR2{} + case "minio": + minioClient, err := GetMinio(global.GVA_CONFIG.Minio.Endpoint, global.GVA_CONFIG.Minio.AccessKeyId, global.GVA_CONFIG.Minio.AccessKeySecret, global.GVA_CONFIG.Minio.BucketName, global.GVA_CONFIG.Minio.UseSSL) + if err != nil { + global.GVA_LOG.Warn("你配置了使用minio,但是初始化失败,请检查minio可用性或安全配置: " + err.Error()) + panic("minio初始化失败") // 建议这样做,用户自己配置了minio,如果报错了还要把服务开起来,使用起来也很危险 + } + return minioClient + default: + return &Local{} + } +} diff --git a/utils/validator.go b/utils/validator.go new file mode 100644 index 0000000..a56dac0 --- /dev/null +++ b/utils/validator.go @@ -0,0 +1,294 @@ +package utils + +import ( + "errors" + "reflect" + "regexp" + "strconv" + "strings" +) + +type Rules map[string][]string + +type RulesMap map[string]Rules + +var CustomizeMap = make(map[string]Rules) + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: RegisterRule +//@description: 注册自定义规则方案建议在路由初始化层即注册 +//@param: key string, rule Rules +//@return: err error + +func RegisterRule(key string, rule Rules) (err error) { + if CustomizeMap[key] != nil { + return errors.New(key + "已注册,无法重复注册") + } else { + CustomizeMap[key] = rule + return nil + } +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: NotEmpty +//@description: 非空 不能为其对应类型的0值 +//@return: string + +func NotEmpty() string { + return "notEmpty" +} + +// @author: [zooqkl](https://github.com/zooqkl) +// @function: RegexpMatch +// @description: 正则校验 校验输入项是否满足正则表达式 +// @param: rule string +// @return: string + +func RegexpMatch(rule string) string { + return "regexp=" + rule +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Lt +//@description: 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Lt(mark string) string { + return "lt=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Le +//@description: 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Le(mark string) string { + return "le=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Eq +//@description: 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Eq(mark string) string { + return "eq=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Ne +//@description: 不等于入参(!=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Ne(mark string) string { + return "ne=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Ge +//@description: 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Ge(mark string) string { + return "ge=" + mark +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Gt +//@description: 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较 +//@param: mark string +//@return: string + +func Gt(mark string) string { + return "gt=" + mark +} + +// +//@author: [piexlmax](https://github.com/piexlmax) +//@function: Verify +//@description: 校验方法 +//@param: st interface{}, roleMap Rules(入参实例,规则map) +//@return: err error + +func Verify(st interface{}, roleMap Rules) (err error) { + compareMap := map[string]bool{ + "lt": true, + "le": true, + "eq": true, + "ne": true, + "ge": true, + "gt": true, + } + + typ := reflect.TypeOf(st) + val := reflect.ValueOf(st) // 获取reflect.Type类型 + + kd := val.Kind() // 获取到st对应的类别 + if kd != reflect.Struct { + return errors.New("expect struct") + } + num := val.NumField() + // 遍历结构体的所有字段 + for i := 0; i < num; i++ { + tagVal := typ.Field(i) + val := val.Field(i) + if tagVal.Type.Kind() == reflect.Struct { + if err = Verify(val.Interface(), roleMap); err != nil { + return err + } + } + if len(roleMap[tagVal.Name]) > 0 { + for _, v := range roleMap[tagVal.Name] { + switch { + case v == "notEmpty": + if isBlank(val) { + return errors.New(tagVal.Name + "值不能为空") + } + case strings.Split(v, "=")[0] == "regexp": + if !regexpMatch(strings.Split(v, "=")[1], val.String()) { + return errors.New(tagVal.Name + "格式校验不通过") + } + case compareMap[strings.Split(v, "=")[0]]: + if !compareVerify(val, v) { + return errors.New(tagVal.Name + "长度或值不在合法范围," + v) + } + } + } + } + } + return nil +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: compareVerify +//@description: 长度和数字的校验方法 根据类型自动校验 +//@param: value reflect.Value, VerifyStr string +//@return: bool + +func compareVerify(value reflect.Value, VerifyStr string) bool { + switch value.Kind() { + case reflect.String: + return compare(len([]rune(value.String())), VerifyStr) + case reflect.Slice, reflect.Array: + return compare(value.Len(), VerifyStr) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return compare(value.Uint(), VerifyStr) + case reflect.Float32, reflect.Float64: + return compare(value.Float(), VerifyStr) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return compare(value.Int(), VerifyStr) + default: + return false + } +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: isBlank +//@description: 非空校验 +//@param: value reflect.Value +//@return: bool + +func isBlank(value reflect.Value) bool { + switch value.Kind() { + case reflect.String, reflect.Slice: + return value.Len() == 0 + case reflect.Bool: + return !value.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return value.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return value.Uint() == 0 + case reflect.Float32, reflect.Float64: + return value.Float() == 0 + case reflect.Interface, reflect.Ptr: + return value.IsNil() + } + return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface()) +} + +//@author: [piexlmax](https://github.com/piexlmax) +//@function: compare +//@description: 比较函数 +//@param: value interface{}, VerifyStr string +//@return: bool + +func compare(value interface{}, VerifyStr string) bool { + VerifyStrArr := strings.Split(VerifyStr, "=") + val := reflect.ValueOf(value) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64) + if VErr != nil { + return false + } + switch { + case VerifyStrArr[0] == "lt": + return val.Int() < VInt + case VerifyStrArr[0] == "le": + return val.Int() <= VInt + case VerifyStrArr[0] == "eq": + return val.Int() == VInt + case VerifyStrArr[0] == "ne": + return val.Int() != VInt + case VerifyStrArr[0] == "ge": + return val.Int() >= VInt + case VerifyStrArr[0] == "gt": + return val.Int() > VInt + default: + return false + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + VInt, VErr := strconv.Atoi(VerifyStrArr[1]) + if VErr != nil { + return false + } + switch { + case VerifyStrArr[0] == "lt": + return val.Uint() < uint64(VInt) + case VerifyStrArr[0] == "le": + return val.Uint() <= uint64(VInt) + case VerifyStrArr[0] == "eq": + return val.Uint() == uint64(VInt) + case VerifyStrArr[0] == "ne": + return val.Uint() != uint64(VInt) + case VerifyStrArr[0] == "ge": + return val.Uint() >= uint64(VInt) + case VerifyStrArr[0] == "gt": + return val.Uint() > uint64(VInt) + default: + return false + } + case reflect.Float32, reflect.Float64: + VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64) + if VErr != nil { + return false + } + switch { + case VerifyStrArr[0] == "lt": + return val.Float() < VFloat + case VerifyStrArr[0] == "le": + return val.Float() <= VFloat + case VerifyStrArr[0] == "eq": + return val.Float() == VFloat + case VerifyStrArr[0] == "ne": + return val.Float() != VFloat + case VerifyStrArr[0] == "ge": + return val.Float() >= VFloat + case VerifyStrArr[0] == "gt": + return val.Float() > VFloat + default: + return false + } + default: + return false + } +} + +func regexpMatch(rule, matchStr string) bool { + return regexp.MustCompile(rule).MatchString(matchStr) +} diff --git a/utils/validator_test.go b/utils/validator_test.go new file mode 100644 index 0000000..d8d71dc --- /dev/null +++ b/utils/validator_test.go @@ -0,0 +1,37 @@ +package utils + +import ( + "git.echol.cn/loser/xiecheng_server/model/common/request" + "testing" +) + +type PageInfoTest struct { + PageInfo request.PageInfo + Name string +} + +func TestVerify(t *testing.T) { + PageInfoVerify := Rules{"Page": {NotEmpty()}, "PageSize": {NotEmpty()}, "Name": {NotEmpty()}} + var testInfo PageInfoTest + testInfo.Name = "test" + testInfo.PageInfo.Page = 0 + testInfo.PageInfo.PageSize = 0 + err := Verify(testInfo, PageInfoVerify) + if err == nil { + t.Error("校验失败,未能捕捉0值") + } + testInfo.Name = "" + testInfo.PageInfo.Page = 1 + testInfo.PageInfo.PageSize = 10 + err = Verify(testInfo, PageInfoVerify) + if err == nil { + t.Error("校验失败,未能正常检测name为空") + } + testInfo.Name = "test" + testInfo.PageInfo.Page = 1 + testInfo.PageInfo.PageSize = 10 + err = Verify(testInfo, PageInfoVerify) + if err != nil { + t.Error("校验失败,未能正常通过检测") + } +} diff --git a/utils/verify.go b/utils/verify.go new file mode 100644 index 0000000..43a8672 --- /dev/null +++ b/utils/verify.go @@ -0,0 +1,19 @@ +package utils + +var ( + IdVerify = Rules{"ID": []string{NotEmpty()}} + ApiVerify = Rules{"Path": {NotEmpty()}, "Description": {NotEmpty()}, "ApiGroup": {NotEmpty()}, "Method": {NotEmpty()}} + MenuVerify = Rules{"Path": {NotEmpty()}, "Name": {NotEmpty()}, "Component": {NotEmpty()}, "Sort": {Ge("0")}} + MenuMetaVerify = Rules{"Title": {NotEmpty()}} + LoginVerify = Rules{"CaptchaId": {NotEmpty()}, "Username": {NotEmpty()}, "Password": {NotEmpty()}} + RegisterVerify = Rules{"Username": {NotEmpty()}, "NickName": {NotEmpty()}, "Password": {NotEmpty()}, "AuthorityId": {NotEmpty()}} + PageInfoVerify = Rules{"Page": {NotEmpty()}, "PageSize": {NotEmpty()}} + CustomerVerify = Rules{"CustomerName": {NotEmpty()}, "CustomerPhoneData": {NotEmpty()}} + AutoCodeVerify = Rules{"Abbreviation": {NotEmpty()}, "StructName": {NotEmpty()}, "PackageName": {NotEmpty()}} + AutoPackageVerify = Rules{"PackageName": {NotEmpty()}} + AuthorityVerify = Rules{"AuthorityId": {NotEmpty()}, "AuthorityName": {NotEmpty()}} + AuthorityIdVerify = Rules{"AuthorityId": {NotEmpty()}} + OldAuthorityVerify = Rules{"OldAuthorityId": {NotEmpty()}} + ChangePasswordVerify = Rules{"Password": {NotEmpty()}, "NewPassword": {NotEmpty()}} + SetUserAuthorityVerify = Rules{"AuthorityId": {NotEmpty()}} +) diff --git a/utils/zip.go b/utils/zip.go new file mode 100644 index 0000000..ef35d10 --- /dev/null +++ b/utils/zip.go @@ -0,0 +1,53 @@ +package utils + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// 解压 +func Unzip(zipFile string, destDir string) ([]string, error) { + zipReader, err := zip.OpenReader(zipFile) + var paths []string + if err != nil { + return []string{}, err + } + defer zipReader.Close() + + for _, f := range zipReader.File { + if strings.Contains(f.Name, "..") { + return []string{}, fmt.Errorf("%s 文件名不合法", f.Name) + } + fpath := filepath.Join(destDir, f.Name) + paths = append(paths, fpath) + if f.FileInfo().IsDir() { + os.MkdirAll(fpath, os.ModePerm) + } else { + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return []string{}, err + } + + inFile, err := f.Open() + if err != nil { + return []string{}, err + } + defer inFile.Close() + + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return []string{}, err + } + defer outFile.Close() + + _, err = io.Copy(outFile, inFile) + if err != nil { + return []string{}, err + } + } + } + return paths, nil +}