Ver Fonte

基础框架

xu há 1 ano atrás
commit
9f5dc6167a
100 ficheiros alterados com 26255 adições e 0 exclusões
  1. 2 0
      .gitattributes
  2. 32 0
      .gitignore
  3. 76 0
      CODE_OF_CONDUCT.md
  4. 19 0
      CONTRIBUTING.md
  5. 201 0
      LICENSE
  6. 78 0
      Makefile
  7. 34 0
      README.md
  8. 99 0
      deploy/docker-compose/docker-compose-dev.yaml
  9. 90 0
      deploy/docker-compose/docker-compose.yaml
  10. 18 0
      deploy/docker/Dockerfile
  11. 19 0
      deploy/docker/entrypoint.sh
  12. 148 0
      deploy/kubernetes/server/gva-server-configmap.yaml
  13. 74 0
      deploy/kubernetes/server/gva-server-deployment.yaml
  14. 21 0
      deploy/kubernetes/server/gva-server-service.yaml
  15. 32 0
      deploy/kubernetes/web/gva-web-configmap.yaml
  16. 51 0
      deploy/kubernetes/web/gva-web-deploymemt.yaml
  17. 18 0
      deploy/kubernetes/web/gva-web-ingress.yaml
  18. 21 0
      deploy/kubernetes/web/gva-web-service.yaml
  19. BIN
      docs/gin-vue-admin.png
  20. 49 0
      gin-vue-admin.code-workspace
  21. 1068 0
      lc_basic_framework.sql
  22. 24 0
      server/Dockerfile
  23. 54 0
      server/README.md
  24. 13 0
      server/api/v1/enter.go
  25. 11 0
      server/api/v1/example/enter.go
  26. 150 0
      server/api/v1/example/exa_breakpoint_continue.go
  27. 110 0
      server/api/v1/example/exa_file_upload_download.go
  28. 30 0
      server/api/v1/system/enter.go
  29. 231 0
      server/api/v1/system/sys_api.go
  30. 177 0
      server/api/v1/system/sys_authority.go
  31. 80 0
      server/api/v1/system/sys_authority_btn.go
  32. 70 0
      server/api/v1/system/sys_captcha.go
  33. 68 0
      server/api/v1/system/sys_casbin.go
  34. 129 0
      server/api/v1/system/sys_dictionary.go
  35. 148 0
      server/api/v1/system/sys_dictionary_detail.go
  36. 33 0
      server/api/v1/system/sys_jwt_blacklist.go
  37. 278 0
      server/api/v1/system/sys_menu.go
  38. 149 0
      server/api/v1/system/sys_operation_record.go
  39. 473 0
      server/api/v1/system/sys_user.go
  40. 220 0
      server/config.docker.yaml
  41. 221 0
      server/config.yaml
  42. 9 0
      server/config/captcha.go
  43. 30 0
      server/config/config.go
  44. 14 0
      server/config/cors.go
  45. 32 0
      server/config/db_list.go
  46. 11 0
      server/config/email.go
  47. 5 0
      server/config/excel.go
  48. 14 0
      server/config/gorm_mssql.go
  49. 13 0
      server/config/gorm_mysql.go
  50. 14 0
      server/config/gorm_oracle.go
  51. 21 0
      server/config/gorm_pgsql.go
  52. 17 0
      server/config/gorm_sqlite.go
  53. 8 0
      server/config/jwt.go
  54. 41 0
      server/config/mongo.go
  55. 10 0
      server/config/oss_aliyun.go
  56. 13 0
      server/config/oss_aws.go
  57. 9 0
      server/config/oss_huawei.go
  58. 6 0
      server/config/oss_local.go
  59. 11 0
      server/config/oss_qiniu.go
  60. 10 0
      server/config/oss_tencent.go
  61. 9 0
      server/config/redis.go
  62. 13 0
      server/config/system.go
  63. 60 0
      server/config/zap.go
  64. 9 0
      server/core/internal/constant.go
  65. 97 0
      server/core/internal/cutter.go
  66. 21 0
      server/core/internal/file_rotatelogs.go
  67. 101 0
      server/core/internal/zap.go
  68. 40 0
      server/core/server.go
  69. 19 0
      server/core/server_other.go
  70. 21 0
      server/core/server_win.go
  71. 71 0
      server/core/viper.go
  72. 28 0
      server/core/zap.go
  73. 62 0
      server/dao/exa_file_upload_download.go
  74. 113 0
      server/dao/sys_api.go
  75. 60 0
      server/dao/sys_authority.go
  76. 62 0
      server/dao/sys_authority_btn.go
  77. 39 0
      server/dao/sys_authority_menu.go
  78. 139 0
      server/dao/sys_base_menu.go
  79. 70 0
      server/dao/sys_dictionary.go
  80. 101 0
      server/dao/sys_dictionary_detail.go
  81. 21 0
      server/dao/sys_jwt_blacklist.go
  82. 10 0
      server/dao/sys_menu_btn.go
  83. 76 0
      server/dao/sys_operation_record.go
  84. 155 0
      server/dao/sys_user.go
  85. 11 0
      server/dao/sys_user_authority.go
  86. 7018 0
      server/docs/docs.go
  87. 6992 0
      server/docs/swagger.json
  88. 4279 0
      server/docs/swagger.yaml
  89. 53 0
      server/global/global.go
  90. 14 0
      server/global/model.go
  91. 143 0
      server/go.mod
  92. 892 0
      server/go.sum
  93. 36 0
      server/initialize/db_list.go
  94. 55 0
      server/initialize/gorm.go
  95. 59 0
      server/initialize/gorm_mssql.go
  96. 55 0
      server/initialize/gorm_mysql.go
  97. 52 0
      server/initialize/gorm_oracle.go
  98. 50 0
      server/initialize/gorm_pgsql.go
  99. 42 0
      server/initialize/gorm_sqlite.go
  100. 0 0
      server/initialize/internal/gorm.go

+ 2 - 0
.gitattributes

@@ -0,0 +1,2 @@
+*.sql linguist-language=GO
+*.html linguist-language=GO

+ 32 - 0
.gitignore

@@ -0,0 +1,32 @@
+.idea/
+/web/node_modules
+/web/dist
+
+.DS_Store
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+/server/log/
+/server/gva
+/server/latest_log
+/server/__debug_bin*
+
+*.iml
+web/.pnpm-debug.log
+web/pnpm-lock.yaml

+ 76 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at 303176530@qq.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq

+ 19 - 0
CONTRIBUTING.md

@@ -0,0 +1,19 @@
+
+### Contributing Guide
+#### 1 Issue Guidelines
+
+- Issues are exclusively for bug reports, feature requests and design-related topics. Other questions may be closed directly. If any questions come up when you are using Element, please hit [Gitter](https://gitter.im/element-en/Lobby) for help.
+
+- Before submitting an issue, please check if similar problems have already been issued.
+
+#### 2 Pull Request Guidelines
+
+- Fork this repository to your own account. Do not create branches here.
+
+- Commit info should be formatted as `[File Name]: Info about commit.` (e.g. `README.md: Fix xxx bug`)
+
+- <font color=red>Make sure PRs are created to `develop` branch instead of `master` branch.</font>
+
+- If your PR fixes a bug, please provide a description about the related bug.
+
+- Merging a PR takes two maintainers: one approves the changes after reviewing, and then the other reviews and merges.

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+    Copyright 2019 北京翻转极光科技有限责任公司
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 78 - 0
Makefile

@@ -0,0 +1,78 @@
+SHELL = /bin/bash
+
+#SCRIPT_DIR         = $(shell pwd)/etc/script
+#请选择golang版本
+BUILD_IMAGE_SERVER  = golang:1.18
+#请选择node版本
+BUILD_IMAGE_WEB     = node:16
+#项目名称
+PROJECT_NAME        = github.com/flipped-aurora/gin-vue-admin/server
+#配置文件目录
+CONFIG_FILE         = config.yaml
+#镜像仓库命名空间
+IMAGE_NAME          = gva
+#镜像地址
+REPOSITORY          = registry.cn-hangzhou.aliyuncs.com/${IMAGE_NAME}
+
+ifeq ($(TAGS_OPT),)
+TAGS_OPT            = latest
+else
+endif
+
+ifeq ($(PLUGIN),)
+PLUGIN            = email
+else
+endif
+
+#容器环境前后端共同打包
+build: build-web build-server
+	docker run --name build-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_SERVER} make build-local
+
+#容器环境打包前端
+build-web:
+	docker run --name build-web-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_WEB} make build-web-local
+
+#容器环境打包后端
+build-server:
+	docker run --name build-server-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_SERVER} make build-server-local
+
+#构建web镜像
+build-image-web:
+	@cd web/ && docker build -t ${REPOSITORY}/web:${TAGS_OPT} .
+
+#构建server镜像
+build-image-server:
+	@cd server/ && docker build -t ${REPOSITORY}/server:${TAGS_OPT} .
+
+#本地环境打包前后端
+build-local:
+	if [ -d "build" ];then rm -rf build; else echo "OK!"; fi \
+	&& if [ -f "/.dockerenv" ];then echo "OK!"; else  make build-web-local && make build-server-local; fi \
+	&& mkdir build && cp -r web/dist build/ && cp server/server build/ && cp -r server/resource build/resource 
+
+#本地环境打包前端
+build-web-local:
+	@cd web/ && if [ -d "dist" ];then rm -rf dist; else echo "OK!"; fi \
+	&& yarn config set registry http://mirrors.cloud.tencent.com/npm/ && yarn install && yarn build
+
+#本地环境打包后端
+build-server-local:
+	@cd server/ && if [ -f "server" ];then rm -rf server; else echo "OK!"; fi \
+	&& 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 -ldflags "-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') -X main.Version=${TAGS_OPT}" -v
+
+#打包前后端二合一镜像
+image: build 
+	docker build -t ${REPOSITORY}/gin-vue-admin:${TAGS_OPT} -f deploy/docker/Dockerfile .
+
+#尝鲜版
+images: build build-image-web build-image-server
+	docker build -t ${REPOSITORY}/all:${TAGS_OPT} -f deploy/docker/Dockerfile .
+	
+#插件快捷打包: make plugin PLUGIN="这里是插件文件夹名称,默认为email"
+plugin:
+	if [ -d ".plugin" ];then rm -rf .plugin ; else echo "OK!"; fi && mkdir -p .plugin/${PLUGIN}/{server/plugin,web/plugin} \
+	&& if [ -d "server/plugin/${PLUGIN}" ];then cp -r server/plugin/${PLUGIN} .plugin/${PLUGIN}/server/plugin/ ; else echo "OK!"; fi \
+	&& if [ -d "web/src/plugin/${PLUGIN}" ];then cp -r web/src/plugin/${PLUGIN} .plugin/${PLUGIN}/web/plugin/ ; else echo "OK!"; fi \
+	&& cd .plugin && zip -r ${PLUGIN}.zip ${PLUGIN} && mv ${PLUGIN}.zip ../ && cd ..

+ 34 - 0
README.md

@@ -0,0 +1,34 @@
+### 项目运行
+
+
+
+#### 前端
+
+```cmd
+npm i
+或
+pnpm i
+或
+cnpm i
+
+npm run server
+```
+
+
+
+#### 后端
+
+
+```
+创建数据库    执行lc_basic_framework.sql
+```
+
+[lc_basic_framework.sql](.\lc_basic_framework.sql) 
+
+
+#### 管理员账户
+
+账号: admin
+
+密码: 123456
+

+ 99 - 0
deploy/docker-compose/docker-compose-dev.yaml

@@ -0,0 +1,99 @@
+version: "3"
+
+# 声明一个名为network的networks,subnet为network的子网地址,默认网关是177.7.0.1
+networks:
+  network:
+    ipam:
+      driver: default
+      config:
+        - subnet: '177.7.0.0/16'
+        
+# 设置mysql,redis持久化保存
+volumes:
+  mysql:
+  redis:
+
+services:
+  web:
+    image: node:16
+    container_name: gva-web
+    hostname: gva-web           #可以通过容器名访问
+    restart: always
+    ports:
+      - '8080:8080'
+    depends_on:
+      - server
+    working_dir: /web       # 如果docker 设置了workdir 则此处不需要设置
+    #若网络不太好,请自行换源,如下
+    #command: bash -c "yarn config set registry https://registry.npm.taobao.org --global && yarn install && yarn serve"
+    command: bash -c "yarn install && yarn serve"
+    volumes:
+      - ../../web:/web
+    networks:
+      network:
+        ipv4_address: 177.7.0.11
+
+  server:
+    image: golang:1.18
+    container_name: gva-server
+    hostname: gva-server
+    restart: always
+    ports:
+      - '8888:8888'
+    depends_on:
+      mysql:
+        condition: service_healthy
+      redis:
+        condition: service_healthy
+    volumes:
+      - ../../server:/server
+    working_dir: /server    # 如果docker 设置了workdir 则此处不需要设置 
+    command: bash -c "go env -w GOPROXY=https://goproxy.cn,direct && go mod tidy && go run main.go"
+    links:
+      - mysql
+      - redis
+    networks:
+      network:
+        ipv4_address: 177.7.0.12
+
+  mysql:
+    image: mysql:8.0.21    # 如果您是 arm64 架构:如 MacOS 的 M1,请修改镜像为 image: mysql/mysql-server:8.0.21
+    container_name: gva-mysql
+    hostname: gva-mysql
+    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #设置utf8字符集
+    restart: always
+    ports:
+      - "13306:3306"  # host物理直接映射端口为13306
+    environment:
+      #MYSQL_ROOT_PASSWORD: 'Aa@6447985' # root管理员用户密码
+      MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
+      MYSQL_USER: 'gva'
+      MYSQL_PASSWORD: 'Aa@6447985'
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "gva", "-pAa@6447985"]
+      interval: 10s
+      timeout: 5s
+      retries: 3
+    volumes:
+      - mysql:/var/lib/mysql
+    networks:
+      network:
+        ipv4_address: 177.7.0.13
+
+  redis:
+    image: redis:6.0.6
+    container_name: gva-redis # 容器名
+    hostname: gva-redis
+    restart: always
+    ports:
+      - '16379:6379'
+    healthcheck:
+      test: ["CMD-SHELL", "redis-cli ping | grep PONG || exit 1"]
+      interval: 10s
+      timeout: 5s
+      retries: 3
+    volumes:
+      - redis:/data
+    networks:
+      network:
+        ipv4_address: 177.7.0.14

+ 90 - 0
deploy/docker-compose/docker-compose.yaml

@@ -0,0 +1,90 @@
+version: "3"
+
+# 声明一个名为network的networks,subnet为network的子网地址,默认网关是177.7.0.1
+networks:
+  network:
+    ipam:
+      driver: default
+      config:
+        - subnet: '177.7.0.0/16'
+        
+# 设置mysql,redis持久化保存
+volumes:
+  mysql:
+  redis:
+  
+services:
+  web:
+    build:
+      context: ../../web
+      dockerfile: ./Dockerfile
+    container_name: gva-web
+    restart: always
+    ports:
+      - '8080:8080'
+    depends_on:
+      - server
+    command: [ 'nginx-debug', '-g', 'daemon off;' ]
+    networks:
+      network:
+        ipv4_address: 177.7.0.11
+
+  server:
+    build:
+      context: ../../server
+      dockerfile: ./Dockerfile
+    container_name: gva-server
+    restart: always
+    ports:
+      - '8888:8888'
+    depends_on:
+      mysql:
+        condition: service_healthy
+      redis:
+        condition: service_healthy
+    links:
+      - mysql
+      - redis
+    networks:
+      network:
+        ipv4_address: 177.7.0.12
+
+  mysql:
+    image: mysql:8.0.21       # 如果您是 arm64 架构:如 MacOS 的 M1,请修改镜像为 image: mysql/mysql-server:8.0.21
+    container_name: gva-mysql
+    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #设置utf8字符集
+    restart: always
+    ports:
+      - "13306:3306"  # host物理直接映射端口为13306
+    environment:
+      #MYSQL_ROOT_PASSWORD: 'Aa@6447985' # root管理员用户密码
+      MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
+      MYSQL_USER: 'gva'
+      MYSQL_PASSWORD: 'Aa@6447985'
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "gva", "-pAa@6447985"]
+      interval: 10s
+      timeout: 5s
+      retries: 3
+    volumes:
+      - mysql:/var/lib/mysql
+    networks:
+      network:
+        ipv4_address: 177.7.0.13
+
+  redis:
+    image: redis:6.0.6
+    container_name: gva-redis # 容器名
+    restart: always
+    ports:
+      - '16379:6379'
+    healthcheck:
+      test: ["CMD-SHELL", "redis-cli ping | grep PONG || exit 1"]
+      interval: 10s
+      timeout: 5s
+      retries: 3
+    volumes:
+      - redis:/data
+    networks:
+      network:
+        ipv4_address: 177.7.0.14

+ 18 - 0
deploy/docker/Dockerfile

@@ -0,0 +1,18 @@
+FROM centos:7
+WORKDIR /opt
+ENV LANG=en_US.utf8
+COPY deploy/docker/entrypoint.sh .
+COPY build/ /usr/share/nginx/html/
+COPY server/config.yaml /usr/share/nginx/html/config.yaml
+COPY web/.docker-compose/nginx/conf.d/nginx.conf /etc/nginx/conf.d/nginx.conf
+RUN set -ex \
+    && echo "LANG=en_US.utf8" > /etc/locale.conf \
+    && echo "net.core.somaxconn = 1024" >> /etc/sysctl.conf \
+    && echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf \
+    && yum -y install epel-release \
+    && yum -y localinstall http://mirrors.ustc.edu.cn/mysql-repo/mysql57-community-release-el7.rpm \
+    && yum -y install mysql-community-server git redis nginx go npm --nogpgcheck && chmod +x ./entrypoint.sh \
+    && npm install -g yarn && go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct \
+    && echo "start" > /dev/null
+EXPOSE 80
+ENTRYPOINT ["./entrypoint.sh"]

+ 19 - 0
deploy/docker/entrypoint.sh

@@ -0,0 +1,19 @@
+#!/bin/bash
+if [ ! -d "/var/lib/mysql/gva" ]; then
+    mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql
+    mysqld --daemonize --user=mysql
+    sleep 5s
+    mysql -uroot -e "create database gva default charset 'utf8' collate 'utf8_bin'; grant all on gva.* to 'root'@'127.0.0.1' identified by '123456'; flush privileges;"
+else
+    mysqld --daemonize --user=mysql
+fi
+redis-server &
+if [ "$1" = "actions" ]; then
+    cd /opt/gva/server && go run main.go &
+    cd /opt/gva/web/ && yarn serve &
+else 
+    /usr/sbin/nginx &
+    cd /usr/share/nginx/html/ && ./server &
+fi
+echo "gva ALL start!!!"
+tail -f /dev/null

+ 148 - 0
deploy/kubernetes/server/gva-server-configmap.yaml

@@ -0,0 +1,148 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: config.yaml
+  annotations:
+    flipped-aurora/gin-vue-admin: backend
+    github: "https://github.com/flipped-aurora/gin-vue-admin.git"
+    app.kubernetes.io/version: 0.0.1
+  labels:
+    app: gva-server
+    version: gva-vue3
+data:
+  config.yaml: |
+    # github.com/flipped-aurora/gin-vue-admin/server Global Configuration
+
+    # jwt configuration
+    jwt:
+      signing-key: 'qmPlus'
+      expires-time: 604800
+      buffer-time: 86400
+
+    # zap logger configuration
+    zap:
+      level: 'info'
+      format: 'console'
+      prefix: '[github.com/flipped-aurora/gin-vue-admin/server]'
+      director: 'log'
+      link-name: 'latest_log'
+      show-line: true
+      encode-level: 'LowercaseColorLevelEncoder'
+      stacktrace-key: 'stacktrace'
+      log-in-console: true
+
+    # redis configuration
+    redis:
+      db: 0
+      addr: '127.0.0.1:6379'
+      password: ''
+
+    # email configuration
+    email:
+      to: 'xxx@qq.com'
+      port: 465
+      from: 'xxx@163.com'
+      host: 'smtp.163.com'
+      is-ssl: true
+      secret: 'xxx'
+      nickname: 'test'
+
+    # casbin configuration
+    casbin:
+      model-path: './resource/rbac_model.conf'
+
+    # system configuration
+    system:
+      env: 'develop'  # 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-multipoint: false
+
+    # captcha configuration
+    captcha:
+      key-long: 6
+      img-width: 240
+      img-height: 80
+
+    # mysql connect configuration
+    # 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://www.github.com/flipped-aurora/gin-vue-admin/server.com/docs/first)
+    mysql:
+      path: ''
+      config: ''
+      db-name: ''
+      username: ''
+      password: ''
+      max-idle-conns: 10
+      max-open-conns: 100
+      log-mode: false
+      log-zap: ""
+
+    # local configuration
+    local:
+      path: 'uploads/file'
+
+    # autocode configuration
+    autocode:
+      transfer-restart: true
+      root: ""
+      server: /server
+      server-api: /api/v1/autocode
+      server-initialize: /initialize
+      server-model: /model/autocode
+      server-request: /model/autocode/request/
+      server-router: /router/autocode
+      server-service: /service/autocode
+      web: /web/src
+      web-api: /api
+      web-flow: /view
+      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: 'xxxxxxxx'
+      secret-key: 'xxxxxxxx'
+      base-url: 'https://gin.vue.admin'
+      path-prefix: 'github.com/flipped-aurora/gin-vue-admin/server'
+
+    # 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: 需要清理的表名
+        # compareField: 需要比较时间的字段
+        # interval: 时间间隔, 具体配置详看 time.ParseDuration() 中字符串表示 且不能为负数
+        # 2160h = 24 * 30 * 3 -> 三个月
+        { tableName: "sys_operation_records" , compareField: "created_at", interval: "2160h" },
+        { tableName: "jwt_blacklists" , compareField: "created_at", interval: "168h" }
+        #{ tableName: "log2" , compareField: "created_at", interval: "2160h" }
+      ]

+ 74 - 0
deploy/kubernetes/server/gva-server-deployment.yaml

@@ -0,0 +1,74 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gva-server
+  annotations:
+    flipped-aurora/gin-vue-admin: backend
+    github: "https://github.com/flipped-aurora/gin-vue-admin.git"
+    app.kubernetes.io/version: 0.0.1
+  labels:
+    app: gva-server
+    version: gva-vue3
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gva-server
+      version: gva-vue3
+  template:
+    metadata:
+      labels:
+        app: gva-server
+        version: gva-vue3
+    spec:
+      containers:
+        - name: gin-vue-admin-container
+          image: registry.cn-hangzhou.aliyuncs.com/gva/server:latest
+          imagePullPolicy: Always
+          ports:
+            - containerPort: 8888
+              name: http
+          volumeMounts:
+            - mountPath: /go/src/github.com/flipped-aurora/gin-vue-admin/server/config.docker.yaml
+              name: config
+              subPath: config.yaml
+            - mountPath: /etc/localtime
+              name: localtime
+          resources:
+            limits:
+              cpu: 1000m
+              memory: 2000Mi
+            requests:
+              cpu: 100m
+              memory: 200Mi
+          livenessProbe:
+            failureThreshold: 1
+            periodSeconds: 5
+            successThreshold: 1
+            tcpSocket:
+              port: 8888
+            timeoutSeconds: 1
+          readinessProbe:
+            failureThreshold: 3
+            initialDelaySeconds: 30
+            periodSeconds: 5
+            successThreshold: 1
+            tcpSocket:
+              port: 8888
+            timeoutSeconds: 1
+          startupProbe:
+            failureThreshold: 40
+            periodSeconds: 5
+            successThreshold: 1
+            tcpSocket:
+              port: 8888
+            timeoutSeconds: 1
+      #imagePullSecrets:
+      #  - name: docker-registry
+      volumes:
+        - name: localtime
+          hostPath:
+            path: /etc/localtime
+        - name: config
+          configMap:
+            name: config.yaml

+ 21 - 0
deploy/kubernetes/server/gva-server-service.yaml

@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: gva-server
+  annotations:
+    flipped-aurora/gin-vue-admin: backend
+    github: "https://github.com/flipped-aurora/gin-vue-admin.git"
+    app.kubernetes.io/version: 0.0.1
+  labels:
+    app: gva-server
+    version: gva-vue3
+spec:
+  selector:
+    app: gva-server
+    version: gva-vue3
+  ports:
+    - port: 8888
+      name: http
+      targetPort: 8888
+  type: ClusterIP
+#  type: NodePort

+ 32 - 0
deploy/kubernetes/web/gva-web-configmap.yaml

@@ -0,0 +1,32 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: my.conf
+data:
+  my.conf: |
+    server {
+        listen       8080;
+        server_name localhost;
+
+        #charset koi8-r;
+        #access_log  logs/host.access.log  main;
+
+        location / {
+            root /usr/share/nginx/html;
+            add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
+            try_files $uri $uri/ /index.html;
+        }
+
+        location /api {
+            proxy_set_header Host $http_host;
+            proxy_set_header  X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            rewrite ^/api/(.*)$ /$1 break;         #重写
+            proxy_pass http://gva-server:8888;     # 设置代理服务器的协议和地址
+         }
+
+        location /api/swagger/index.html {
+            proxy_pass http://gva-server:8888/swagger/index.html;
+         }
+     }

+ 51 - 0
deploy/kubernetes/web/gva-web-deploymemt.yaml

@@ -0,0 +1,51 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gva-web
+  annotations:
+    flipped-aurora/gin-vue-admin: ui
+    github: "https://github.com/flipped-aurora/gin-vue-admin.git"
+    app.kubernetes.io/version: 0.0.1
+  labels:
+    app: gva-web
+    version: gva-vue3
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gva-web
+      version: gva-vue3
+  template:
+    metadata:
+      labels:
+        app: gva-web
+        version: gva-vue3
+    spec:
+      containers:
+        - name: gin-vue-admin-nginx-container
+          image: registry.cn-hangzhou.aliyuncs.com/gva/web:latest
+          imagePullPolicy: Always
+          ports:
+            - containerPort: 8080
+              name: http
+          readinessProbe:
+            tcpSocket:
+              port: 8080
+            initialDelaySeconds: 10
+            periodSeconds: 10
+            successThreshold: 1
+            failureThreshold: 3
+          resources:
+            limits:
+              cpu: 500m
+              memory: 1000Mi
+            requests:
+              cpu: 100m
+              memory: 100Mi
+          volumeMounts:
+            - mountPath: /etc/nginx/conf.d/
+              name: nginx-config
+      volumes:
+        - name: nginx-config
+          configMap:
+            name: my.conf

+ 18 - 0
deploy/kubernetes/web/gva-web-ingress.yaml

@@ -0,0 +1,18 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: gva-ingress
+  annotations:
+    kubernetes.io/ingress.class: "nginx"
+spec:
+  rules:
+  - host: demo.gin-vue-admin.com
+    http:
+      paths:
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: gva-web
+            port:
+              number: 8080

+ 21 - 0
deploy/kubernetes/web/gva-web-service.yaml

@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: gva-web
+  annotations:
+    flipped-aurora/gin-vue-admin: ui
+    github: "https://github.com/flipped-aurora/gin-vue-admin.git"
+    app.kubernetes.io/version: 0.0.1
+  labels:
+    app: gva-web
+    version: gva-vue3
+spec:
+#  type: NodePort
+  type: ClusterIP
+  ports:
+    - name: http
+      port: 8080
+      targetPort: 8080
+  selector:
+    app: gva-web
+    version: gva-vue3

BIN
docs/gin-vue-admin.png


+ 49 - 0
gin-vue-admin.code-workspace

@@ -0,0 +1,49 @@
+{
+  "folders": [
+    {
+      "path": "server",
+      "name": "backend"
+    },
+    {
+      "path": "web",
+      "name": "frontend"
+    },
+    {
+      "path": ".",
+      "name": "root"
+    }
+  ],
+  "settings": {
+    "go.toolsEnvVars": {
+      "GOPROXY": "https://goproxy.cn,direct",
+      "GONOPROXY": "none;"
+    }
+  },
+  "launch": {
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "type": "go",
+        "request": "launch",
+        "name": "Backend",
+        "cwd": "${workspaceFolder:backend}",
+        "program": "${workspaceFolder:backend}/"
+      },
+      {
+        "type": "node",
+        "request": "launch",
+        "cwd": "${workspaceFolder:frontend}",
+        "name": "Frontend",
+        "runtimeExecutable": "npm",
+        "runtimeArgs": ["run-script", "serve"]
+      }
+    ],
+    "compounds": [
+      {
+        "name": "Both (Backend & Frontend)",
+        "configurations": ["Backend", "Frontend"],
+        "stopAll": true
+      }
+    ]
+  }
+}

Diff do ficheiro suprimidas por serem muito extensas
+ 1068 - 0
lc_basic_framework.sql


+ 24 - 0
server/Dockerfile

@@ -0,0 +1,24 @@
+FROM golang:alpine as builder
+
+WORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/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"
+
+WORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/server
+
+COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/server ./
+COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/resource ./resource/
+COPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/config.docker.yaml ./
+
+EXPOSE 8888
+ENTRYPOINT ./server -c config.docker.yaml

+ 54 - 0
server/README.md

@@ -0,0 +1,54 @@
+## server项目结构
+
+```shell
+├── api
+│   └── v1
+├── config
+├── core
+├── docs
+├── global
+├── initialize
+│   └── internal
+├── middleware
+├── model
+│   ├── request
+│   └── response
+├── packfile
+├── resource
+│   ├── excel
+│   ├── page
+│   └── template
+├── router
+├── service
+├── source
+└── utils
+    ├── timer
+    └── upload
+```
+
+| 文件夹       | 说明                    | 描述                        |
+| ------------ | ----------------------- | --------------------------- |
+| `api`        | api层                   | api层 |
+| `--v1`       | v1版本接口              | v1版本接口                  |
+| `config`     | 配置包                  | config.yaml对应的配置结构体 |
+| `core`       | 核心文件                | 核心组件(zap, viper, server)的初始化 |
+| `docs`       | swagger文档目录         | swagger文档目录 |
+| `global`     | 全局对象                | 全局对象 |
+| `initialize` | 初始化 | router,redis,gorm,validator, timer的初始化 |
+| `--internal` | 初始化内部函数 | gorm 的 longger 自定义,在此文件夹的函数只能由 `initialize` 层进行调用 |
+| `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |
+| `model`      | 模型层                  | 模型对应数据表              |
+| `--request`  | 入参结构体              | 接收前端发送到后端的数据。  |
+| `--response` | 出参结构体              | 返回给前端的数据结构体      |
+| `packfile`   | 静态文件打包            | 静态文件打包 |
+| `resource`   | 静态资源文件夹          | 负责存放静态文件                |
+| `--excel` | excel导入导出默认路径 | excel导入导出默认路径 |
+| `--page` | 表单生成器 | 表单生成器 打包后的dist |
+| `--template` | 模板 | 模板文件夹,存放的是代码生成器的模板 |
+| `router`     | 路由层                  | 路由层 |
+| `service`    | service层               | 存放业务逻辑问题 |
+| `source` | source层 | 存放初始化数据的函数 |
+| `utils`      | 工具包                  | 工具函数封装            |
+| `--timer` | timer | 定时器接口封装 |
+| `--upload`      | oss                  | oss接口封装        |
+

+ 13 - 0
server/api/v1/enter.go

@@ -0,0 +1,13 @@
+package v1
+
+import (
+	"server/api/v1/example"
+	"server/api/v1/system"
+)
+
+type ApiGroup struct {
+	SystemApiGroup  system.ApiGroup
+	ExampleApiGroup example.ApiGroup
+}
+
+var ApiGroupApp = new(ApiGroup)

+ 11 - 0
server/api/v1/example/enter.go

@@ -0,0 +1,11 @@
+package example
+
+import "server/service"
+
+type ApiGroup struct {
+	FileUploadAndDownloadApi
+}
+
+var (
+	fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
+)

+ 150 - 0
server/api/v1/example/exa_breakpoint_continue.go

@@ -0,0 +1,150 @@
+package example
+
+import (
+	"fmt"
+	"io"
+	"mime/multipart"
+	"strconv"
+
+	"server/model/example"
+
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/global"
+	"server/model/common/response"
+	exampleRes "server/model/example/response"
+	"server/utils"
+)
+
+// 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 [post]
+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)
+}

+ 110 - 0
server/api/v1/example/exa_file_upload_download.go

@@ -0,0 +1,110 @@
+package example
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	exampleRes "server/model/example/response"
+)
+
+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 dao.ExaFileUploadAndDownload
+	noSave := c.DefaultQuery("noSave", "0")
+	_, header, err := c.Request.FormFile("file")
+	if err != nil {
+		global.GVA_LOG.Error("接收文件失败!", zap.Error(err))
+		response.FailWithMessage("接收文件失败", c)
+		return
+	}
+	file, err = fileUploadAndDownloadService.UploadFile(header, noSave) // 文件上传后拿到文件路径
+	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 dao.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 dao.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.PageInfo                                        true  "页码, 每页大小"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页文件列表,返回包括列表,总数,页码,每页数量"
+// @Router    /fileUploadAndDownload/getFileList [post]
+func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	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)
+}

+ 30 - 0
server/api/v1/system/enter.go

@@ -0,0 +1,30 @@
+package system
+
+import "server/service"
+
+type ApiGroup struct {
+	JwtApi
+	BaseApi
+	CasbinApi
+	SystemApiApi
+	AuthorityApi
+	DictionaryApi
+	AuthorityMenuApi
+	OperationRecordApi
+	DictionaryDetailApi
+	AuthorityBtnApi
+}
+
+var (
+	apiService              = service.ServiceGroupApp.SystemServiceGroup.ApiService
+	jwtService              = service.ServiceGroupApp.SystemServiceGroup.JwtService
+	menuService             = service.ServiceGroupApp.SystemServiceGroup.MenuService
+	userService             = service.ServiceGroupApp.SystemServiceGroup.UserService
+	casbinService           = service.ServiceGroupApp.SystemServiceGroup.CasbinService
+	baseMenuService         = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService
+	authorityService        = service.ServiceGroupApp.SystemServiceGroup.AuthorityService
+	dictionaryService       = service.ServiceGroupApp.SystemServiceGroup.DictionaryService
+	operationRecordService  = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
+	dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
+	authorityBtnService     = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService
+)

+ 231 - 0
server/api/v1/system/sys_api.go

@@ -0,0 +1,231 @@
+package system
+
+import (
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	systemReq "server/model/system/request"
+	systemRes "server/model/system/response"
+	"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 dao.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)
+}
+
+// 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 dao.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 dao.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) {
+	apis, err := apiService.GetAllApis()
+	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)
+}

+ 177 - 0
server/api/v1/system/sys_authority.go

@@ -0,0 +1,177 @@
+package system
+
+import (
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	systemRes "server/model/system/response"
+	"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 dao.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 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)
+}
+
+// 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 dao.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 [post]
+func (a *AuthorityApi) UpdateAuthority(c *gin.Context) {
+	var auth dao.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) {
+	var pageInfo request.PageInfo
+	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 := authorityService.GetAuthorityInfoList(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)
+}
+
+// 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 dao.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
+	}
+	err = authorityService.SetDataAuthority(auth)
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败"+err.Error(), c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}

+ 80 - 0
server/api/v1/system/sys_authority_btn.go

@@ -0,0 +1,80 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/global"
+	"server/model/common/response"
+	"server/model/system/request"
+)
+
+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)
+}

+ 70 - 0
server/api/v1/system/sys_captcha.go

@@ -0,0 +1,70 @@
+package system
+
+import (
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/mojocn/base64Captcha"
+	"go.uber.org/zap"
+	"server/global"
+	"server/model/common/response"
+	systemRes "server/model/system/response"
+)
+
+// 当开启多服务器部署时,替换下面的配置,使用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
+}

+ 68 - 0
server/api/v1/system/sys_casbin.go

@@ -0,0 +1,68 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/global"
+	"server/model/common/response"
+	"server/model/system/request"
+	systemRes "server/model/system/response"
+	"server/utils"
+)
+
+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
+	}
+	err = casbinService.UpdateCasbin(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)
+}

+ 129 - 0
server/api/v1/system/sys_dictionary.go

@@ -0,0 +1,129 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/response"
+)
+
+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 dao.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 dao.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 dao.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 dao.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)
+}

+ 148 - 0
server/api/v1/system/sys_dictionary_detail.go

@@ -0,0 +1,148 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/response"
+	"server/model/system/request"
+	"server/utils"
+)
+
+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 dao.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 dao.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 dao.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 dao.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)
+}

+ 33 - 0
server/api/v1/system/sys_jwt_blacklist.go

@@ -0,0 +1,33 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/response"
+	"server/utils"
+)
+
+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 := dao.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)
+}

+ 278 - 0
server/api/v1/system/sys_menu.go

@@ -0,0 +1,278 @@
+package system
+
+import (
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	systemReq "server/model/system/request"
+	systemRes "server/model/system/response"
+	"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 = []dao.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) {
+	menus, err := menuService.GetBaseMenuTree()
+	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
+	}
+	if err := menuService.AddMenuAuthority(authorityMenu.Menus, 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(&param)
+	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(&param)
+	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 dao.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("删除失败", 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 dao.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) {
+	var pageInfo request.PageInfo
+	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
+	}
+	menuList, total, err := menuService.GetInfoList()
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     menuList,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}

+ 149 - 0
server/api/v1/system/sys_operation_record.go

@@ -0,0 +1,149 @@
+package system
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	systemReq "server/model/system/request"
+	"server/utils"
+)
+
+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 dao.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 dao.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 dao.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)
+}

+ 473 - 0
server/api/v1/system/sys_user.go

@@ -0,0 +1,473 @@
+package system
+
+import (
+	"server/dao"
+	"strconv"
+	"time"
+
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	systemReq "server/model/system/request"
+	systemRes "server/model/system/response"
+	"server/utils"
+
+	"github.com/gin-gonic/gin"
+	"github.com/redis/go-redis/v9"
+	"go.uber.org/zap"
+)
+
+// 框架自带的登录无加密 返回公钥
+
+func (b *BaseApi) ReturnPublicKey(c *gin.Context) {
+	rsa := utils.RSA{}
+	publicKey := rsa.ReturnPublicKey()
+	response.OkWithData(gin.H{"publicKey": publicKey}, c)
+}
+
+// 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)
+
+	//解密
+	rsa := utils.RSA{}
+	l.Password = rsa.Encryption(l.Password)
+
+	if !oc || (l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true)) {
+		u := &dao.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 dao.SysUser) {
+	j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+	claims := j.CreateClaims(systemReq.BaseClaims{
+		UUID:        user.UUID,
+		ID:          user.ID,
+		NickName:    user.NickName,
+		Username:    user.Username,
+		AuthorityId: user.AuthorityId,
+	})
+	token, err := j.CreateToken(claims)
+	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 dao.JwtBlacklist
+		blackJWT.Jwt = jwtStr
+		if err := jwtService.JsonInBlacklist(blackJWT); err != nil {
+			response.FailWithMessage("jwt作废失败", c)
+			return
+		}
+		if err := jwtService.SetRedisJWT(token, user.Username); 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
+	}
+
+	user := &dao.SysUser{Username: r.Username, NickName: r.NickName, Password: r.Password, HeaderImg: r.HeaderImg, AuthorityId: r.AuthorityId, 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 := &dao.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      request.PageInfo                                        true  "页码, 每页大小"
+// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  "分页获取用户列表,返回包括列表,总数,页码,每页数量"
+// @Router    /user/getUserList [post]
+func (b *BaseApi) GetUserList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	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)
+	j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+	claims.AuthorityId = sua.AuthorityId
+	if token, err := j.CreateToken(*claims); err != nil {
+		global.GVA_LOG.Error("修改失败!", zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+	} else {
+		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
+	}
+	err = userService.SetUserAuthorities(sua.ID, sua.AuthorityId)
+	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 user.AuthorityIds != 0 {
+		err = userService.SetUserAuthorities(user.ID, user.AuthorityIds)
+		if err != nil {
+			global.GVA_LOG.Error("设置失败!", zap.Error(err))
+			response.FailWithMessage("设置失败", c)
+			return
+		}
+	}
+	err = userService.SetUserInfo(dao.SysUser{
+		GVA_MODEL: global.GVA_MODEL{
+			ID: user.ID,
+		},
+		NickName:  user.NickName,
+		HeaderImg: user.HeaderImg,
+		Phone:     user.Phone,
+		Email:     user.Email,
+		SideMode:  user.SideMode,
+		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(dao.SysUser{
+		GVA_MODEL: global.GVA_MODEL{
+			ID: user.ID,
+		},
+		NickName:  user.NickName,
+		HeaderImg: user.HeaderImg,
+		Phone:     user.Phone,
+		Email:     user.Email,
+		SideMode:  user.SideMode,
+		Enable:    user.Enable,
+	})
+	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 dao.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)
+}

+ 220 - 0
server/config.docker.yaml

@@ -0,0 +1,220 @@
+# github.com/flipped-aurora/gin-vue-admin/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: "[github.com/flipped-aurora/gin-vue-admin/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: github.com/flipped-aurora/gin-vue-admin/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: github.com/flipped-aurora/gin-vue-admin/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 # 布尔值

+ 221 - 0
server/config.yaml

@@ -0,0 +1,221 @@
+aliyun-oss:
+    endpoint: yourEndpoint
+    access-key-id: yourAccessKeyId
+    access-key-secret: yourAccessKeySecret
+    bucket-name: yourBucketName
+    bucket-url: yourBucketUrl
+    base-path: yourBasePath
+autocode:
+    server-model: /model/%s
+    server-router: /router/%s
+    server: /server
+    server-api: /api/v1/%s
+    server-plug: /plugin/%s
+    server-initialize: /initialize
+    root: E:\GoWorks\src\lc_finance
+    web-table: /view
+    web: /web/src
+    server-service: /service/%s
+    server-request: /model/%s/request/
+    web-api: /api
+    web-form: /view
+    transfer-restart: true
+aws-s3:
+    bucket: xxxxx-10005608
+    region: ap-shanghai
+    endpoint: ""
+    secret-id: your-secret-id
+    secret-key: your-secret-key
+    base-url: https://gin.vue.admin
+    path-prefix: github.com/flipped-aurora/gin-vue-admin/server
+    s3-force-path-style: false
+    disable-ssl: false
+captcha:
+    key-long: 6
+    img-width: 240
+    img-height: 80
+    open-captcha: 0
+    open-captcha-timeout: 3600
+cors:
+    mode: strict-whitelist
+    whitelist:
+        - allow-origin: example1.com
+          allow-methods: POST, GET
+          allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
+          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+          allow-credentials: true
+        - allow-origin: example2.com
+          allow-methods: GET, POST
+          allow-headers: content-type
+          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
+          allow-credentials: true
+db-list:
+    - type: ""
+      alias-name: ""
+      prefix: ""
+      port: ""
+      config: ""
+      db-name: ""
+      username: ""
+      password: ""
+      path: ""
+      engine: ""
+      log-mode: ""
+      max-idle-conns: 10
+      max-open-conns: 100
+      singular: false
+      log-zap: false
+      disable: true
+email:
+    to: xxx@qq.com
+    from: xxx@163.com
+    host: smtp.163.com
+    secret: xxx
+    nickname: test
+    port: 465
+    is-ssl: true
+excel:
+    dir: ./resource/excel/
+hua-wei-obs:
+    path: you-path
+    bucket: you-bucket
+    endpoint: you-endpoint
+    access-key: you-access-key
+    secret-key: you-secret-key
+jwt:
+    signing-key: 23cb7052-8c96-4acf-9136-578347b2d4c8
+    expires-time: 7d
+    buffer-time: 1d
+    issuer: qmPlus
+local:
+    path: uploads/file
+    store-path: uploads/file
+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: ""
+mssql:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+mysql:
+    prefix: ""
+    port: "3306"
+    config: charset=utf8mb4&parseTime=True&loc=Local
+    db-name: lc_finance
+    username: root
+    password: root
+    path: 127.0.0.1
+    engine: ""
+    log-mode: error
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+oracle:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+pgsql:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+qiniu:
+    zone: ZoneHuaDong
+    bucket: ""
+    img-path: ""
+    access-key: ""
+    secret-key: ""
+    use-https: false
+    use-cdn-domains: false
+redis:
+    addr: 127.0.0.1:6379
+    password: ""
+    db: 0
+    useCluster: false
+    clusterAddrs:
+        - 172.21.0.3:7000
+        - 172.21.0.4:7001
+        - 172.21.0.2:7002
+sqlite:
+    prefix: ""
+    port: ""
+    config: ""
+    db-name: ""
+    username: ""
+    password: ""
+    path: ""
+    engine: ""
+    log-mode: ""
+    max-idle-conns: 10
+    max-open-conns: 100
+    singular: false
+    log-zap: false
+system:
+    db-type: mysql
+    oss-type: local
+    router-prefix: ""
+    addr: 8888
+    iplimit-count: 15000
+    iplimit-time: 3600
+    use-multipoint: false
+    use-redis: false
+    use-mongo: false
+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: github.com/flipped-aurora/gin-vue-admin/server
+zap:
+    level: info
+    prefix: '[github.com/flipped-aurora/gin-vue-admin/server]'
+    format: console
+    director: log
+    encode-level: LowercaseColorLevelEncoder
+    stacktrace-key: stacktrace
+    max-age: 0
+    show-line: true
+    log-in-console: true

+ 9 - 0
server/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(秒)
+}

+ 30 - 0
server/config/config.go

@@ -0,0 +1,30 @@
+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"`
+	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"`
+	// 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"`
+
+	Excel Excel `mapstructure:"excel" json:"excel" yaml:"excel"`
+
+	// 跨域配置
+	Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"`
+}

+ 14 - 0
server/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"`
+}

+ 32 - 0
server/config/db_list.go

@@ -0,0 +1,32 @@
+package config
+
+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写入日志文件
+}
+
+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"`
+}

+ 11 - 0
server/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
+}

+ 5 - 0
server/config/excel.go

@@ -0,0 +1,5 @@
+package config
+
+type Excel struct {
+	Dir string `mapstructure:"dir" json:"dir" yaml:"dir"`
+}

+ 14 - 0
server/config/gorm_mssql.go

@@ -0,0 +1,14 @@
+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"
+}
+
+func (m *Mssql) GetLogMode() string {
+	return m.LogMode
+}

+ 13 - 0
server/config/gorm_mysql.go

@@ -0,0 +1,13 @@
+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
+}
+
+func (m *Mysql) GetLogMode() string {
+	return m.LogMode
+}

+ 14 - 0
server/config/gorm_oracle.go

@@ -0,0 +1,14 @@
+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
+
+}
+
+func (m *Oracle) GetLogMode() string {
+	return m.LogMode
+}

+ 21 - 0
server/config/gorm_pgsql.go

@@ -0,0 +1,21 @@
+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
+}
+
+func (m *Pgsql) GetLogMode() string {
+	return m.LogMode
+}

+ 17 - 0
server/config/gorm_sqlite.go

@@ -0,0 +1,17 @@
+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")
+}
+
+func (s *Sqlite) GetLogMode() string {
+	return s.LogMode
+}

+ 8 - 0
server/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"`                   // 签发者
+}

+ 41 - 0
server/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)
+}

+ 10 - 0
server/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"`
+}

+ 13 - 0
server/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"`
+}

+ 9 - 0
server/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"`
+}

+ 6 - 0
server/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"` // 本地文件存储路径
+}

+ 11 - 0
server/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上传加速
+}

+ 10 - 0
server/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"`
+}

+ 9 - 0
server/config/redis.go

@@ -0,0 +1,9 @@
+package config
+
+type Redis struct {
+	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"` // 集群模式下的节点地址列表
+}

+ 13 - 0
server/config/system.go

@@ -0,0 +1,13 @@
+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
+}

+ 60 - 0
server/config/zap.go

@@ -0,0 +1,60 @@
+package config
+
+import (
+	"go.uber.org/zap/zapcore"
+	"strings"
+)
+
+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"` // 栈名
+
+	MaxAge       int  `mapstructure:"max-age" json:"max-age" yaml:"max-age"`                      // 日志留存时间
+	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"` // 输出控制台
+}
+
+// ZapEncodeLevel 根据 EncodeLevel 返回 zapcore.LevelEncoder
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *Zap) ZapEncodeLevel() zapcore.LevelEncoder {
+	switch {
+	case z.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
+		return zapcore.LowercaseLevelEncoder
+	case z.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
+		return zapcore.LowercaseColorLevelEncoder
+	case z.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
+		return zapcore.CapitalLevelEncoder
+	case z.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
+		return zapcore.CapitalColorLevelEncoder
+	default:
+		return zapcore.LowercaseLevelEncoder
+	}
+}
+
+// TransportLevel 根据字符串转化为 zapcore.Level
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *Zap) TransportLevel() zapcore.Level {
+	z.Level = strings.ToLower(z.Level)
+	switch z.Level {
+	case "debug":
+		return zapcore.DebugLevel
+	case "info":
+		return zapcore.InfoLevel
+	case "warn":
+		return zapcore.WarnLevel
+	case "error":
+		return zapcore.ErrorLevel
+	case "dpanic":
+		return zapcore.DPanicLevel
+	case "panic":
+		return zapcore.PanicLevel
+	case "fatal":
+		return zapcore.FatalLevel
+	default:
+		return zapcore.DebugLevel
+	}
+}

+ 9 - 0
server/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"
+)

+ 97 - 0
server/core/internal/cutter.go

@@ -0,0 +1,97 @@
+package internal
+
+import (
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+)
+
+type Cutter struct {
+	level    string        // 日志级别(debug, info, warn, error, dpanic, panic, fatal)
+	format   string        // 时间格式(2006-01-02)
+	Director string        // 日志文件夹
+	file     *os.File      // 文件句柄
+	mutex    *sync.RWMutex // 读写锁
+}
+
+type CutterOption func(*Cutter)
+
+// WithCutterFormat 设置时间格式
+func WithCutterFormat(format string) CutterOption {
+	return func(c *Cutter) {
+		c.format = format
+	}
+}
+
+func NewCutter(director string, level string, options ...CutterOption) *Cutter {
+	rotate := &Cutter{
+		level:    level,
+		Director: director,
+		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()
+	}()
+	var business string
+	if strings.Contains(string(bytes), "business") {
+		var compile *regexp.Regexp
+		compile, err = regexp.Compile(`{"business": "([^,]+)"}`)
+		if err != nil {
+			return 0, err
+		}
+		if compile.Match(bytes) {
+			finds := compile.FindSubmatch(bytes)
+			business = string(finds[len(finds)-1])
+			bytes = compile.ReplaceAll(bytes, []byte(""))
+		}
+		compile, err = regexp.Compile(`"business": "([^,]+)"`)
+		if err != nil {
+			return 0, err
+		}
+		if compile.Match(bytes) {
+			finds := compile.FindSubmatch(bytes)
+			business = string(finds[len(finds)-1])
+			bytes = compile.ReplaceAll(bytes, []byte(""))
+		}
+	}
+	format := time.Now().Format(c.format)
+	formats := make([]string, 0, 4)
+	formats = append(formats, c.Director)
+	if format != "" {
+		formats = append(formats, format)
+	}
+	if business != "" {
+		formats = append(formats, business)
+	}
+	formats = append(formats, c.level+".log")
+	filename := filepath.Join(formats...)
+	dirname := filepath.Dir(filename)
+	err = os.MkdirAll(dirname, 0755)
+	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)
+}

+ 21 - 0
server/core/internal/file_rotatelogs.go

@@ -0,0 +1,21 @@
+package internal
+
+import (
+	"go.uber.org/zap/zapcore"
+	"os"
+	"server/global"
+)
+
+var FileRotatelogs = new(fileRotatelogs)
+
+type fileRotatelogs struct{}
+
+// GetWriteSyncer 获取 zapcore.WriteSyncer
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (r *fileRotatelogs) GetWriteSyncer(level string) zapcore.WriteSyncer {
+	fileWriter := NewCutter(global.GVA_CONFIG.Zap.Director, level, WithCutterFormat("2006-01-02"))
+	if global.GVA_CONFIG.Zap.LogInConsole {
+		return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter))
+	}
+	return zapcore.AddSync(fileWriter)
+}

+ 101 - 0
server/core/internal/zap.go

@@ -0,0 +1,101 @@
+package internal
+
+import (
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"server/global"
+	"time"
+)
+
+var Zap = new(_zap)
+
+type _zap struct{}
+
+// GetEncoder 获取 zapcore.Encoder
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetEncoder() zapcore.Encoder {
+	if global.GVA_CONFIG.Zap.Format == "json" {
+		return zapcore.NewJSONEncoder(z.GetEncoderConfig())
+	}
+	return zapcore.NewConsoleEncoder(z.GetEncoderConfig())
+}
+
+// GetEncoderConfig 获取zapcore.EncoderConfig
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetEncoderConfig() zapcore.EncoderConfig {
+	return zapcore.EncoderConfig{
+		MessageKey:     "message",
+		LevelKey:       "level",
+		TimeKey:        "time",
+		NameKey:        "logger",
+		CallerKey:      "caller",
+		StacktraceKey:  global.GVA_CONFIG.Zap.StacktraceKey,
+		LineEnding:     zapcore.DefaultLineEnding,
+		EncodeLevel:    global.GVA_CONFIG.Zap.ZapEncodeLevel(),
+		EncodeTime:     z.CustomTimeEncoder,
+		EncodeDuration: zapcore.SecondsDurationEncoder,
+		EncodeCaller:   zapcore.FullCallerEncoder,
+	}
+}
+
+// GetEncoderCore 获取Encoder的 zapcore.Core
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetEncoderCore(l zapcore.Level, level zap.LevelEnablerFunc) zapcore.Core {
+	writer := FileRotatelogs.GetWriteSyncer(l.String()) // 日志分割
+	return zapcore.NewCore(z.GetEncoder(), writer, level)
+}
+
+// CustomTimeEncoder 自定义日志输出时间格式
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) CustomTimeEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
+	encoder.AppendString(global.GVA_CONFIG.Zap.Prefix + t.Format("2006/01/02 - 15:04:05.000"))
+}
+
+// GetZapCores 根据配置文件的Level获取 []zapcore.Core
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetZapCores() []zapcore.Core {
+	cores := make([]zapcore.Core, 0, 7)
+	for level := global.GVA_CONFIG.Zap.TransportLevel(); level <= zapcore.FatalLevel; level++ {
+		cores = append(cores, z.GetEncoderCore(level, z.GetLevelPriority(level)))
+	}
+	return cores
+}
+
+// GetLevelPriority 根据 zapcore.Level 获取 zap.LevelEnablerFunc
+// Author [SliverHorn](https://github.com/SliverHorn)
+func (z *_zap) GetLevelPriority(level zapcore.Level) zap.LevelEnablerFunc {
+	switch level {
+	case zapcore.DebugLevel:
+		return func(level zapcore.Level) bool { // 调试级别
+			return level == zap.DebugLevel
+		}
+	case zapcore.InfoLevel:
+		return func(level zapcore.Level) bool { // 日志级别
+			return level == zap.InfoLevel
+		}
+	case zapcore.WarnLevel:
+		return func(level zapcore.Level) bool { // 警告级别
+			return level == zap.WarnLevel
+		}
+	case zapcore.ErrorLevel:
+		return func(level zapcore.Level) bool { // 错误级别
+			return level == zap.ErrorLevel
+		}
+	case zapcore.DPanicLevel:
+		return func(level zapcore.Level) bool { // dpanic级别
+			return level == zap.DPanicLevel
+		}
+	case zapcore.PanicLevel:
+		return func(level zapcore.Level) bool { // panic级别
+			return level == zap.PanicLevel
+		}
+	case zapcore.FatalLevel:
+		return func(level zapcore.Level) bool { // 终止级别
+			return level == zap.FatalLevel
+		}
+	default:
+		return func(level zapcore.Level) bool { // 调试级别
+			return level == zap.DebugLevel
+		}
+	}
+}

+ 40 - 0
server/core/server.go

@@ -0,0 +1,40 @@
+package core
+
+import (
+	"fmt"
+	"go.uber.org/zap"
+	"server/global"
+	"server/initialize"
+	"server/service/system"
+)
+
+type server interface {
+	ListenAndServe() error
+}
+
+func RunWindowsServer() {
+	if global.GVA_CONFIG.System.UseMultipoint || global.GVA_CONFIG.System.UseRedis {
+		// 初始化redis服务
+		initialize.Redis()
+	}
+	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()
+	Router.Static("/form-generator", "./resource/page")
+
+	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.Println("龙弛")
+	global.GVA_LOG.Error(s.ListenAndServe().Error())
+}

+ 19 - 0
server/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 = 20 * time.Second
+	s.WriteTimeout = 20 * time.Second
+	s.MaxHeaderBytes = 1 << 20
+	return s
+}

+ 21 - 0
server/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:    20 * time.Second,
+		WriteTimeout:   20 * time.Second,
+		MaxHeaderBytes: 1 << 20,
+	}
+}

+ 71 - 0
server/core/viper.go

@@ -0,0 +1,71 @@
+package core
+
+import (
+	"flag"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"os"
+	"server/core/internal"
+
+	"github.com/fsnotify/fsnotify"
+	"github.com/spf13/viper"
+
+	"server/global"
+	_ "server/packfile"
+)
+
+// 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
+					fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigDefaultFile)
+				case gin.ReleaseMode:
+					config = internal.ConfigReleaseFile
+					fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigReleaseFile)
+				case gin.TestMode:
+					config = internal.ConfigTestFile
+					fmt.Printf("您正在使用gin模式的%s环境名称,config的路径为%s\n", gin.Mode(), internal.ConfigTestFile)
+				}
+			} 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)
+	}
+
+	return v
+}

+ 28 - 0
server/core/zap.go

@@ -0,0 +1,28 @@
+package core
+
+import (
+	"fmt"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"os"
+	"server/core/internal"
+	"server/global"
+	"server/utils"
+)
+
+// 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)
+	}
+
+	cores := internal.Zap.GetZapCores()
+	logger = zap.New(zapcore.NewTee(cores...))
+
+	if global.GVA_CONFIG.Zap.ShowLine {
+		logger = logger.WithOptions(zap.AddCaller())
+	}
+	return logger
+}

+ 62 - 0
server/dao/exa_file_upload_download.go

@@ -0,0 +1,62 @@
+package dao
+
+import (
+	"server/global"
+)
+
+type ExaFileUploadAndDownload struct {
+	global.GVA_MODEL
+	Name string `json:"name" gorm:"comment:文件名"` // 文件名
+	Url  string `json:"url" gorm:"comment:文件地址"` // 文件地址
+	Tag  string `json:"tag" gorm:"comment:文件标签"` // 文件标签
+	Key  string `json:"key" gorm:"comment:编号"`   // 编号
+}
+
+func (ExaFileUploadAndDownload) TableName() string {
+	return "exa_file_upload_and_downloads"
+}
+
+// TODO:文件查询
+
+// QueryFileById 查询文件 按id
+func QueryFileById(id uint) (exaFileUploadAndDownload ExaFileUploadAndDownload, err error) {
+	err = global.GVA_DB.Where("id = ?", id).First(&exaFileUploadAndDownload).Error
+	return exaFileUploadAndDownload, err
+}
+
+// GetFileRecordInfoList 分页获取数据
+func GetFileRecordInfoList(limit, offset int, keyword string) (fileLists []ExaFileUploadAndDownload, total int64, err error) {
+
+	db := global.GVA_DB.Model(&ExaFileUploadAndDownload{})
+	if len(keyword) > 0 {
+		db = db.Where("name LIKE ?", "%"+keyword+"%")
+	}
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Order("updated_at desc").Find(&fileLists).Error
+	return fileLists, total, err
+}
+
+// TODO:文件新增
+
+// CreateFile 创建文件
+func (e ExaFileUploadAndDownload) CreateFile() error {
+	return global.GVA_DB.Create(&e).Error
+}
+
+// TODO:文件修改
+
+// EditFileName 编辑文件名或者备注
+func (e ExaFileUploadAndDownload) EditFileName() (fileFromDb ExaFileUploadAndDownload, err error) {
+	err = global.GVA_DB.Where("id = ?", e.ID).First(&fileFromDb).Update("name", e.Name).Error
+	return
+}
+
+// TODO:文件删除
+
+// DeleteFile 删除文件
+func (e ExaFileUploadAndDownload) DeleteFile() error {
+	return global.GVA_DB.Where("id = ?", e.ID).Unscoped().Delete(&e).Error
+}

+ 113 - 0
server/dao/sys_api.go

@@ -0,0 +1,113 @@
+package dao
+
+import (
+	"fmt"
+	"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"
+}
+
+// TODO:Api查询
+
+// QueryApiByPathMethod 查询api 按路径和方法
+func QueryApiByPathMethod(path, method string) (sysApi SysApi, err error) {
+	err = global.GVA_DB.Where("path = ? AND method = ?", path, method).First(&sysApi).Error
+	return
+}
+
+// QueryApiById 查询api 按id
+func QueryApiById(id uint) (sysApi SysApi, err error) {
+	err = global.GVA_DB.First(&sysApi, "id = ?", id).Error
+	return
+}
+
+//@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 (api SysApi) GetAPIInfoList(limit, offset int, order string, desc bool) (apiList []SysApi, total int64, err error) {
+
+	db := global.GVA_DB.Model(&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
+}
+
+// GetAllApis 查询所有api
+func GetAllApis() (apis []SysApi, err error) {
+	err = global.GVA_DB.Find(&apis).Error
+	return
+}
+
+// TODO:Api新增
+
+// CreateApi 新增api
+func (api SysApi) CreateApi() error {
+	return global.GVA_DB.Create(&api).Error
+}
+
+// TODO:Api修改
+
+// SaveApi 更新api
+func (api SysApi) SaveApi() error {
+	return global.GVA_DB.Save(&api).Error
+}
+
+// TODO:Api删除
+
+// DeleteApi 删除api
+func (api SysApi) DeleteApi() error {
+	return global.GVA_DB.Delete(&api).Error
+}

+ 60 - 0
server/dao/sys_authority.go

@@ -0,0 +1,60 @@
+package dao
+
+import (
+	"server/global"
+	"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"
+}
+
+// TODO:角色查询
+
+// QueryAuthorityById 查询角色 按id
+func QueryAuthorityById(authorityId uint) (sysAuthority SysAuthority, err error) {
+	err = global.GVA_DB.Where("authority_id = ?", authorityId).First(&sysAuthority).Error
+	return
+}
+
+// GetAuthorityInfoList 分页获取角色列表
+func GetAuthorityInfoList(limit, offset int) (sysAuthority []SysAuthority, total int64, err error) {
+	db := global.GVA_DB.Model(&SysAuthority{})
+	if err = db.Where("parent_id = ?", "0").Count(&total).Error; total == 0 || err != nil {
+		return
+	}
+	var authority []SysAuthority
+	err = db.Limit(limit).Offset(offset).Preload("DataAuthorityId").Where("parent_id = ?", "0").Find(&authority).Error
+
+	return authority, total, err
+}
+
+// GetAuthorityInfo 获取所有角色信息
+func (sa SysAuthority) GetAuthorityInfo() (sysAuthority SysAuthority, err error) {
+	err = global.GVA_DB.Preload("DataAuthorityId").Where("authority_id = ?", sa.AuthorityId).First(&sysAuthority).Error
+	return sysAuthority, err
+}
+
+// TODO:角色新增
+// TODO:角色修改
+
+// UpdateAuthority 修改角色
+func (sa SysAuthority) UpdateAuthority() error {
+	return global.GVA_DB.Model(&SysAuthority{}).Updates(&sa).Error
+}
+
+// TODO:角色删除

+ 62 - 0
server/dao/sys_authority_btn.go

@@ -0,0 +1,62 @@
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+)
+
+type SysAuthorityBtn struct {
+	AuthorityId      uint           `gorm:"comment:角色ID"`
+	SysMenuID        uint           `gorm:"comment:菜单ID"`
+	SysBaseMenuBtnID uint           `gorm:"comment:菜单按钮ID"`
+	SysBaseMenuBtn   SysBaseMenuBtn ` gorm:"comment:按钮详情"`
+}
+
+// TODO:按钮查询
+
+// QueryAuthorityBtnAndSysBaseMenuBtnById 查询角色按钮和菜单按钮 按id
+func QueryAuthorityBtnAndSysBaseMenuBtnById(authorityId uint) (btns []SysAuthorityBtn, err error) {
+	err = global.GVA_DB.Where("authority_id = ?", authorityId).Preload("SysBaseMenuBtn").Find(&btns).Error
+	return btns, err
+}
+
+// QueryAuthorityBtnByAuthorityIdMenuId 查询角色按钮 按角色id和菜单id
+func QueryAuthorityBtnByAuthorityIdMenuId(authorityId, sysMenuId uint) (authorityBtn []SysAuthorityBtn, err error) {
+	err = global.GVA_DB.Find(&authorityBtn, "authority_id = ? and sys_menu_id = ?", authorityId, sysMenuId).Error
+	return
+}
+
+// QueryAuthorityBtn 查询角色按钮 按菜单按钮id global.GVA_DB.First(&dao.SysAuthorityBtn{}, "sys_base_menu_btn_id = ?", ID).Error
+func QueryAuthorityBtn(Id string) (sysAuthorityBtn SysAuthorityBtn, err error) {
+	err = global.GVA_DB.Model(&SysAuthorityBtn{}).First(sysAuthorityBtn, "sys_base_menu_btn_id =?", Id).Error
+	return
+}
+
+// TODO:按钮新增
+// TODO:按钮修改
+
+func SetAuthorityBtn(menuId, authorityId uint, selected []uint) (err error) {
+	return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		var authorityBtn []SysAuthorityBtn
+		err = tx.Delete(&[]SysAuthorityBtn{}, "authority_id = ? and sys_menu_id = ?", authorityId, menuId).Error
+		if err != nil {
+			return err
+		}
+		for _, v := range selected {
+			authorityBtn = append(authorityBtn, SysAuthorityBtn{
+				AuthorityId:      authorityId,
+				SysMenuID:        menuId,
+				SysBaseMenuBtnID: v,
+			})
+		}
+		if len(authorityBtn) > 0 {
+			err = tx.Create(&authorityBtn).Error
+		}
+		if err != nil {
+			return err
+		}
+		return err
+	})
+}
+
+// TODO:按钮删除

+ 39 - 0
server/dao/sys_authority_menu.go

@@ -0,0 +1,39 @@
+package dao
+
+import "server/global"
+
+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"
+}
+
+// TODO:菜单查询
+
+// QueryAuthorityMenuByAuthorityId 查询角色菜单 按角色id
+func QueryAuthorityMenuByAuthorityId(authorityId uint) (SysAuthorityMenus []SysAuthorityMenu, err error) {
+	err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&SysAuthorityMenus).Error
+	return SysAuthorityMenus, err
+}
+
+// QuerySysBaseMenuIdById 查询菜单id 按角色id
+func QuerySysBaseMenuIdById(authorityId uint) (menuIds []string, err error) {
+	err = global.GVA_DB.Model(&SysAuthorityMenu{}).Where("sys_authority_authority_id = ?", authorityId).Pluck("sys_base_menu_id", &menuIds).Error
+	return
+}
+
+// TODO:菜单新增
+// TODO:菜单修改
+// TODO:菜单删除

+ 139 - 0
server/dao/sys_base_menu.go

@@ -0,0 +1,139 @@
+package dao
+
+import (
+	"errors"
+	"gorm.io/gorm"
+	"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"
+}
+
+// TODO:菜单查询
+
+// QueryBaseMenuAndParametersByMenuId 查询菜单和参数 按菜单id
+func QueryBaseMenuAndParametersByMenuId(menuIds []string) (baseMenu []SysBaseMenu, err error) {
+	err = global.GVA_DB.Where("id in (?)", menuIds).Order("sort").Preload("Parameters").Find(&baseMenu).Error
+	return
+}
+
+// QueryBaseMenuByName 查询菜单 按名字
+func QueryBaseMenuByName(name string) (baseMenu SysBaseMenu, err error) {
+	err = global.GVA_DB.Where("name = ?", name).First(&baseMenu).Error
+	return
+}
+
+// QueryBaseMenuAndMenuBtnAndParameters 查询菜单和菜单按钮和参数
+func QueryBaseMenuAndMenuBtnAndParameters() (allMenus []SysBaseMenu, err error) {
+	err = global.GVA_DB.Order("sort").Preload("MenuBtn").Preload("Parameters").Find(&allMenus).Error
+	return
+}
+
+// QueryBaseMenuByIdAndDefaultRouter 查询菜单 按菜单id和默认路由
+func QueryBaseMenuByIdAndDefaultRouter(defaultRouter string, menuIds []string) (allMenus []SysBaseMenu, err error) {
+	err = global.GVA_DB.First(&allMenus, "name = ? and id in (?)", defaultRouter, menuIds).Error
+	return
+}
+
+func QueryBaseMenuAndMenuBtnAndParameterById(id int) (menu SysBaseMenu, err error) {
+	err = global.GVA_DB.Preload("MenuBtn").Preload("Parameters").Where("id = ?", id).First(&menu).Error
+	return
+}
+
+// TODO:菜单新增
+
+// CreateBaseMenu 创建菜单
+func (bm SysBaseMenu) CreateBaseMenu() error {
+	return global.GVA_DB.Create(&bm).Error
+}
+
+// TODO:菜单修改
+
+func (bm SysBaseMenu) UpdateBaseMenu() (err error) {
+	var oldMenu SysBaseMenu
+
+	err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		tx.Where("id = ?", bm.ID).Find(&oldMenu)
+		if oldMenu.Name != bm.Name {
+			if !errors.Is(tx.Where("id <> ? AND name = ?", bm.ID, bm.Name).First(&SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {
+				global.GVA_LOG.Debug("存在相同name修改失败")
+				return errors.New("存在相同name修改失败")
+			}
+		}
+		txErr := tx.Unscoped().Delete(&SysBaseMenuParameter{}, "sys_base_menu_id = ?", bm.ID).Error
+		if txErr != nil {
+			global.GVA_LOG.Debug(txErr.Error())
+			return txErr
+		}
+		txErr = tx.Unscoped().Delete(&SysBaseMenuBtn{}, "sys_base_menu_id = ?", bm.ID).Error
+		if txErr != nil {
+			global.GVA_LOG.Debug(txErr.Error())
+			return txErr
+		}
+		if len(bm.Parameters) > 0 {
+			for k := range bm.Parameters {
+				bm.Parameters[k].SysBaseMenuID = bm.ID
+			}
+			txErr = tx.Create(&bm.Parameters).Error
+			if txErr != nil {
+				global.GVA_LOG.Debug(txErr.Error())
+				return txErr
+			}
+		}
+
+		if len(bm.MenuBtn) > 0 {
+			for k := range bm.MenuBtn {
+				bm.MenuBtn[k].SysBaseMenuID = bm.ID
+			}
+			txErr = tx.Create(&bm.MenuBtn).Error
+			if txErr != nil {
+				global.GVA_LOG.Debug(txErr.Error())
+				return txErr
+			}
+		}
+
+		txErr = tx.Model(&oldMenu).Updates(bm).Error
+		if txErr != nil {
+			global.GVA_LOG.Debug(txErr.Error())
+			return txErr
+		}
+		return nil
+	})
+	return err
+}
+
+// TODO:菜单删除

+ 70 - 0
server/dao/sys_dictionary.go

@@ -0,0 +1,70 @@
+// 自动生成模板SysDictionary
+package dao
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+)
+
+// SysDictionary 如果含有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"
+}
+
+// TODO:字典查询
+
+// QueryDictionaryByType 查询字典 按类型
+func QueryDictionaryByType(t string) (sysDictionary SysDictionary, err error) {
+	err = global.GVA_DB.First(&SysDictionary{}, "type = ?", t).Error
+	return sysDictionary, err
+}
+
+// QueryDictionaryAndDetailsById 查询字典和详情 按字典id
+func QueryDictionaryAndDetailsById(id uint) (sysDictionary SysDictionary, err error) {
+	err = global.GVA_DB.Where("id =?", id).Preload("SysDictionaryDetails").First(&sysDictionary).Error
+	return sysDictionary, err
+}
+
+// GetSysDictionary 根据id或者type获取字典单条数据
+func GetSysDictionary(Type string, Id uint, flag bool) (sysDictionary SysDictionary, err error) {
+	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
+}
+
+// GetSysDictionaryInfoList 分页获取字典列表
+func GetSysDictionaryInfoList() (sysDictionaries []SysDictionary, err error) {
+	err = global.GVA_DB.Find(&sysDictionaries).Error
+	return
+}
+
+// TODO:字典新增
+
+// CreateDictionary 创建字典
+func (sd SysDictionary) CreateDictionary() error {
+	return global.GVA_DB.Create(&sd).Error
+}
+
+// TODO:字典修改
+
+// UpdateDictionary 修改字典
+func (sd SysDictionary) UpdateDictionary() error {
+	return global.GVA_DB.Model(&SysDictionary{}).Updates(&sd).Error
+}
+
+// TODO:字典删除
+
+// DeleteDictionary 删除字典
+func (sd SysDictionary) DeleteDictionary() error {
+	return global.GVA_DB.Delete(&sd).Error
+}

+ 101 - 0
server/dao/sys_dictionary_detail.go

@@ -0,0 +1,101 @@
+// 自动生成模板SysDictionaryDetail
+package dao
+
+import (
+	"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"
+}
+
+// TODO:字典详情查询
+
+// GetSysDictionaryDetailById 查询字典详情 按id
+func GetSysDictionaryDetailById(id uint) (sysDictionaryDetail SysDictionaryDetail, err error) {
+	err = global.GVA_DB.Where("id = ?", id).First(&sysDictionaryDetail).Error
+	return sysDictionaryDetail, err
+}
+
+// GetSysDictionaryDetailInfoList 分页获取字典详情列表
+func (sdd SysDictionaryDetail) GetSysDictionaryDetailInfoList(limit, offset int) (sysDictionaryDetails []SysDictionaryDetail, total int64, err error) {
+	// 创建db
+	db := global.GVA_DB.Model(&SysDictionaryDetail{})
+	// 如果有条件搜索 下方会自动创建搜索语句
+	if sdd.Label != "" {
+		db = db.Where("label LIKE ?", "%"+sdd.Label+"%")
+	}
+	if sdd.Value != "" {
+		db = db.Where("value = ?", sdd.Value)
+	}
+	if sdd.Status != nil {
+		db = db.Where("status = ?", sdd.Status)
+	}
+	if sdd.SysDictionaryID != 0 {
+		db = db.Where("sys_dictionary_id = ?", sdd.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
+}
+
+// GetDictionaryListByDictionaryId 查询字典列表 按字典id
+func GetDictionaryListByDictionaryId(dictionaryID uint) (sysDictionaryDetails []SysDictionaryDetail, err error) {
+	err = global.GVA_DB.Find(&sysDictionaryDetails, "sys_dictionary_id = ?", dictionaryID).Error
+	return sysDictionaryDetails, err
+}
+
+// GetDictionaryListByType 查询字典列表 按字典类型
+func GetDictionaryListByType(t string) (sysDictionaryDetails []SysDictionaryDetail, err error) {
+	db := global.GVA_DB.Model(&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
+}
+
+// GetDictionaryInfoByValue 查询字典列表 按字典内容
+func GetDictionaryInfoByValue(dictionaryID uint, value uint) (sysDictionaryDetail SysDictionaryDetail, err error) {
+	err = global.GVA_DB.First(&sysDictionaryDetail, "sys_dictionary_id = ? and value = ?", dictionaryID, value).Error
+	return sysDictionaryDetail, err
+}
+
+// GetDictionaryInfoByTypeValue 查询字典列表 按字典类型和字典内容
+func GetDictionaryInfoByTypeValue(t string, value uint) (sysDictionaryDetail SysDictionaryDetail, err error) {
+	db := global.GVA_DB.Model(&SysDictionaryDetail{}).Joins("JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id")
+	err = db.First(&sysDictionaryDetail, "sys_dictionaries.type = ? and sys_dictionary_details.value = ?", t, value).Error
+	return sysDictionaryDetail, err
+}
+
+// TODO:字典详情新增
+
+// CreateSysDictionaryDetail 创建字典详情数据
+func (sdd SysDictionaryDetail) CreateSysDictionaryDetail() error {
+	return global.GVA_DB.Create(&sdd).Error
+}
+
+// TODO:字典详情修改
+
+// UpdateSysDictionaryDetail 更新字典数据
+func (sdd SysDictionaryDetail) UpdateSysDictionaryDetail() error {
+	return global.GVA_DB.Save(&sdd).Error
+}
+
+// TODO:字典详情删除
+
+// DeleteSysDictionaryDetail 删除字典详情
+func (sdd SysDictionaryDetail) DeleteSysDictionaryDetail() error {
+	return global.GVA_DB.Delete(&sdd).Error
+}

+ 21 - 0
server/dao/sys_jwt_blacklist.go

@@ -0,0 +1,21 @@
+package dao
+
+import (
+	"server/global"
+)
+
+type JwtBlacklist struct {
+	global.GVA_MODEL
+	Jwt string `gorm:"type:text;comment:jwt"`
+}
+
+// TODO:JwtBlack查询
+// TODO:JwtBlack新增
+
+// CreateJwtBlack 创建jwt黑名单
+func (jbl JwtBlacklist) CreateJwtBlack() error {
+	return global.GVA_DB.Create(&jbl).Error
+}
+
+// TODO:JwtBlack修改
+// TODO:JwtBlack删除

+ 10 - 0
server/dao/sys_menu_btn.go

@@ -0,0 +1,10 @@
+package dao
+
+import "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"`
+}

+ 76 - 0
server/dao/sys_operation_record.go

@@ -0,0 +1,76 @@
+// 自动生成模板SysOperationRecord
+package dao
+
+import (
+	"time"
+
+	"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"`
+}
+
+// TODO:操作查询
+
+// GetSysOperationRecord 根据id获取单条操作记录
+func GetSysOperationRecord(id uint) (sysOperationRecord SysOperationRecord, err error) {
+	err = global.GVA_DB.Where("id = ?", id).First(&sysOperationRecord).Error
+	return
+}
+
+// GetSysOperationRecordInfoList 分页获取操作记录列表
+func (sor SysOperationRecord) GetSysOperationRecordInfoList(limit, offset int) (sysOperationRecords []SysOperationRecord, total int64, err error) {
+	// 创建db
+	db := global.GVA_DB.Model(&SysOperationRecord{})
+	// 如果有条件搜索 下方会自动创建搜索语句
+	if sor.Method != "" {
+		db = db.Where("method = ?", sor.Method)
+	}
+	if sor.Path != "" {
+		db = db.Where("path LIKE ?", "%"+sor.Path+"%")
+	}
+	if sor.Status != 0 {
+		db = db.Where("status = ?", sor.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
+}
+
+// TODO:操作新增
+
+func (sor SysOperationRecord) CreateSysOperationRecord() error {
+	return global.GVA_DB.Create(&sor).Error
+}
+
+// TODO:操作修改
+
+// TODO:操作删除
+
+// DeleteSysOperationRecordByIds 删除操作记录按id
+func DeleteSysOperationRecordByIds(ids []int) (err error) {
+	err = global.GVA_DB.Delete(&[]SysOperationRecord{}, "id in (?)", ids).Error
+	return err
+}
+
+// DeleteSysOperationRecord 删除操作记录
+func (sor SysOperationRecord) DeleteSysOperationRecord() (err error) {
+	err = global.GVA_DB.Delete(&sor).Error
+	return err
+}

+ 155 - 0
server/dao/sys_user.go

@@ -0,0 +1,155 @@
+package dao
+
+import (
+	"errors"
+	"github.com/gofrs/uuid/v5"
+	"gorm.io/gorm"
+	"server/global"
+)
+
+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:用户昵称"`                                            // 用户昵称
+	SideMode    string       `json:"sideMode" gorm:"default:dark;comment:用户侧边主题"`                                          // 用户侧边主题
+	HeaderImg   string       `json:"headerImg" gorm:"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像"` // 用户头像
+	BaseColor   string       `json:"baseColor" gorm:"default:#fff;comment:基础颜色"`                                           // 基础颜色
+	ActiveColor string       `json:"activeColor" gorm:"default:#1890ff;comment:活跃颜色"`                                      // 活跃颜色
+	AuthorityId uint         `json:"authorityId" gorm:"default:888;comment:用户角色ID"`                                        // 用户角色ID
+	Authority   SysAuthority `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"`
+	Phone       string       `json:"phone"  gorm:"comment:用户手机号"`                     // 用户手机号
+	Email       string       `json:"email"  gorm:"comment:用户邮箱"`                      // 用户邮箱
+	Enable      int          `json:"enable" gorm:"default:1;comment:用户是否被冻结 1正常 2冻结"` //用户是否被冻结 1正常 2冻结
+}
+
+func (SysUser) TableName() string {
+	return "sys_users"
+}
+
+// TODO:查询
+// TODO:新增
+// TODO:修改
+// TODO:删除
+
+// TODO: 用户查询
+
+// QueryUserByUserName  按用户名查询用户
+func QueryUserByUserName(userName string) (SysUser, error) {
+	var user SysUser
+	err := global.GVA_DB.Where("username =?", userName).First(&user).Error
+	return user, err
+}
+
+// QueryUserByUserId  按用户Id查询用户
+func QueryUserByUserId(id uint) (SysUser, error) {
+	var user SysUser
+	err := global.GVA_DB.Where("id =?", id).First(&user).Error
+	return user, err
+}
+
+// QueryUserAndAuthorityByUserName 按用户名查询用户和角色
+func QueryUserAndAuthorityByUserName(userName string) (user SysUser, err error) {
+	err = global.GVA_DB.Where("username = ?", userName).Preload("Authority").First(&user).Error
+	return
+}
+
+// QueryUserInfoList 条件查询用户信息列表
+func QueryUserInfoList(limit, offset int) (userList []SysUser, total int64, err error) {
+	db := global.GVA_DB.Model(&SysUser{})
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Preload("Authority").Find(&userList).Error
+	return
+}
+
+// QueryUserAuthority 查询用户角色
+func QueryUserAuthority(id, authorityId uint) (authority SysAuthority, err error) {
+	err = global.GVA_DB.Where("sys_user_id = ? AND sys_authority_authority_id = ?", id, authorityId).First(&authority).Error
+	return
+}
+
+// GetUserInfoByUUID 获取用户信息 按UUID
+func GetUserInfoByUUID(uuid uuid.UUID) (user SysUser, err error) {
+	err = global.GVA_DB.Preload("Authority").First(&user, "uuid = ?", uuid).Error
+	return user, err
+}
+
+// TODO: 用户新增
+
+// CreateUser 创建用户
+func (u SysUser) CreateUser() error {
+	return global.GVA_DB.Create(&u).Error
+}
+
+// TODO: 用户修改
+
+// UpdateUser 修改用户
+func (u SysUser) UpdateUser() error {
+	return global.GVA_DB.Save(&u).Error
+}
+
+// UpdateUserAuthority 修改用户权限
+func UpdateUserAuthority(id, authorityId uint) error {
+	return global.GVA_DB.Model(&SysUser{}).Where("id = ?", id).Update("authority_id", authorityId).Error
+}
+
+// SetUserAuthorities 设置用户权限
+func SetUserAuthorities(id uint, authorityId uint) (err error) {
+	return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		var user SysUser
+		TxErr := tx.Where("id = ?", id).First(&user).Error
+		if TxErr != nil {
+			global.GVA_LOG.Debug(TxErr.Error())
+			return errors.New("查询用户数据失败")
+		}
+		TxErr = tx.Delete(&[]SysUserAuthority{}, "sys_user_id = ?", id).Error
+		if TxErr != nil {
+			return TxErr
+		}
+
+		useAuthority := SysUserAuthority{
+			SysUserId: id, SysAuthorityAuthorityId: authorityId,
+		}
+		TxErr = tx.Create(&useAuthority).Error
+		if TxErr != nil {
+			return TxErr
+		}
+		TxErr = tx.Model(&user).Update("authority_id", authorityId).Error
+		if TxErr != nil {
+			return TxErr
+		}
+		// 返回 nil 提交事务
+		return nil
+	})
+}
+
+// SetUserInfo 修改用户信息
+func (u SysUser) SetUserInfo() error {
+	return global.GVA_DB.Model(&SysUser{}).Updates(&u).Error
+}
+
+// SetUserInfoById 修改用户信息 按id
+func (u SysUser) SetUserInfoById() error {
+	return global.GVA_DB.Model(&SysUser{}).
+		Where("id=?", u.ID).
+		Updates(&u).Error
+}
+
+// TODO: 用户删除
+
+// DeleteUser 删除用户
+func DeleteUser(id int) (err error) {
+	return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
+		if err := tx.Where("id = ?", id).Delete(&SysUser{}).Error; err != nil {
+			return err
+		}
+		if err := tx.Delete(&[]SysUserAuthority{}, "sys_user_id = ?", id).Error; err != nil {
+			return err
+		}
+		return nil
+	})
+}

+ 11 - 0
server/dao/sys_user_authority.go

@@ -0,0 +1,11 @@
+package dao
+
+// 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 do ficheiro suprimidas por serem muito extensas
+ 7018 - 0
server/docs/docs.go


Diff do ficheiro suprimidas por serem muito extensas
+ 6992 - 0
server/docs/swagger.json


Diff do ficheiro suprimidas por serem muito extensas
+ 4279 - 0
server/docs/swagger.yaml


+ 53 - 0
server/global/global.go

@@ -0,0 +1,53 @@
+package global
+
+import (
+	"github.com/qiniu/qmgo"
+	"sync"
+
+	"github.com/songzhibin97/gkit/cache/local_cache"
+	"server/utils/timer"
+
+	"golang.org/x/sync/singleflight"
+
+	"go.uber.org/zap"
+
+	"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_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{}
+
+	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
+}

+ 14 - 0
server/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:"-"` // 删除时间
+}

+ 143 - 0
server/go.mod

@@ -0,0 +1,143 @@
+module server
+
+go 1.22
+
+require (
+	github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
+	github.com/aws/aws-sdk-go v1.44.307
+	github.com/casbin/casbin/v2 v2.87.1
+	github.com/casbin/gorm-adapter/v3 v3.18.0
+	github.com/flipped-aurora/ws v1.0.2
+	github.com/fsnotify/fsnotify v1.6.0
+	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
+	github.com/gin-gonic/gin v1.9.1
+	github.com/glebarez/sqlite v1.8.0
+	github.com/go-sql-driver/mysql v1.7.1
+	github.com/gofrs/uuid/v5 v5.0.0
+	github.com/golang-jwt/jwt/v4 v4.5.0
+	github.com/gookit/color v1.5.4
+	github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible
+	github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84
+	github.com/mojocn/base64Captcha v1.3.6
+	github.com/otiai10/copy v1.7.0
+	github.com/pkg/errors v0.9.1
+	github.com/qiniu/api.v7/v7 v7.4.1
+	github.com/qiniu/qmgo v1.1.8
+	github.com/redis/go-redis/v9 v9.0.5
+	github.com/robfig/cron/v3 v3.0.1
+	github.com/shirou/gopsutil/v3 v3.23.6
+	github.com/songzhibin97/gkit v1.2.11
+	github.com/spf13/viper v1.16.0
+	github.com/stretchr/testify v1.8.4
+	github.com/swaggo/files v1.0.1
+	github.com/swaggo/gin-swagger v1.6.0
+	github.com/swaggo/swag v1.16.2
+	github.com/tencentyun/cos-go-sdk-v5 v0.7.42
+	github.com/unrolled/secure v1.13.0
+	github.com/xuri/excelize/v2 v2.8.0
+	go.mongodb.org/mongo-driver v1.12.1
+	go.uber.org/automaxprocs v1.5.3
+	go.uber.org/zap v1.24.0
+	golang.org/x/crypto v0.22.0
+	golang.org/x/sync v0.5.0
+	golang.org/x/text v0.14.0
+	gorm.io/driver/mysql v1.5.6
+	gorm.io/driver/postgres v1.5.7
+	gorm.io/driver/sqlserver v1.5.1
+	gorm.io/gorm v1.25.9
+	nhooyr.io/websocket v1.8.7
+)
+
+require (
+	github.com/KyleBanks/depth v1.2.1 // indirect
+	github.com/bytedance/sonic v1.9.1 // indirect
+	github.com/casbin/govaluate v1.1.1 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/clbanning/mxj v1.8.4 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/glebarez/go-sqlite v1.21.1 // indirect
+	github.com/go-ole/go-ole v1.2.6 // indirect
+	github.com/go-openapi/jsonpointer v0.20.2 // indirect
+	github.com/go-openapi/jsonreference v0.20.3 // indirect
+	github.com/go-openapi/spec v0.20.12 // indirect
+	github.com/go-openapi/swag v0.22.5 // 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.14.0 // indirect
+	github.com/goccy/go-json v0.10.2 // 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.1 // indirect
+	github.com/google/go-querystring v1.0.0 // indirect
+	github.com/google/uuid v1.3.0 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
+	github.com/jackc/pgx/v5 v5.5.5 // indirect
+	github.com/jackc/puddle/v2 v2.2.1 // 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.13.6 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/microsoft/go-mssqldb v1.1.0 // 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.0 // indirect
+	github.com/mozillazg/go-httpheader v0.2.1 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // 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.3 // indirect
+	github.com/shoenig/go-m1cpu v0.1.6 // indirect
+	github.com/spf13/afero v1.9.5 // indirect
+	github.com/spf13/cast v1.5.1 // indirect
+	github.com/spf13/jwalterweatherman v1.1.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/subosito/gotenv v1.4.2 // indirect
+	github.com/tklauser/go-sysconf v0.3.11 // indirect
+	github.com/tklauser/numcpus v0.6.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.11 // 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-20210125001918-ca9a967f8778 // indirect
+	github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
+	github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
+	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
+	github.com/yusufpapurcu/wmi v1.2.3 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	go.uber.org/multierr v1.8.0 // indirect
+	golang.org/x/arch v0.3.0 // indirect
+	golang.org/x/image v0.15.0 // indirect
+	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/time v0.1.0 // indirect
+	golang.org/x/tools v0.16.1 // indirect
+	google.golang.org/protobuf v1.33.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gorm.io/plugin/dbresolver v1.4.1 // indirect
+	modernc.org/libc v1.24.1 // indirect
+	modernc.org/mathutil v1.5.0 // indirect
+	modernc.org/memory v1.6.0 // indirect
+	modernc.org/sqlite v1.23.0 // indirect
+)

+ 892 - 0
server/go.sum

@@ -0,0 +1,892 @@
+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.44.3/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.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+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/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+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/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+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=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
+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/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
+github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
+github.com/aws/aws-sdk-go v1.44.307 h1:2R0/EPgpZcFSUwZhYImq/srjaOrOfLv5MNRzrFyAM38=
+github.com/aws/aws-sdk-go v1.44.307/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
+github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
+github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
+github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/casbin/casbin/v2 v2.87.1 h1:7H+ENAfYt3HmZJVw++tJsxx/ko7WEHsfNzpOdYTkpYo=
+github.com/casbin/casbin/v2 v2.87.1/go.mod h1:jX8uoN4veP85O/n2674r2qtfSXI6myvxW85f6TH50fw=
+github.com/casbin/gorm-adapter/v3 v3.18.0 h1:0td7v030eK3H5ftXRHx1d5wVPbuYEJP2ObMSUHtA0Ek=
+github.com/casbin/gorm-adapter/v3 v3.18.0/go.mod h1:ekufPNBgVIQvv9JffVGsg7KUv4DjnevTh6AQnBNkoK8=
+github.com/casbin/govaluate v1.1.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
+github.com/casbin/govaluate v1.1.1 h1:J1rFKIBhiC5xr0APd5HP6rDL+xt+BRoyq1pa4o2i/5c=
+github.com/casbin/govaluate v1.1.1/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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+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 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
+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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/flipped-aurora/ws v1.0.2 h1:oEUz7sgrbPENvgli7Q4QpC0NIEbJucgR4yjcDMg/AjY=
+github.com/flipped-aurora/ws v1.0.2/go.mod h1:RdyM2Fnvxx7f7A6WSmU1aAhDrQIAVW7LS/0LsAUE5mE=
+github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
+github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc=
+github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
+github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0y5NY=
+github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E=
+github.com/glebarez/sqlite v1.8.0 h1:02X12E2I/4C1n+v90yTqrjRa8yuo7c3KeHI3FRznCvc=
+github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqSaYLTLx8=
+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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
+github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
+github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ=
+github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM=
+github.com/go-openapi/spec v0.20.12 h1:cgSLbrsmziAP2iais+Vz7kSazwZ8rsUZd6TUzdDgkVI=
+github.com/go-openapi/spec v0.20.12/go.mod h1:iSCgnBcwbMW9SfzJb8iYynXvcY6C/QFrI7otzF7xGM4=
+github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugys=
+github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/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.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+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.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
+github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
+github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
+github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
+github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+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.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
+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/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+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/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+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-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
+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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
+github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
+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/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible h1:3kDd8PIWAdU+qGs/+0QUgsMI2ZSiJPt45Xn0su+x/Q0=
+github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.8+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
+github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
+github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84 h1:pS0A6cr4aHYZnYwC7Uw+rwgb39+nzkm2QhwZ+S6Gn5I=
+github.com/jordan-wright/email v0.0.0-20200824153738-3f5bafa1cd84/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+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.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+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.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/microsoft/go-mssqldb v1.1.0 h1:jsV+tpvcPTbNNKW0o3kiCD69kOHICsfjZ2VcVu2lKYc=
+github.com/microsoft/go-mssqldb v1.1.0/go.mod h1:LzkFdl4z2Ck+Hi+ycGOTbL56VEfgoyA2DvYejrNGbRk=
+github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+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 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
+github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
+github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
+github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
+github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
+github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
+github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
+github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
+github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
+github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
+github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI=
+github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
+github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
+github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/qiniu/api.v7/v7 v7.4.1 h1:BnNUBimLk6nrA/mIwsww9yJRupmViSsb1ndLMC7a9OY=
+github.com/qiniu/api.v7/v7 v7.4.1/go.mod h1:VE5oC5rkE1xul0u1S2N0b2Uxq9/6hZzhyqjgK25XDcM=
+github.com/qiniu/qmgo v1.1.8 h1:E64M+P59aqQpXKI24ClVtluYkLaJLkkeD2hTVhrdMks=
+github.com/qiniu/qmgo v1.1.8/go.mod h1:QvZkzWNEv0buWPx0kdZsSs6URhESVubacxFPlITmvB8=
+github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
+github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
+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.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
+github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08=
+github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU=
+github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
+github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
+github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
+github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
+github.com/songzhibin97/gkit v1.2.11 h1:O8+l6eLMrZ2yNbT6Vohc6ggWnH5zt4P8/3ZEkf8jUL4=
+github.com/songzhibin97/gkit v1.2.11/go.mod h1:axjYsiJWnn/kf/uGiUr9JPHRlt2CQrqfq/fPZ3xIY+M=
+github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
+github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
+github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
+github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
+github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
+github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
+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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
+github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
+github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
+github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
+github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
+github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
+github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
+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.42 h1:Up1704BJjI5orycXKjpVpvuOInt9GC5pqY4knyE9Uds=
+github.com/tencentyun/cos-go-sdk-v5 v0.7.42/go.mod h1:LUFnaqRmGk6pEHOaRmdn2dCZR2j0cSsM5xowWFPTPao=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
+github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
+github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
+github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk=
+github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
+github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
+github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
+github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
+github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
+github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
+github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
+github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg=
+github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o=
+github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
+go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE=
+go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
+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.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
+go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+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-20200119233911-0405dc783f0a/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-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+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.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
+golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
+golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
+golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
+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/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/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.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+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-20190628185345-da137c7871d7/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-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+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.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+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/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.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-20191001151750-bb3f8db39f24/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-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.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.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+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.1.0/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.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+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.4/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+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.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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+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.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
+golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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-20191130070609-6e064ea0cf2d/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-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/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-20200204074204-1cc6d1ef6c74/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.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+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.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
+golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+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=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+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/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/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-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+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/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
+gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
+gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
+gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
+gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
+gorm.io/driver/sqlserver v1.5.1 h1:wpyW/pR26U94uaujltiFGXY7fd2Jw5hC9PB1ZF/Y5s4=
+gorm.io/driver/sqlserver v1.5.1/go.mod h1:AYHzzte2msKTmYBYsSIq8ZUsznLJwBdkB2wpI+kt0nM=
+gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
+gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
+gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
+gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/plugin/dbresolver v1.4.1 h1:Ug4LcoPhrvqq71UhxtF346f+skTYoCa/nEsdjvHwEzk=
+gorm.io/plugin/dbresolver v1.4.1/go.mod h1:CTbCtMWhsjXSiJqiW2R8POvJ2cq18RVOl4WGyT5nhNc=
+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=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
+modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
+modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/sqlite v1.23.0 h1:MWTFBI5H1WLnXpNBh/BTruBVqzzoh28DA0iOnlkkRaM=
+modernc.org/sqlite v1.23.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

+ 36 - 0
server/initialize/db_list.go

@@ -0,0 +1,36 @@
+package initialize
+
+import (
+	"gorm.io/gorm"
+	"server/config"
+	"server/global"
+)
+
+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
+}

+ 55 - 0
server/initialize/gorm.go

@@ -0,0 +1,55 @@
+package initialize
+
+import (
+	"os"
+	"server/dao"
+
+	"go.uber.org/zap"
+	"gorm.io/gorm"
+	"server/global"
+	"server/model/example"
+)
+
+func Gorm() *gorm.DB {
+	switch global.GVA_CONFIG.System.DbType {
+	case "mysql":
+		return GormMysql()
+	case "pgsql":
+		return GormPgSql()
+	case "oracle":
+		return GormOracle()
+	case "mssql":
+		return GormMssql()
+	case "sqlite":
+		return GormSqlite()
+	default:
+		return GormMysql()
+	}
+}
+
+func RegisterTables() {
+	db := global.GVA_DB
+	err := db.AutoMigrate(
+
+		dao.SysApi{},
+		dao.SysUser{},
+		dao.SysBaseMenu{},
+		dao.JwtBlacklist{},
+		dao.SysAuthority{},
+		dao.SysDictionary{},
+		dao.SysOperationRecord{},
+		dao.SysDictionaryDetail{},
+		dao.SysBaseMenuParameter{},
+		dao.SysBaseMenuBtn{},
+		dao.SysAuthorityBtn{},
+
+		example.ExaFile{},
+		example.ExaFileChunk{},
+		dao.ExaFileUploadAndDownload{},
+	)
+	if err != nil {
+		global.GVA_LOG.Error("register table failed", zap.Error(err))
+		os.Exit(0)
+	}
+	global.GVA_LOG.Info("register table success")
+}

+ 59 - 0
server/initialize/gorm_mssql.go

@@ -0,0 +1,59 @@
+/*
+ * @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
+ */
+package initialize
+
+import (
+	"gorm.io/driver/sqlserver"
+	"gorm.io/gorm"
+	"server/config"
+	"server/global"
+	"server/initialize/internal"
+)
+
+// 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
+	}
+}

+ 55 - 0
server/initialize/gorm_mysql.go

@@ -0,0 +1,55 @@
+package initialize
+
+import (
+	_ "github.com/go-sql-driver/mysql"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"server/config"
+	"server/global"
+	"server/initialize/internal"
+)
+
+// 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
+	}
+}

+ 52 - 0
server/initialize/gorm_oracle.go

@@ -0,0 +1,52 @@
+package initialize
+
+import (
+	//"github.com/dzwvip/oracle"
+	"server/config"
+	"server/global"
+	"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
+	}
+}

+ 50 - 0
server/initialize/gorm_pgsql.go

@@ -0,0 +1,50 @@
+package initialize
+
+import (
+	"gorm.io/driver/postgres"
+	"gorm.io/gorm"
+	"server/config"
+	"server/global"
+	"server/initialize/internal"
+)
+
+// 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
+	}
+}

+ 42 - 0
server/initialize/gorm_sqlite.go

@@ -0,0 +1,42 @@
+package initialize
+
+import (
+	"github.com/glebarez/sqlite"
+	"gorm.io/gorm"
+	"server/config"
+	"server/global"
+	"server/initialize/internal"
+)
+
+// 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
+	}
+}

+ 0 - 0
server/initialize/internal/gorm.go


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff