1 项目说明

1.1 简介

01.常用信息
    a.介绍
        Gin-Vue-Admin 是一个全栈管理框架,专为快速开发Web应用程序而设计,具有完整的前后端分离架构。
        基于Go (Gin) 和Vue.js构建,提供了一个全面的开发平台,具备自动化代码生成、AI辅助开发和企业级安全特性。
    b.线上部署
        GitHub地址: https://github.com/flipped-aurora/gin-vue-admin
        GitCode地址: https://gitCode.com/flipped-aurora/gin-vue-admin
        Gitee地址: https://gitee.com/pixelmax/gin-vue-admin
        在线演示: http://demo.gin-vue-admin.com/
        用户名:admin
        密码:123456
    c.项目定位
        Gin-Vue-Admin 作为企业级管理系统的基础框架,专注于为开发者提供:
        🚀 快速开发: AutoCode生成系统,可在1分钟内生成完整的CRUD功能
        🔒 企业安全: JWT认证 + Casbin RBAC授权的双重安全保障
        🔧 高度灵活: 动态路由、菜单管理和API配置
        📚 完整文档: Swagger API文档自动生成
        ☁️ 云原生: 多云文件存储支持(七牛云、阿里云、AWS S3)
        🗄️ 多数据库: 支持MySQL、PostgreSQL、SQLite和MSSQL
        该系统主要面向构建管理后台、内容管理系统和需要用户管理及权限控制的业务应用的开发者。

02.常用信息
    a.环境要求
        Node.js: ≥ 18.16.2
        Go: ≥ 1.22
        MySQL: ≥ 5.7 (引擎必须为 InnoDB)
        Git: 版本控制工具
        推荐使用 Docker 创建 MySQL 数据库以确保环境一致性
    b.前端技术栈
        技术             版本    描述
        Vue.js          3.3.4   渐进式JavaScript框架
        Element Plus    2.3.8   Vue 3 UI组件库
        Pinia           Latest  状态管理(替代Vuex)
        Vue Router      Latest  SPA路由与动态路由
        Vite            Latest  构建工具和开发服务器
    c.后端技术栈
        技术            版本     描述
        Go             ≥ 1.22   编程语言
        Gin            1.9.1    高性能Web框架
        GORM           1.25.2   ORM库,支持自动迁移
        Casbin         Latest   访问控制库(RBAC)
    d.数据库支持
        数据库          版本要求  说明
        MySQL          ≥ 5.7    主数据库,InnoDB引擎
        PostgreSQL     ≥ 9.6    关系型数据库替代方案
        SQLite         Latest   嵌入式数据库选项
        MS SQL Server  Latest   微软数据库支持
        Oracle         Latest   企业级数据库支持
    e.缓存与存储
        Redis: 缓存和会话管理
        七牛云: 对象存储服务
        阿里云OSS: 阿里巴巴云存储
        AWS S3: 亚马逊Web服务存储
    f.开发工具
        Swagger: API文档自动生成
        Viper: 配置管理
        Zap: 结构化日志记录
        fsnotify: 文件系统通知
    g.AI集成
        LLM APIs: AI辅助代码生成
        MCP Protocol: 模型上下文协议,用于AI代理

03.核心功能
    a.安全认证系统
        JWT认证: 无状态的用户身份验证
        Casbin RBAC: 基于角色的访问控制
        多点登录控制: 支持单点登录限制
        API权限管理: 细粒度的接口访问控制
    b.用户权限管理
        用户管理: 系统管理员分配用户角色和权限
        角色管理: 创建权限控制对象,支持API、菜单、按钮权限分配
        菜单管理: 动态菜单配置,实现不同角色不同菜单
        按钮权限: 页面级别的操作权限控制
    c.快速开发工具
        AutoCode生成器: 1分钟生成完整CRUD功能的代码生成器
        表单生成器: 基于 Variant Form 的可视化表单设计
        API自动文档: Swagger自动生成API文档
        RESTful示例: 标准的RESTful API设计参考
    d.文件存储系统
        多云存储: 支持本地、七牛云、阿里云、腾讯云存储
        分片上传: 大文件分片上传功能
        断点续传: 文件上传中断后可继续上传
        文件管理: 完整的文件上传下载管理
    e.系统管理
        配置管理: 前台可视化配置文件修改
        日志管理: 系统操作日志记录和查询
        监控面板: 系统运行状态监控
        数据字典: 系统数据字典管理
    f.用户界面
        富文本编辑器: 内置MarkDown编辑器
        条件搜索: 高级搜索功能示例
        数据导入导出: Excel数据处理功能
        响应式设计: 适配多种设备屏幕
    g.插件生态
        插件中心 NEW: 基于GVA设计的Go插件中心
        微信集成: 微信支付、登录等功能插件
        K8s操作: Kubernetes相关操作插件
        第三方登录: 多种第三方登录方式支持

04.系统设计
    a.图示
        ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
        │   前端层 (Vue)   │    │   后端层 (Go)    │    │    数据层 (DB)   │
        ├─────────────────┤    ├─────────────────┤    ├─────────────────┤
        │ • Vue 3 + Vite  │    │ • Gin Framework │    │ • MySQL/PG/...  │
        │ • Element Plus  │◄──►│ • JWT + Casbin  │◄──►│ • Redis Cache   │
        │ • Pinia Store   │    │ • GORM ORM      │    │ • File Storage  │
        │ • Vue Router    │    │ • Swagger Docs  │    │ • Cloud Storage │
        └─────────────────┘    └─────────────────┘    └─────────────────┘
    b.前端架构 (Vue.js + Vite)
        应用启动: main.js - 应用程序引导
        路由系统: Vue Router - 动态路由管理
        状态管理: Pinia Stores - 全局状态管理
        布局系统: Header, Aside, Tabs - 页面布局组件
        权限控制: 菜单权限、API权限、按钮权限
        组件库: Upload, Select, Export - 通用业务组件
    c.后端架构 (Go + Gin)
        服务启动: main.go - 服务器引导程序
        配置系统: core.Viper - 配置管理系统
        认证中间件: middleware.JWT - JWT身份验证
        授权中间件: middleware.Casbin - 权限控制处理
        API处理器: api.v1 - REST API处理程序
        业务服务: service.* - 业务逻辑服务
        代码生成: service.AutoCode - 自动代码生成引擎
    d.数据架构
        主数据库: global.GVA_DB - 主要数据存储
        缓存系统: global.GVA_REDIS - 缓存和会话
        文件存储: 本地/云端对象存储服务
        权限存储: system.CasbinRule - 策略存储
    e.认证授权流程
        sequenceDiagram
            participant U as 用户
            participant F as 前端
            participant A as API网关
            participant S as 业务服务
            participant D as 数据库

            U->>F: 登录请求
            F->>A: POST /base/login
            A->>S: 验证用户凭据
            S->>D: 查询用户信息
            D-->>S: 返回用户数据
            S->>A: 生成JWT Token
            A-->>F: 返回Token
            F->>F: 存储Token

            U->>F: 访问受保护资源
            F->>A: 请求 + JWT Header
            A->>A: JWT验证
            A->>S: Casbin权限检查
            S-->>A: 权限结果
            A-->>F: 返回数据/拒绝访问
    f.初始化流程
        配置初始化: core.Viper - 加载配置文件
        日志初始化: core.Zap - 设置日志系统
        数据库连接: initialize.Gorm - 建立数据库连接
        数据表注册: initialize.RegisterTables - 注册数据模型
        Redis连接: initialize.Redis - 建立缓存连接
        路由初始化: initialize.Routers - 设置API路由
        服务启动: core.RunWindowsServer - 启动HTTP服务

1.2 环境配置

01.环境准备
    a.环境要求
        Git: 用于代码版本管理。
        Node.js: >= 18.16.2 (推荐使用 LTS 版本)。
        Go: >= 1.22 (推荐使用最新稳定版)。
        MySQL: >= 8.0,存储引擎必须为 InnoDB。
        Redis: >= 6.0 (可选,用于缓存)。
    b.分支状态说明
        main:🟢 活跃维护,主分支,生产环境推荐。
        i18n-dev-new:🟡 更新中,组合式API多语言版本。
        v2.4.x:🔴 停止维护,声明式API版本。
        i18n-dev:🔴 停止维护,声明式API多语言版本。
    c.默认管理员账号
        用户名:admin
        密码:123456
    d.2.4.5版本之前
        闭源项目:无任何限制
        开源项目:需保留原始协议声明
        商业使用:无需额外授权
    e.2.4.5版本及以后
        个人学习项目:完全免费
        企业内部系统(不对外开放,不接入公网):完全免费
        外包项目/对外开放系统:必须获得商业授权
        商业盈利项目:必须获得商业授权

02.Node.js 环境安装
    a.下载安装 Node.js
        访问 Node.js 官网下载并安装 LTS 版本。
        推荐版本: Node.js 18.x 或更高版本。
    b.验证安装
        # 检查 Node.js 版本
        node -v
        # 检查 npm 版本
        npm -v
    c.配置 npm 镜像源(可选)
        # 设置淘宝镜像源
        npm config set registry https://registry.npmmirror.com
        # 验证配置
        npm config get registry

03.Go 环境安装
    a.下载安装 Go
        根据您的网络环境选择下载地址:
        国际用户: https://golang.org/dl/
        国内用户: https://golang.google.cn/dl/
        推荐版本: Go 1.22 或更高版本。
    b.验证安装
        # 检查 Go 版本
        go version
        # 查看 Go 环境信息
        go env
    c.配置 Go 模块代理(推荐)
        # 启用 Go Modules
        go env -w GO111MODULE=on
        # 配置模块代理
        go env -w GOPROXY=https://goproxy.cn,direct
        # 配置私有模块跳过代理
        go env -w GOPRIVATE=*.corp.example.com

04.数据库环境
    a.MySQL 安装
        a.macOS
            # 使用 Homebrew 安装
            brew install mysql
            # 启动 MySQL 服务
            brew services start mysql
        b.Ubuntu/Debian
            # 更新包列表
            sudo apt update
            # 安装 MySQL
            sudo apt install mysql-server
            # 启动 MySQL 服务
            sudo systemctl start mysql
            sudo systemctl enable mysql
        c.Windows
            访问 MySQL 官网下载安装包。
    b.Redis 安装(可选)
        a.macOS
            brew install redis
            brew services start redis
        b.Ubuntu/Debian
            sudo apt install redis-server
            sudo systemctl start redis-server
            sudo systemctl enable redis-server

05.开发工具配置
    a.VS Code 推荐插件
        {
          "recommendations": [
            "golang.go",
            "vue.volar",
            "bradlc.vscode-tailwindcss",
            "esbenp.prettier-vscode",
            "ms-vscode.vscode-typescript-next",
            "formulahendry.auto-rename-tag",
            "christian-kohler.path-intellisense"
          ]
        }
    b.GoLand 配置建议
        Go Modules: 确保启用 Go Modules 支持。
        代码格式化: 配置 gofmt 和 goimports。
        代码检查: 启用 golint 和 go vet。

06.环境验证
    a.完成环境安装后,请运行以下命令验证环境是否正确配置:
        # 检查 Git
        git --version
        # 检查 Node.js 和 npm
        node -v && npm -v
        # 检查 Go
        go version
        # 检查 MySQL(需要先启动服务)
        mysql --version
        # 检查 Redis(如果安装了)
        redis-cli --version
    b.验证结果
        如果所有命令都能正常输出版本信息,说明环境配置成功!

1.3 项目初始化

01.快速启动
    a.克隆项目
        # 克隆主分支代码
        git clone https://github.com/flipped-aurora/gin-vue-admin.git
        # 进入项目目录
        cd gin-vue-admin
    b.项目结构概览
        gin-vue-admin/
        ├── server/          # 后端 Go 项目
        │   ├── main.go      # 程序入口
        │   ├── config.yaml  # 配置文件
        │   └── ...
        ├── web/             # 前端 Vue 项目
        │   ├── src/         # 源代码
        │   ├── package.json # 依赖配置
        │   └── ...
        └── README.md        # 项目说明

02.后端服务启动
    a.打开后端项目
        # 进入后端目录
        cd server
        # 使用 GoLand 打开(如果已安装)
        goland .
        # 或使用 VS Code 打开
        code .
    b.安装依赖
        # 添加缺失的模块依赖,移除未使用的依赖
        # 该命令会根据 go.mod 文件下载所需的第三方包
        go mod tidy
    c.启动后端服务
        方式一:命令行启动
        # 在 server 目录下运行
        go run main.go
        -----------------------------------------------------------------------------------------------------
        方式二:GoLand 启动
        在 GoLand 中打开 main.go 文件
        点击行号旁的绿色三角形按钮
        或使用快捷键 Ctrl+Shift+F10 (Windows/Linux) 或 Cmd+Shift+R (macOS)
        -----------------------------------------------------------------------------------------------------
        方式三:VS Code 启动
        按 F5 或点击调试按钮
        选择 "Go: Launch Package"
    d.验证后端启动
        如果看到以下输出,说明后端服务启动成功:
        [GIN-debug] Listening and serving HTTP on :8888
        访问 http://localhost:8888/health 检查服务状态。

03.前端应用启动
    a.打开前端项目
        # 进入前端目录
        cd web
        # 使用 VS Code 打开
        code .
    b.安装依赖
        # 安装项目依赖
        npm install
        # 或使用 yarn(如果已安装)
        yarn install
        # 或使用 pnpm(推荐,速度更快)
        pnpm install
    c.启动开发服务器
        # 启动开发服务器
        npm run serve
        # 或使用其他包管理器
        yarn serve
        pnpm serve
    d.验证前端启动
        如果看到以下输出,说明前端应用启动成功:
        App running at:
        - Local:   http://localhost:8080/
        - Network: http://192.168.1.100:8080/

04.数据库初始化
    a.访问初始化页面
        在浏览器中访问:http://localhost:8080/#/init
    b.配置数据库信息
        在初始化页面填写数据库连接信息:
        数据库类型: 选择 MySQL
        主机地址: 127.0.0.1
        端口: 3306
        用户名: root
        密码: 您的数据库密码
        数据库名: gva(如果不存在会自动创建)
    c.执行初始化
        确认所有信息无误
        点击 "立即初始化" 按钮
        等待初始化完成
    d.初始化完成
        初始化成功后,系统会:
        ✅ 创建所有必要的数据表
        ✅ 插入基础数据(管理员账号、菜单、权限等)
        ✅ 自动跳转到登录页面
    e.默认管理员账号
        用户名:admin
        密码:123456

1.4 AI助手集成

01.MCP AI助手集成
    a.功能说明
        通过MCP(Model Context Protocol)让AI编辑工具深度理解GVA项目结构,实现智能化的代码生成和项目管理。
    b.版本要求
        使用MCP功能需要GVA版本 ≥ 2.8.4。
        推荐使用模型:claude > gemini > gpt = kimi。
    c.核心特性
        智能代码生成:AI自动创建完整的CRUD模板。
        智能文件搜索:自动定位相关文件并提供精准修改建议。
        自动化流程:一键生成API接口和菜单配置。
        上下文理解:AI深度理解项目架构,提供更准确的代码联动。

02.AI编辑工具配置
    a.支持的AI编辑工具
        Trae
        Cursor
        Claude Code
        Windsurf
        Codebubby
        其他支持MCP协议的AI编辑器
    b.第一步:启动GVA项目
        确保你的GVA项目正在运行,MCP服务会自动在 http://127.0.0.1:8888/sse 启动。
    c.第二步:配置AI编辑器
        在你的AI编辑工具的配置文件中添加以下MCP配置:
        {
          "mcpServers": {
            "GVA Helper": {
              "url": "http://127.0.0.1:8888/sse"
            }
          }
        }
    d.第三步:重启编辑器
        保存配置后重启你的AI编辑工具,等待MCP连接建立,MCP状态显示绿色即表示连接成功。

03.AI助手新能力
    a.配置完成后,AI助手将获得以下超能力:
        深度理解项目:自动识别GVA项目结构和代码模式。
        智能代码生成:根据需求自动生成完整的功能模块。
        精准文件定位:快速找到相关文件并提供修改建议。
        全栈开发:同时处理前端、后端、数据库的代码生成。
        UI自动化:自动配置路由、菜单和权限系统。
    b.使用示例
        只需要告诉AI:"我想创建一个用户管理模块",AI就会:
        自动生成用户表结构。
        创建完整的CRUD API。
        生成前端管理页面。
        配置菜单和路由。
        设置权限控制。

06.配置文件说明
    a.yaml
        mcp:
            name: GVA_MCP  # MCP服务名称
            version: v1.0.0 # 版本号
            sse_path: /sse # SSE路径
            message_path: /message # 消息路径
            url_prefix: '' # URL前缀
            ## v2.8.6后可用
            addr: 8889 # 监听地址 在separate为true时有效 (暂时未实现独立运行功能,敬请期待)
            separate: false # 是否隔离 开启以后mcp将不会跟随gva本体启动 建议在生产环境开启
    b.自动填写页面参数示例
        点击生成后后端会获得MCP模板。
        在模板的handle函数中书写业务逻辑即可实现一个简单的mcp工具。

1.5 Swagger

01.Swagger API 文档
    a.功能说明
        Swagger 是一个用于设计、构建、记录和使用 RESTful Web 服务的开源软件框架。
        Gin-Vue-Admin 集成了 Swagger 来自动生成和维护 API 文档。
    b.核心功能
        API 文档自动生成:从代码注释自动生成文档。
        在线测试:直接在浏览器中测试 API。
        可视化界面:清晰的 API 结构展示。
        实时更新:代码变更时文档自动同步。

02.安装 Swagger
    a.方式一:直接安装(推荐)
        # 安装最新版本的 swag 工具
        go install github.com/swaggo/swag/cmd/swag@latest
    b.方式二:使用代理安装
        如果遇到网络问题,建议配置 Go 模块代理:
        # 启用 Go Modules
        go env -w GO111MODULE=on
        # 配置国内代理(选择其一)
        go env -w GOPROXY=https://goproxy.cn,direct
        # 或者使用
        # go env -w GOPROXY=https://goproxy.io,direct
        # 安装 swag 工具
        go install github.com/swaggo/swag/cmd/swag@latest
    c.验证安装
        安装完成后,验证 swag 工具是否正确安装:
        # 检查 swag 版本
        swag --version
        # 查看帮助信息
        swag --help

03.配置 Swagger 注释
    a.主程序注释
        在 main.go 文件中添加 API 基本信息:
        // @title           Gin-Vue-Admin API
        // @version         1.0
        // @description     This is a sample server for Gin-Vue-Admin.
        // @termsOfService  https://github.com/flipped-aurora/gin-vue-admin

        // @contact.name   API Support
        // @contact.url    https://github.com/flipped-aurora/gin-vue-admin/issues
        // @contact.email  [email protected]

        // @license.name  Apache 2.0
        // @license.url   http://www.apache.org/licenses/LICENSE-2.0.html

        // @host      localhost:8888
        // @BasePath  /

        // @securityDefinitions.apikey ApiKeyAuth
        // @in header
        // @name x-token

        func main() {
            // 应用程序代码
        }
    b.API 接口注释
        为每个 API 接口添加详细注释:
        // CreateUser 创建用户
        // @Tags      用户管理
        // @Summary   创建用户
        // @Description 创建新的用户账号
        // @Security  ApiKeyAuth
        // @accept    application/json
        // @Produce   application/json
        // @Param     data  body      request.CreateUserReq  true  "用户信息"
        // @Success   200   {object}  response.Response{data=response.UserResponse}  "创建成功"
        // @Failure   400   {object}  response.Response  "请求参数错误"
        // @Failure   500   {object}  response.Response  "内部服务器错误"
        // @Router    /user [post]
        func (u *UserApi) CreateUser(c *gin.Context) {
            // 接口实现代码
        }
    c.数据模型注释
        为数据结构添加注释:
        // User 用户信息
        type User struct {
            ID       uint   `json:"id" example:"1"`                    // 用户ID
            Username string `json:"username" example:"admin"`          // 用户名
            Email    string `json:"email" example:"[email protected]"` // 邮箱
            Phone    string `json:"phone" example:"13800138000"`       // 手机号
            Status   int    `json:"status" example:"1"`                // 状态:1-启用,0-禁用
        }

04.生成 API 文档
    a.生成文档
        # 在项目根目录(包含 main.go 的目录)下运行,生成 Swagger 文档
        swag init
    b.生成成功
        命令执行成功后,会在项目中生成以下文件:
        server/
        ├── docs/
        │   ├── docs.go      # 文档数据
        │   ├── swagger.json # JSON 格式文档
        │   └── swagger.yaml # YAML 格式文档
        └── main.go
    c.自动化生成
        a.您也可以在 main.go 中添加 go:generate 指令来自动化文档生成
            //go:generate swag init
            package main
        b.然后使用 go generate 命令
            go generate

05.访问 Swagger 文档
    a.启动服务
        # 后端服务正在运行:
        go run main.go
    b.访问文档
        # 在浏览器中访问 Swagger UI:
        本地访问: http://localhost:8888/swagger/index.html
    c.文档功能
        Swagger UI 提供以下功能:
        API 列表: 查看所有可用的 API 接口。
        接口详情: 查看每个接口的参数、响应等详细信息。
        在线测试: 直接在页面中测试 API 接口。
        下载文档: 下载 JSON 或 YAML 格式的 API 文档。

06.使用技巧
    a.接口分组
        使用 @Tags 注释对接口进行分组:
        // @Tags 用户管理
        // @Tags 权限管理
        // @Tags 系统设置
    b.参数验证
        结合 binding 标签进行参数验证:
        type CreateUserReq struct {
            Username string `json:"username" binding:"required" example:"admin"`     // 用户名(必填)
            Password string `json:"password" binding:"required,min=6" example:"123456"` // 密码(必填,最少6位)
            Email    string `json:"email" binding:"email" example:"[email protected]"` // 邮箱(邮箱格式)
        }
    c.响应示例
        提供详细的响应示例:
        // @Success 200 {object} response.Response{data=[]model.User} "获取成功"
        // @Success 200 {object} response.PageResult{list=[]model.User} "分页获取成功"
    d.错误处理
        定义常见的错误响应:
        // @Failure 400 {object} response.Response "请求参数错误"
        // @Failure 401 {object} response.Response "未授权"
        // @Failure 403 {object} response.Response "权限不足"
        // @Failure 404 {object} response.Response "资源不存在"
        // @Failure 500 {object} response.Response "内部服务器错误"

07.高级配置
    a.自定义配置
        创建 .swaggo 配置文件:
        # .swaggo
        dir: ./
        general_info: main.go
        output_dir: ./docs
        parse_vendor: false
        parse_dependency: false
        parse_internal: false
        generate_types: false
    b.排除特定文件
        使用 --exclude 参数排除不需要解析的文件:
        swag init --exclude ./vendor
    c.指定输出目录
        指定输出目录:
        swag init --output ./api-docs

08.最佳实践
    1.及时更新文档:每次修改 API 后都要重新生成文档。
    2.详细注释:为每个接口提供清晰的描述和示例。
    3.统一规范:团队内部统一注释格式和命名规范。
    4.版本管理:为不同版本的 API 维护对应的文档。
    5.测试验证:使用 Swagger UI 测试接口确保文档准确性。

1.6 VsCode

01.快速开始
    a.打开工作区
        a.推荐使用 VS Code 的工作区功能来管理整个项目
            # 进入项目根目录
            cd gin-vue-admin
            # 使用 VS Code 打开整个项目
            code .
        b.或者打开预配置的工作区文件
            VS Code 工作区。
    b.工作区配置
        a.创建 .vscode/gin-vue-admin.code-workspace 文件:
            {
              "folders": [
                {
                  "name": "🔧 Server (Go)",
                  "path": "./server"
                },
                {
                  "name": "🎨 Web (Vue)",
                  "path": "./web"
                },
                {
                  "name": "📚 Docs",
                  "path": "./docs"
                }
              ],
              "settings": {
                "go.gopath": "",
                "go.goroot": "",
                "go.toolsManagement.autoUpdate": true,
                "typescript.preferences.importModuleSpecifier": "relative",
                "eslint.workingDirectories": ["web"],
                "editor.codeActionsOnSave": {
                  "source.fixAll.eslint": true
                }
              },
              "extensions": {
                "recommendations": [
                  "golang.go",
                  "vue.volar",
                  "bradlc.vscode-tailwindcss",
                  "esbenp.prettier-vscode",
                  "ms-vscode.vscode-typescript-next"
                ]
              }
            }

02.必备插件安装
    a.Go 开发插件
        Go (golang.go):Go 语言官方插件,提供语法高亮、智能提示、调试等功能。
        Go Outliner (766b.go-outliner):显示 Go 文件的结构大纲。
    b.Vue 开发插件
        Vue Language Features (Volar) (vue.volar):Vue 3 官方语言支持,替代 Vetur,提供更好的 TypeScript 支持。
        TypeScript Vue Plugin (Volar) (vue.vscode-typescript-vue-plugin):Vue 文件的 TypeScript 支持。
    c.通用开发插件
        Prettier - Code formatter (esbenp.prettier-vscode):代码格式化工具。
        ESLint (dbaeumer.vscode-eslint):JavaScript/TypeScript 代码检查。
        Tailwind CSS IntelliSense (bradlc.vscode-tailwindcss):Tailwind CSS 智能提示。
        Auto Rename Tag (formulahendry.auto-rename-tag):自动重命名配对的 HTML/XML 标签。
        Path Intellisense (christian-kohler.path-intellisense):文件路径智能提示。
    d.安装插件
        使用命令行安装推荐插件:
        code --install-extension golang.go
        code --install-extension vue.volar
        code --install-extension vue.vscode-typescript-vue-plugin
        code --install-extension esbenp.prettier-vscode
        code --install-extension dbaeumer.vscode-eslint
        code --install-extension bradlc.vscode-tailwindcss
        code --install-extension formulahendry.auto-rename-tag
        code --install-extension christian-kohler.path-intellisense

03.运行和调试配置
    a.创建调试配置
        在项目根目录创建 .vscode/launch.json 文件:
        -----------------------------------------------------------------------------------------------------
        {
          "version": "0.2.0",
          "configurations": [
            {
              "name": "🔧 Launch Server (Go)",
              "type": "go",
              "request": "launch",
              "mode": "auto",
              "program": "${workspaceFolder}/server/main.go",
              "cwd": "${workspaceFolder}/server",
              "env": {
                "GIN_MODE": "debug"
              },
              "args": [],
              "showLog": true,
              "console": "integratedTerminal"
            },
            {
              "name": "🎨 Launch Web (Node)",
              "type": "node",
              "request": "launch",
              "cwd": "${workspaceFolder}/web",
              "runtimeExecutable": "npm",
              "runtimeArgs": ["run", "serve"]
            }
          ],
          "compounds": [
            {
              "name": "🚀 Launch Both (Server + Web)",
              "configurations": [
                "🔧 Launch Server (Go)",
                "🎨 Launch Web (Node)"
              ],
              "stopAll": true
            }
          ]
        }
    b.创建任务配置
        创建 .vscode/tasks.json 文件:
        -----------------------------------------------------------------------------------------------------
        {
          "version": "2.0.0",
          "tasks": [
            {
              "label": "🔧 Build Server",
              "type": "shell",
              "command": "go",
              "args": ["build", "-o", "gin-vue-admin", "main.go"],
              "options": {
                "cwd": "${workspaceFolder}/server"
              },
              "group": "build",
              "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared"
              }
            },
            {
              "label": "🎨 Build Web",
              "type": "shell",
              "command": "npm",
              "args": ["run", "build"],
              "options": {
                "cwd": "${workspaceFolder}/web"
              },
              "group": "build",
              "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared"
              }
            },
            {
              "label": "📝 Generate Swagger",
              "type": "shell",
              "command": "swag",
              "args": ["init"],
              "options": {
                "cwd": "${workspaceFolder}/server"
              },
              "group": "build",
              "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared"
              }
            }
          ]
        }
    c.运行项目
        a.方式一:使用调试面板
            按 Ctrl+Shift+D (Windows/Linux) 或 Cmd+Shift+D (macOS) 打开调试面板。
            选择要运行的配置:
            Launch Server (Go): 仅启动后端服务。
            Launch Web (Node): 仅启动前端应用。
            Launch Both (Server + Web): 同时启动前后端。
        b.方式二:使用终端
            # 启动后端(在 server 目录)
            cd server
            go run main.go

            # 启动前端(在 web 目录)
            cd web
            npm run serve
        c.方式三:使用任务
            按 Ctrl+Shift+P (Windows/Linux) 或 Cmd+Shift+P (macOS)。
            输入 "Tasks: Run Task"。
            选择要执行的任务。

04.Go 环境配置
    a.配置 Go 模块代理
        为了提高依赖下载速度,建议配置 Go 模块代理:
        # 启用 Go Modules
        go env -w GO111MODULE=on

        # 配置模块代理
        go env -w GOPROXY=https://goproxy.cn,direct

        # 配置校验和数据库
        go env -w GOSUMDB=sum.golang.google.cn
    b.VS Code Go 插件配置
        在 VS Code 设置中添加以下配置:
        {
          "go.toolsManagement.autoUpdate": true,
          "go.useLanguageServer": true,
          "go.gocodeAutoBuild": false,
          "go.lintOnSave": "package",
          "go.vetOnSave": "package",
          "go.buildOnSave": "package",
          "go.testOnSave": false,
          "go.coverOnSave": false,
          "go.formatTool": "goimports",
          "go.gotoSymbol.includeImports": true,
          "go.gotoSymbol.includeGoroot": true,
          "gopls": {
            "experimentalWorkspaceModule": true,
            "completeUnimported": true,
            "usePlaceholders": true,
            "deepCompletion": true
          }
        }
    c.安装 Go 工具
        在 VS Code 中按 Ctrl+Shift+P,输入 "Go: Install/Update Tools",选择所有工具进行安装。

05.前端开发配置
    a.Prettier 配置
        在 web 目录创建 .prettierrc 文件:
        {
          "semi": false,
          "singleQuote": true,
          "tabWidth": 2,
          "trailingComma": "none",
          "printWidth": 100,
          "bracketSpacing": true,
          "arrowParens": "avoid"
        }
    b.ESLint 配置
        确保 web 目录有正确的 .eslintrc.js 配置文件。
    c.VS Code 前端设置
        在 VS Code 设置中添加以下配置:
        {
          "typescript.preferences.importModuleSpecifier": "relative",
          "typescript.suggest.autoImports": true,
          "editor.codeActionsOnSave": {
            "source.fixAll.eslint": true
          },
          "eslint.workingDirectories": ["web"],
          "vetur.validation.template": false,
          "vetur.validation.script": false,
          "vetur.validation.style": false
        }

06.实用技巧
    a.代码片段
        创建 Go 代码片段 .vscode/go.code-snippets:
        {
          "Gin Handler": {
            "prefix": "ginhandler",
            "body": [
              "// $1 $2",
              "// @Tags $3",
              "// @Summary $2",
              "// @Security ApiKeyAuth",
              "// @accept application/json",
              "// @Produce application/json",
              "// @Success 200 {object} response.Response \"成功\"",
              "// @Router /$4 [$5]",
              "func (a *$6Api) $1(c *gin.Context) {",
              "\t$0",
              "}"
            ],
            "description": "Create a Gin handler with Swagger annotations"
          }
        }
    b.快捷键配置
        在 keybindings.json 中添加自定义快捷键:
        [
          {
            "key": "ctrl+shift+r",
            "command": "workbench.action.tasks.runTask",
            "args": "🔧 Build Server"
          },
          {
            "key": "ctrl+shift+w",
            "command": "workbench.action.tasks.runTask",
            "args": "🎨 Build Web"
          }
        ]
    c.文件关联
        在设置中添加文件关联:
        {
          "files.associations": {
            "*.vue": "vue",
            "*.go": "go",
            "*.yaml": "yaml",
            "*.yml": "yaml"
          }
        }

07.调试技巧
    a.Go 调试
        在代码行号左侧点击设置断点。
        使用 F5 开始调试。
        使用 F10 单步执行,F11 步入函数。
        在调试控制台查看变量值。
    b.前端调试
        使用浏览器开发者工具。
        在 VS Code 中安装 "Debugger for Chrome" 插件。
        配置浏览器调试。
    c.日志查看
        使用集成终端查看应用日志。
        配置输出面板显示不同类型的日志。

08.性能优化
    a.排除文件
        在 .vscode/settings.json 中排除不必要的文件:
        {
          "files.exclude": {
            "**/node_modules": true,
            "**/dist": true,
            "**/.git": true,
            "**/vendor": true
          },
          "search.exclude": {
            "**/node_modules": true,
            "**/dist": true,
            "**/vendor": true
          }
        }
    b.禁用不需要的插件
        在工作区中禁用不相关的插件以提高性能。

2 代码生成

2.1 创建包名

01.创建package
    点击左侧菜单栏的自动化Package进入页面,点击新增打开抽屉

02.填写信息
    抽屉中关键属性为包名,此处填写小写字母开头的驼峰式命名单词,这是你自动化代码的基础包,
    所有在创建自动化代码时候选择本package的代码,都会创建在由本功能自动创建出的文件夹下。
    此处展示以showGva为例,自动生成的文件目录。

03.完成创建
    创建完成后,会在web和server下创建对应的package文件夹,
    如下所示 web/src/api/showGva web/src/view/showGva server/api/showGva
    内含文件 enter.go server/router/showGva
    内含文件 enter.go server/service/showGva
    内含文件 enter.go

2.2 使用指南

01.代码生成器
    a.功能说明
        通过代码生成器可以从现有数据库创建代码,选择数据库中的表,然后生成对应的代码。
    b.界面名称与生成结构体
        Struct名称(StructName):结构体名称,位于 server/model 文件夹下,首字母必须大写。
        TableName:指定表名(非必填),数据库中生成的与结构体对应的数据表名。
        Struct简称(Abbreviation):简称会作为入参对象名和路由 group。
        Struct中文名称(Description):中文描述作为自动api描述。
        文件名称(PackageName):生成文件的默认名称,使用小驼峰形式命名。
        Package(包):生成的目标包,必选。
        业务库(BusinessDB):选择业务库,可选。
        使用GVA结构(GvaModel):建议选中。
        创建资源标识(AutoCreateResource):可选。
        自动创建api(AutoCreateApiToSql):建议选中。
        自动创建菜单(AutoCreateMenuToSql):建议选中。
        自动移动文件(AutoMoveFile):建议选中。

02.字段界面说明
    a.组件内容名称与生成结构体
        Field名称(FieldName):结构体名称,首字母大写。
        Field中文名(FieldDesc):结构体中文名称。
        FieldJSON(FieldJson):golang struct tag json。
        数据库字段名(ColumnName):数据库字段名。
        Field数据类型(FieldType):字段对应golang数据类型。
        数据库字段长度(DataTypeLong):字段数据类型长度。
        数据库字段描述(Comment):数据库字段描述。
        默认值(DefaultValue):数据库字段默认值。
        是否必填(Require):自动创建前后端必填校验。
        校验失败文案(ErrorText):必填校验失败的文案。
        是否排序(Sort):自动创建前后端排序代码。
        前端可见(Front):前端是否创建本字段。
        主键(PrimaryKey):数据库主键。
        是否可清空(Clearable):前端输入框右侧是否出现X。
        Field查询条件(FieldSearchType):搜索类型。
        关联字典(DictType):关联字典标记。
        数据源配置(dataSource:{table,label,value}):数据源配置。

03.生成一步到位代码包
    a.自行设计业务基础结构体模型
        点击左侧菜单中的 系统工具 → 代码生成器。
        填写好 Struct名称、TableName、Struct简称、Struct中文名称、文件名称。
        选择好 自动创建api、自动移动文件 按钮。
        点击 新增Field 按钮,为数据表、struct结构体创建字段。
    b.从数据库的选择表进行生成结构体
        点击左侧菜单中的系统工具 > 代码生成器。
        点击 点这里从现有数据库创建代码。
        选择 数据库名 以及 表名。
        点击使用此表创建。
        自行编辑好各个Filed的所需的搜索条件。
    c.点击生成代码按钮
        完成1.1或1.2步骤操作。
        代码会自动移动到前后端你所创建的package文件夹下。
        提示:在创建自动化代码时候会自动创建enter.go。

04.注册路由和数据库表
    a.注册路由
        server/initialize/router.go
        初始化总路由。
    b.注册数据库表
        server/initialize/gorm.go
        将你的model结构配置进入 db.AutoMigrate()内部即可。

06.配置目录菜单
    a.进入系统 超级管理员 → 菜单管理 菜单,点击 新增根菜单 按钮,配置菜单信息。
        略
    b.配置项说明
        路由name:对应进入列表显示页面时的访问路径。
        路由path:选中后边的“添加参数”后才可以输入。
        是否隐藏:是否在系统左侧目录菜单显示时,隐藏掉该目录菜单。
        父节点Id:该目录菜单的父级目录菜单。
        文件路径:对应前端项目中 /view/ PackageName (自建)/StructName.vue 文件。
        展示名称:该目录菜单显示在系统左侧目录菜单中的名称。
        图标:该目录菜单显示在系统左侧目录菜单中的图标。
        排序标记:用于调整该目录菜单在系统左侧目录菜单中显示的上下位置。
        keepAlive:是否使用keepAlive缓存。

07.配置后端接口
    a.如果在第一步的自动创建api打钩了即可跳过此步。
        略
    b.进入系统 超级管理员 → api管理 菜单,点击 新增api 按钮,配置接口信息。
        略
    c.配置项说明
        路径:接口路径。
        请求:根据接口实际选择。
        api分组:对应 struct 简称。
        api简介:对api的简要说明。

08.配置角色权限
    a.进入系统 超级管理员 → 角色管理 菜单,找到需要设置权限的角色,点击对应的 设置权限 按钮,配置角色相关权限。
        略
    b.配置项说明
        角色菜单:勾选该角色可以访问的目录菜单。
        角色api:勾选该角色可以访问的接口。

09.完善新增表单弹窗/页面
    a.注意
        在 v2.3.5 版本后,不再需要手动创建表单。
    b.进入系统 系统工具 → 表单生成器 菜单,根据自己的实际需求,将左侧组件拖拽至中间画布区域,并在右侧设置组件属性。
        略
    c.配置项说明
        组件类型:默认是左侧选中的组件类型。
        字段名:对应 Step3 中的 FieldJSON 字段。
        标题:即组件label。
        占位提示:占位提示。
    d.复制代码
        点击画布上方的 复制代码 按钮。
        选择 生成类型 是 页面 还是 弹窗。
        点击 确定 按钮,复制表单代码。
    e.代码应用
        取出 <el-form>……</el-form> 部分代码,覆盖掉前端项目中 src → view → PackageName(自建) → StructName.vue 中的表单。
        将 js 部分data方法里返回的对象复制到前端项目中对应的 .vue 文件的 js 部分。

2.3 常见问题

01.127.0.0.1拒绝连接
    a.出现这种情况
        旧版本,请到 web/src/view/systemTools/formCreate/index.vue 找到 127.0.0.1 替换为本机或服务器ip
        新版本,请前往相对应的环境变量中修改 制定参数 path : VITE_BASE_PATH port :VITE_SERVER_PORT
    b.修改对应IP
        server项目默认端口是 8888 ,如果你修改,那也要把 127.0.0.1:8888 相应的端口修改 ip:自定义端口

02.生产使用表单生成器
    a.需修改web/src/view/systemTools/formCreate/index.vue
        <template>
          <div style="height:80vh">
            <iframe width="100%" height="100%" :src="`${basePath}:${basePort}/form-generator/#/`" frameborder="0" />
          </div>
        </template>
        -----------------------------------------------------------------------------------------------------
        修改为
        -----------------------------------------------------------------------------------------------------
        <template>
          <div style="height:80vh">
            <iframe width="100%" height="100%" :src="`${basePath}/form-generator/#/`" frameborder="0" />
          </div>
        </template>
    b.添加修改nginx配置
        location  /form-generator {
            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;
            proxy_pass http://127.0.0.1:8888;
        }
    c.同时web/.env.production配置为
        ENV = 'production'

        VITE_CLI_PORT = 8080
        VITE_SERVER_PORT = 8888
        VITE_BASE_API = /api
        #下方修改为你的线上域名
        VITE_BASE_PATH = https://你的线上域名

2.4 开发规范指南

01.Go代码规范
    a.命名规范
        // 包名:小写,简短,有意义
        package user
        package system

        // 常量:大写,下划线分隔
        const (
            MAX_RETRY_COUNT = 3
            DEFAULT_TIMEOUT = 30
            API_VERSION     = "v1"
        )

        // 变量:驼峰命名
        var (
            userService    *UserService
            configFilePath string
            isDebugMode    bool
        )

        // 函数:大写开头(公开),小写开头(私有)
        func GetUserList() []User {}
        func createUser() error {}

        // 结构体:大写开头,驼峰命名
        type UserService struct {
            db    *gorm.DB
            redis *redis.Client
        }

        // 接口:以 -er 结尾或描述性名称
        type UserRepository interface {
            Create(user *User) error
            GetByID(id uint) (*User, error)
        }

        type Validator interface {
            Validate() error
        }
    b.注释规范
        // Package user 提供用户管理相关功能
        // 包括用户的增删改查、权限验证等操作
        package user

        // UserService 用户服务,负责处理用户相关的业务逻辑
        type UserService struct {
            db    *gorm.DB
            redis *redis.Client
        }

        // NewUserService 创建用户服务实例
        // 参数:
        //   db: 数据库连接
        //   redis: Redis连接
        // 返回:
        //   *UserService: 用户服务实例
        func NewUserService(db *gorm.DB, redis *redis.Client) *UserService {
            return &UserService{
                db:    db,
                redis: redis,
            }
        }

        // GetUserByID 根据用户ID获取用户信息
        // 该方法会先从缓存中查找,如果缓存中不存在则从数据库查询
        // 参数:
        //   id: 用户ID
        // 返回:
        //   *User: 用户信息,如果用户不存在则返回nil
        //   error: 错误信息
        func (s *UserService) GetUserByID(id uint) (*User, error) {
            // 实现逻辑...
        }
    c.错误处理规范
        // 自定义错误类型
        type UserError struct {
            Code    int    `json:"code"`
            Message string `json:"message"`
            Data    string `json:"data,omitempty"`
        }

        func (e *UserError) Error() string {
            return e.Message
        }

        // 错误常量定义
        var (
            ErrUserNotFound     = &UserError{Code: 1001, Message: "用户不存在"}
            ErrUserExists       = &UserError{Code: 1002, Message: "用户已存在"}
            ErrInvalidPassword  = &UserError{Code: 1003, Message: "密码格式不正确"}
            ErrPermissionDenied = &UserError{Code: 1004, Message: "权限不足"}
        )

        // 错误处理示例
        func (s *UserService) CreateUser(user *User) error {
            // 验证用户是否已存在
            exists, err := s.userExists(user.Username)
            if err != nil {
                return fmt.Errorf("检查用户是否存在失败: %w", err)
            }
            if exists {
                return ErrUserExists
            }

            // 验证密码强度
            if err := s.validatePassword(user.Password); err != nil {
                return fmt.Errorf("密码验证失败: %w", err)
            }

            // 创建用户
            if err := s.db.Create(user).Error; err != nil {
                return fmt.Errorf("创建用户失败: %w", err)
            }

            return nil
        }
    d.结构体标签规范
        type User struct {
            ID        uint      `json:"id" gorm:"primarykey;comment:用户ID"`
            Username  string    `json:"username" gorm:"uniqueIndex;size:64;not null;comment:用户名" validate:"required,min=3,max=64"`
            Password  string    `json:"-" gorm:"size:255;not null;comment:密码" validate:"required,min=8"`
            Email     string    `json:"email" gorm:"uniqueIndex;size:128;comment:邮箱" validate:"email"`
            Phone     string    `json:"phone" gorm:"size:20;comment:手机号" validate:"phone"`
            Status    int       `json:"status" gorm:"default:1;comment:状态(1:启用 0:禁用)"`
            CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
            UpdatedAt time.Time `json:"updatedAt" gorm:"comment:更新时间"`
            DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
        }

        // TableName 指定表名
        func (User) TableName() string {
            return "sys_users"
        }

02.JavaScript/Vue 代码规范
    a.命名规范
        // 常量:大写,下划线分隔
        const API_BASE_URL = 'http://localhost:8888'
        const MAX_FILE_SIZE = 10 * 1024 * 1024

        // 变量:驼峰命名
        const userName = 'admin'
        const isLoggedIn = true
        const userList = []

        // 函数:驼峰命名,动词开头
        function getUserList() {}
        function createUser() {}
        function validateForm() {}
        function handleSubmit() {}

        // 类:大写开头,驼峰命名
        class UserService {
          constructor() {}

          async getUsers() {}
          async createUser(userData) {}
        }

        // 组件:大写开头,驼峰命名
        const UserList = {
          name: 'UserList',
          // ...
        }
    b.Vue 组件规范
        <template>
          <!-- 使用语义化的HTML标签 -->
          <div class="user-list-container">
            <!-- 条件渲染使用 v-if/v-show -->
            <div v-if="loading" class="loading-wrapper">
              <el-loading />
            </div>

            <!-- 列表渲染使用 v-for,必须添加 key -->
            <div
              v-for="user in userList"
              :key="user.id"
              class="user-item"
              @click="handleUserClick(user)"
            >
              {{ user.username }}
            </div>

            <!-- 事件处理使用方法名,不使用内联表达式 -->
            <el-button @click="handleAddUser">添加用户</el-button>
          </div>
        </template>

        <script>
        import { ref, reactive, computed, onMounted } from 'vue'
        import { ElMessage } from 'element-plus'
        import { getUserList, createUser } from '@/api/user'

        export default {
          name: 'UserList',

          // Props 定义要详细
          props: {
            departmentId: {
              type: [String, Number],
              required: true,
              validator: (value) => value > 0
            },
            showActions: {
              type: Boolean,
              default: true
            }
          },

          // Emits 声明
          emits: ['user-selected', 'user-created'],

          setup(props, { emit }) {
            // 响应式数据
            const loading = ref(false)
            const userList = ref([])
            const searchForm = reactive({
              keyword: '',
              status: 1
            })

            // 计算属性
            const filteredUsers = computed(() => {
              return userList.value.filter(user =>
                user.username.includes(searchForm.keyword)
              )
            })

            // 方法
            const fetchUserList = async () => {
              try {
                loading.value = true
                const { data } = await getUserList({
                  departmentId: props.departmentId,
                  ...searchForm
                })
                userList.value = data.list
              } catch (error) {
                ElMessage.error('获取用户列表失败')
                console.error('获取用户列表失败:', error)
              } finally {
                loading.value = false
              }
            }

            const handleUserClick = (user) => {
              emit('user-selected', user)
            }

            const handleAddUser = () => {
              // 处理添加用户逻辑
            }

            // 生命周期
            onMounted(() => {
              fetchUserList()
            })

            return {
              loading,
              userList,
              searchForm,
              filteredUsers,
              fetchUserList,
              handleUserClick,
              handleAddUser
            }
          }
        }
        </script>

        <style lang="scss" scoped>
        // 使用 BEM 命名规范
        .user-list-container {
          padding: 20px;

          .loading-wrapper {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 200px;
          }

          .user-item {
            padding: 10px;
            border: 1px solid #e4e7ed;
            border-radius: 4px;
            margin-bottom: 10px;
            cursor: pointer;
            transition: all 0.3s;

            &:hover {
              background-color: #f5f7fa;
              border-color: #409eff;
            }

            &--active {
              background-color: #ecf5ff;
              border-color: #409eff;
            }
          }
        }
        </style>
    c.API 调用规范
        // api/user.js
        import request from '@/utils/request'

        // API 函数命名:动词 + 资源名
        export function getUserList(params) {
          return request({
            url: '/user/getUserList',
            method: 'post',
            data: params
          })
        }

        export function createUser(data) {
          return request({
            url: '/user/register',
            method: 'post',
            data
          })
        }

        export function updateUser(id, data) {
          return request({
            url: `/user/setUserInfo`,
            method: 'put',
            data: { ...data, ID: id }
          })
        }

        export function deleteUser(id) {
          return request({
            url: '/user/deleteUser',
            method: 'delete',
            data: { ID: id }
          })
        }

        // 在组件中使用
        import { getUserList, createUser } from '@/api/user'

        export default {
          setup() {
            const fetchUsers = async () => {
              try {
                const response = await getUserList({ page: 1, pageSize: 10 })
                if (response.code === 0) {
                  userList.value = response.data.list
                } else {
                  ElMessage.error(response.msg || '获取用户列表失败')
                }
              } catch (error) {
                ElMessage.error('网络错误,请稍后重试')
                console.error('API调用失败:', error)
              }
            }

            return { fetchUsers }
          }
        }

03.Git工作流
    a.分支管理
        # 主要分支
        main/master    # 主分支,用于生产环境
        develop        # 开发分支,用于集成开发

        # 功能分支
        feature/user-management     # 功能开发分支
        feature/api-optimization    # 功能开发分支

        # 修复分支
        hotfix/login-bug           # 紧急修复分支
        bugfix/user-validation     # 普通修复分支

        # 发布分支
        release/v1.2.0             # 发布准备分支
    b.提交规范
        a.提交信息格式
            <type>(<scope>): <subject>

            <body>

            <footer>
        b.类型说明
            # 功能相关
            feat:     新功能
            fix:      修复bug
            perf:     性能优化

            # 代码相关
            refactor: 重构代码
            style:    代码格式调整

            # 文档和测试
            docs:     文档更新
            test:     测试相关

            # 构建和配置
            build:    构建系统或依赖更新
            ci:       CI配置更新
            chore:    其他杂项

            # 版本相关
            revert:   回滚提交
        c.提交示例
            # 新功能
            git commit -m "feat(user): 添加用户批量导入功能

            - 支持Excel文件导入
            - 添加数据验证
            - 支持导入进度显示

            Closes #123"

            # 修复bug
            git commit -m "fix(auth): 修复JWT token过期时间计算错误

            修复了token过期时间计算不准确的问题,
            现在使用UTC时间进行计算。

            Fixes #456"

            # 重构
            git commit -m "refactor(api): 重构用户API响应结构

            BREAKING CHANGE: 用户API响应格式发生变化
            - 将user_info改为userInfo
            - 添加了权限信息字段"
    c.分支操作流程
        a.功能开发流程
            # 1. 从develop分支创建功能分支
            git checkout develop
            git pull origin develop
            git checkout -b feature/user-management

            # 2. 开发功能
            # ... 编写代码 ...

            # 3. 提交代码
            git add .
            git commit -m "feat(user): 添加用户管理功能"

            # 4. 推送到远程
            git push origin feature/user-management

            # 5. 创建Pull Request
            # 在GitHub/GitLab上创建PR,请求合并到develop分支

            # 6. 代码审查通过后合并
            # 删除功能分支
            git checkout develop
            git pull origin develop
            git branch -d feature/user-management
            git push origin --delete feature/user-management
        b.紧急修复流程
            # 1. 从main分支创建hotfix分支
            git checkout main
            git pull origin main
            git checkout -b hotfix/login-bug

            # 2. 修复问题
            # ... 修复代码 ...

            # 3. 提交修复
            git add .
            git commit -m "fix(auth): 修复登录验证失败问题"

            # 4. 合并到main和develop
            git checkout main
            git merge hotfix/login-bug
            git push origin main

            git checkout develop
            git merge hotfix/login-bug
            git push origin develop

            # 5. 删除hotfix分支
            git branch -d hotfix/login-bug
            git push origin --delete hotfix/login-bug

            # 6. 创建版本标签
            git tag -a v1.1.1 -m "修复登录验证问题"
            git push origin v1.1.1

04.团队协作
    a.代码审查
        a.Pull Request 模板
            ## 变更描述

            简要描述本次变更的内容和目的。

            ## 变更类型

            - [ ] 新功能
            - [ ] Bug修复
            - [ ] 性能优化
            - [ ] 重构
            - [ ] 文档更新
            - [ ] 测试相关

            ## 测试

            - [ ] 单元测试通过
            - [ ] 集成测试通过
            - [ ] 手动测试通过
            - [ ] 性能测试通过(如适用)

            ## 检查清单

            - [ ] 代码符合项目规范
            - [ ] 添加了必要的注释
            - [ ] 更新了相关文档
            - [ ] 没有引入新的安全风险
            - [ ] 向后兼容(或已标记为破坏性变更)

            ## 相关Issue

            Closes #123
            Related to #456

            ## 截图(如适用)

            <!-- 添加截图或GIF来展示变更效果 -->

            ## 额外说明

            <!-- 任何需要审查者注意的特殊说明 -->
        b.代码审查要点
            ### 功能性审查
            - [ ] 功能是否按需求实现
            - [ ] 边界条件是否处理
            - [ ] 错误处理是否完善
            - [ ] 性能是否可接受

            ### 代码质量审查
            - [ ] 代码结构是否清晰
            - [ ] 命名是否规范
            - [ ] 注释是否充分
            - [ ] 是否有代码重复

            ### 安全性审查
            - [ ] 输入验证是否充分
            - [ ] 权限控制是否正确
            - [ ] 敏感信息是否保护
            - [ ] SQL注入等安全问题

            ### 测试审查
            - [ ] 测试覆盖率是否足够
            - [ ] 测试用例是否合理
            - [ ] 集成测试是否通过
    b.项目管理
        a.Issue 模板1
            ## Bug报告

            ### 环境信息
            - 操作系统:
            - 浏览器:
            - 项目版本:

            ### 问题描述

            简要描述遇到的问题。

            ### 复现步骤

            1. 打开页面
            2. 点击按钮
            3. 查看结果

            ### 期望行为

            描述期望的正确行为。

            ### 实际行为

            描述实际发生的行为。

            ### 截图

            如果适用,添加截图来帮助解释问题。

            ### 额外信息

            添加任何其他有助于解决问题的信息。
        b.Issue 模板2
            ## 功能需求

            ### 需求描述

            详细描述新功能的需求。

            ### 用户故事

            作为 [用户角色],我希望 [功能描述],以便 [价值/目的]。

            ### 验收标准

            - [ ] 标准1
            - [ ] 标准2
            - [ ] 标准3

            ### 设计稿

            <!-- 如果有设计稿,请添加链接或图片 -->

            ### 技术要求

            - 前端技术栈:
            - 后端技术栈:
            - 数据库变更:

            ### 优先级

            - [ ] 高
            - [ ] 中
            - [ ] 低

05.开发工具配置
    a.VS Code 配置
        a.settings.json
            {
              "go.formatTool": "goimports",
              "go.lintTool": "golangci-lint",
              "go.testFlags": ["-v"],
              "editor.formatOnSave": true,
              "editor.codeActionsOnSave": {
                "source.organizeImports": true
              },
              "eslint.validate": [
                "javascript",
                "javascriptreact",
                "vue"
              ],
              "vetur.format.defaultFormatter.html": "prettier",
              "vetur.format.defaultFormatter.js": "prettier",
              "vetur.format.defaultFormatter.css": "prettier"
            }
        b.推荐扩展
            {
              "recommendations": [
                "golang.go",
                "vue.volar",
                "esbenp.prettier-vscode",
                "dbaeumer.vscode-eslint",
                "bradlc.vscode-tailwindcss",
                "ms-vscode.vscode-json",
                "redhat.vscode-yaml"
              ]
            }
    b.Git Hooks
        a.pre-commit
            #!/bin/sh
            # .git/hooks/pre-commit

            echo "Running pre-commit checks..."

            # 检查Go代码格式
            if ! gofmt -l . | grep -q '^$'; then
                echo "Go code is not formatted. Please run 'gofmt -w .'"
                exit 1
            fi

            # 运行Go测试
            if ! go test ./...; then
                echo "Go tests failed"
                exit 1
            fi

            # 检查前端代码格式
            cd web
            if ! npm run lint; then
                echo "Frontend linting failed"
                exit 1
            fi

            echo "Pre-commit checks passed!"
        b.commit-msg
            #!/bin/sh
            # .git/hooks/commit-msg

            commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(:markdown-math{encoded=".%2B"})?: .{1,50}'

            if ! grep -qE ":markdown-math{single="true" encoded="commit_regex%22%20%22"}1"; then
                echo "Invalid commit message format!"
                echo "Format: <type>(<scope>): <subject>"
                echo "Example: feat(user): add user registration"
                exit 1
            fi

06.代码质量工具
    a.golangci.yml
        linters-settings:
          govet:
            check-shadowing: true
          golint:
            min-confidence: 0
          gocyclo:
            min-complexity: 15
          maligned:
            suggest-new: true
          dupl:
            threshold: 100
          goconst:
            min-len: 2
            min-occurrences: 2
          misspell:
            locale: US
          lll:
            line-length: 140
          goimports:
            local-prefixes: github.com/flipped-aurora/gin-vue-admin

        linters:
          enable:
            - bodyclose
            - deadcode
            - depguard
            - dogsled
            - dupl
            - errcheck
            - gochecknoinits
            - goconst
            - gocyclo
            - gofmt
            - goimports
            - golint
            - gosec
            - gosimple
            - govet
            - ineffassign
            - interfacer
            - lll
            - misspell
            - nakedret
            - scopelint
            - staticcheck
            - structcheck
            - stylecheck
            - typecheck
            - unconvert
            - unparam
            - unused
            - varcheck
            - whitespace

        run:
          timeout: 5m
    b.eslintrc.js
        module.exports = {
          root: true,
          env: {
            node: true,
            browser: true,
            es2021: true
          },
          extends: [
            'plugin:vue/vue3-essential',
            '@vue/standard'
          ],
          parserOptions: {
            ecmaVersion: 2021,
            sourceType: 'module'
          },
          rules: {
            'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
            'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
            'vue/multi-word-component-names': 'off',
            'space-before-function-paren': ['error', 'never'],
            'comma-dangle': ['error', 'never']
          }
        }

07.质量保证
    a.测试规范
        a.单元测试
            // user_service_test.go
            package service

            import (
                "testing"
                "github.com/stretchr/testify/assert"
                "github.com/stretchr/testify/mock"
            )

            func TestUserService_CreateUser(t *testing.T) {
                // 准备测试数据
                user := &User{
                    Username: "testuser",
                    Email:    "[email protected]",
                    Password: "password123",
                }

                // 创建mock
                mockDB := new(MockDB)
                mockDB.On("Create", mock.AnythingOfType("*User")).Return(nil)

                // 创建服务实例
                service := NewUserService(mockDB, nil)

                // 执行测试
                err := service.CreateUser(user)

                // 断言结果
                assert.NoError(t, err)
                mockDB.AssertExpectations(t)
            }

            func TestUserService_CreateUser_UserExists(t *testing.T) {
                user := &User{
                    Username: "existinguser",
                    Email:    "[email protected]",
                    Password: "password123",
                }

                mockDB := new(MockDB)
                mockDB.On("First", mock.AnythingOfType("*User"), mock.Anything).Return(nil)

                service := NewUserService(mockDB, nil)

                err := service.CreateUser(user)

                assert.Error(t, err)
                assert.Equal(t, ErrUserExists, err)
            }
        b.集成测试
            // integration_test.go
            package test

            import (
                "bytes"
                "encoding/json"
                "net/http"
                "net/http/httptest"
                "testing"
                "github.com/gin-gonic/gin"
                "github.com/stretchr/testify/assert"
            )

            func TestUserAPI_CreateUser(t *testing.T) {
                // 设置测试环境
                gin.SetMode(gin.TestMode)
                router := setupRouter()

                // 准备测试数据
                userData := map[string]interface{}{
                    "username": "testuser",
                    "email":    "[email protected]",
                    "password": "password123",
                }

                jsonData, _ := json.Marshal(userData)

                // 创建请求
                req, _ := http.NewRequest("POST", "/user/register", bytes.NewBuffer(jsonData))
                req.Header.Set("Content-Type", "application/json")

                // 执行请求
                w := httptest.NewRecorder()
                router.ServeHTTP(w, req)

                // 验证响应
                assert.Equal(t, http.StatusOK, w.Code)

                var response map[string]interface{}
                err := json.Unmarshal(w.Body.Bytes(), &response)
                assert.NoError(t, err)
                assert.Equal(t, float64(0), response["code"])
            }
    b.性能测试
        // benchmark_test.go
        package service

        import (
            "testing"
        )

        func BenchmarkUserService_GetUserByID(b *testing.B) {
            service := setupUserService()

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                _, err := service.GetUserByID(1)
                if err != nil {
                    b.Fatal(err)
                }
            }
        }

        func BenchmarkUserService_GetUserList(b *testing.B) {
            service := setupUserService()

            b.ResetTimer()
            for i := 0; i < b.N; i++ {
                _, _, err := service.GetUserList(PageInfo{Page: 1, PageSize: 10})
                if err != nil {
                    b.Fatal(err)
                }
            }
        }

2.5 常见问题解答

01.安装和启动问题
    a.后端启动问题
        a.问题:数据库连接失败
            a.错误信息
                failed to initialize database, got error dial tcp 127.0.0.1:3306: connect: connection refused
            b.解决方案
                a.检查数据库服务是否启动
                    # MySQL
                    sudo systemctl status mysql
                    # 或
                    brew services list | grep mysql

                    # PostgreSQL
                    sudo systemctl status postgresql
                    # 或
                    brew services list | grep postgresql
                b.检查配置文件 config.yaml
                    mysql:
                      path: 127.0.0.1
                      port: "3306"
                      config: charset=utf8mb4&parseTime=True&loc=Local
                      db-name: gin_vue_admin
                      username: root
                      password: "your_password"
                      prefix: ""
                      singular: false
                      engine: ""
                      max-idle-conns: 10
                      max-open-conns: 100
                      log-mode: ""
                      log-zap: false
                c.测试数据库连接
                    mysql -h 127.0.0.1 -P 3306 -u root -p
                d.创建数据库
                    CREATE DATABASE gin_vue_admin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
        b.问题:Redis 连接失败
            a.错误信息
                failed to ping redis, got error dial tcp 127.0.0.1:6379: connect: connection refused
            b.解决方案
                a.启动 Redis 服务
                    # Linux
                    sudo systemctl start redis

                    # macOS
                    brew services start redis

                    # 手动启动
                    redis-server
                b.检查 Redis 配置
                    redis:
                      db: 0
                      addr: 127.0.0.1:6379
                      password: ""
                c.测试 Redis 连接
                    redis-cli ping
        c.问题:端口被占用
            a.错误信息
                listen tcp :8888: bind: address already in use
            b.解决方案
                a.查找占用端口的进程
                    # Linux/macOS
                    lsof -i :8888
                    netstat -tulpn | grep 8888

                    # Windows
                    netstat -ano | findstr 8888
                b.终止占用进程
                    # Linux/macOS
                    kill -9 <PID>

                    # Windows
                    taskkill /PID <PID> /F
                c.或修改配置文件中的端口
                    system:
                      addr: 8889  # 修改为其他端口
    b.前端启动问题
        a.问题:依赖安装失败
            a.错误信息
                npm ERR! code ERESOLVECONFLICT
                npm ERR! ERESOLVECONFLICT unable to resolve dependency tree
            b.解决方案
                a.清理缓存和依赖
                    # 删除 node_modules 和 package-lock.json
                    rm -rf node_modules package-lock.json

                    # 清理 npm 缓存
                    npm cache clean --force

                    # 重新安装
                    npm install
                b.使用 yarn 替代 npm
                    # 安装 yarn
                    npm install -g yarn

                    # 使用 yarn 安装依赖
                    yarn install
                c.使用 --legacy-peer-deps 参数
                    npm install --legacy-peer-deps
        b.问题:Node.js 版本不兼容
            a.错误信息
                engine "node" is incompatible with this module
            b.解决方案
                a.检查 Node.js 版本
                    node --version
                b.安装推荐版本(Node.js 16+)
                    # 使用 nvm 管理 Node.js 版本
                    nvm install 18
                    nvm use 18
                c.或修改 package.json 中的引擎要求
                    {
                      "engines": {
                        "node": ">=14.0.0"
                      }
                    }
        c.问题:Vite 开发服务器启动失败
            a.错误信息
                Error: Cannot find module '@vitejs/plugin-vue'
            b.解决方案
                a.重新安装 Vite 相关依赖
                    npm install @vitejs/plugin-vue @vitejs/plugin-vue-jsx --save-dev
                b.检查 vite.config.js 配置
                    import { defineConfig } from 'vite'
                    import vue from '@vitejs/plugin-vue'
                    import { resolve } from 'path'

                    export default defineConfig({
                      plugins: [vue()],
                      resolve: {
                        alias: {
                          '@': resolve(__dirname, 'src')
                        }
                      },
                      server: {
                        port: 8080,
                        proxy: {
                          '/api': {
                            target: 'http://localhost:8888',
                            changeOrigin: true,
                            rewrite: (path) => path.replace(/^\/api/, '')
                          }
                        }
                      }
                    })

02.开发问题
    a.API 请求问题
        a.问题:跨域请求被阻止
            a.错误信息
                Access to XMLHttpRequest at 'http://localhost:8888/api/login' from origin 'http://localhost:8080' has been blocked by CORS policy
            b.解决方案
                a.后端配置 CORS 中间件
                    // middleware/cors.go
                    func Cors() gin.HandlerFunc {
                        return gin.HandlerFunc(func(c *gin.Context) {
                            method := c.Request.Method
                            origin := c.Request.Header.Get("Origin")
                            c.Header("Access-Control-Allow-Origin", origin)
                            c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
                            c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
                            c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
                            c.Header("Access-Control-Allow-Credentials", "true")
                            if method == "OPTIONS" {
                                c.AbortWithStatus(204)
                                return
                            }
                            c.Next()
                        })
                    }
                b.前端配置代理
                    // vite.config.js
                    export default defineConfig({
                      server: {
                        proxy: {
                          '/api': {
                            target: 'http://localhost:8888',
                            changeOrigin: true,
                            rewrite: (path) => path.replace(/^\/api/, '')
                          }
                        }
                      }
                    })
        b.问题:Token 验证失败
            a.错误信息
                {
                  "code": 7,
                  "data": {},
                  "msg": "token过期"
                }
            b.解决方案
                a.检查 Token 是否正确设置
                    // utils/request.js
                    service.interceptors.request.use(
                      config => {
                        const token = store.getters['user/token']
                        if (token) {
                          config.headers['x-token'] = token
                        }
                        return config
                      },
                      error => {
                        return Promise.reject(error)
                      }
                    )
                b.实现 Token 自动刷新
                    // utils/request.js
                    service.interceptors.response.use(
                      response => {
                        const res = response.data
                        if (res.code === 7) {
                          // Token 过期,尝试刷新
                          return store.dispatch('user/refreshToken').then(() => {
                            // 重新发送原请求
                            return service(response.config)
                          }).catch(() => {
                            // 刷新失败,跳转登录页
                            store.dispatch('user/logout')
                            router.push('/login')
                            return Promise.reject(new Error('Token 过期'))
                          })
                        }
                        return response
                      },
                      error => {
                        return Promise.reject(error)
                      }
                    )
    b.权限问题
        a.问题:权限验证失败
            a.错误信息
                {
                  "code": 7,
                  "data": {},
                  "msg": "权限不足"
                }
            b.解决方案
                a.检查用户角色权限配置
                    -- 查看用户权限
                    SELECT u.username, a.authority_name, ar.authority_id, ar.path, ar.method
                    FROM sys_users u
                    JOIN sys_authorities a ON u.authority_id = a.authority_id
                    JOIN sys_authority_menus am ON a.authority_id = am.sys_authority_authority_id
                    JOIN sys_base_menus m ON am.sys_base_menu_id = m.id
                    JOIN casbin_rule ar ON a.authority_id = ar.v0
                    WHERE u.username = 'your_username';
                b.添加权限规则
                    // 在系统管理 -> 角色管理 -> API权限中添加对应的权限
                    // 或通过代码添加
                    casbinService := service.ServiceGroupApp.SystemServiceGroup.CasbinService
                    casbinService.UpdateCasbin(authorityId, casbinInfos)
                c.检查 Casbin 配置
                    // config/casbin.go
                    func (c *Casbin) TableName() string {
                        return c.CasbinRule
                    }
    c.数据库问题
        a.问题:数据库迁移失败
            a.错误信息
                Error 1071: Specified key was too long; max key length is 767 bytes
            b.解决方案
                a.修改 MySQL 配置
                    -- 设置 innodb_large_prefix
                    SET GLOBAL innodb_large_prefix = 1;
                    SET GLOBAL innodb_file_format = 'Barracuda';
                    SET GLOBAL innodb_file_per_table = 1;
                b.修改表结构
                    // 在模型中指定索引长度
                    type SysUser struct {
                        Username string `gorm:"index:idx_username,length:191;comment:用户登录名"`
                        Email    string `gorm:"index:idx_email,length:191;comment:用户邮箱"`
                    }
        b.问题:外键约束错误
            a.错误信息
                Error 1452: Cannot add or update a child row: a foreign key constraint fails
            b.解决方案
                a.检查外键关联的数据是否存在
                    -- 检查关联数据
                    SELECT * FROM sys_authorities WHERE authority_id = 'your_authority_id';
                b.临时禁用外键检查
                    SET FOREIGN_KEY_CHECKS = 0;
                    -- 执行你的操作
                    SET FOREIGN_KEY_CHECKS = 1;
                c.修改 GORM 配置
                    // config/gorm.go
                    func GormConfig() *gorm.Config {
                        return &gorm.Config{
                            DisableForeignKeyConstraintWhenMigrating: true,
                            // 其他配置...
                        }
                    }

03.前端问题
    a.路由问题
        a.问题:路由跳转失败
            a.错误信息
                Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location
            b.解决方案
                a.检查路由跳转逻辑
                    // 避免重复跳转
                    if (this.$route.path !== '/target-path') {
                      this.$router.push('/target-path')
                    }
                b.使用 replace 替代 push
                    this.$router.replace('/target-path')
        b.问题:动态路由不生效
            a.解决方案
                a.检查路由注册顺序
                    // router/index.js
                    // 确保动态路由在静态路由之后注册
                    const routes = [
                      // 静态路由
                      { path: '/login', component: Login },
                      // 动态路由
                      ...asyncRoutes
                    ]
                b.检查权限验证逻辑
                    // store/modules/router.js
                    const actions = {
                      async GenerateRoutes({ commit }, roles) {
                        const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
                        commit('SET_ROUTES', accessedRoutes)
                        return accessedRoutes
                      }
                    }
    b.组件问题
        a.问题:Element Plus 组件样式异常
            a.解决方案:
                a.检查样式导入
                    // main.js
                    import 'element-plus/dist/index.css'
                b.检查主题配置
                    // styles/element-variables.scss
                    @forward 'element-plus/theme-chalk/src/common/var.scss' with (
                      $colors: (
                        'primary': (
                          'base': #409eff,
                        ),
                      )
                    );
        b.问题:组件响应式失效
            a.解决方案
                a.检查数据响应式声明
                    // Vue 3 Composition API
                    import { ref, reactive } from 'vue'

                    export default {
                      setup() {
                        const count = ref(0)
                        const state = reactive({
                          name: 'test'
                        })

                        return {
                          count,
                          state
                        }
                      }
                    }
                b.检查数组/对象更新方式
                    // 错误方式
                    this.list[0] = newItem

                    // 正确方式
                    this.$set(this.list, 0, newItem)
                    // 或
                    this.list.splice(0, 1, newItem)

04.部署问题
    a.Docker 部署问题
        a.问题:Docker 镜像构建失败
            a.错误信息
                failed to solve with frontend dockerfile.v0: failed to read dockerfile
            b.解决方案
                a.检查 Dockerfile 语法
                    # 确保 Dockerfile 格式正确
                    FROM golang:1.19-alpine AS builder

                    WORKDIR /app
                    COPY . .
                    RUN go mod download
                    RUN go build -o main .

                    FROM alpine:latest
                    RUN apk --no-cache add ca-certificates
                    WORKDIR /root/
                    COPY --from=builder /app/main .
                    COPY --from=builder /app/config.yaml .
                    CMD ["./main"]
                b.检查 .dockerignore 文件
                    node_modules
                    .git
                    .gitignore
                    README.md
                    Dockerfile
                    .dockerignore
        b.问题:容器启动失败
            a.错误信息
                standard_init_linux.go: exec user process caused: no such file or directory
            b.解决方案
                a.检查可执行文件权限
                    RUN chmod +x ./main
                b.使用正确的基础镜像
                    # 对于 Go 程序,使用 alpine 或 scratch
                    FROM alpine:latest
                    # 或
                    FROM scratch
    b.Nginx 配置问题
        a.问题:前端路由 404
            a.解决方案
                server {
                    listen 80;
                    server_name your-domain.com;
                    root /var/www/html;
                    index index.html;

                    # 处理前端路由
                    location / {
                        try_files $uri $uri/ /index.html;
                    }

                    # API 代理
                    location /api {
                        proxy_pass http://backend:8888;
                        proxy_set_header Host $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;
                    }
                }
        b.问题:静态资源加载失败
            a.解决方案
                server {
                    # 静态资源缓存
                    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
                        expires 1y;
                        add_header Cache-Control "public, immutable";
                        try_files $uri =404;
                    }

                    # 字体文件
                    location ~* \.(woff|woff2|ttf|eot)$ {
                        expires 1y;
                        add_header Cache-Control "public";
                        add_header Access-Control-Allow-Origin "*";
                    }
                }

05.调试技巧
    a.后端调试
        a.启用详细日志
            # config.yaml
            zap:
              level: 'debug'  # 设置为 debug 级别
              format: 'console'
              prefix: '[gin-vue-admin]'
              director: 'log'
              show-line: true
              encode-level: 'LowercaseColorLevelEncoder'
              stacktrace-key: 'stacktrace'
              log-in-console: true
        b.使用 Delve 调试器
            # 安装 Delve
            go install github.com/go-delve/delve/cmd/dlv@latest

            # 启动调试
            dlv debug main.go

            # 在代码中设置断点
            (dlv) break main.main
            (dlv) continue
    b.前端调试
        a.使用 Vue DevTools
            安装浏览器扩展
            在开发环境中启用
            // main.js
            if (process.env.NODE_ENV === 'development') {
              app.config.devtools = true
            }
        b.网络请求调试
            // utils/request.js
            service.interceptors.request.use(
              config => {
                console.log('Request:', config)
                return config
              }
            )

            service.interceptors.response.use(
              response => {
                console.log('Response:', response)
                return response
              },
              error => {
                console.error('Request Error:', error)
                return Promise.reject(error)
              }
            )

06.性能问题
    a.后端性能优化
        a.数据库查询优化
            // 使用索引
            db.Where("username = ?", username).First(&user)

            // 预加载关联数据
            db.Preload("Authorities").Find(&users)

            // 分页查询
            db.Limit(pageSize).Offset(offset).Find(&users)

            // 只查询需要的字段
            db.Select("id, username, email").Find(&users)
        b.缓存优化
            // 使用 Redis 缓存
            func GetUserFromCache(userID uint) (*system.SysUser, error) {
                key := fmt.Sprintf("user:%d", userID)
                cached := global.GVA_REDIS.Get(context.Background(), key).Val()

                if cached != "" {
                    var user system.SysUser
                    err := json.Unmarshal([]byte(cached), &user)
                    return &user, err
                }

                // 从数据库查询并缓存
                var user system.SysUser
                err := global.GVA_DB.First(&user, userID).Error
                if err != nil {
                    return nil, err
                }

                userJSON, _ := json.Marshal(user)
                global.GVA_REDIS.Set(context.Background(), key, userJSON, 30*time.Minute)

                return &user, nil
            }
    b.前端性能优化
        a.组件懒加载
            // 路由懒加载
            const routes = [
              {
                path: '/dashboard',
                component: () => import('@/views/dashboard/index.vue')
              }
            ]

            // 组件懒加载
            export default {
              components: {
                HeavyComponent: () => import('@/components/HeavyComponent.vue')
              }
            }
        b.虚拟滚动
            <template>
              <el-table
                v-loading="loading"
                :data="visibleData"
                height="400"
                @scroll="handleScroll"
              >
                <!-- 表格列定义 -->
              </el-table>
            </template>

            <script>
            export default {
              data() {
                return {
                  allData: [],
                  visibleData: [],
                  scrollTop: 0,
                  itemHeight: 50,
                  containerHeight: 400
                }
              },
              computed: {
                startIndex() {
                  return Math.floor(this.scrollTop / this.itemHeight)
                },
                endIndex() {
                  const visibleCount = Math.ceil(this.containerHeight / this.itemHeight)
                  return Math.min(this.startIndex + visibleCount, this.allData.length)
                }
              },
              watch: {
                startIndex() {
                  this.updateVisibleData()
                },
                endIndex() {
                  this.updateVisibleData()
                }
              },
              methods: {
                updateVisibleData() {
                  this.visibleData = this.allData.slice(this.startIndex, this.endIndex)
                },
                handleScroll(event) {
                  this.scrollTop = event.target.scrollTop
                }
              }
            }
            </script>

07.获取帮助
    a.提交 Bug 报告
        a.环境信息
            操作系统版本
            Go 版本
            Node.js 版本
            数据库版本
        b.错误描述
            详细的错误信息
            复现步骤
            期望行为
        c.相关代码
            最小复现代码
            配置文件
            日志信息
    b.常用调试命令
        # 查看 Go 版本
        go version

        # 查看 Node.js 版本
        node --version
        npm --version

        # 查看数据库版本
        mysql --version
        redis-server --version

        # 查看端口占用
        lsof -i :8888
        netstat -tulpn | grep 8888

        # 查看进程
        ps aux | grep gin-vue-admin

        # 查看日志
        tail -f log/server.log
        journalctl -u gin-vue-admin -f

        # Docker 相关
        docker ps
        docker logs container_name
        docker exec -it container_name /bin/sh

3 前端管理

3.1 操作指南

01.技术栈
    a.核心框架
        Vue 3:渐进式 JavaScript 框架
        Vite 4:下一代前端构建工具
        Element Plus:基于 Vue 3 的组件库
    b.状态管理
        Pinia:Vue 3 官方推荐的状态管理库
        Vue Router 4:Vue.js 官方路由管理器
    c.开发工具
        TypeScript:JavaScript 的超集(可选)
        ESLint:代码质量检查工具
        Prettier:代码格式化工具
        Sass/SCSS:CSS 预处理器
    d.构建优化
        Vite Plugin:丰富的插件生态
        Tree Shaking:自动移除未使用代码
        Code Splitting:代码分割优化
        Hot Module Replacement:热模块替换

02.前端目录结构
    web
    ├── babel.config.js
    ├── Dockerfile
    ├── favicon.ico
    ├── index.html                  -- 主页面
    ├── limit.js                    -- 助手代码
    ├── package.json                -- 包管理器代码
    ├── src                         -- 源代码
    │   ├── api                    -- api 组
    │   ├── App.vue                -- 主页面
    │   ├── assets                 -- 静态资源
    │   ├── components             -- 全局组件
    │   ├── core                   -- gva 组件包
    │   │   ├── config.js         -- gva网站配置文件
    │   │   ├── gin-vue-admin.js  -- 注册欢迎文件
    │   │   └── global.js         -- 统一导入文件
    │   ├── directive              -- v-auth 注册文件
    │   ├── main.js                -- 主文件
    │   ├── permission.js          -- 路由中间件
    │   ├── pinia                  -- pinia 状态管理器,取代vuex
    │   │   ├── index.js          -- 入口文件
    │   │   └── modules           -- modules
    │   │       ├── dictionary.js
    │   │       ├── router.js
    │   │       └── user.js
    │   ├── router                 -- 路由声明文件
    │   │   └── index.js
    │   ├── style                  -- 全局样式
    │   │   ├── base.scss
    │   │   ├── basics.scss
    │   │   ├── element_visiable.scss  -- 此处可以全局覆盖 element-plus 样式
    │   │   ├── iconfont.css           -- 顶部几个icon的样式文件
    │   │   ├── main.scss
    │   │   ├── mobile.scss
    │   │   └── newLogin.scss
    │   ├── utils                  -- 方法包库
    │   │   ├── asyncRouter.js    -- 动态路由相关
    │   │   ├── btnAuth.js        -- 动态权限按钮相关
    │   │   ├── bus.js            -- 全局mitt声明文件
    │   │   ├── date.js           -- 日期相关
    │   │   ├── dictionary.js     -- 获取字典方法
    │   │   ├── downloadImg.js    -- 下载图片方法
    │   │   ├── format.js         -- 格式整理相关
    │   │   ├── image.js          -- 图片相关方法
    │   │   ├── page.js           -- 设置页面标题
    │   │   ├── request.js        -- 统一请求文件
    │   │   └── stringFun.js      -- 字符串文件
    |   ├── view                   -- 主要view代码
    |   |   ├── about              -- 关于我们
    |   |   ├── dashboard          -- 面板
    |   |   ├── error              -- 错误
    |   |   ├── example            -- 上传案例
    |   |   ├── iconList           -- icon列表
    |   |   ├── init               -- 初始化数据
    |   |   ├── layout             -- layout约束页面
    |   |   |   ├── aside          -- 侧边栏
    |   |   |   ├── bottomInfo     -- bottomInfo
    |   |   |   ├── screenfull     -- 全屏设置
    |   |   |   ├── setting        -- 系统设置
    |   |   |   └── index.vue      -- base 约束
    |   |   ├── login              --登录
    |   |   ├── person             --个人中心
    |   |   ├── superAdmin         -- 超级管理员操作
    |   |   ├── system             -- 系统检测页面
    |   |   ├── systemTools        -- 系统配置相关页面
    |   |   └── routerHolder.vue   -- page 入口页面
    ├── vite.config.js             -- vite 配置文件
    └── yarn.lock

03.开发环境配置
    a.环境要求
        Node.js >= 16.0.0
        npm >= 8.0.0 或 yarn >= 1.22.0
        Git版本控制工具
    b.安装依赖
        # 进入前端目录
        cd web
        # 使用npm安装
        npm install
        # 或使用yarn安装
    c.开发命令
        # 启动开发服务器
        npm run serve
        # 或
        yarn serve
        # 构建生产版本
        npm run build
        # 或
        yarn build
        # 代码检查
        npm run lint
        # 或
        yarn lint
        # 代码格式化
        npm run format
        # 或
        yarn format

04.核心配置文件
    a.Vite配置 (vite.config.js)
        import { defineConfig } from 'vite'
        import vue from '@vitejs/plugin-vue'
        import { resolve } from 'path'
        import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'

        export default defineConfig({
          plugins: [
            vue(),
            createSvgIconsPlugin({
              iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
              symbolId: 'icon-[dir]-[name]'
            })
          ],
          resolve: {
            alias: {
              '@': resolve(__dirname, 'src')
            }
          },
          server: {
            port: 8080,
            proxy: {
              '/api': {
                target: 'http://127.0.0.1:8888',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
              }
            }
          },
          build: {
            outDir: 'dist',
            assetsDir: 'assets',
            rollupOptions: {
              output: {
                chunkFileNames: 'js/[name]-[hash].js',
                entryFileNames: 'js/[name]-[hash].js',
                assetFileNames: '[ext]/[name]-[hash].[ext]'
              }
            }
          }
        })
    b.项目配置 (src/core/config.js)
        // 系统全局配置
        export const config = {
          appName: 'Gin-Vue-Admin',
          appLogo: 'logoIco.png',
          showProgressBar: true,
          progressBarColor: '#409EFF',
          showInfoTip: true,

          // 布局配置
          layout: {
            showTagsView: true,
            showSidebarLogo: true,
            fixedHeader: true,
            sidebarTextTheme: true,
            showGreyMode: false,
            showColorWeakness: false
          },

          // 主题配置
          theme: {
            primaryColor: '#409EFF',
            successColor: '#67C23A',
            warningColor: '#E6A23C',
            dangerColor: '#F56C6C',
            infoColor: '#909399'
          }
        }

05.核心架构
    a.路由系统
        a.静态路由配置
            // src/router/index.js
            import { createRouter, createWebHistory } from 'vue-router'
            import { asyncRouterHandle } from '@/utils/asyncRouter'

            // 静态路由
            const routes = [
              {
                path: '/login',
                name: 'Login',
                component: () => import('@/view/login/index.vue')
              },
              {
                path: '/',
                redirect: '/dashboard'
              },
              {
                path: '/layout',
                name: 'Layout',
                component: () => import('@/view/layout/index.vue'),
                children: [
                  {
                    path: '/dashboard',
                    name: 'Dashboard',
                    component: () => import('@/view/dashboard/index.vue')
                  }
                ]
              }
            ]

            const router = createRouter({
              history: createWebHistory(),
              routes
            })

            export default router
        b.动态路由处理
            // src/utils/asyncRouter.js
            import { asyncRoutes } from '@/router/asyncRouter'

            // 动态路由处理
            export function asyncRouterHandle(asyncRouter) {
              asyncRouter.forEach(item => {
                if (item.component) {
                  if (item.component === 'view/routerHolder.vue') {
                    item.component = () => import('@/view/routerHolder.vue')
                  } else {
                    const component = item.component
                    item.component = () => import(`@/view/${component}`)
                  }
                }
                if (item.children) {
                  asyncRouterHandle(item.children)
                }
              })
              return asyncRouter
            }

            // 格式化路由
            export function formatRouter(routes, routeMap) {
              const newRoutes = []
              routes.forEach(item => {
                if (item.path === 'dashboard') {
                  item.component = () => import('@/view/dashboard/index.vue')
                } else if (item.component) {
                  item.component = routeMap[item.component] || (() => import(`@/view/error/404.vue`))
                }
                if (item.children && item.children.length > 0) {
                  item.children = formatRouter(item.children, routeMap)
                }
                newRoutes.push(item)
              })
              return newRoutes
            }
    b.状态管理 (Pinia)
        a.用户状态管理
            // src/pinia/modules/user.js
            import { defineStore } from 'pinia'
            import { login, getUserInfo, logout } from '@/api/user'
            import { jsonInBlacklist } from '@/api/jwt'
            import router from '@/router/index'

            export const useUserStore = defineStore('user', {
              state: () => ({
                userInfo: {
                  uuid: '',
                  nickName: '',
                  headerImg: '',
                  authority: {},
                  sideMode: 'dark',
                  activeColor: '#1890ff',
                  baseColor: '#fff'
                },
                token: '',
                mode: 'light'
              }),

              getters: {
                // 获取用户信息
                getUserInfo: (state) => state.userInfo,
                // 获取token
                getToken: (state) => state.token,
                // 获取模式
                getMode: (state) => state.mode
              },

              actions: {
                // 登录
                async LoginIn(loginInfo) {
                  try {
                    const res = await login(loginInfo)
                    if (res.code === 0) {
                      this.setUserInfo(res.data.user)
                      this.setToken(res.data.token)
                      await this.GetUserInfo()
                      return true
                    }
                  } catch (error) {
                    console.error('登录失败:', error)
                    return false
                  }
                },

                // 获取用户信息
                async GetUserInfo() {
                  try {
                    const res = await getUserInfo()
                    if (res.code === 0) {
                      this.setUserInfo(res.data.userInfo)
                    }
                    return res
                  } catch (error) {
                    console.error('获取用户信息失败:', error)
                  }
                },

                // 登出
                async LoginOut() {
                  try {
                    const res = await logout()
                    if (res.code === 0) {
                      this.userInfo = {}
                      this.token = ''
                      localStorage.clear()
                      router.push({ name: 'Login' })
                    }
                  } catch (error) {
                    console.error('登出失败:', error)
                  }
                },

                // 设置用户信息
                setUserInfo(userInfo) {
                  this.userInfo = { ...this.userInfo, ...userInfo }
                },

                // 设置token
                setToken(token) {
                  this.token = token
                  localStorage.setItem('token', token)
                },

                // 设置模式
                setMode(mode) {
                  this.mode = mode
                  localStorage.setItem('mode', mode)
                }
              }
            })
        b.路由状态管理
            // src/pinia/modules/router.js
            import { defineStore } from 'pinia'
            import { asyncRouterHandle } from '@/utils/asyncRouter'
            import { getMenu } from '@/api/menu'

            export const useRouterStore = defineStore('router', {
              state: () => ({
                asyncRouters: [],
                keepAliveRouters: [],
                routerList: [],
                addRouters: [],
                routerMap: {}
              }),

              actions: {
                // 设置动态路由
                async SetAsyncRouter() {
                  try {
                    const res = await getMenu()
                    if (res.code === 0) {
                      const asyncRouter = res.data.menus || []
                      this.asyncRouters = asyncRouterHandle(asyncRouter)
                      this.routerList = res.data.menus || []
                      return this.asyncRouters
                    }
                  } catch (error) {
                    console.error('获取菜单失败:', error)
                  }
                },

                // 设置keep-alive路由
                setKeepAliveRouters(history) {
                  this.keepAliveRouters = history
                }
              }
            })
    c.HTTP请求封装
        // src/utils/request.js
        import axios from 'axios'
        import { ElMessage, ElMessageBox } from 'element-plus'
        import { useUserStore } from '@/pinia/modules/user'
        import router from '@/router/index'

        // 创建axios实例
        const service = axios.create({
          baseURL: import.meta.env.VITE_BASE_API,
          timeout: 99999
        })

        // 请求拦截器
        service.interceptors.request.use(
          config => {
            const userStore = useUserStore()

            // 添加token
            if (userStore.token) {
              config.headers['x-token'] = userStore.token
            }

            // 添加请求时间戳
            config.headers['x-timestamp'] = Date.now()

            return config
          },
          error => {
            console.error('请求错误:', error)
            return Promise.reject(error)
          }
        )

        // 响应拦截器
        service.interceptors.response.use(
          response => {
            const res = response.data

            // 处理文件下载
            if (response.headers['content-type'] === 'application/octet-stream') {
              return response
            }

            // 业务错误处理
            if (res.code !== 0) {
              ElMessage({
                message: res.msg || '请求失败',
                type: 'error',
                duration: 5 * 1000
              })

              // token过期处理
              if (res.code === 1004 || res.code === 1005) {
                const userStore = useUserStore()
                userStore.LoginOut()
                return Promise.reject(new Error(res.msg || '登录过期'))
              }

              return Promise.reject(new Error(res.msg || '请求失败'))
            }

            return res
          },
          error => {
            console.error('响应错误:', error)

            let message = '网络错误'
            if (error.response) {
              switch (error.response.status) {
                case 401:
                  message = '未授权,请重新登录'
                  break
                case 403:
                  message = '权限不足'
                  break
                case 404:
                  message = '请求的资源不存在'
                  break
                case 500:
                  message = '服务器内部错误'
                  break
                default:
                  message = `连接错误${error.response.status}`
              }
            }

            ElMessage({
              message,
              type: 'error',
              duration: 5 * 1000
            })

            return Promise.reject(error)
          }
        )

        export default service

06.组件开发
    a.全局组件注册
        // src/core/global.js
        import GvaIcon from '@/components/gva-icon/index.vue'
        import GvaTable from '@/components/gva-table/index.vue'
        import GvaForm from '@/components/gva-form/index.vue'
        import GvaUpload from '@/components/gva-upload/index.vue'

        // 全局组件列表
        const components = {
          GvaIcon,
          GvaTable,
          GvaForm,
          GvaUpload
        }

        // 注册全局组件
        export function setupGlobalComponents(app) {
          Object.keys(components).forEach(key => {
            app.component(key, components[key])
          })
        }
    b.自定义组件示例
        <!-- src/components/gva-table/index.vue -->
        <template>
          <div class="gva-table">
            <el-table
              ref="tableRef"
              v-loading="loading"
              :data="tableData"
              :height="height"
              :max-height="maxHeight"
              :stripe="stripe"
              :border="border"
              :size="size"
              :fit="fit"
              :show-header="showHeader"
              :highlight-current-row="highlightCurrentRow"
              :current-row-key="currentRowKey"
              :row-class-name="rowClassName"
              :row-style="rowStyle"
              :cell-class-name="cellClassName"
              :cell-style="cellStyle"
              :header-row-class-name="headerRowClassName"
              :header-row-style="headerRowStyle"
              :header-cell-class-name="headerCellClassName"
              :header-cell-style="headerCellStyle"
              :row-key="rowKey"
              :empty-text="emptyText"
              :default-expand-all="defaultExpandAll"
              :expand-row-keys="expandRowKeys"
              :default-sort="defaultSort"
              :tooltip-effect="tooltipEffect"
              :show-summary="showSummary"
              :sum-text="sumText"
              :summary-method="summaryMethod"
              :span-method="spanMethod"
              :select-on-indeterminate="selectOnIndeterminate"
              :indent="indent"
              :lazy="lazy"
              :load="load"
              :tree-props="treeProps"
              @select="handleSelect"
              @select-all="handleSelectAll"
              @selection-change="handleSelectionChange"
              @cell-mouse-enter="handleCellMouseEnter"
              @cell-mouse-leave="handleCellMouseLeave"
              @cell-click="handleCellClick"
              @cell-dblclick="handleCellDblclick"
              @row-click="handleRowClick"
              @row-contextmenu="handleRowContextmenu"
              @row-dblclick="handleRowDblclick"
              @header-click="handleHeaderClick"
              @header-contextmenu="handleHeaderContextmenu"
              @sort-change="handleSortChange"
              @filter-change="handleFilterChange"
              @current-change="handleCurrentChange"
              @header-dragend="handleHeaderDragend"
              @expand-change="handleExpandChange"
            >
              <slot />
            </el-table>

            <!-- 分页组件 -->
            <div v-if="showPagination" class="gva-pagination">
              <el-pagination
                v-model:current-page="currentPage"
                v-model:page-size="pageSize"
                :page-sizes="pageSizes"
                :small="small"
                :disabled="disabled"
                :background="background"
                :layout="layout"
                :total="total"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
              />
            </div>
          </div>
        </template>

        <script setup>
        import { ref, computed } from 'vue'

        // Props定义
        const props = defineProps({
          // 表格数据
          tableData: {
            type: Array,
            default: () => []
          },
          // 加载状态
          loading: {
            type: Boolean,
            default: false
          },
          // 是否显示分页
          showPagination: {
            type: Boolean,
            default: true
          },
          // 分页配置
          total: {
            type: Number,
            default: 0
          },
          currentPage: {
            type: Number,
            default: 1
          },
          pageSize: {
            type: Number,
            default: 10
          },
          pageSizes: {
            type: Array,
            default: () => [10, 20, 50, 100]
          },
          layout: {
            type: String,
            default: 'total, sizes, prev, pager, next, jumper'
          },
          // 表格配置
          height: [String, Number],
          maxHeight: [String, Number],
          stripe: Boolean,
          border: Boolean,
          size: String,
          fit: {
            type: Boolean,
            default: true
          },
          showHeader: {
            type: Boolean,
            default: true
          },
          highlightCurrentRow: Boolean,
          currentRowKey: [String, Number],
          rowClassName: [String, Function],
          rowStyle: [Object, Function],
          cellClassName: [String, Function],
          cellStyle: [Object, Function],
          headerRowClassName: [String, Function],
          headerRowStyle: [Object, Function],
          headerCellClassName: [String, Function],
          headerCellStyle: [Object, Function],
          rowKey: [String, Function],
          emptyText: String,
          defaultExpandAll: Boolean,
          expandRowKeys: Array,
          defaultSort: Object,
          tooltipEffect: String,
          showSummary: Boolean,
          sumText: String,
          summaryMethod: Function,
          spanMethod: Function,
          selectOnIndeterminate: {
            type: Boolean,
            default: true
          },
          indent: {
            type: Number,
            default: 16
          },
          lazy: Boolean,
          load: Function,
          treeProps: {
            type: Object,
            default: () => ({
              hasChildren: 'hasChildren',
              children: 'children'
            })
          },
          small: Boolean,
          disabled: Boolean,
          background: {
            type: Boolean,
            default: true
          }
        })

        // Emits定义
        const emit = defineEmits([
          'select',
          'select-all',
          'selection-change',
          'cell-mouse-enter',
          'cell-mouse-leave',
          'cell-click',
          'cell-dblclick',
          'row-click',
          'row-contextmenu',
          'row-dblclick',
          'header-click',
          'header-contextmenu',
          'sort-change',
          'filter-change',
          'current-change',
          'header-dragend',
          'expand-change',
          'size-change',
          'page-change'
        ])

        // 表格引用
        const tableRef = ref()

        // 事件处理
        const handleSelect = (selection, row) => emit('select', selection, row)
        const handleSelectAll = (selection) => emit('select-all', selection)
        const handleSelectionChange = (selection) => emit('selection-change', selection)
        const handleCellMouseEnter = (row, column, cell, event) => emit('cell-mouse-enter', row, column, cell, event)
        const handleCellMouseLeave = (row, column, cell, event) => emit('cell-mouse-leave', row, column, cell, event)
        const handleCellClick = (row, column, cell, event) => emit('cell-click', row, column, cell, event)
        const handleCellDblclick = (row, column, cell, event) => emit('cell-dblclick', row, column, cell, event)
        const handleRowClick = (row, column, event) => emit('row-click', row, column, event)
        const handleRowContextmenu = (row, column, event) => emit('row-contextmenu', row, column, event)
        const handleRowDblclick = (row, column, event) => emit('row-dblclick', row, column, event)
        const handleHeaderClick = (column, event) => emit('header-click', column, event)
        const handleHeaderContextmenu = (column, event) => emit('header-contextmenu', column, event)
        const handleSortChange = (data) => emit('sort-change', data)
        const handleFilterChange = (filters) => emit('filter-change', filters)
        const handleCurrentChange = (currentRow, oldCurrentRow) => emit('current-change', currentRow, oldCurrentRow)
        const handleHeaderDragend = (newWidth, oldWidth, column, event) => emit('header-dragend', newWidth, oldWidth, column, event)
        const handleExpandChange = (row, expandedRows) => emit('expand-change', row, expandedRows)

        // 分页事件处理
        const handleSizeChange = (size) => emit('size-change', size)
        const handlePageChange = (page) => emit('page-change', page)

        // 暴露表格方法
        defineExpose({
          tableRef,
          clearSelection: () => tableRef.value?.clearSelection(),
          toggleRowSelection: (row, selected) => tableRef.value?.toggleRowSelection(row, selected),
          toggleAllSelection: () => tableRef.value?.toggleAllSelection(),
          toggleRowExpansion: (row, expanded) => tableRef.value?.toggleRowExpansion(row, expanded),
          setCurrentRow: (row) => tableRef.value?.setCurrentRow(row),
          clearSort: () => tableRef.value?.clearSort(),
          clearFilter: (columnKeys) => tableRef.value?.clearFilter(columnKeys),
          doLayout: () => tableRef.value?.doLayout(),
          sort: (prop, order) => tableRef.value?.sort(prop, order)
        })
        </script>

        <style lang="scss" scoped>
        .gva-table {
          .gva-pagination {
            margin-top: 20px;
            text-align: right;
          }
        }
        </style>

07.权限控制
    a.权限指令
        // src/directive/auth.js
        import { useUserStore } from '@/pinia/modules/user'

        // 权限检查函数
        function checkPermission(el, binding) {
          const { value } = binding
          const userStore = useUserStore()
          const roles = userStore.userInfo.authority?.defaultRouter || []

          if (value && value instanceof Array && value.length > 0) {
            const permissionRoles = value
            const hasPermission = roles.some(role => {
              return permissionRoles.includes(role)
            })

            if (!hasPermission) {
              el.parentNode && el.parentNode.removeChild(el)
            }
          } else {
            throw new Error('权限指令需要传入数组参数')
          }
        }

        // 权限指令
        export default {
          mounted(el, binding) {
            checkPermission(el, binding)
          },
          updated(el, binding) {
            checkPermission(el, binding)
          }
        }
    b.按钮权限控制
        // src/utils/btnAuth.js
        import { useUserStore } from '@/pinia/modules/user'

        // 检查按钮权限
        export function checkBtnPermission(btnName) {
          const userStore = useUserStore()
          const btnAuth = userStore.userInfo.authority?.btns || []
          return btnAuth.includes(btnName)
        }

        // 权限按钮组件
        export function useBtnAuth() {
          const userStore = useUserStore()

          const hasAuth = (btnName) => {
            const btnAuth = userStore.userInfo.authority?.btns || []
            return btnAuth.includes(btnName)
          }

          return {
            hasAuth
          }
        }

08.主题定制
    a.Element Plus 主题定制
        // src/style/element_variables.scss
        @forward 'element-plus/theme-chalk/src/common/var.scss' with (
          $colors: (
            'primary': (
              'base': #409eff,
            ),
            'success': (
              'base': #67c23a,
            ),
            'warning': (
              'base': #e6a23c,
            ),
            'danger': (
              'base': #f56c6c,
            ),
            'error': (
              'base': #f56c6c,
            ),
            'info': (
              'base': #909399,
            ),
          )
        );

        // 自定义组件样式
        .el-button {
          border-radius: 4px;

          &.is-round {
            border-radius: 20px;
          }
        }

        .el-card {
          border-radius: 8px;
          box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        }

        .el-table {
          .el-table__header {
            th {
              background-color: #fafafa;
              color: #606266;
              font-weight: 500;
            }
          }
        }
    b.暗色主题支持
        // src/style/dark.scss
        [data-theme='dark'] {
          --el-bg-color: #141414;
          --el-bg-color-page: #0a0a0a;
          --el-bg-color-overlay: #1d1e1f;
          --el-text-color-primary: #e5eaf3;
          --el-text-color-regular: #cfd3dc;
          --el-text-color-secondary: #a3a6ad;
          --el-text-color-placeholder: #8d9095;
          --el-text-color-disabled: #6c6e72;
          --el-border-color: #4c4d4f;
          --el-border-color-light: #414243;
          --el-border-color-lighter: #363637;
          --el-border-color-extra-light: #2b2b2c;
          --el-border-color-dark: #58585b;
          --el-border-color-darker: #636466;
          --el-fill-color: #303133;
          --el-fill-color-light: #262727;
          --el-fill-color-lighter: #1d1d1d;
          --el-fill-color-extra-light: #191919;
          --el-fill-color-dark: #39393a;
          --el-fill-color-darker: #424243;
          --el-fill-color-blank: transparent;

          // 自定义组件暗色样式
          .layout-container {
            background-color: var(--el-bg-color-page);
          }

          .gva-card {
            background-color: var(--el-bg-color);
            border-color: var(--el-border-color);
          }
        }

09.响应式设计
    a.移动端适配
        // src/style/mobile.scss
        @media screen and (max-width: 768px) {
          .layout-container {
            .aside {
              position: fixed;
              top: 0;
              left: -210px;
              z-index: 1001;
              transition: left 0.3s;

              &.mobile-show {
                left: 0;
              }
            }

            .main-container {
              margin-left: 0;

              .navbar {
                .hamburger-container {
                  display: block;
                }
              }
            }
          }

          .gva-table {
            .el-table {
              font-size: 12px;
            }

            .gva-pagination {
              .el-pagination {
                justify-content: center;

                .el-pagination__sizes,
                .el-pagination__jump {
                  display: none;
                }
              }
            }
          }

          .gva-form {
            .el-form-item {
              margin-bottom: 15px;

              .el-form-item__label {
                line-height: 20px;
                margin-bottom: 5px;
              }
            }
          }
        }

        @media screen and (max-width: 480px) {
          .gva-search-box {
            .el-form {
              .el-form-item {
                width: 100%;
                margin-right: 0;
                margin-bottom: 10px;
              }
            }
          }

          .gva-btn-list {
            .el-button {
              margin-bottom: 10px;
              width: 100%;
            }
          }
        }

10.性能优化
    a.路由懒加载
        // 路由懒加载配置
        const routes = [
          {
            path: '/dashboard',
            name: 'Dashboard',
            component: () => import(/* webpackChunkName: "dashboard" */ '@/view/dashboard/index.vue')
          },
          {
            path: '/system',
            name: 'System',
            component: () => import(/* webpackChunkName: "system" */ '@/view/system/index.vue'),
            children: [
              {
                path: 'user',
                name: 'User',
                component: () => import(/* webpackChunkName: "system-user" */ '@/view/system/user/index.vue')
              }
            ]
          }
        ]
    b.组件懒加载
        <template>
          <div>
            <!-- 使用 Suspense 包装异步组件 -->
            <Suspense>
              <template #default>
                <AsyncComponent />
              </template>
              <template #fallback>
                <div class="loading">加载中...</div>
              </template>
            </Suspense>
          </div>
        </template>

        <script setup>
        import { defineAsyncComponent } from 'vue'

        // 异步组件
        const AsyncComponent = defineAsyncComponent({
          loader: () => import('@/components/heavy-component.vue'),
          loadingComponent: () => import('@/components/loading.vue'),
          errorComponent: () => import('@/components/error.vue'),
          delay: 200,
          timeout: 3000
        })
        </script>
    c.图片懒加载
        <template>
          <div class="image-container">
            <img
              v-lazy="imageSrc"
              :alt="imageAlt"
              class="lazy-image"
              @load="handleImageLoad"
              @error="handleImageError"
            >
          </div>
        </template>

        <script setup>
        import { ref } from 'vue'

        const props = defineProps({
          imageSrc: {
            type: String,
            required: true
          },
          imageAlt: {
            type: String,
            default: ''
          }
        })

        const imageLoaded = ref(false)
        const imageError = ref(false)

        const handleImageLoad = () => {
          imageLoaded.value = true
        }

        const handleImageError = () => {
          imageError.value = true
        }
        </script>

        <style scoped>
        .image-container {
          position: relative;
          overflow: hidden;
        }

        .lazy-image {
          width: 100%;
          height: auto;
          transition: opacity 0.3s;
        }

        .lazy-image[lazy=loading] {
          opacity: 0.3;
        }

        .lazy-image[lazy=loaded] {
          opacity: 1;
        }

        .lazy-image[lazy=error] {
          opacity: 0.3;
        }
        </style>

11.测试
    a.单元测试配置
        // vitest.config.js
        import { defineConfig } from 'vitest/config'
        import vue from '@vitejs/plugin-vue'
        import { resolve } from 'path'

        export default defineConfig({
          plugins: [vue()],
          test: {
            globals: true,
            environment: 'jsdom',
            setupFiles: ['./tests/setup.js']
          },
          resolve: {
            alias: {
              '@': resolve(__dirname, 'src')
            }
          }
        })
    b.组件测试示例
        // tests/components/GvaTable.test.js
        import { mount } from '@vue/test-utils'
        import { describe, it, expect } from 'vitest'
        import GvaTable from '@/components/gva-table/index.vue'
        import { ElTable, ElPagination } from 'element-plus'

        describe('GvaTable', () => {
          it('renders table with data', () => {
            const tableData = [
              { id: 1, name: '张三', age: 25 },
              { id: 2, name: '李四', age: 30 }
            ]

            const wrapper = mount(GvaTable, {
              props: {
                tableData,
                total: 2,
                currentPage: 1,
                pageSize: 10
              },
              global: {
                components: {
                  ElTable,
                  ElPagination
                }
              }
            })

            expect(wrapper.find('.gva-table').exists()).toBe(true)
            expect(wrapper.find('.gva-pagination').exists()).toBe(true)
          })

          it('emits page-change event when page changes', async () => {
            const wrapper = mount(GvaTable, {
              props: {
                tableData: [],
                total: 100,
                currentPage: 1,
                pageSize: 10
              }
            })

            await wrapper.vm.handlePageChange(2)

            expect(wrapper.emitted('page-change')).toBeTruthy()
            expect(wrapper.emitted('page-change')[0]).toEqual([2])
          })
        })

12.最佳实践
    a.代码规范
        使用 ESLint + Prettier 保证代码质量
        遵循 Vue 3 Composition API 最佳实践
        组件命名使用 PascalCase
        文件命名使用 kebab-case
    b.性能优化
        合理使用 v-memo 和 v-once
        避免在模板中使用复杂计算
        使用 shallowRef 和 shallowReactive 优化响应式
        合理拆分组件,避免组件过大
    c.安全防护
        对用户输入进行验证和过滤
        使用 v-html 时注意 XSS 防护
        敏感信息不要存储在前端
        使用 HTTPS 传输数据
    d.用户体验
        提供加载状态提示
        合理的错误处理和提示
        响应式设计适配移动端
        无障碍访问支持

13.常见问题
    a.分类1
        Q: 如何解决路由懒加载失败?
        A: 检查路径是否正确,确保组件文件存在,可以添加错误处理。
    b.分类2
        Q: Element Plus 样式不生效?
        A: 确保正确导入样式文件,检查 CSS 优先级和作用域。
    c.分类3
        Q: Pinia 状态丢失?
        A: 检查是否正确持久化状态,页面刷新时重新初始化状态。
    d.分类4
        Q: 打包后静态资源路径错误?
        A: 检查 Vite 配置中的 base 路径和 publicPath 设置。

3.2 环境变量

01.env环境变量
    a.env.development
        ENV = 'development'  // 标识 不要管

        VITE_CLI_PORT = 8080
        VITE_SERVER_PORT = 8888
        VITE_BASE_API = /api
        VITE_FILE_API = /api
        VITE_BASE_PATH = http://127.0.0.1
        VITE_POSITION = close  // open为开启代码定位功能,close为关闭代码定位功能
        VITE_EDITOR = vscode  // 可选 vscode webstorm
        // VITE_EDITOR = webstorm 如果使用webstorm开发且要使用dom定位到代码行功能 请先自定添加 webstorm到环境变量 再将VITE_EDITOR值修改为webstorm
        // 如果使用docker-compose开发模式,设置为下面的地址或本机主机IP
        // VITE_BASE_PATH = http://177.7.0.12

        // 打开代码定位功能的情况下,在web页面按住键盘的shift+alt+鼠标左键点击代码行,即可在编辑器中打开对应的代码文件
    b.env.production
        ENV = 'production'  // 标识 不要管

        #下方为上线需要用到的程序代理前缀,一般用于nginx代理转发
        VITE_BASE_API = /api
        VITE_FILE_API = /api
        #下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量)
        VITE_BASE_PATH = https://demo.gin-vue-admin.com

3.3 按钮权限

01.按钮权限
    a.说明
        实现方式是采用 vue 原生的注册指令方式
    b.实现代码
        // 权限按钮展示指令
        import { useUserStore } from '@/pinia/modules/user'
        export default {
          install: (app) => {
            const userStore = useUserStore()
            app.directive('auth', {
              // 当被绑定的元素插入到 DOM 中时……
              mounted: function(el, binding) {
                const userInfo = userStore.userInfo
                let type = ''
                switch (Object.prototype.toString.call(binding.value)) {
                  case '[object Array]':
                    type = 'Array'
                    break
                  case '[object String]':
                    type = 'String'
                    break
                  case '[object Number]':
                    type = 'Number'
                    break
                  default:
                    type = ''
                    break
                }
                if (type === '') {
                  el.parentNode.removeChild(el)
                  return
                }
                const waitUse = binding.value.toString().split(',')
                let flag = waitUse.some(item => item === userInfo.authorityId)
                if (binding.modifiers.not) {
                  flag = !flag
                }
                if (!flag) {
                  el.parentNode.removeChild(el)
                }
              }
            })
          }
        }

02.创建按钮
    a.分类1
        进入菜单管理界面,点击新增或编辑,点击下方的新增可控按钮填入按钮名称(英文且不重复)和描述后点击确定
    b.分类2
        进入权限管理,点击设置权限-->分配菜单,如果此菜单有可控按钮,则会显示分配按钮权限字样,点击进行分配
    c.分类3
        点击确定,完成分配。

03.代码操作
    a.说明
        进入拥有可被控按钮菜单对应的前端页面,添加引入按钮控制组件,并在template中添加v-auth指令赋予需要被控制的按钮名称
    b.代码
        <template>
          <div>
        //   btnAuth为权限组件固定写法 .a  a为菜单管理中创建的按钮名称 配置过后即可实现对按钮的控制
            <div v-auth="btnAuth.a">按钮1</div>
            <div v-auth="btnAuth.b">按钮2</div>
            <div v-auth="btnAuth.c">按钮3</div>
          </div>
        </template>

        <script setup>
            import { useBtnAuth } from '@/utils/btnAuth'
            const btnAuth = useBtnAuth()
        </script>

3.4 字典方法

01.字典功能
    a.说明
        字典是在后端数据库中存储的kv对,通过字典的value找到对应的文本展示内容 字典管理中进行录入
    b.说明
        具体录入视频参考 https://www.bilibili.com/video/BV1kv4y1g7nT?p=12&vd_source=f2640257c21e3b547a790461ed94875e

02.字典方法
    import { getDict } from '@/utils/dictionary'
    // getDict 方法 可以根据在字典录入的字典的type找到对应的字典内容数组 从而在前端可以实现系列使用字典的操作
    // 例如:

    const sexDict = await getDict("sex")
    // res即返回的字典数组
    // [{value:0,lable:"男"},{value:1,lable:"女"}]

    import { showDictLabel } from '@/utils/dictionary'
    // showDictLabel 方法 可以根据在字典录入的字典和传入的value值匹配出对应的字典label
    // 例如:

    const label = showDictLabel(sexDict,0)
    // label即返回的内容
    // 男

    // 或者在html部分直接使用 {{showDictLabel(sexDict,0)}} 获得文字 男

3.5 自定义全局皮肤

01.自定义样式
    a.说明
        v2.6.4版本支持通过config.json进行页面配置
        配置文件为 /web/src/config.json
    b.代码
        {
            "weakness":false,
                "grey":false,
                "primaryColor":"#3b82f6",
                "showTabs":true,
                "darkMode":"auto",
                "layout_side_width":200,
                "layout_side_collapsed_width":60,
                "layout_side_item_height":44,
                "show_watermark":true
        }
    c.说明
        本json文件可以通过页面中可视化调整获得

3.6 自定义图标

01.自定义图标【菜单以及直接使用】
    a.说明
        在2023/12/10后下载的版本即可使用,之前的版本请自行升级
    b.使用
        我们以文件 web/assets/icons/customer-gva.svg 为例,
        只需要把svg文件放入前端 web/assets/icons/ 目录下即可自动注册,然后在菜单配置中选择使用或者直接使用标签<customer-gva></customer-gva>即可使用
        svg文件不可以有宽和高,如果需要自动适配颜色,需要在svg文件中添加fill="currentColor"属性,如果使用特定颜色svg自行填充fill即可
    c.实现代码:/web/core/global.js
        const createIconComponent = (svgContent) => ({
            name: 'SvgIcon',
            props: {
                iconClass: {
                    type: String,
                    default: '',
                },
                className: {
                    type: String,
                    default: '',
                },
            },
            render() {
                const { className } = this
                return h('span', {
                    class: className,
                    ariaHidden: true,
                    innerHTML: svgContent,
                })
            },
        })

        const registerIcons = async(app) => {
            const iconModules = import.meta.glob('@/assets/icons/**/*.svg')
            for (const path in iconModules) {
                const response = await fetch(path)
                const svgContent = await response.text()
                const iconName = path.split('/').pop().replace(/\.svg$/, '')
                const iconComponent = createIconComponent(svgContent)
                config.logs.push({
                    'key': iconName,
                    'label': iconName,
                })
                app.component(iconName, iconComponent)
            }
        }

3.7 开启TypeScript

01.开启TypeScript
    a.说明
        在创建.vue文件的时候,.vue内部的所有<script>标签,添加lang="ts"属性,
        例:<script lang="ts"> <script lang="ts" setup> 即可在当前的vue组件中开启ts模式。您可以畅快得使用您需要的ts语法。
    b.说明
        如果您其他组件不需要ts,那么按照原始的不携带lang=ts的标签即可继续使用js进行开发,
        项目支持不同组件使用不同的语言进行同步开发。您可以根据您的喜好和需求做出最佳选择。

3.8 导出Excel

01.名词解释
    a.分类
        业务库:注:需要提前到db-list自行配置多数据库,如未配置需配置后重启服务方可使用。若无法选择,请到config.yaml中设置disabled:false,选择导入导出的目标库。
        模板名称: 模板的中文标记,主要用来用户助记。
        表名称: 需要导出数据的表,输入表名即可。
        模板标识: 模板的唯一标识,用来区分不同的模板。在使用导出组件时候作为 templateId 参数传入。
        关联条件: 此处可以添加多个关联,需要选择和写入的参数为,join方式[inner, left, right],关联表,关联条件。
    b.模板信息: 此处接收一个json string,用来配置导出的表头,格式如下:
        {
            "表的列": "导出的中文名称列",
        }
    c.示例(xxx.xxx标识带json模式)
        {
            "id": "ID",
            "name": "名称",
            "age": "年龄",
            "info.id": "用户信息组ID"
        }
    d.总结
        默认导出条数: 默认导出的条数,如果不填写则默认为全量导出,实际使用中以ExportExcel的入参limit为准。
        默认排序条件: 默认的排序条件,如果不填写则默认为无排序,实际使用中以ExportExcel的入参order为准。
        导出条件:此处可以添加多条,每条需要填入的信息为 json中输入的key, 对应表的column, 条件。

02.组件使用
    a.在你需要导出的页面<script>标签添加下方的组件
        // 导出组件
        import ExportExcel from '@/components/exportExcel/exportExcel.vue'
        // 导入组件
        import ImportExcel from '@/components/exportExcel/importExcel.vue'
        // 导出模板组件
        import ExportTemplate from '@/components/exportExcel/exportTemplate.vue'
    b.然后在<template>中使用即可
        <!-- 导出组件-->
        <ExportExcel templateId="api" :condition="你的查询条件对象" :limit="10" :offset="10" order="id desc"/>

        <!-- 导入组件 handleSuccess为导入成功后的回调函数-->
        <ImportExcel templateId="api" @on-success="handleSuccess"/>

        <!-- 导出模板-->
        <ExportTemplate templateId="api" />
    c.入参解释
        condition: {  // 可以传入查询条件 根据模板中配置的查询条件映射关系进行有条件导出
            type: Object,
            default: () => ({})
        }
        limit: {   // 可以限制条目 根据模板中可以配置默认的条目限制 此处入参的优先级高于模板中的配置
            type: Number,
            default: 0
        }
        offset: {  // 可以限制偏移量 根据模板中可以配置默认的偏移量 此处入参的优先级高于模板中的配置
            type: Number,
            default: 0
        }
        order: {  // 可以限制排序 根据模板中可以配置默认的排序 此处入参的优先级高于模板中的配置
            type: String,
            default: ''
        }
        onSuccess: { // 导入成功后的回调函数
            type: Function,
            default: () => {}
        }
        此标签会产生一个按钮,点击即可导出对应表,后续会更新导出条件,敬请期待。

4 后端管理

4.1 操作指南

01.server项目结构
    ├── api
    │   └── v1
    ├── config
    ├── core
    ├── docs
    ├── global
    ├── initialize
    │   └── internal
    ├── middleware
    ├── model
    │   ├── request
    │   └── response
    ├── packfile
    ├── resource
    │   ├── excel
    │   ├── page
    │   └── template
    ├── router
    ├── service
    ├── source
    └── utils
        ├── timer
        └── upload

02.说明
    文件夹      说明                    描述
    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接口封装

4.2 配置文件

01.JWT
    a.yaml配置
        # jwt configuration
        jwt:
          signing-key: 'qmPlus'
          expires-time: 7d
          buffer-time: 1d
    b.struct定义
        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"`                   // 签发者
        }
    c.描述
        配置名           类型        说明
        signing-key     string      jwt的签名
        expires-time    int64       过期时间
        buffer-time     int64       缓冲时间(过期前这段时间内有过请求会刷新jwt续期)
        issuer          string      jwt签发者

02.Zap
    a.yaml配置
        # zap logger configuration
        zap:
          level: 'info'
          format: 'console'
          prefix: '[GIN-VUE-ADMIN]'
          director: 'log'
          show-line: true
          encode-level: 'LowercaseColorLevelEncoder'
          stacktrace-key: 'stacktrace'
          log-in-console: true
          retention-day: 7
    b.struct定义
        type Zap struct {
            Level         string `mapstructure:"level" json:"level" yaml:"level"`                            // 级别
            Prefix        string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`                         // 日志前缀
            Format        string `mapstructure:"format" json:"format" yaml:"format"`                         // 输出
            Director      string `mapstructure:"director" json:"director"  yaml:"director"`                  // 日志文件夹
            EncodeLevel   string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"`       // 编码级
            StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` // 栈名
            ShowLine      bool   `mapstructure:"show-line" json:"show-line" yaml:"show-line"`                // 显示行
            LogInConsole  bool   `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` // 输出控制台
            RetentionDay  int    `mapstructure:"retention-day" json:"retention-day" yaml:"retention-day"`    // 日志保留天数
        }
    c.描述
        配置名           类型         说明
        level           string       level的模式的详细说明,请看zap官方文档
        format          string       console: 控制台形式输出日志 json: json格式输出日志
        prefix          string       日志的前缀
        director        string       存放日志的文件夹,修改即可,不需要手动创建
        show_line       bool         显示行号, 默认为true,不建议修改
        encode_level    string       LowercaseLevelEncoder:小写
        stacktrace_key  string       堆栈的名称,即在json格式输出日志时的josn的key
        log_in_console  bool         是否输出到控制台,默认为true
        retention_day   int          日志保留天数

03.Redis
    a.yaml配置
        # redis configuration
        redis:
          name: ''
          addr: '127.0.0.1:6379'
          password: ''
          db: 0
          use-cluster: false
          cluster-addrs: []
    b.struct定义
        type Redis struct {
            Name         string   `mapstructure:"name" json:"name" yaml:"name"`                         // 代表当前实例的名字
            Addr         string   `mapstructure:"addr" json:"addr" yaml:"addr"`                         // 服务器地址:端口
            Password     string   `mapstructure:"password" json:"password" yaml:"password"`             // 密码
            DB           int      `mapstructure:"db" json:"db" yaml:"db"`                               // 单实例模式下redis的哪个数据库
            UseCluster   bool     `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"`       // 是否使用集群模式
            ClusterAddrs []string `mapstructure:"clusterAddrs" json:"clusterAddrs" yaml:"clusterAddrs"` // 集群模式下的节点地址列表
        }
    c.描述
        配置名           类型        说明
        name            string      代表当前实例的名字
        addr            string      redis连接地址及端口
        password        string      密码
        db              int         redis的哪个数据库
        use-cluster     bool        是否使用集群模式
        cluster-addrs   []string    集群模式下的节点地址列表

04.Email
    a.yaml配置
        # email configuration
        email:
          to: '[email protected]'
          port: 465
          from: '[email protected]'
          host: 'smtp.163.com'
          is-ssl: true
          secret: 'xxx'
          nickname: 'test'
          is-loginauth: false
    b.struct定义
        type Email struct {
            To          string `mapstructure:"to" json:"to" yaml:"to"`                               // 收件人:多个以英文逗号分隔 例:[email protected] [email protected] 正式开发中请把此项目作为参数使用
            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
            IsLoginAuth bool   `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth   是否使用LoginAuth认证方式(适用于IBM、微软邮箱服务器等)
        }
    c.描述
        配置名           类型      说明
        to              string    邮件接收者,可以是多个,以英文逗号(,)进行区分,最好别带空格,如果是一个邮箱最后请不要加英文逗号(,)
        from            string    发件人邮箱
        host            string    邮箱的主服务器地址
        secret          string    密钥,用于登录的密钥,最好不要用邮箱密码
        nickname        string    发件人昵称
        port            int       邮件服务端口
        is-ssl          bool      是否使用SSL
        is-loginauth    bool      是否使用LoginAuth认证方式(适用于IBM、微软邮箱服务器等)

05.Casbin
    a.yaml配置
        # casbin configuration
        casbin:
          model-path: './resource/rbac_model.conf'
    b.struct定义
        type Casbin struct {
            ModelPath string `mapstructure:"model-path" json:"modelPath" yaml:"model-path"`
        }
    c.描述
        配置名         类型     说明                                                          建议是否修改
        model-path    string   存放casbin模型的相对路径,默认值为./resource/rbac_model.conf    不推荐修改

06.System
    a.yaml配置
        # system configuration
        system:
          env: 'public'
          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
          use-strict-auth: false
          disabled-auto-migrate: false
    b.struct定义
        type System struct {
            Env           string `mapstructure:"env" json:"env" yaml:"env"`                                     // 环境值
            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"`       // 限制同IP访问次数
            LimitTimeIP   int    `mapstructure:"iplimit-time" json:"iplimit-time" yaml:"iplimit-time"`          // 限制时间
            UseMultipoint bool   `mapstructure:"use-multipoint" json:"use-multipoint" yaml:"use-multipoint"`    // 多点登录拦截
            UseRedis      bool   `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"`                   // 使用redis
            UseMongo      bool   `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"`                   // 使用mongo
            UseStrictAuth bool   `mapstructure:"use-strict-auth" json:"use-strict-auth" yaml:"use-strict-auth"` // 使用树形角色分配模式
            DisabledAutoMigrate   bool   `mapstructure:"disabled-auto-migrate" json:"disabled-auto-migrate" yaml:"disabled-auto-migrate"`           // 是否关闭自动迁移数据库
        }
    c.描述
        配置名                   类型       说明
        env                     string     环境模式,"develop"为开发模式(跳过身份验证),"public"为生产模式
        addr                    int        后端服务端口,默认8888
        db-type                 string     数据库类型,支持:mysql、pgsql、sqlite、mssql、oracle
        oss-type                string     对象存储类型:local(本地存储)、qiniu(七牛云)、aliyun(阿里云)、minio
        router-prefix           string     路由前缀,用于API路由统一前缀
        use-multipoint          bool       是否启用多点登录拦截(单点登录),默认false
        use-redis               bool       是否使用Redis缓存,默认false
        iplimit-count           int        IP限流:指定时间段内同IP最大访问次数,默认15000
        iplimit-time            int        IP限流:限制时间窗口(秒),默认3600
        use-mongo               bool       是否使用MongoDB数据库,默认false
        use-strict-auth         bool       是否开启严格角色模式(树形角色分配),默认false
        disabled-auto-migrate   bool       是否自动迁移数据库,默认false,生产环境建议开启

07.Captcha
    a.yaml配置
        # captcha configuration
        captcha:
          key-long: 6
          img-width: 240
          img-height: 80
          open-captcha: 0
          open-captcha-timeout: 3600
    b.struct定义
        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(秒)
        }
    c.描述
        配置名                  类型        说明
        key-long               int         验证码长度
        img-width              int         验证码宽度
        img-height             int         验证码高度
        open-captcha           int         防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码次数
        open-captcha-timeout   int         防爆破验证码超时时间,单位:s(秒)

08.Mysql [pgsql,sqlite,mssql,oracle]
    a.yaml配置
        # mysql connect configuration
        mysql:
          path: ''
          port: ''
          config: ''
          db-name: ''
          username: ''
          password: ''
          prefix: ''
          singular: false
          engine: 'InnoDB'
          max-idle-conns: 10
          max-open-conns: 100
          log-mode: 'info'
          log-zap: false
    b.struct定义
        type Mysql struct {
            GeneralDB `yaml:",inline" mapstructure:",squash"`
        }

        type GeneralDB struct {
            Path         string `mapstructure:"path" json:"path" yaml:"path"`
            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"`
            Prefix       string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`
            Singular     bool   `mapstructure:"singular" json:"singular" yaml:"singular"`
            Engine       string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"`
            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"`
            LogMode      string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"`
            LogZap       bool   `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"`
        }
    c.描述
        配置名           类型            说明
        path            string          数据库服务器地址
        port            string          数据库端口
        username        string          数据库用户名
        password        string          数据库密码
        db-name         string          数据库名
        config          string          数据库连接高级配置
        prefix          string          表名前缀
        singular        bool            是否使用单数表名
        engine          string          数据库引擎,默认InnoDB
        max-idle-conns  int             设置空闲中的最大连接数
        max-open-conns  int             设置打开到数据库的最大连接数
        log-mode        string          开启Gorm全局日志等级:"silent"、"error"、"warn"、"info",默认info
        log-zap         bool            是否通过zap写入日志文件

09.Local
    a.yaml配置
        # local configuration
        local:
          path: 'uploads/file'
          store-path: 'uploads/file'
    b.struct定义
        type Local struct {
            Path      string `mapstructure:"path" json:"path" yaml:"path"`                   // 本地文件访问路径
            StorePath string `mapstructure:"store-path" json:"store-path" yaml:"store-path"` // 本地文件存储路径
        }
    c.描述
        配置名         类型          说明
        path          string        本地文件访问路径
        store-path    string        本地文件存储路径

10.Qiniu
    a.yaml配置
        # qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
        qiniu:
          zone: '你的空间区域'
          bucket: '你的空间名'
          img-path: '你的oss域名'
          use-https: false
          access-key: 'xxxxxxxxxxxxxxxxxxxxxxxxx'
          secret-key: 'xxxxxxxxxxxxxxxxxxxxxxxxx'
          use-cdn-domains: false
    b.struct定义
        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:"imgPath" yaml:"img-path"`
            UseHTTPS      bool   `mapstructure:"use-https" json:"useHttps" yaml:"use-https"`
            AccessKey     string `mapstructure:"access-key" json:"accessKey" yaml:"access-key"`
            SecretKey     string `mapstructure:"secret-key" json:"secretKey" yaml:"secret-key"`
            UseCdnDomains bool   `mapstructure:"use-cdn-domains" json:"useCdnDomains" yaml:"use-cdn-domains"`
        }
    c.描述
        配置名            类型       说明
        zone             string     存储区域 Zone ,可配置选项为 ZoneHuadong / ZoneHuabei / ZoneHuanan / ZoneBeimei / ZoneXinjiapo
        bucket           string     存储空间
        img-path         string     CDN 加速域名
        use-https        bool       是否使用https
        access-key       string     秘钥AK
        secret-key       string     秘钥SK
        use-cdn-domains  bool       上传是否使用CDN上传加速

11.AutoCode
    a.yaml配置
        # autocode configuration
        autocode:
          web: '/web/src'
          root: ''
          server: '/server'
          module: 'github.com/flipped-aurora/gin-vue-admin/server'
          ai-path: ''
    b.struct定义
        type Autocode struct {
            Web    string `mapstructure:"web" json:"web" yaml:"web"`
            Root   string `mapstructure:"root" json:"root" yaml:"root"`
            Server string `mapstructure:"server" json:"server" yaml:"server"`
            Module string `mapstructure:"module" json:"module" yaml:"module"`
            AiPath string `mapstructure:"ai-path" json:"ai-path" yaml:"ai-path"`
        }
    c.描述
        配置名         类型        说明
        web           string      前端项目路径
        root          string      项目根目录,自动适配,请不要手动配置
        server        string      服务端项目路径
        module        string      Go模块名称
        ai-path       string      AI相关路径

4.3 认证系统

01.认证机制概述
    a.JWT认证流程
        sequenceDiagram
            participant C as 客户端
            participant S as 服务器
            participant DB as 数据库

            C->>S: 1. 登录请求 (用户名/密码)
            S->>DB: 2. 验证用户凭据
            DB-->>S: 3. 返回用户信息
            S->>S: 4. 生成 JWT Token
            S-->>C: 5. 返回 Token

            Note over C: 客户端存储 Token

            C->>S: 6. API 请求 + Authorization Header
            S->>S: 7. 验证 Token
            S-->>C: 8. 返回数据或拒绝访问

02.JWT配置
    a.配置文件设置
        jwt:
          signing-key: 'qmPlus'           # JWT 签名密钥
          expires-time: 604800s           # Token 过期时间 (7天)
          buffer-time: 86400s             # Token 缓冲时间 (1天)
          issuer: 'qmPlus'               # 签发者
    b.配置参数说明
        参数            类型         说明                                    默认值
        signing-key     string      JWT 签名密钥,用于生成和验证 Token        qmPlus
        expires-time    duration    Token 有效期,过期后需要重新登录          604800s (7天)
        buffer-time     duration    Token 缓冲时间,在此时间内可以刷新 Token   86400s (1天)
        issuer          string      Token 签发者标识                         qmPlus

03.核心组件
    a.JWT中间件,server/middleware/jwt.go
        // JWTAuth JWT认证中间件
        func JWTAuth() gin.HandlerFunc {
            return func(c *gin.Context) {
                // 从请求头获取 Token
                token := c.Request.Header.Get("x-token")
                if token == "" {
                    response.FailWithDetailed(gin.H{"reload": true}, "未登录或非法访问", c)
                    c.Abort()
                    return
                }

                // 验证 Token
                j := utils.NewJWT()
                claims, err := j.ParseToken(token)
                if err != nil {
                    if err == utils.TokenExpired {
                        response.FailWithDetailed(gin.H{"reload": true}, "授权已过期", c)
                        c.Abort()
                        return
                    }
                    response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
                    c.Abort()
                    return
                }

                // 将用户信息存储到上下文
                c.Set("claims", claims)
                c.Next()
            }
        }
    b.JWT工具类,server/utils/jwt.go
        type JWT struct {
            SigningKey []byte
        }

        type CustomClaims struct {
            BaseClaims
            BufferTime int64
            jwt.StandardClaims
        }

        type BaseClaims struct {
            UUID        uuid.UUID
            ID          uint
            Username    string
            NickName    string
            AuthorityId string
        }

        // CreateToken 创建Token
        func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
            token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
            return token.SignedString(j.SigningKey)
        }

        // ParseToken 解析Token
        func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
            token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
                return j.SigningKey, nil
            })

            if err != nil {
                if ve, ok := err.(*jwt.ValidationError); ok {
                    if ve.Errors&jwt.ValidationErrorMalformed != 0 {
                        return nil, TokenMalformed
                    } else if ve.Errors&jwt.ValidationErrorExpired != 0 {
                        return nil, TokenExpired
                    } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
                        return nil, TokenNotValidYet
                    } else {
                        return nil, TokenInvalid
                    }
                }
            }

            if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
                return claims, nil
            }
            return nil, TokenInvalid
        }

04.登录实现
    a.登录API,server/api/v1/sys_user.go
        // Login 用户登录
        func (b *BaseApi) Login(c *gin.Context) {
            var l systemReq.Login
            err := c.ShouldBindJSON(&l)
            if err != nil {
                response.FailWithMessage(err.Error(), c)
                return
            }

            // 验证码校验
            if store.Verify(l.CaptchaId, l.Captcha, true) {
                // 验证用户凭据
                u := &system.SysUser{Username: l.Username, Password: l.Password}
                user, err := userService.Login(u)
                if err != nil {
                    global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
                    response.FailWithMessage("用户名不存在或者密码错误", c)
                    return
                }

                // 生成 JWT Token
                b.tokenNext(c, *user)
            } else {
                response.FailWithMessage("验证码错误", c)
            }
        }

        // tokenNext 生成Token并返回
        func (b *BaseApi) tokenNext(c *gin.Context, user system.SysUser) {
            j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)}
            claims := j.CreateClaims(utils.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 {
                response.OkWithDetailed(systemRes.LoginResponse{
                    User:      user,
                    Token:     token,
                    ExpiresAt: claims.StandardClaims.ExpiresAt * 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
                }
                response.OkWithDetailed(systemRes.LoginResponse{
                    User:      user,
                    Token:     token,
                    ExpiresAt: claims.StandardClaims.ExpiresAt * 1000,
                }, "登录成功", c)
            } else if err != nil {
                global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
                response.FailWithMessage("设置登录状态失败", c)
            } else {
                var blackJWT system.JwtBlacklist
                blackJWT.Jwt = jwtStr
                if err := jwtService.JsonInBlacklist(blackJWT); err != nil {
                    response.FailWithMessage("jwt作废失败", c)
                    return
                }
                if err := jwtService.SetRedisJWT(token, user.Username); err != nil {
                    response.FailWithMessage("设置登录状态失败", c)
                    return
                }
                response.OkWithDetailed(systemRes.LoginResponse{
                    User:      user,
                    Token:     token,
                    ExpiresAt: claims.StandardClaims.ExpiresAt * 1000,
                }, "登录成功", c)
            }
        }

05.Token刷新机制
    a.自动刷新,当 Token 即将过期时(在 buffer-time 时间内),系统会自动刷新 Token
        // RefreshToken 刷新Token
        func (j *JWT) RefreshToken(tokenString string) (string, error) {
            jwt.TimeFunc = func() time.Time {
                return time.Unix(0, 0)
            }

            token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
                return j.SigningKey, nil
            })

            if err != nil {
                return "", err
            }

            if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
                jwt.TimeFunc = time.Now
                claims.StandardClaims.ExpiresAt = time.Now().Add(time.Duration(global.GVA_CONFIG.JWT.ExpiresTime) * time.Second).Unix()
                return j.CreateToken(*claims)
            }
            return "", TokenInvalid
        }

06.Token黑名单
    a.黑名单机制,为了支持用户登出和 Token 撤销,系统实现了 JWT 黑名单机制
        // JwtBlacklist JWT黑名单结构体
        type JwtBlacklist struct {
            global.GVA_MODEL
            Jwt string `gorm:"type:text;comment:jwt"`
        }

        // JsonInBlacklist 拉黑jwt
        func (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err error) {
            err = global.GVA_DB.Create(&jwtList).Error
            if err != nil {
                return
            }
            global.BlackCache.SetDefault(jwtList.Jwt, struct{}{})
            return
        }

        // IsBlacklist 判断JWT是否在黑名单内部
        func (jwtService *JwtService) IsBlacklist(jwt string) bool {
            _, ok := global.BlackCache.Get(jwt)
            return ok
        }

07.安全最佳实践
    a.密钥管理
        使用强随机密钥作为签名密钥
        定期轮换签名密钥
        将密钥存储在安全的配置文件中
    b.Token生命周期
        设置合理的过期时间(建议不超过24小时)
        实现 Token 刷新机制
        支持主动撤销 Token
    c.传输安全
        始终使用 HTTPS 传输 Token
        在请求头中传递 Token,避免在 URL 中暴露
        客户端安全存储 Token
    d.多点登录控制
        system:
          use-multipoint: true  # 启用单点登录限制

08.常见问题
    a.Token过期如何处理?
        系统会返回特定的错误码,前端应该引导用户重新登录或自动刷新 Token。
    b.如何实现记住登录状态?
        可以设置较长的 Token 过期时间,或者实现 Refresh Token 机制。
    c.多设备登录如何控制?
        通过配置 use-multipoint: true 启用单点登录,或者实现设备管理功能。
    d.JWT密钥泄露怎么办?
        立即更换密钥,使所有现有 Token 失效,要求用户重新登录。

4.4 权限系统

00.汇总
    Gin-Vue-Admin 采用 Casbin 实现基于角色的访问控制 (RBAC),提供灵活、强大的权限管理机制,支持多层级权限控制。

01.权限模型概述
    a.RBAC权限模型
        用户 (User) ──┐
                      ├─→ 角色 (Role) ──→ 权限 (Permission) ──→ 资源 (Resource)
        用户组 (Group) ┘
    b.权限层级结构
        graph TD
            A[超级管理员] --> B[系统管理员]
            A --> C[业务管理员]
            B --> D[普通用户]
            C --> D

            B --> E[用户管理权限]
            B --> F[系统配置权限]
            C --> G[业务数据权限]
            D --> H[基础查看权限]

            E --> I[API: /user/*]
            F --> J[API: /system/*]
            G --> K[API: /business/*]
            H --> L[API: /base/*]

02.Casbin配置
    a.模型配置文件,server/resource/rbac_model.conf
        [request_definition]
        r = sub, obj, act

        [policy_definition]
        p = sub, obj, act

        [role_definition]
        g = _, _

        [policy_effect]
        e = some(where (p.eft == allow))

        [matchers]
        m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
    b.配置参数说明
        配置项              说明
        request_definition  请求定义:主体(sub)、对象(obj)、动作(act)
        policy_definition   策略定义:权限规则格式
        role_definition     角色定义:角色继承关系
        policy_effect       策略效果:允许访问的条件
        matchers            匹配器:权限验证逻辑

03.核心组件
    a.Casbin中间件,server/middleware/casbin_rbac.go
        // CasbinHandler Casbin权限验证中间件
        func CasbinHandler() gin.HandlerFunc {
            return func(c *gin.Context) {
                claims, _ := c.Get("claims")
                waitUse := claims.(*utils.CustomClaims)

                // 获取请求信息
                obj := c.Request.URL.Path
                act := c.Request.Method
                sub := waitUse.AuthorityId

                // Casbin权限验证
                e := casbinService.Casbin()
                success, _ := e.Enforce(sub, obj, act)

                if !success {
                    response.FailWithDetailed(gin.H{}, "权限不足", c)
                    c.Abort()
                    return
                }
                c.Next()
            }
        }
    b.Casbin服务,server/service/sys_casbin.go
        type CasbinService struct{}

        // UpdateCasbin 更新Casbin权限
        func (casbinService *CasbinService) UpdateCasbin(authorityId string, casbinInfos []request.CasbinInfo) error {
            casbinService.ClearCasbin(0, authorityId)
            rules := [][]string{}
            for _, v := range casbinInfos {
                rules = append(rules, []string{authorityId, v.Path, v.Method})
            }
            e := casbinService.Casbin()
            success, _ := e.AddPolicies(rules)
            if !success {
                return errors.New("存在相同api,添加失败,请联系管理员")
            }
            return nil
        }

        // GetPolicyPathByAuthorityId 获取权限列表
        func (casbinService *CasbinService) GetPolicyPathByAuthorityId(authorityId string) (pathMaps []request.CasbinInfo) {
            e := casbinService.Casbin()
            list := e.GetFilteredPolicy(0, authorityId)
            for _, v := range list {
                pathMaps = append(pathMaps, request.CasbinInfo{
                    Path:   v[1],
                    Method: v[2],
                })
            }
            return pathMaps
        }

        // ClearCasbin 清除权限
        func (casbinService *CasbinService) ClearCasbin(v int, p ...string) bool {
            e := casbinService.Casbin()
            success, _ := e.RemoveFilteredPolicy(v, p...)
            return success
        }

        // Casbin 获取Casbin实例
        func (casbinService *CasbinService) Casbin() *casbin.Enforcer {
            return global.GVA_CASBIN
        }

04.权限数据结构
    a.角色表 (sys_authorities)
        type SysAuthority struct {
            CreatedAt     time.Time
            UpdatedAt     time.Time
            DeletedAt     *time.Time `sql:"index"`
            AuthorityId   string     `json:"authorityId" gorm:"not null;unique;primary_key;comment:角色ID;size:90"`
            AuthorityName string     `json:"authorityName" gorm:"comment:角色名"`
            ParentId      string     `json:"parentId" gorm:"comment:父角色ID"`
            DataAuthorityId []string `json:"dataAuthorityId" gorm:"-"`
            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"`
        }
    b.权限规则表 (casbin_rule)
        type CasbinRule struct {
            ID    uint   `gorm:"primaryKey;autoIncrement"`
            Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
            V0    string `gorm:"size:512;uniqueIndex:unique_index"`
            V1    string `gorm:"size:512;uniqueIndex:unique_index"`
            V2    string `gorm:"size:512;uniqueIndex:unique_index"`
            V3    string `gorm:"size:512;uniqueIndex:unique_index"`
            V4    string `gorm:"size:512;uniqueIndex:unique_index"`
            V5    string `gorm:"size:512;uniqueIndex:unique_index"`
        }
    c.API权限表 (sys_apis)
        type SysApi struct {
            global.GVA_MODEL
            Path        string `json:"path" gorm:"comment:api路径"`
            Description string `json:"description" gorm:"comment:api中文描述"`
            ApiGroup    string `json:"apiGroup" gorm:"comment:api组"`
            Method      string `json:"method" gorm:"default:POST;comment:方法"`
        }

05.权限管理功能
    a.角色管理
        a.创建角色
            // CreateAuthority 创建角色
            func (authorityService *AuthorityService) CreateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) {
                var authorityBox system.SysAuthority
                if !errors.Is(global.GVA_DB.Where("authority_id = ?", auth.AuthorityId).First(&authorityBox).Error, gorm.ErrRecordNotFound) {
                    return auth, errors.New("存在相同角色id")
                }
                err = global.GVA_DB.Create(&auth).Error
                return auth, err
            }
        b.角色继承
            // 设置角色继承关系
            e := casbinService.Casbin()
            e.AddRoleForUser("user1", "role1")  // 用户继承角色
            e.AddRoleForUser("role1", "role2")  // 角色继承角色
    b.API权限管理
        a.分配API权限
            // UpdateCasbinApi 更新API权限
            func (casbinService *CasbinService) UpdateCasbinApi(oldPath string, newPath string, oldMethod string, newMethod string) error {
                err := global.GVA_DB.Model(&gormadapter.CasbinRule{}).Where("v1 = ? AND v2 = ?", oldPath, oldMethod).Updates(map[string]interface{}{
                    "v1": newPath,
                    "v2": newMethod,
                }).Error
                e := casbinService.Casbin()
                err = e.LoadPolicy()
                return err
            }
        b.API权限验证
            // 权限验证示例
            func checkPermission(userId, path, method string) bool {
                e := casbinService.Casbin()

                // 获取用户角色
                roles := e.GetRolesForUser(userId)

                // 检查权限
                for _, role := range roles {
                    if ok, _ := e.Enforce(role, path, method); ok {
                        return true
                    }
                }
                return false
            }
    c.菜单权限管理
        a.菜单权限表 (sys_base_menus)
            type SysBaseMenu struct {
                global.GVA_MODEL
                MenuLevel     uint                                     `json:"-"`
                ParentId      string                                   `json:"parentId" gorm:"comment:父菜单ID"`
                Path          string                                   `json:"path" gorm:"comment:路由path"`
                Name          string                                   `json:"name" gorm:"comment:路由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"`
            }
        b.动态菜单生成
            // GetMenuTree 获取动态菜单树
            func (menuService *MenuService) GetMenuTree(authorityId string) (menus []system.SysMenu, err error) {
                menuTree, err := menuService.getMenuTreeMap(authorityId)
                menus = menuTree["0"]
                for i := 0; i < len(menus); i++ {
                    err = menuService.getChildrenList(&menus[i], menuTree)
                }
                return menus, err
            }
    d.按钮权限管理
        a.按钮权限表 (sys_base_menu_btns)
            type SysBaseMenuBtn struct {
                global.GVA_MODEL
                Name          string `json:"name" gorm:"comment:按钮关键key"`
                Desc          string `json:"desc" gorm:"comment:按钮备注"`
                SysBaseMenuID uint   `json:"sysBaseMenuID" gorm:"comment:菜单ID"`
            }
        b.前端按钮权限控制
            <template>
              <!-- 使用 v-auth 指令控制按钮显示 -->
              <el-button v-auth="'user:create'" @click="createUser">
                创建用户
              </el-button>

              <el-button v-auth="'user:delete'" @click="deleteUser">
                删除用户
              </el-button>
            </template>

            <script>
            // 权限指令实现
            app.directive('auth', {
              mounted(el, binding) {
                const { value } = binding
                const userStore = useUserStore()

                if (!userStore.hasPermission(value)) {
                  el.style.display = 'none'
                }
              }
            })
            </script>

06.权限同步机制
    a.权限缓存更新
        // 权限变更时同步缓存
        func (casbinService *CasbinService) FreshCasbin() (err error) {
            e := casbinService.Casbin()
            err = e.LoadPolicy()
            return err
        }

        // 清除用户权限缓存
        func (casbinService *CasbinService) ClearUserCache(userId string) {
            // 清除Redis中的用户权限缓存
            global.GVA_REDIS.Del(context.Background(), "user:permissions:"+userId)
        }
    b.实时权限验证
        // 实时权限检查
        func (casbinService *CasbinService) CheckPermission(userId, resource, action string) bool {
            // 1. 检查缓存
            cacheKey := fmt.Sprintf("permission:%s:%s:%s", userId, resource, action)
            if result, err := global.GVA_REDIS.Get(context.Background(), cacheKey).Result(); err == nil {
                return result == "true"
            }

            // 2. 实时验证
            e := casbinService.Casbin()
            hasPermission, _ := e.Enforce(userId, resource, action)

            // 3. 缓存结果
            global.GVA_REDIS.Set(context.Background(), cacheKey, hasPermission, time.Minute*5)

            return hasPermission
        }

07.前端权限集成
    a.路由权限控制
        // router/permission.js
        import { useUserStore } from '@/pinia/modules/user'

        router.beforeEach(async (to, from, next) => {
          const userStore = useUserStore()

          // 检查登录状态
          if (!userStore.token) {
            if (to.path !== '/login') {
              return next('/login')
            }
            return next()
          }

          // 检查路由权限
          if (to.meta.requiresAuth) {
            const hasPermission = await userStore.checkRoutePermission(to.path)
            if (!hasPermission) {
              return next('/403')
            }
          }

          next()
        })
    b.API权限拦截
        // utils/request.js
        import axios from 'axios'
        import { useUserStore } from '@/pinia/modules/user'

        // 请求拦截器
        axios.interceptors.request.use(
          config => {
            const userStore = useUserStore()

            // 添加 Token
            if (userStore.token) {
              config.headers['x-token'] = userStore.token
            }

            return config
          },
          error => Promise.reject(error)
        )

        // 响应拦截器
        axios.interceptors.response.use(
          response => response,
          error => {
            if (error.response?.status === 403) {
              // 权限不足处理
              ElMessage.error('权限不足')
              return Promise.reject(error)
            }

            if (error.response?.status === 401) {
              // Token 过期处理
              const userStore = useUserStore()
              userStore.logout()
              router.push('/login')
            }

            return Promise.reject(error)
          }
        )

08.安全最佳实践
    a.最小权限原则
        用户只获得完成工作所需的最小权限
        定期审查和清理不必要的权限
        实现权限的时效性控制
    b.权限分离
        管理权限与业务权限分离
        读权限与写权限分离
        敏感操作需要额外验证
    c.审计日志
        // 权限操作日志
        type PermissionLog struct {
            UserID    string    `json:"user_id"`
            Action    string    `json:"action"`
            Resource  string    `json:"resource"`
            Result    bool      `json:"result"`
            IP        string    `json:"ip"`
            UserAgent string    `json:"user_agent"`
            Timestamp time.Time `json:"timestamp"`
        }

        // 记录权限操作
        func LogPermissionCheck(userID, action, resource string, result bool, c *gin.Context) {
            log := PermissionLog{
                UserID:    userID,
                Action:    action,
                Resource:  resource,
                Result:    result,
                IP:        c.ClientIP(),
                UserAgent: c.GetHeader("User-Agent"),
                Timestamp: time.Now(),
            }

            // 记录到数据库或日志文件
            global.GVA_LOG.Info("Permission Check", zap.Any("log", log))
        }

09.常见问题
    a.权限修改后不生效?
        需要调用 e.LoadPolicy() 重新加载权限策略,或重启应用。
    b.如何实现数据权限控制?
        可以在 Casbin 规则中添加数据范围字段,或使用自定义的数据过滤器。
    c.权限验证性能如何优化?
        使用 Redis 缓存权限结果,设置合理的缓存过期时间。
    d.如何实现临时权限?
        可以在权限规则中添加时间字段,或使用定时任务清理过期权限。

4.5 代码生成器

01.功能概述
    a.生成内容
        后端代码:Model、Service、API、Router
        前端代码:Vue 页面、API 接口、路由配置
        数据库:自动建表、字段验证
        权限配置:API 权限、菜单权限
    b.支持特性
        增删改查:完整的 CRUD 操作
        条件查询:支持多字段条件筛选
        分页查询:自动分页处理
        文件上传:支持文件字段处理
        关联查询:支持表关联操作
        自定义模板:可自定义代码模板

02.使用方式
    a.Web界面生成
        a.访问系统管理 → 代码生成器,通过可视化界面配置
            系统工具 → 代码生成器 → 新增
        b.配置步骤
            a.基础信息配置
                表名称
                表描述
                结构体名称
                包名
                文件名
            b.字段配置
                字段名称
                字段类型
                数据库类型
                字段描述
                是否必填
                查询条件
                字典类型
            c.生成选项
                生成模块
                生成路径
                是否覆盖
    b.命令行生成
        # 进入项目目录
        cd server

        # 运行代码生成器
        go run main.go -c=config.yaml -gva=gen
    c.API接口生成
        # 使用 curl 调用生成接口
        curl -X POST "http://localhost:8888/autoCode/createTemp" \
          -H "Content-Type: application/json" \
          -d '{
            "structName": "User",
            "tableName": "sys_users",
            "packageName": "system",
            "fields": [
              {
                "fieldName": "Name",
                "fieldDesc": "用户名",
                "fieldType": "string",
                "dataType": "varchar(255)",
                "fieldJson": "name",
                "require": true,
                "errorText": "请输入用户名"
              }
            ]
          }'

03.配置详解
    a.基础配置结构
        type AutoCodeStruct struct {
            StructName         string                 `json:"structName"`         // 结构体名称
            TableName          string                 `json:"tableName"`          // 表名
            PackageName        string                 `json:"packageName"`        // 包名
            HumpPackageName    string                 `json:"humpPackageName"`    // 驼峰包名
            Abbreviation       string                 `json:"abbreviation"`       // 缩写
            Description        string                 `json:"description"`        // 描述
            AutoCreateApiToSql bool                   `json:"autoCreateApiToSql"` // 自动创建API
            AutoCreateResource bool                   `json:"autoCreateResource"` // 自动创建资源
            AutoMoveFile       bool                   `json:"autoMoveFile"`       // 自动移动文件
            Fields             []Field                `json:"fields"`             // 字段列表
            DictTypes          []string               `json:"dictTypes"`          // 字典类型
            Package            string                 `json:"package"`            // 完整包路径
            PackageT           string                 `json:"packageT"`           // 模板包路径
            NeedValid          bool                   `json:"needValid"`          // 需要验证
            HasTimer           bool                   `json:"hasTimer"`           // 包含时间字段
            NeedSort           bool                   `json:"needSort"`           // 需要排序
            HasSearchTimer     bool                   `json:"hasSearchTimer"`     // 包含搜索时间
            HasFile            bool                   `json:"hasFile"`            // 包含文件字段
            HasDataSource      bool                   `json:"hasDataSource"`      // 包含数据源
            HasRichText        bool                   `json:"hasRichText"`        // 包含富文本
            HasPic             bool                   `json:"hasPic"`             // 包含图片
            LogicDelete        bool                   `json:"logicDelete"`        // 逻辑删除
            MenuID             string                 `json:"menuID"`             // 菜单ID
        }
    b.字段配置结构
        type Field struct {
            FieldName       string `json:"fieldName"`       // 字段名
            FieldDesc       string `json:"fieldDesc"`       // 字段描述
            FieldType       string `json:"fieldType"`       // Go字段类型
            FieldJson       string `json:"fieldJson"`       // JSON标签
            DataType        string `json:"dataType"`        // 数据库字段类型
            ColumnName      string `json:"columnName"`      // 数据库列名
            Comment         string `json:"comment"`         // 注释
            Require         bool   `json:"require"`         // 是否必填
            ErrorText       string `json:"errorText"`       // 错误提示
            Clearable       bool   `json:"clearable"`       // 是否可清空
            Sort            bool   `json:"sort"`            // 是否排序字段
            PrimaryKey      bool   `json:"primaryKey"`      // 是否主键
            DefaultValue    string `json:"defaultValue"`    // 默认值
            DictType        string `json:"dictType"`        // 字典类型
            Front           bool   `json:"front"`           // 前端显示
            Desc            bool   `json:"desc"`            // 降序
            SearchType      string `json:"searchType"`      // 搜索类型
            FieldSearchType string `json:"fieldSearchType"` // 字段搜索类型
            FieldSearchHide bool   `json:"fieldSearchHide"` // 隐藏搜索
            FieldIndexType  string `json:"fieldIndexType"`  // 索引类型
            CheckDataSource bool   `json:"checkDataSource"` // 检查数据源
            DataSource      struct {
                Association int    `json:"association"` // 关联类型
                Table       string `json:"table"`       // 关联表
                Label       string `json:"label"`       // 显示字段
                Value       string `json:"value"`       // 值字段
            } `json:"dataSource"` // 数据源配置
        }

04.模板系统
    a.模板目录结构
        server/resource/template/
        ├── web/
        │   ├── api.js.tpl           # 前端API模板
        │   ├── form.vue.tpl         # 表单页面模板
        │   ├── table.vue.tpl        # 列表页面模板
        │   └── ...
        └── server/
            ├── api.go.tpl           # 后端API模板
            ├── model.go.tpl         # 模型模板
            ├── request.go.tpl       # 请求结构模板
            ├── router.go.tpl        # 路由模板
            ├── service.go.tpl       # 服务模板
            └── ...
    b.自定义模板
        a.后端模板示例
            // model.go.tpl
            package {{.PackageName}}

            import (
                "github.com/flipped-aurora/gin-vue-admin/server/global"
                {{- if .HasTimer}}
                "time"
                {{- end}}
            )

            // {{.StructName}} 结构体  {{.Description}}
            type {{.StructName}} struct {
                global.GVA_MODEL {{- range .Fields}}
                {{- if .PrimaryKey}}
                {{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"primarykey{{- if .Comment}};comment:{{.Comment}}{{- end}}"`
                {{- else}}
                {{.FieldName}} {{.FieldType}} `json:"{{.FieldJson}}" form:"{{.FieldJson}}" gorm:"{{- if .ColumnName}}column:{{.ColumnName}};{{- end}}{{- if .Comment}}comment:{{.Comment}};{{- end}}{{- if .DataType}}type:{{.DataType}};{{- end}}{{- if .DefaultValue}}default:{{.DefaultValue}};{{- end}}"`
                {{- end}}{{- end}}
            }

            // TableName {{.StructName}} 表名
            func ({{.StructName}}) TableName() string {
                return "{{.TableName}}"
            }
        b.前端模板示例
            <!-- table.vue.tpl -->
            <template>
              <div>
                <div class="gva-search-box">
                  <el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit">
                    {{- range .Fields}}
                    {{- if .FieldSearchType}}
                    <el-form-item label="{{.FieldDesc}}" prop="{{.FieldJson}}">
                      {{- if eq .FieldSearchType "LIKE" }}
                      <el-input v-model="searchInfo.{{.FieldJson}}" placeholder="搜索条件" />
                      {{- else if eq .FieldSearchType "BETWEEN" }}
                      <template v-if="searchInfo.{{.FieldJson}} && searchInfo.{{.FieldJson}}.length === 2">
                        <el-date-picker
                          v-model="searchInfo.{{.FieldJson}}"
                          type="datetimerange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                        />
                      </template>
                      {{- end}}
                    </el-form-item>
                    {{- end}}
                    {{- end}}
                    <el-form-item>
                      <el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
                      <el-button icon="refresh" @click="onReset">重置</el-button>
                    </el-form-item>
                  </el-form>
                </div>
                <div class="gva-table-box">
                  <div class="gva-btn-list">
                    <el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
                    <el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
                  </div>
                  <el-table
                    ref="multipleTable"
                    style="width: 100%"
                    tooltip-effect="dark"
                    :data="tableData"
                    row-key="ID"
                    @selection-change="handleSelectionChange"
                  >
                    <el-table-column type="selection" width="55" />
                    {{- range .Fields}}
                    {{- if .Front}}
                    <el-table-column align="left" label="{{.FieldDesc}}" prop="{{.FieldJson}}" width="120" />
                    {{- end}}
                    {{- end}}
                    <el-table-column align="left" label="操作">
                      <template #default="scope">
                        <el-button type="primary" link class="table-button" @click="getDetails(scope.row)"><icon name="edit" />变更</el-button>
                        <el-button type="primary" link class="table-button" @click="deleteRow(scope.row)"><icon name="delete" />删除</el-button>
                      </template>
                    </el-table-column>
                  </el-table>
                  <div class="gva-pagination">
                    <el-pagination
                      layout="total, sizes, prev, pager, next, jumper"
                      :current-page="page"
                      :page-size="pageSize"
                      :page-sizes="[10, 30, 50, 100]"
                      :total="total"
                      @current-change="handleCurrentChange"
                      @size-change="handleSizeChange"
                    />
                  </div>
                </div>
              </div>
            </template>

05.生成器核心代码
    a.代码生成服务
        type AutoCodeService struct{}

        // CreateTemp 创建代码
        func (autoCodeService *AutoCodeService) CreateTemp(autoCode system.AutoCodeStruct, ids ...uint) (err error) {
            // 1. 创建基础目录
            basePath := "autocode_template/"
            if autoCode.AutoMoveFile {
                basePath = ""
            }

            // 2. 生成后端代码
            for _, value := range autoCode.Fields {
                if value.FieldType == "time.Time" {
                    autoCode.HasTimer = true
                }
                if value.FieldSearchType != "" {
                    autoCode.NeedSort = true
                }
                if value.DictType != "" {
                    autoCode.DictTypes = append(autoCode.DictTypes, value.DictType)
                }
            }

            // 3. 解析模板并生成文件
            templates := []string{
                "server/api.go.tpl",
                "server/model.go.tpl",
                "server/request.go.tpl",
                "server/router.go.tpl",
                "server/service.go.tpl",
                "web/api.js.tpl",
                "web/table.vue.tpl",
                "web/form.vue.tpl",
            }

            for _, tmpl := range templates {
                if err = autoCodeService.generateFile(tmpl, autoCode, basePath); err != nil {
                    return err
                }
            }

            // 4. 自动注入路由和API
            if autoCode.AutoCreateApiToSql {
                if err = autoCodeService.AutoCreateApi(&autoCode); err != nil {
                    return err
                }
            }

            return nil
        }

        // generateFile 生成单个文件
        func (autoCodeService *AutoCodeService) generateFile(templatePath string, data system.AutoCodeStruct, basePath string) error {
            // 读取模板文件
            templateContent, err := ioutil.ReadFile("resource/template/" + templatePath)
            if err != nil {
                return err
            }

            // 解析模板
            tmpl, err := template.New("autocode").Parse(string(templateContent))
            if err != nil {
                return err
            }

            // 生成目标文件路径
            outputPath := autoCodeService.getOutputPath(templatePath, data, basePath)

            // 创建目录
            if err = os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
                return err
            }

            // 生成文件
            file, err := os.Create(outputPath)
            if err != nil {
                return err
            }
            defer file.Close()

            // 执行模板
            return tmpl.Execute(file, data)
        }
    b.自动注入功能
        // AutoCreateApi 自动创建API
        func (autoCodeService *AutoCodeService) AutoCreateApi(autoCode *system.AutoCodeStruct) error {
            apis := []system.SysApi{
                {
                    Path:        "/" + autoCode.Abbreviation + "/create" + autoCode.StructName,
                    Description: "新增" + autoCode.Description,
                    ApiGroup:    autoCode.Description,
                    Method:      "POST",
                },
                {
                    Path:        "/" + autoCode.Abbreviation + "/delete" + autoCode.StructName,
                    Description: "删除" + autoCode.Description,
                    ApiGroup:    autoCode.Description,
                    Method:      "DELETE",
                },
                {
                    Path:        "/" + autoCode.Abbreviation + "/delete" + autoCode.StructName + "ByIds",
                    Description: "批量删除" + autoCode.Description,
                    ApiGroup:    autoCode.Description,
                    Method:      "DELETE",
                },
                {
                    Path:        "/" + autoCode.Abbreviation + "/update" + autoCode.StructName,
                    Description: "更新" + autoCode.Description,
                    ApiGroup:    autoCode.Description,
                    Method:      "PUT",
                },
                {
                    Path:        "/" + autoCode.Abbreviation + "/find" + autoCode.StructName,
                    Description: "根据ID获取" + autoCode.Description,
                    ApiGroup:    autoCode.Description,
                    Method:      "GET",
                },
                {
                    Path:        "/" + autoCode.Abbreviation + "/get" + autoCode.StructName + "List",
                    Description: "获取" + autoCode.Description + "列表",
                    ApiGroup:    autoCode.Description,
                    Method:      "GET",
                },
            }

            for _, api := range apis {
                if err := apiService.CreateApi(api); err != nil {
                    return err
                }
            }

            return nil
        }

        // AutoCreateMenu 自动创建菜单
        func (autoCodeService *AutoCodeService) AutoCreateMenu(autoCode *system.AutoCodeStruct) error {
            menu := system.SysBaseMenu{
                ParentId:  "0",
                Path:      autoCode.Abbreviation,
                Name:      autoCode.StructName,
                Component: "view/" + autoCode.PackageName + "/" + autoCode.PackageName + ".vue",
                Sort:      999,
                Meta: system.Meta{
                    Title: autoCode.Description,
                    Icon:  "setting",
                },
            }

            return menuService.AddBaseMenu(menu)
        }

06.高级功能
    a.关联表生成
        // 一对多关联配置
        type Association struct {
            Type        string `json:"type"`        // 关联类型:hasOne, hasMany, belongsTo
            ForeignKey  string `json:"foreignKey"`  // 外键
            References  string `json:"references"`  // 引用字段
            Table       string `json:"table"`       // 关联表
            StructName  string `json:"structName"`  // 关联结构体
        }

        // 生成关联查询代码
        func generateAssociationCode(associations []Association) string {
            var code strings.Builder

            for _, assoc := range associations {
                switch assoc.Type {
                case "hasMany":
                    code.WriteString(fmt.Sprintf(
                        "db.Preload(\"%s\").Find(&result)\n",
                        assoc.StructName,
                    ))
                case "belongsTo":
                    code.WriteString(fmt.Sprintf(
                        "db.Preload(\"%s\").Find(&result)\n",
                        assoc.StructName,
                    ))
                }
            }

            return code.String()
        }
    b.字典类型支持
        // 字典字段处理
        func processDictField(field Field) string {
            if field.DictType != "" {
                return fmt.Sprintf(`
                <el-select v-model="formData.%s" placeholder="请选择%s">
                  <el-option
                    v-for="item in %sOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>`,
                    field.FieldJson,
                    field.FieldDesc,
                    field.DictType,
                )
            }
            return fmt.Sprintf(`<el-input v-model="formData.%s" placeholder="请输入%s" />`,
                field.FieldJson, field.FieldDesc)
        }
    c.文件上传字段
        // 文件上传字段模板
        const fileUploadTemplate = `
        <el-upload
          class="upload-demo"
          :action="path"
          :headers="{ 'x-token': userStore.token }"
          :on-success="handleFileSuccess"
          :before-upload="beforeFileUpload"
        >
          <el-button type="primary">点击上传</el-button>
          <template #tip>
            <div class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
          </template>
        </el-upload>
        `

        // 生成文件上传处理函数
        func generateFileUploadMethods(fields []Field) string {
            var methods strings.Builder

            for _, field := range fields {
                if field.FieldType == "file" {
                    methods.WriteString(fmt.Sprintf(`
        // %s文件上传成功回调
        const handle%sSuccess = (response) => {
          formData.value.%s = response.data.file.url
        }

        // %s文件上传前校验
        const before%sUpload = (file) => {
          const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
          const isLt2M = file.size / 1024 / 1024 < 2

          if (!isJPG) {
            ElMessage.error('上传头像图片只能是 JPG/PNG 格式!')
          }
          if (!isLt2M) {
            ElMessage.error('上传头像图片大小不能超过 2MB!')
          }
          return isJPG && isLt2M
        }`,
                        strings.Title(field.FieldJson),
                        field.FieldJson,
                        field.FieldDesc,
                        strings.Title(field.FieldJson),
                    ))
                }
            }

            return methods.String()
        }

07.生成统计
    a.代码生成记录
        type AutoCodeHistory struct {
            ID            uint      `json:"ID" gorm:"primarykey"`
            CreatedAt     time.Time `json:"CreatedAt"`
            UpdatedAt     time.Time `json:"UpdatedAt"`
            StructName    string    `json:"structName" gorm:"comment:结构体名称"`
            TableName     string    `json:"tableName" gorm:"comment:表名"`
            RequestMeta   string    `json:"requestMeta" gorm:"type:text;comment:请求meta信息"`
            AutoCodePath  string    `json:"autoCodePath" gorm:"comment:自动生成代码路径"`
            InjectionMeta string    `json:"injectionMeta" gorm:"type:text;comment:注入meta信息"`
            ApiIDs        string    `json:"apiIDs" gorm:"comment:api表注册内容"`
            Flag          int       `json:"flag" gorm:"comment:表示对应状态 0 代表创建, 1 代表回滚 ...."`
        }

        // 记录生成历史
        func (autoCodeService *AutoCodeService) CreateAutoCodeHistory(meta, path, injectionMeta, apiIDs string, autoCode system.AutoCodeStruct) error {
            return global.GVA_DB.Create(&system.SysAutoCodeHistory{
                RequestMeta:   meta,
                AutoCodePath:  path,
                InjectionMeta: injectionMeta,
                StructName:    autoCode.StructName,
                TableName:     autoCode.TableName,
                ApiIDs:        apiIDs,
                Flag:          0,
            }).Error
        }
    b.代码回滚
        // RollBack 回滚自动生成的代码
        func (autoCodeService *AutoCodeService) RollBack(id uint) error {
            var history system.SysAutoCodeHistory
            if err := global.GVA_DB.First(&history, id).Error; err != nil {
                return err
            }

            // 删除生成的文件
            var meta system.AutoCodeStruct
            if err := json.Unmarshal([]byte(history.RequestMeta), &meta); err != nil {
                return err
            }

            // 删除后端文件
            serverFiles := []string{
                fmt.Sprintf("app/%s/model/%s.go", meta.PackageName, meta.PackageName),
                fmt.Sprintf("app/%s/request/%s.go", meta.PackageName, meta.PackageName),
                fmt.Sprintf("app/%s/service/%s.go", meta.PackageName, meta.PackageName),
                fmt.Sprintf("app/%s/api/%s.go", meta.PackageName, meta.PackageName),
                fmt.Sprintf("app/%s/router/%s.go", meta.PackageName, meta.PackageName),
            }

            for _, file := range serverFiles {
                if err := os.Remove(file); err != nil && !os.IsNotExist(err) {
                    return err
                }
            }

            // 删除前端文件
            webFiles := []string{
                fmt.Sprintf("../web/src/api/%s.js", meta.PackageName),
                fmt.Sprintf("../web/src/view/%s/%s.vue", meta.PackageName, meta.PackageName),
                fmt.Sprintf("../web/src/view/%s/%sForm.vue", meta.PackageName, meta.PackageName),
            }

            for _, file := range webFiles {
                if err := os.Remove(file); err != nil && !os.IsNotExist(err) {
                    return err
                }
            }

            // 删除API记录
            var apiIDs []uint
            if err := json.Unmarshal([]byte(history.ApiIDs), &apiIDs); err == nil {
                global.GVA_DB.Delete(&system.SysApi{}, apiIDs)
            }

            // 更新历史记录状态
            return global.GVA_DB.Model(&history).Update("flag", 1).Error
        }

08.前端集成
    a.代码生成器页面
        <template>
          <div class="auto-code">
            <el-card>
              <template #header>
                <div class="card-header">
                  <span>代码生成器</span>
                </div>
              </template>

              <el-form ref="autoCodeForm" :model="form" :rules="rules" label-width="120px">
                <el-form-item label="结构体名称" prop="structName">
                  <el-input v-model="form.structName" placeholder="请输入结构体名称" />
                </el-form-item>

                <el-form-item label="表名" prop="tableName">
                  <el-input v-model="form.tableName" placeholder="请输入表名" />
                </el-form-item>

                <el-form-item label="包名" prop="packageName">
                  <el-input v-model="form.packageName" placeholder="请输入包名" />
                </el-form-item>

                <el-form-item label="结构体描述" prop="description">
                  <el-input v-model="form.description" placeholder="请输入结构体描述" />
                </el-form-item>

                <!-- 字段配置 -->
                <el-form-item label="字段配置">
                  <el-table :data="form.fields" border>
                    <el-table-column label="字段名" prop="fieldName" />
                    <el-table-column label="字段描述" prop="fieldDesc" />
                    <el-table-column label="字段类型" prop="fieldType" />
                    <el-table-column label="数据库类型" prop="dataType" />
                    <el-table-column label="操作">
                      <template #default="scope">
                        <el-button @click="editField(scope.$index)">编辑</el-button>
                        <el-button @click="deleteField(scope.$index)">删除</el-button>
                      </template>
                    </el-table-column>
                  </el-table>

                  <el-button @click="addField" style="margin-top: 10px;">添加字段</el-button>
                </el-form-item>

                <el-form-item>
                  <el-button type="primary" @click="generateCode">生成代码</el-button>
                  <el-button @click="resetForm">重置</el-button>
                </el-form-item>
              </el-form>
            </el-card>
          </div>
        </template>

        <script setup>
        import { ref, reactive } from 'vue'
        import { createTemp } from '@/api/autoCode'
        import { ElMessage } from 'element-plus'

        const form = reactive({
          structName: '',
          tableName: '',
          packageName: '',
          description: '',
          fields: []
        })

        const rules = {
          structName: [{ required: true, message: '请输入结构体名称', trigger: 'blur' }],
          tableName: [{ required: true, message: '请输入表名', trigger: 'blur' }],
          packageName: [{ required: true, message: '请输入包名', trigger: 'blur' }]
        }

        const generateCode = async () => {
          try {
            const res = await createTemp(form)
            if (res.code === 0) {
              ElMessage.success('代码生成成功')
            }
          } catch (error) {
            ElMessage.error('代码生成失败')
          }
        }
        </script>

09.常见问题
    a.生成的代码编译失败?
        检查字段类型配置是否正确,确保 Go 类型和数据库类型匹配。
    b.前端页面显示异常?
        检查字段的前端显示配置,确保必要的字段已设置为前端显示。
    c.如何自定义生成模板?
        修改 server/resource/template/ 目录下的模板文件,重启服务即可生效。
    d.生成后如何添加自定义逻辑?
        在生成的代码基础上添加自定义方法,避免直接修改生成的核心 CRUD 方法。

4.6 对象存储

01.说明
    当前支持本地、阿里云、腾讯云、七牛云、AWS S3、华为云、Cloudflare R2 七种对象存储方式,可根据实际情况进行配置。

02.配置
    a.分类1
        local:
            path: uploads/file
            store-path: uploads/file
    b.分类2
        aliyun-oss:
            endpoint: yourEndpoint
            access-key-id: yourAccessKeyId
            access-key-secret: yourAccessKeySecret
            bucket-name: yourBucketName
            bucket-url: yourBucketUrl
            base-path: yourBasePath
    c.分类3
        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
    d.分类4
        qiniu:
            zone: ZoneHuaDong
            bucket: ""
            img-path: ""
            access-key: ""
            secret-key: ""
            use-https: false
            use-cdn-domains: false
    e.分类5
        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
    f.分类6
        hua-wei-obs:
            path: you-path
            bucket: you-bucket
            endpoint: you-endpoint
            access-key: you-access-key
            secret-key: you-secret-key
    g.分类7
        cloudflare-r2:
            bucket: xxxx0bucket
            base-url: https://gin.vue.admin.com
            path: uploads
            account-id: xxx_account_id
            access-key-id: xxx_key_id
            secret-access-key: xxx_secret_key

4.7 多数据库支持

01.文件配置
    a.说明
        在配置文件中新增db-list选项 db-list是文件中mysql与pymysql的一个超集,为了兼容低版本,目前没有在页面初始化的时候直接生成到db-list中
    b.代码
        db-list: [
          {
            disabled: false, # 是否禁用,填ture将不被初始化
            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,
          }
        ]

02.使用
    a.说明
        在config.yaml中正确配置db-list参数后,在main文件中添加初始化方法
        initialize.DBList()   # 初始化多数据库列表
    b.说明
        使用时根据配置的alias-name从 global.GetGlobalDBByDBName(alias-name)
        或者global.MustGetGlobalDBByDBName(alias-name)方法中获取db对象,
        两个方法的区别是MustGetGlobalDBByDBName会在alias-name对应db对象不存在时panic

4.8 严格角色模式

01.介绍
    开启严格角色模式后,用户只能访问其拥有的角色权限,无法访问其他角色的权限。

02.配置
    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
      use-strict-auth: true  # 这里修改为true

03.使用
    如果为顶级角色 则可以看到自己的角色且可以分配自己角色的相关api权限和菜单权限
    如果为子角色 则无法看到自己角色,能看到自己角色的下级所有角色,
    且可以对下级以及下级的所有角色做权限分配,分配范围为自己所有用的角色的所有权限

4.9 viper

01.优先级说明
    a.说明
        path 可变参数是为了给单元测试留下一个口子,方便使用相对路径或者绝对路径指定config文件所在的位置
        使用 ./server -c xxx/config.yaml 使用命令行进行传递的值赋值给config变量
        ConfigEnv 是定义在server/core/internal/constant.go 的一个常量,可自行修改为自己想要的环境变量
        最后会按照 Gin 框架自带的环境变量 GIN_MODE 进行匹配 server/core/internal/constant.go 文件中的定义。
    b.注意
        GIN_MODE 只能有三个值,debug、release、test, 其他值会panic的

02.GIN_MODE 使用场景说明
    a.说明
        有三个分支,开发分支 develop,测试分支 test ,生产分支 release
        但是三个分支的链接的数据库,oss都是不同的,所以就会有三个配置文件,这个不可能用文档保存的
        所以一般使用git的.gitattributes文件,每个分支都有属于自己的分支的配置文件以及Dockerfile
        在Dockerfile文件里指定是以下的任意一行代码即可,这样就可以控制每种环境对应的配置文件
    b.代码
        ENV GIN_MODE=debug
        ENV GIN_MODE=release
        ENV GIN_MODE=test

4.10 zap

01.基于zap的扩展路由日志中间件
    a.说明
        在 server/middleware 目录新建一个 logger.go 文件
        将以下代码复制粘贴进 logger.go 文件
    b.代码
        package middleware

        import (
            "gin-vue-admin/global"
            "github.com/gin-gonic/gin"
            "go.uber.org/zap"
            "time"
        )

        // ZapLogger 接收gin框架的路由日志
        func ZapLogger() gin.HandlerFunc {
            return func(c *gin.Context) {
                start := time.Now()
                path := c.Request.URL.Path
                query := c.Request.URL.RawQuery
                c.Next()

                cost := time.Since(start)
                global.GVA_LOG.Info(path,
                    zap.Int("status", c.Writer.Status()),
                    zap.String("method", c.Request.Method),
                    zap.String("path", path),
                    zap.String("query", query),
                    zap.String("ip", c.ClientIP()),
                    zap.String("user-agent", c.Request.UserAgent()),
                    zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
                    zap.Duration("cost", cost),
                )
            }
        }
    c.使用ZapLogger()中间件
        var Router = gin.Default()
        // 将上面的代码替换为
        var Router = gin.New()
        Router.Use(middleware.ZapLogger(), gin.Recovery())
    d.注意事项
        在 server/initialize/router.go 文件中
        一定要 ZapLogger 中间件 注册为全局中间件 ,不然不会生效的
        gin.Recovery() 与 Logger() 为Gin框架的 gin.Default() 默认使用的全局中间件
        -----------------------------------------------------------------------------------------------------
        // 此代码为Gin框架的源码
        // Default returns an Engine instance with the Logger and Recovery middleware already attached.
        func Default() *Engine {
          debugPrintWARNINGDefault()
          engine := New()
          engine.Use(Logger(), Recovery())
          return engine
        }

02.基于zap的Recovery捕获panic异常中间件
    a.说明
        在 server/middleware 目录新建一个 logger.go 文件,将以下代码复制粘贴进 logger.go 文件
    b.代码
        package middleware

        import (
            "gin-vue-admin/global"
            "github.com/gin-gonic/gin"
            "go.uber.org/zap"
            "net"
            "net/http"
            "net/http/httputil"
            "os"
            "runtime/debug"
            "strings"
        )

        // ZapRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
        func ZapRecovery(stack bool) gin.HandlerFunc {
            return func(c *gin.Context) {
                defer func() {
                    if err := recover(); err != nil {
                        // Check for a broken connection, as it is not really a
                        // condition that warrants a panic stack trace.
                        var brokenPipe bool
                        if ne, ok := err.(*net.OpError); ok {
                            if se, ok := ne.Err.(*os.SyscallError); ok {
                                if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
                                    brokenPipe = true
                                }
                            }
                        }

                        httpRequest, _ := httputil.DumpRequest(c.Request, false)
                        if brokenPipe {
                            global.GVA_LOG.Error(c.Request.URL.Path,
                                zap.Any("error", err),
                                zap.String("request", string(httpRequest)),
                            )
                            // If the connection is dead, we can't write a status to it.
                            _ = c.Error(err.(error)) // nolint: errcheck
                            c.Abort()
                            return
                        }

                        if stack {
                            global.GVA_LOG.Error("[Recovery from panic]",
                                zap.Any("error", err),
                                zap.String("request", string(httpRequest)),
                                zap.String("stack", string(debug.Stack())),
                            )
                        } else {
                            global.GVA_LOG.Error("[Recovery from panic]",
                                zap.Any("error", err),
                                zap.String("request", string(httpRequest)),
                            )
                        }
                        c.AbortWithStatus(http.StatusInternalServerError)
                    }
                }()
                c.Next()
            }
        }
    c.v2.3.0版本以上无需添加此文件代码
        a.使用ZapRecovery()中间件
            var Router = gin.Default()
            // 将上面的代码替换为
            var Router = gin.New()
            Router.Use(middleware.ZapLogger(), middleware.ZapRecovery())

            // V2.3.0版本请使用以下代码
            var Router = gin.Default()
            // 将上面的代码替换为
            var Router = gin.New()
            Router.Use(middleware.ZapLogger(), middleware.GinRecovery())
        b.Zap日志库使用指南&&配置指南
            // Zap日志库的配置选择在 config.yaml (./server/config.yaml)下的zap
            # zap logger configuration
            zap:
              level: 'debug'
              format: 'console'
              prefix: '[GIN-VUE-ADMIN]'
              director: 'log'
              link_name: 'latest_log'
              show_line: true
              encode_level: 'LowercaseColorLevelEncoder'
              stacktrace_key: 'stacktrace'
              log_in_console: true
            -------------------------------------------------------------------------------------------------
            配置名          配置的类型    说明
            level           string       level的模式的详细说明,请看zap官方文档
                                         info: info模式,无错误的堆栈信息,只输出信息
                                         debug: debug模式,有错误的堆栈详细信息
                                         warn: warn模式
                                         error: error模式,有错误的堆栈详细信息
                                         dpanic: dpanic模式
                                         panic: panic模式
                                         fatal: fatal模式
            format          string       console: 控制台形式输出日志
                                         json: json格式输出日志
            prefix          string       日志的前缀
            director        string       存放日志的文件夹,修改即可,不需要手动创建
            link_name       string       在server目录下会生成一个link_name的软连接文件,链接的是director配置项的最新日志文件
            show_line       bool         显示行号, 默认为true,不建议修改
            encode_level    string       LowercaseLevelEncoder:小写
                                         LowercaseColorLevelEncoder:小写带颜色
                                         CapitalLevelEncoder: 大写
                                         CapitalColorLevelEncoder: 大写带颜色
            stacktrace_key  string       堆栈的名称,即在json格式输出日志时的josn的key
            log_in_console  bool         是否输出到控制台,默认为true
            -------------------------------------------------------------------------------------------------
            开发环境 || 调试环境配置建议
            level:debug
            format:console
            encode_level:LowercaseColorLevelEncoder或者encode_leve:CapitalColorLevelEncoder
            -------------------------------------------------------------------------------------------------
            部署环境配置建议
            level:error
            format:json
            encode_level: LowercaseLevelEncoder 或者 encode_level:CapitalLevelEncoder
            log_in_console: false
            -------------------------------------------------------------------------------------------------
            建议只是建议,按照自己的需求进行即可,给出建议仅供参考

4.11 gorm

01.mysql
    a.server/config/gorm_mysql.go
        a.gorm-mysql
            package config

            type Mysql struct {
                Path         string `mapstructure:"path" json:"path" yaml:"path"`                             // 服务器地址
                Port         string `mapstructure:"port" json:"port" yaml:"port"`                             // 端口
                Config       string `mapstructure:"config" json:"config" yaml:"config"`                       // 高级配置
                Dbname       string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`                     // 数据库名
                Username     string `mapstructure:"username" json:"username" yaml:"username"`                 // 数据库用户名
                Password     string `mapstructure:"password" json:"password" yaml:"password"`                 // 数据库密码
                MaxIdleConns int    `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` // 空闲中的最大连接数
                MaxOpenConns int    `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
                LogMode      string `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`                  // 是否开启Gorm全局日志
                LogZap       bool   `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`                     // 是否通过zap写入日志文件
            }

02.pgsql
    a.server/config/gorm_pgsql.go
        a.gorm-pgsql
            package config

            type Pgsql struct {
                Path         string `mapstructure:"path" json:"path" yaml:"path"`                             // 服务器地址:端口
                Port         string `mapstructure:"port" json:"port" yaml:"port"`                             //:端口
                Config       string `mapstructure:"config" json:"config" yaml:"config"`                       // 高级配置
                Dbname       string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`                     // 数据库名
                Username     string `mapstructure:"username" json:"username" yaml:"username"`                 // 数据库用户名
                Password     string `mapstructure:"password" json:"password" yaml:"password"`                 // 数据库密码
                MaxIdleConns int    `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` // 空闲中的最大连接数
                MaxOpenConns int    `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
                LogMode      string `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`                  // 是否开启Gorm全局日志
                LogZap       bool   `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`                     // 是否通过zap写入日志文件
            }
    b.server/config/config.go
        a.在system选项下 选择db-type为mysql或者pgsql
            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-multipoint: false
              # IP限制次数 一个小时15000次
              iplimit-count: 15000
              #  IP限制一个小时
              iplimit-time: 3600
        b.config.yaml 配置字段详解
            mysql:
              path: ''   # 链接地址
              port: ''   # 链接端口
              config: ''  # 其他配置 例如时区
              db-name: ''  # 数据库名称
              username: '' # 数据库用户名
              password: '' # 数据库密码
              max-idle-conns: 10 # 连接池相关
              max-open-conns: 100 # 连接池相关
              log-mode: "" # 是控制台打印日志级别 "silent"、"error"、"warn"、"info" 不填默认info  填入silent可以关闭控制台日志
              log-zap: false # 日志是否用zap保存到本地

4.12 定时任务

01.接口地址 : gin-vue-admin/server/utils/timer/Timer
    type Timer interface {
        // 寻找所有Cron
        FindCronList() map[string]*taskManager
        // 添加Task 方法形式以秒的形式加入
        AddTaskByFuncWithSecond(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) // 添加Task Func以秒的形式加入
        // 添加Task 接口形式以秒的形式加入
        AddTaskByJobWithSeconds(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error)
        // 通过函数的方法添加任务
        AddTaskByFunc(cronName string, spec string, task func(), taskName string, option ...cron.Option) (cron.EntryID, error)
        // 通过接口的方法添加任务 要实现一个带有 Run方法的接口触发
        AddTaskByJob(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error)
        // 获取对应taskName的cron 可能会为空
        FindCron(cronName string) (*taskManager, bool)
        // 指定cron开始执行
        StartCron(cronName string)
        // 指定cron停止执行
        StopCron(cronName string)
        // 查找指定cron下的指定task
        FindTask(cronName string, taskName string) (*task, bool)
        // 根据id删除指定cron下的指定task
        RemoveTask(cronName string, id int)
        // 根据taskName删除指定cron下的指定task
        RemoveTaskByName(cronName string, taskName string)
        // 清理掉指定cronName
        Clear(cronName string)
        // 停止所有的cron
        Close()
    }

02.使用Demo
    type Job struct{}

    func (j *Job) Run() {
        fmt.Println("testFunc") // 每天打印一遍
    }

    func Demo() {
        // 在gva中 global.GVA_Timer 已经是初始化Timer对象供使用
        // 如果你想自己调用 utils.NewTimerTask() 即可拿到 Timer 接口
        // demo 演示
        t := utils.NewTimerTask()
        // spec 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron?utm_source=godoc
        // 同一个 taskName 中也可以设置多个任务
        id , err := t.AddTaskByFunc("testFunc", "@daily", func() {
            fmt.Println("testFunc") // 每天打印一遍
        })
        if err != nil {
            // ...
        }
        // job 实例
        id1 , err1 := t.AddTaskByJob("testFunc", "@daily", &Job{})
        if err1 != nil {
             // ...
        }

        // 使用 AddTaskByFunc AddTaskByJob 添加的任务默认是激活状态
        // 想要停止调用 StopTask 即可
        t.StopTask("testFunc")

        // 也可以调用 t.FindCron() 获取cron原生对象,调用它的更多方法

        // t.Remove() 删除任务
        t.Remove("testFunc",id)



        // t.Close() 释放资源
        t.Close()
    }

03.项目内示例,文件路径gin-vue-admin/server/initialize/timer.go
    package initialize

    import (
        "fmt"
        "github.com/flipped-aurora/gin-vue-admin/server/task"

        "github.com/robfig/cron/v3"

        "github.com/flipped-aurora/gin-vue-admin/server/global"
    )

    func Timer() {
        go func() {
            var option []cron.Option
            option = append(option, cron.WithSeconds())
            // 清理DB定时任务
            _, err := global.GVA_Timer.AddTaskByFunc("ClearDB", "@daily", func() {
                err := task.ClearTable(global.GVA_DB) // 定时任务方法定在task文件包中
                if err != nil {
                    fmt.Println("timer error:", err)
                }
            }, option...)
            if err != nil {
                fmt.Println("add timer error:", err)
            }

            // 其他定时任务定在这里 参考上方使用方法

            //_, err := global.GVA_Timer.AddTaskByFunc("定时任务标识", "corn表达式", func() {
            //  具体执行内容...
            //  ......
            //}, option...)
            //if err != nil {
            //  fmt.Println("add timer error:", err)
            //}
        }()
    }

4.13 AI助手集成

01.项目
    革命性的AI开发体验! 通过MCP(Model Context Protocol)让AI编辑工具深度理解GVA项目结构,实现智能化的代码生成和项目管理。
    版本要求:使用MCP功能需要GVA版本 ≥ 2.8.4,请确保您的项目版本满足要求。
    尽量使用 claude > gemini > gpt = kimi 模型 已达到更好效果

02.核心特性
    智能代码生成:AI自动创建完整的CRUD模板
    智能文件搜索:自动定位相关文件并提供精准修改建议
    自动化流程:一键生成API接口和菜单配置
    上下文理解:AI深度理解项目架构,提供更准确的代码联动

03.AI编辑工具配置
    a.支持的AI编辑工具
        Trae (尽量使用 trae.ai 国外版)
        Cursor
        Claude Code
        Windsurf
        Codebubby
        其他支持MCP协议的AI编辑器
    b.第一步:启动GVA项目
        确保你的GVA项目正在运行,MCP服务会自动在 http://127.0.0.1:8888/sse 启动
    c.第二步:配置AI编辑器
        在你的AI编辑工具的配置文件中添加以下MCP配置:
        {
          "mcpServers": {
            "GVA Helper": {
              "url": "http://127.0.0.1:8888/sse"
            }
          }
        }
    d.第三步:重启编辑器
        保存配置后重启你的AI编辑工具,等待MCP连接建立,MCP状态显示绿色即表示连接成功
    e.AI助手新能力
        配置完成后,AI助手将获得以下超能力:
        深度理解项目:自动识别GVA项目结构和代码模式
        智能代码生成:根据需求自动生成完整的功能模块
        精准文件定位:快速找到相关文件并提供修改建议
        全栈开发:同时处理前端、后端、数据库的代码生成
        UI自动化:自动配置路由、菜单和权限系统
    f.使用示例
        只需要告诉AI:"我想创建一个用户管理模块",AI就会:
        自动生成用户表结构
        创建完整的CRUD API
        生成前端管理页面
        配置菜单和路由
        设置权限控制

04.配置文件说明
    mcp:
        name: GVA_MCP  # MCP服务名称
        version: v1.0.0 # 版本号
        sse_path: /sse # SSE路径
        message_path: /message # 消息路径
        url_prefix: '' # URL前缀
        ## v2.8.6后可用
        addr: 8889 # 监听地址 在separate为true时有效 (暂时未实现独立运行功能,敬请期待)
        separate: false # 是否隔离 开启以后mcp将不会跟随gva本体启动 建议在生产环境开启

4.14 数据库设计

01.数据库架构概览
    a.数据库选择
        Gin-Vue-Admin 支持多种数据库:
        MySQL 8.0+ (推荐)
        PostgreSQL 12+
        SQLite 3 (开发环境)
        SQL Server 2019+
    b.核心模块
        数据库架构
        ├── 系统管理模块
        │   ├── 用户管理 (sys_users)
        │   ├── 角色管理 (sys_authorities)
        │   ├── 菜单管理 (sys_base_menus)
        │   ├── API管理 (sys_apis)
        │   └── 权限规则 (casbin_rule)
        ├── 基础功能模块
        │   ├── 字典管理 (sys_dictionaries)
        │   ├── 文件上传 (exa_file_upload_and_downloads)
        │   ├── 操作历史 (sys_operation_records)
        │   └── JWT黑名单 (jwt_blacklists)
        ├── 代码生成模块
        │   ├── 自动代码 (sys_auto_codes)
        │   └── 代码历史 (sys_auto_code_histories)
        └── 示例模块
            ├── 客户管理 (exa_customers)
            └── 文件分片 (exa_file_chunks)

02.核心表结构
    a.用户管理表
        a.sys_users (用户表)
            CREATE TABLE `sys_users` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `uuid` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户UUID',
              `username` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户登录名',
              `password` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户登录密码',
              `nick_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT '系统用户' COMMENT '用户昵称',
              `side_mode` varchar(191) COLLATE utf8mb4_general_ci DEFAULT 'dark' COMMENT '主题模式',
              `header_img` varchar(191) COLLATE utf8mb4_general_ci DEFAULT 'https://qmplusimg.henrongyi.top/gva_header.jpg' COMMENT '用户头像',
              `base_color` varchar(191) COLLATE utf8mb4_general_ci DEFAULT '#fff' COMMENT '基础颜色',
              `active_color` varchar(191) COLLATE utf8mb4_general_ci DEFAULT '#1890ff' COMMENT '活跃颜色',
              `authority_id` bigint unsigned DEFAULT '888' COMMENT '用户角色ID',
              `phone` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户手机号',
              `email` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户邮箱',
              `enable` tinyint(1) DEFAULT '1' COMMENT '用户是否被冻结 1正常 2冻结',
              PRIMARY KEY (`id`),
              UNIQUE KEY `idx_sys_users_username` (`username`),
              UNIQUE KEY `idx_sys_users_uuid` (`uuid`),
              KEY `idx_sys_users_deleted_at` (`deleted_at`),
              KEY `idx_sys_users_authority_id` (`authority_id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
        b.sys_authorities (角色表)
            CREATE TABLE `sys_authorities` (
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `authority_id` bigint unsigned NOT NULL COMMENT '角色ID',
              `authority_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色名',
              `parent_id` bigint unsigned DEFAULT NULL COMMENT '父角色ID',
              `default_router` varchar(191) COLLATE utf8mb4_general_ci DEFAULT 'dashboard' COMMENT '默认菜单',
              PRIMARY KEY (`authority_id`),
              UNIQUE KEY `idx_sys_authorities_authority_id` (`authority_id`),
              KEY `idx_sys_authorities_deleted_at` (`deleted_at`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表';
        c.sys_user_authority (用户角色关联表)
            CREATE TABLE `sys_user_authority` (
              `sys_user_id` bigint unsigned NOT NULL COMMENT '用户ID',
              `sys_authority_authority_id` bigint unsigned NOT NULL COMMENT '角色ID',
              PRIMARY KEY (`sys_user_id`,`sys_authority_authority_id`),
              KEY `fk_sys_user_authority_sys_authority` (`sys_authority_authority_id`),
              CONSTRAINT `fk_sys_user_authority_sys_authority` FOREIGN KEY (`sys_authority_authority_id`) REFERENCES `sys_authorities` (`authority_id`),
              CONSTRAINT `fk_sys_user_authority_sys_user` FOREIGN KEY (`sys_user_id`) REFERENCES `sys_users` (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户角色关联表';
    b.权限管理表
        a.casbin_rule (权限规则表)
            CREATE TABLE `casbin_rule` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT,
              `ptype` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '策略类型',
              `v0` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色ID',
              `v1` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '资源路径',
              `v2` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求方法',
              `v3` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
              `v4` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
              `v5` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
              PRIMARY KEY (`id`),
              UNIQUE KEY `idx_casbin_rule` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Casbin权限规则表';
            sys_apis (API管理表)
        b.sys_apis (API管理表)
            CREATE TABLE `sys_apis` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'API ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `path` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'API路径',
              `description` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'API描述',
              `api_group` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'API分组',
              `method` varchar(191) COLLATE utf8mb4_general_ci DEFAULT 'POST' COMMENT '请求方法',
              PRIMARY KEY (`id`),
              KEY `idx_sys_apis_deleted_at` (`deleted_at`),
              KEY `idx_sys_apis_path_method` (`path`,`method`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='API管理表';
            sys_authority_menus (角色菜单关联表)
        c.sys_authority_menus (角色菜单关联表)
            CREATE TABLE `sys_authority_menus` (
              `sys_authority_authority_id` bigint unsigned NOT NULL COMMENT '角色ID',
              `sys_base_menu_id` bigint unsigned NOT NULL COMMENT '菜单ID',
              PRIMARY KEY (`sys_authority_authority_id`,`sys_base_menu_id`),
              KEY `fk_sys_authority_menus_sys_base_menu` (`sys_base_menu_id`),
              CONSTRAINT `fk_sys_authority_menus_sys_authority` FOREIGN KEY (`sys_authority_authority_id`) REFERENCES `sys_authorities` (`authority_id`),
              CONSTRAINT `fk_sys_authority_menus_sys_base_menu` FOREIGN KEY (`sys_base_menu_id`) REFERENCES `sys_base_menus` (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色菜单关联表';
    c.菜单管理表
        a.sys_base_menus (基础菜单表)
            CREATE TABLE `sys_base_menus` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `menu_level` bigint unsigned DEFAULT NULL COMMENT '菜单层级',
              `parent_id` bigint unsigned DEFAULT NULL COMMENT '父菜单ID',
              `path` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由路径',
              `name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由名称',
              `hidden` tinyint(1) DEFAULT NULL COMMENT '是否隐藏',
              `component` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '组件路径',
              `sort` bigint DEFAULT NULL COMMENT '排序',
              `active_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '高亮菜单',
              `keep_alive` tinyint(1) DEFAULT NULL COMMENT '是否缓存',
              `default_menu` tinyint(1) DEFAULT NULL COMMENT '是否默认菜单',
              `title` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '菜单标题',
              `icon` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '菜单图标',
              `close_tab` tinyint(1) DEFAULT NULL COMMENT '是否关闭标签',
              PRIMARY KEY (`id`),
              KEY `idx_sys_base_menus_deleted_at` (`deleted_at`),
              KEY `idx_sys_base_menus_parent_id` (`parent_id`),
              KEY `idx_sys_base_menus_sort` (`sort`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='基础菜单表';
        b.sys_base_menu_btns (菜单按钮表)
            CREATE TABLE `sys_base_menu_btns` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '按钮ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '按钮名称',
              `desc` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '按钮描述',
              `sys_base_menu_id` bigint unsigned DEFAULT NULL COMMENT '菜单ID',
              PRIMARY KEY (`id`),
              KEY `idx_sys_base_menu_btns_deleted_at` (`deleted_at`),
              KEY `idx_sys_base_menu_btns_sys_base_menu_id` (`sys_base_menu_id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单按钮表';
    d.系统功能表
        a.sys_dictionaries (字典表)
            CREATE TABLE `sys_dictionaries` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '字典ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字典名称',
              `type` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字典类型',
              `status` tinyint(1) DEFAULT NULL COMMENT '状态',
              `desc` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '描述',
              PRIMARY KEY (`id`),
              UNIQUE KEY `idx_sys_dictionaries_type` (`type`),
              KEY `idx_sys_dictionaries_deleted_at` (`deleted_at`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='字典表';
        b.sys_dictionary_details (字典详情表)
            CREATE TABLE `sys_dictionary_details` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '字典详情ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `label` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '展示值',
              `value` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字典值',
              `extend` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '扩展值',
              `status` tinyint(1) DEFAULT NULL COMMENT '启用状态',
              `sort` bigint DEFAULT NULL COMMENT '排序标记',
              `sys_dictionary_id` bigint unsigned DEFAULT NULL COMMENT '关联字典ID',
              PRIMARY KEY (`id`),
              KEY `idx_sys_dictionary_details_deleted_at` (`deleted_at`),
              KEY `idx_sys_dictionary_details_sys_dictionary_id` (`sys_dictionary_id`),
              KEY `idx_sys_dictionary_details_sort` (`sort`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='字典详情表';
        c.sys_operation_records (操作记录表)
            CREATE TABLE `sys_operation_records` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求IP',
              `method` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求方法',
              `path` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求路径',
              `status` bigint DEFAULT NULL COMMENT '请求状态',
              `latency` bigint DEFAULT NULL COMMENT '延迟时间',
              `agent` text COLLATE utf8mb4_general_ci COMMENT '代理信息',
              `error_message` text COLLATE utf8mb4_general_ci COMMENT '错误信息',
              `body` text COLLATE utf8mb4_general_ci COMMENT '请求Body',
              `resp` text COLLATE utf8mb4_general_ci COMMENT '响应Body',
              `user_id` bigint unsigned DEFAULT NULL COMMENT '用户ID',
              PRIMARY KEY (`id`),
              KEY `idx_sys_operation_records_deleted_at` (`deleted_at`),
              KEY `idx_sys_operation_records_user_id` (`user_id`),
              KEY `idx_sys_operation_records_created_at` (`created_at`),
              KEY `idx_sys_operation_records_method_path` (`method`,`path`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作记录表';
    e.文件管理表
        a.exa_file_upload_and_downloads (文件上传下载表)
            CREATE TABLE `exa_file_upload_and_downloads` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '文件ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件名',
              `url` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件地址',
              `tag` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件标签',
              `key` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件唯一标识',
              PRIMARY KEY (`id`),
              KEY `idx_exa_file_upload_and_downloads_deleted_at` (`deleted_at`),
              KEY `idx_exa_file_upload_and_downloads_tag` (`tag`),
              KEY `idx_exa_file_upload_and_downloads_key` (`key`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文件上传下载表';
        b.exa_file_chunks (文件分片表)
            CREATE TABLE `exa_file_chunks` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '分片ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `exa_file_id` bigint unsigned DEFAULT NULL COMMENT '文件ID',
              `file_chunk_number` bigint DEFAULT NULL COMMENT '分片编号',
              `file_chunk_path` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '分片路径',
              PRIMARY KEY (`id`),
              KEY `idx_exa_file_chunks_deleted_at` (`deleted_at`),
              KEY `idx_exa_file_chunks_exa_file_id` (`exa_file_id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文件分片表';
    f.代码生成表
        a.sys_auto_codes (自动代码表)
            CREATE TABLE `sys_auto_codes` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '代码ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `package_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '包名',
              `label` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '展示名',
              `desc` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '描述',
              PRIMARY KEY (`id`),
              KEY `idx_sys_auto_codes_deleted_at` (`deleted_at`),
              KEY `idx_sys_auto_codes_package_name` (`package_name`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='自动代码表';
        b.sys_auto_code_histories (代码生成历史表)
            CREATE TABLE `sys_auto_code_histories` (
              `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '历史ID',
              `created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
              `updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
              `deleted_at` datetime(3) DEFAULT NULL COMMENT '删除时间',
              `package` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '包名',
              `business_db` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '业务数据库',
              `table_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '表名',
              `menu_id` bigint unsigned DEFAULT NULL COMMENT '菜单ID',
              `request_meta` text COLLATE utf8mb4_general_ci COMMENT '请求元数据',
              `auto_code_path` text COLLATE utf8mb4_general_ci COMMENT '自动生成代码路径',
              `injection_meta` text COLLATE utf8mb4_general_ci COMMENT '注入元数据',
              `struct_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '结构体名称',
              `struct_cn_name` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '结构体中文名称',
              `api_ids` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'API IDs',
              `flag` bigint DEFAULT NULL COMMENT '标记',
              PRIMARY KEY (`id`),
              KEY `idx_sys_auto_code_histories_deleted_at` (`deleted_at`),
              KEY `idx_sys_auto_code_histories_package` (`package`),
              KEY `idx_sys_auto_code_histories_table_name` (`table_name`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='代码生成历史表';

02.表关系设计
    a.用户权限关系图
        用户 (sys_users)
            ↓ (1:N)
        用户角色关联 (sys_user_authority)
            ↓ (N:1)
        角色 (sys_authorities)
            ↓ (1:N)
        角色菜单关联 (sys_authority_menus)
            ↓ (N:1)
        菜单 (sys_base_menus)
            ↓ (1:N)
        菜单按钮 (sys_base_menu_btns)

        角色 (sys_authorities)
            ↓ (通过Casbin)
        权限规则 (casbin_rule)
            ↓ (关联)
        API (sys_apis)
    b.核心关系说明
        a.用户-角色关系
            // 用户结构体
            type SysUser struct {
                global.GVA_MODEL
                UUID        uuid.UUID      `json:"uuid" gorm:"index;comment:用户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"`
                Authority   SysAuthority   `json:"authority" gorm:"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色"`
                Authorities []SysAuthority `json:"authorities" gorm:"many2many:sys_user_authority;"`
                Phone       string         `json:"phone" gorm:"comment:用户手机号"`
                Email       string         `json:"email" gorm:"comment:用户邮箱"`
                Enable      int            `json:"enable" gorm:"default:1;comment:用户是否被冻结 1正常 2冻结"`
            }
        b.角色-菜单关系
            // 角色结构体
            type SysAuthority struct {
                CreatedAt     time.Time       `json:"createdAt"`
                UpdatedAt     time.Time       `json:"updatedAt"`
                DeletedAt     *gorm.DeletedAt `json:"-" gorm:"index"`
                AuthorityId   uint            `json:"authorityId" gorm:"not null;unique;primary_key;comment:角色ID"`
                AuthorityName string          `json:"authorityName" gorm:"comment:角色名"`
                ParentId      *uint           `json:"parentId" gorm:"comment:父角色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:默认菜单"`
            }
        c.菜单层级关系
            // 菜单结构体
            type SysBaseMenu struct {
                global.GVA_MODEL
                MenuLevel     uint                                     `json:"-"`
                ParentId      *uint                                    `json:"parentId" gorm:"comment:父菜单ID"`
                Path          string                                   `json:"path" gorm:"comment:路由path"`
                Name          string                                   `json:"name" gorm:"comment:路由name"`
                Hidden        *bool                                    `json:"hidden" gorm:"comment:是否在列表隐藏"`
                Component     string                                   `json:"component" gorm:"comment:对应前端文件路径"`
                Sort          int                                      `json:"sort" gorm:"comment:排序标记"`
                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"`
                Authorities   []SysAuthority                           `json:"authorities" gorm:"many2many:sys_authority_menus;"`
                Children      []SysBaseMenu                            `json:"children" gorm:"-"`
                MenuBtn       []SysBaseMenuBtn                         `json:"menuBtn"`
                Parameters    []SysBaseMenuParameter                   `json:"parameters"`
            }

03.索引优化策略
    a.主要索引设计
        a.用户表索引
            -- 主键索引
            PRIMARY KEY (`id`)

            -- 唯一索引
            UNIQUE KEY `idx_sys_users_username` (`username`)
            UNIQUE KEY `idx_sys_users_uuid` (`uuid`)

            -- 普通索引
            KEY `idx_sys_users_deleted_at` (`deleted_at`)
            KEY `idx_sys_users_authority_id` (`authority_id`)
            KEY `idx_sys_users_email` (`email`)
            KEY `idx_sys_users_phone` (`phone`)

            -- 复合索引
            KEY `idx_sys_users_enable_deleted` (`enable`, `deleted_at`)
        b.操作记录表索引
            -- 时间范围查询索引
            KEY `idx_sys_operation_records_created_at` (`created_at`)

            -- 用户操作查询索引
            KEY `idx_sys_operation_records_user_id` (`user_id`)

            -- API路径查询索引
            KEY `idx_sys_operation_records_method_path` (`method`, `path`)

            -- 状态查询索引
            KEY `idx_sys_operation_records_status` (`status`)

            -- 复合查询索引
            KEY `idx_operation_user_time` (`user_id`, `created_at`, `method`)
        c.权限规则表索引
            -- Casbin查询优化索引
            UNIQUE KEY `idx_casbin_rule` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)

            -- 角色权限查询索引
            KEY `idx_casbin_rule_v0` (`v0`)

            -- 资源权限查询索引
            KEY `idx_casbin_rule_v1_v2` (`v1`, `v2`)
    b.查询优化示例
        a.用户列表查询优化
            -- 优化前:全表扫描
            SELECT * FROM sys_users
            WHERE deleted_at IS NULL
            AND enable = 1
            ORDER BY created_at DESC
            LIMIT 10 OFFSET 0;

            -- 优化后:使用复合索引
            SELECT id, username, nick_name, email, phone, created_at
            FROM sys_users
            WHERE enable = 1 AND deleted_at IS NULL
            ORDER BY created_at DESC
            LIMIT 10 OFFSET 0;

            -- 对应索引
            KEY `idx_users_enable_deleted_created` (`enable`, `deleted_at`, `created_at`)
        b.操作日志查询优化
            -- 按用户和时间范围查询
            SELECT * FROM sys_operation_records
            WHERE user_id = 1
            AND created_at BETWEEN '2023-01-01' AND '2023-12-31'
            AND deleted_at IS NULL
            ORDER BY created_at DESC;

            -- 对应索引
            KEY `idx_operation_user_time_deleted` (`user_id`, `created_at`, `deleted_at`)
        c.权限验证查询优化
            -- Casbin权限验证查询
            SELECT * FROM casbin_rule
            WHERE ptype = 'p'
            AND v0 = '888'
            AND v1 = '/user/getUserList'
            AND v2 = 'POST';

            -- 已有唯一索引覆盖此查询
            UNIQUE KEY `idx_casbin_rule` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)

04.数据库配置
    a.GORM 配置
        // config/gorm.go
        type My   struct {
            GeneralDB `yaml:",inline" mapstructure:",squash"`
        }

        type GeneralDB struct {
            Path         string `mapstructure:"path" json:"path" yaml:"path"`
            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"`
            Prefix       string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`
            Singular     bool   `mapstructure:"singular" json:"singular" yaml:"singular"`
            Engine       string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"`
            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"`
            LogMode      string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"`
            LogZap       bool   `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"`
        }
    b.连接池配置
        // initialize/gorm.go
        func GormMy  () *gorm.DB {
            m := global.GVA_CONFIG.My
            if m.Dbname == "" {
                return nil
            }
            my  Config := my  .Config{
                DSN:                       m.Dsn(),
                DefaultStringSize:         191,
                SkipInitializeWithVersion: false,
            }
            if db, err := gorm.Open(my  .New(my  Config), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
                return nil
            } else {
                db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
                  DB, _ := db.DB()
                  DB.SetMaxIdleConns(m.MaxIdleConns)
                  DB.SetMaxOpenConns(m.MaxOpenConns)
                return db
            }
        }
    c.数据库迁移
        // initialize/gorm.go
        func RegisterTables() {
            db := global.GVA_DB
            err := db.AutoMigrate(
                // 系统模块
                system.SysUser{},
                system.SysAuthority{},
                system.SysApi{},
                system.SysBaseMenu{},
                system.SysBaseMenuBtn{},
                system.SysBaseMenuParameter{},
                system.SysAutoCode{},
                system.SysAutoCodeHistory{},
                system.SysDictionary{},
                system.SysDictionaryDetail{},
                system.SysOperationRecord{},

                // 示例模块
                example.ExaFile{},
                example.ExaFileChunk{},
                example.ExaFileUploadAndDownload{},
                example.ExaCustomer{},
            )
            if err != nil {
                global.GVA_LOG.Error("register table failed", zap.Error(err))
                os.Exit(0)
            }
            global.GVA_LOG.Info("register table success")
        }

05.数据库维护
    a.备份策略
        a.全量备份脚本
            #!/bin/bash
            # backup_full.sh

            DB_NAME="gin_vue_admin"
            DB_USER="root"
            DB_PASS="password"
            BACKUP_DIR="/backup/my  "
            DATE=$(date +"%Y%m%d_%H%M%S")

            # 创建备份目录
            mkdir -p $BACKUP_DIR

            # 全量备份
            my  dump -u$DB_USER -p$DB_PASS \
              --single-transaction \
              --routines \
              --triggers \
              --events \
              --hex-blob \
              $DB_NAME > $BACKUP_DIR/full_backup_$DATE.

            # 压缩备份文件
            gzip $BACKUP_DIR/full_backup_$DATE.

            # 删除7天前的备份
            find $BACKUP_DIR -name "full_backup_*.  .gz" -mtime +7 -delete

            echo "Full backup completed: full_backup_$DATE.  .gz"
        b.增量备份脚本
            #!/bin/bash
            # backup_incremental.sh

            DB_NAME="gin_vue_admin"
            DB_USER="root"
            DB_PASS="password"
            BACKUP_DIR="/backup/my  /incremental"
            DATE=$(date +"%Y%m%d_%H%M%S")

            # 创建备份目录
            mkdir -p $BACKUP_DIR

            # 增量备份(基于binlog)
            my  binlog --start-datetime="$(date -d '1 hour ago' '+%Y-%m-%d %H:%M:%S')" \
              --stop-datetime="$(date '+%Y-%m-%d %H:%M:%S')" \
              /var/lib/my  /my  -bin.* > $BACKUP_DIR/incremental_$DATE.

            # 压缩备份文件
            gzip $BACKUP_DIR/incremental_$DATE.

            echo "Incremental backup completed: incremental_$DATE.  .gz"
    b.性能监控
        a.慢查询监控
            -- 开启慢查询日志
            SET GLOBAL slow_query_log = 'ON';
            SET GLOBAL long_query_time = 2;
            SET GLOBAL log_queries_not_using_indexes = 'ON';

            -- 查看慢查询统计
            SELECT
                SCHEMA_NAME,
                DIGEST_TEXT,
                COUNT_STAR,
                AVG_TIMER_WAIT/1000000000 AS avg_time_seconds,
                MAX_TIMER_WAIT/1000000000 AS max_time_seconds
            FROM performance_schema.events_statements_summary_by_digest
            WHERE SCHEMA_NAME = 'gin_vue_admin'
            ORDER BY AVG_TIMER_WAIT DESC
            LIMIT 10;
        b.索引使用情况
            -- 查看未使用的索引
            SELECT
                t.TABLE_SCHEMA,
                t.TABLE_NAME,
                t.INDEX_NAME,
                t.NON_UNIQUE,
                t.COLUMN_NAME
            FROM information_schema.STATISTICS t
            LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage i
                ON t.TABLE_SCHEMA = i.OBJECT_SCHEMA
                AND t.TABLE_NAME = i.OBJECT_NAME
                AND t.INDEX_NAME = i.INDEX_NAME
            WHERE t.TABLE_SCHEMA = 'gin_vue_admin'
                AND i.INDEX_NAME IS NULL
                AND t.INDEX_NAME != 'PRIMARY'
            ORDER BY t.TABLE_NAME, t.INDEX_NAME;

            -- 查看索引使用频率
            SELECT
                OBJECT_SCHEMA,
                OBJECT_NAME,
                INDEX_NAME,
                COUNT_READ,
                COUNT_WRITE,
                COUNT_FETCH,
                COUNT_INSERT,
                COUNT_UPDATE,
                COUNT_DELETE
            FROM performance_schema.table_io_waits_summary_by_index_usage
            WHERE OBJECT_SCHEMA = 'gin_vue_admin'
            ORDER BY COUNT_READ DESC;
    c.数据清理
        a.操作日志清理
            -- 清理30天前的操作日志
            DELETE FROM sys_operation_records
            WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);

            -- 清理软删除的数据(谨慎操作)
            DELETE FROM sys_users
            WHERE deleted_at IS NOT NULL
            AND deleted_at < DATE_SUB(NOW(), INTERVAL 90 DAY);
        b.定期维护脚本
            -- 优化表
            OPTIMIZE TABLE sys_operation_records;
            OPTIMIZE TABLE sys_users;
            OPTIMIZE TABLE casbin_rule;

            -- 分析表
            ANALYZE TABLE sys_operation_records;
            ANALYZE TABLE sys_users;
            ANALYZE TABLE casbin_rule;

            -- 检查表
            CHECK TABLE sys_operation_records;
            CHECK TABLE sys_users;
            CHECK TABLE casbin_rule;

06.数据字典
    a.状态码定义
        字段    值      说明
        enable  1       用户启用
        enable  2       用户冻结
        status  1       字典启用
        status  0       字典禁用
        hidden  true    菜单隐藏
        hidden  false   菜单显示
    b.默认数据
        a.默认角色
            INSERT INTO `sys_authorities` VALUES
            ('2023-01-01 00:00:00.000','2023-01-01 00:00:00.000',NULL,888,'普通用户',0,'dashboard'),
            ('2023-01-01 00:00:00.000','2023-01-01 00:00:00.000',NULL,8881,'普通用户子角色',888,'dashboard'),
            ('2023-01-01 00:00:00.000','2023-01-01 00:00:00.000',NULL,9528,'测试角色',0,'dashboard');
        b.默认用户
            INSERT INTO `sys_users` VALUES
            (1,'2023-01-01 00:00:00.000','2023-01-01 00:00:00.000',NULL,'a303176530-3dda-4a33-b261-61809d378a34','admin','$2a$10$2XLuViXqLcn18zyGHU.9COFYqI16yGHjs6pXU5klUkdOKWjjceYUC','超级管理员','dark','https://qmplusimg.henrongyi.top/gva_header.jpg','#fff','#1890ff',888,'17611111111','[email protected]',1);

5 部署指南

5.1 项目上线

01.前端
    在web目录下执行 npm run build 得到 dist文件夹 将dist文件夹上传到服务器 建议使用nginx进行代理 并且设置 proxy 把请求代理到后端

02.后端
    a.在Windows 编译到 Linux 和Mac
        # 交叉编译到Linux
        set GOOS=linux
        set GOARCH=amd64
        go build -o app-linux

        # 交叉编译到Mac
        set GOOS=darwin
        set GOARCH=amd64
        go build -o app-mac
    b.在Linux下交叉编译到Windows和Mac
        # 交叉编译到Windows
        GOOS=windows GOARCH=amd64 go build -o app-windows.exe

        # 交叉编译到Mac
        GOOS=darwin GOARCH=amd64 go build -o app-mac
    c.在Mac下交叉编译到Windows和Linux
        # 交叉编译到Windows
        GOOS=windows GOARCH=amd64 go build -o app-windows.exe

        # 交叉编译到Linux
        GOOS=linux GOARCH=amd64 go build -o app-linux
    d.说明
        在 server下 go build . 得到一个可执行文件然后将可执行文件和config.yaml 以及 resource 文件夹上传至服务器
        三者最好放在同一路径下,最终服务器目录结构可能如下
        ├── breakpointDir  // 后续断点续传自动生成
        ├── chunk   // 后续断点续传自动生成
        ├── fileDir   // 后续断点续传自动生成
        ├── finish   // 后续断点续传自动生成
        ├── resource
        │   └── 子目录文件
        ├── dist
        │   └── 子目录文件
        ├── gin-vue-admin
        ├── config.yaml

03.Nginx的配置(以下内容节取自授权文档)
    a.安装Nginx
        yum install -y nginx
        #安装所有模块
        yum -y install nginx-all-modules.noarch

        # 启动nginx
        systemctl start nginx && systemctl enable nginx
    b.打开编辑配置文件
        vim /etc/nginx/nginx.conf
    c.参考配置代码如下
        user root;
        events {
          worker_connections  1024;  ## Default: 1024
        }

        http {
             include       mime.types;
            default_type  application/octet-stream;
            sendfile        on;
            keepalive_timeout  65;

        server {
            listen 80;
            index index.php index.html index.htm default.php default.htm default.html;
            server_name home.mychat.cloud;
            root 你的dist所在位置;

            location  /api {
                rewrite ^/api/(.*)$ /$1 break;
                proxy_pass http://127.0.0.1:8888; # 设置代理服务器的协议和地址  端口要和后端部署保持一致
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
        }
    d.重启生效
        sudo systemctl restart nginx
        后端地址未作修改时默认为http://127.0.0.1:8888

5.2 docker

01.web前端项目单独打包
    a.使用 nginx 镜像,my.conf 来源于 gin-vue-admin 的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://127.0.0.1:8888; # 设置代理服务器的协议和地址
            }

           location /api/swagger/index.html {
               proxy_pass http://127.0.0.1:8888/swagger/index.html;
            }
        }
    b.Dockerfile 来源于 gin-vue-admin 的dockerfile_web
        # 声明镜像来源为node:12.16.1
        FROM node:12.16.1

        # 声明工作目录
        WORKDIR /gva_web/

        # 拷贝整个web项目到当前工作目录
        COPY . .

        # 通过npm下载cnpm
        RUN npm install -g cnpm --registry=https://registry.npm.taobao.org

        # 使用cnpm进行安装依赖
        RUN cnpm install || npm install

        # 使用npm run build命令打包web项目
        RUN npm run build
        # ===================================================== 以下为多阶段构建 ==========================================================

        # 声明镜像来源为nginx:alpine, alpine 镜像小
        FROM nginx:alpine

        # 镜像编写者及邮箱
        LABEL MAINTAINER="SliverHorn@[email protected]"

        # 从.docker-compose/nginx/conf.d/目录拷贝my.conf到容器内的/etc/nginx/conf.d/my.conf
        COPY .docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf

        # 从第一阶段进行拷贝文件
        COPY --from=0 /gva_web/dist /usr/share/nginx/html

        # 查看/etc/nginx/nginx.conf文件
        RUN cat /etc/nginx/nginx.conf

        # 查看 /etc/nginx/conf.d/my.conf
        RUN cat /etc/nginx/conf.d/my.conf

        # 查看 文件是否拷贝成功
        RUN ls -al /usr/share/nginx/html

02.server项目单独打包
    a.Dockerfile 来源于 gin-vue-admin 的 Dockerfile
        # 声明镜像来源为golang:alpine
        FROM golang:alpine

        # 声明工作目录
        WORKDIR /go/src/gin-vue-admin

        # 拷贝整个server项目到工作目录
        COPY . .

        # go generate 编译前自动执行代码
        # go env 查看go的环境变量
        # go build -o server . 打包项目生成文件名为server的二进制文件
        RUN go generate && go env && go build -o server .

        # ==================================================== 以下为多阶段构建 ==========================================================

        # 声明镜像来源为alpine:latest
        FROM alpine:latest

        # 镜像编写者及邮箱
        LABEL MAINTAINER="SliverHorn@[email protected]"

        # 声明工作目录
        WORKDIR /go/src/gin-vue-admin

        # 把/go/src/gin-vue-admin整个文件夹的文件到当前工作目录
        COPY --from=0 /go/src/gin-vue-admin ./

        EXPOSE 8888

        # 运行打包好的二进制 并用-c 指定config.docker.yaml配置文件
        ENTRYPOINT ./server -c config.docker.yaml

03.根据Dockerfile生成Docker镜像
    # -d 后台运行
    # -p 映射端口:内部端口
    # -name 容器名字
    # gva-server:1.0为docker build时的-t的参数
    docker run -d -p 8888:8888 --name gva-server-v1 gva-server:1.0

    # -it 以可交互模式运行并进入容器, 使用快捷键Ctrl + p + q即后台运行程序,Ctrl+c为退出容器
    # -p 映射端口:内部端口
    # -name 容器名字
    # gva-server:1.0为docker build时的-t的参数
    docker run -it -p 8888:8888 --name gva-server-v1 gva-server:1.0

5.3 docker-compose

01.Web使用Docker打包示例
    a.使用 nginx 镜像,my.conf 来源于 gin-vue-admin 的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://177.7.0.12:8888; # 设置代理服务器的协议和地址
            }

           location /api/swagger/index.html {
               proxy_pass http://127.0.0.1:8888/swagger/index.html;
            }
        }
    b.Dockerfile 来源于 gin-vue-admin 的Dockerfile
        # 声明镜像来源为node:12.16.1
        FROM node:12.16.1

        # 声明工作目录
        WORKDIR /gva_web/

        # 拷贝整个web项目到当前工作目录
        COPY . .

        # 通过npm下载cnpm
        RUN npm install -g cnpm --registry=https://registry.npm.taobao.org

        # 使用cnpm进行安装依赖
        RUN cnpm install || npm install

        # 使用npm run build命令打包web项目
        RUN npm run build
        # ===================================================== 以下为多阶段构建 ==========================================================

        # 声明镜像来源为nginx:alpine, alpine 镜像小
        FROM nginx:alpine

        # 镜像编写者及邮箱
        LABEL MAINTAINER="SliverHorn@[email protected]"

        # 从.docker-compose/nginx/conf.d/目录拷贝my.conf到容器内的/etc/nginx/conf.d/my.conf
        COPY .docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf

        # 从第一阶段进行拷贝文件
        COPY --from=0 /gva_web/dist /usr/share/nginx/html

        # 查看/etc/nginx/nginx.conf文件
        RUN cat /etc/nginx/nginx.conf

        # 查看 /etc/nginx/conf.d/my.conf
        RUN cat /etc/nginx/conf.d/my.conf

        # 查看 文件是否拷贝成功
        RUN ls -al /usr/share/nginx/html

02.Server使用Docker打包示例
    a.说明
        mysql -> path 的 mysql 会自动获取mysql服务的容器内部ip及端口
        redis -> path 的 redis 会自动获取redis服务的容器内部ip
    b.Dockerfile 来源于 gin-vue-admin 的 Dockerfile
        # 声明镜像来源为golang:alpine
        FROM golang:alpine

        # 声明工作目录
        WORKDIR /go/src/gin-vue-admin

        # 拷贝整个server项目到工作目录
        COPY . .

        # go generate 编译前自动执行代码
        # go env 查看go的环境变量
        # go build -o server . 打包项目生成文件名为server的二进制文件
        RUN go generate && go env && go build -o server .

        # ==================================================== 以下为多阶段构建 ==========================================================

        # 声明镜像来源为alpine:latest
        FROM alpine:latest

        # 镜像编写者及邮箱
        LABEL MAINTAINER="SliverHorn@[email protected]"

        # 声明工作目录
        WORKDIR /go/src/gin-vue-admin

        # 把/go/src/gin-vue-admin中的可执行文件以及配置文件(resource模板文件)添加进入docker中
        COPY --from=0 /go/src/gin-vue-admin/server ./
        COPY --from=0 /go/src/gin-vue-admin/config.docker.yaml ./
        COPY --from=0 /go/src/gin-vue-admin/resource ./

        EXPOSE 8888

        # 运行打包好的二进制 并用-c 指定config.docker.yaml配置文件
        ENTRYPOINT ./server -c config.docker.yaml

03.docker-compose.yaml详解
    a.docker-compose.yaml 来源于 gin-vue-admin 的 docker-compose.yaml
        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:

        services:
          # web服务
          web:
            build:
              context: ./web
              # 指定dockerfile启动容器
              dockerfile: ./Dockerfile
            # 自定义容器名
            container_name: gva-web
            # 容器启动失败是否重启
            restart: always
            # 映射端口
            ports:
              - '8080:8080'
            # web服务依赖于server服务
            depends_on:
              - server
            command: [ 'nginx-debug', '-g', 'daemon off;' ]
            networks:
              network:
                # 在network网络下的容器内部的Ipv4地址
                ipv4_address: 177.7.0.11

          # server服务
          server:
            build:
              context: ./server
              # 指定dockerfile启动容器
              dockerfile: ./Dockerfile
            # 自定义容器名
            container_name: gva-server
            # 容器启动失败是否重启
            restart: always
            # 映射端口
            ports:
              - '8888:8888'
            # server服务依赖于mysql服务于redis服务
            depends_on:
              - mysql
              - redis
            networks:
              network:
                # 在network网络下的容器内部的Ipv4地址
                ipv4_address: 177.7.0.12

          mysql:
            # 指定mysql镜像版本
            # 如果您是 arm64 架构:如 MacOS 的 M1,请修改镜像为 image: mysql/mysql-server:8.0.21
            image: mysql:8.0.21
            # 自定义容器名
            container_name: gva-mysql
            # 设置utf8字符集
            command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
            # 容器启动失败是否重启
            restart: always
            # 映射端口
            ports:
              - "13306:3306"  # host物理直接映射端口为13306
            # 系统环境变量
            environment:
              MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
              MYSQL_ROOT_PASSWORD: 'Aa@6447985' # root管理员用户密码
            # 映射数据卷到数据库
            volumes:
              - mysql:/var/lib/mysql
            networks:
              network:
                # 在network网络下的容器内部的Ipv4地址
                ipv4_address: 177.7.0.13

          # redis服务
          redis:
            # 指定redis镜像版本
            image: redis:6.0.6
            # 自定义容器名
            container_name: gva-redis # 容器名
            # 容器启动失败是否重启
            restart: always
            # 映射端口
            ports:
              - '6379:6379'
            networks:
              network:
                # 在network网络下的容器内部的Ipv4地址
                ipv4_address: 177.7.0.14

04.常用命令
    # 使用docker-compose启动四个容器
    docker-compose -f deploy/docker-compose/docker-compose.yaml up
    # 如果您修改了某些配置选项,可以使用此命令重新打包镜像
    docker-compose -f deploy/docker-compose/docker-compose.yaml up --build
    # 使用docker-compose 后台启动
    docker-compose -f deploy/docker-compose/docker-compose.yaml up -d
    # 使用docker-compose 重新打包镜像并后台启动
    docker-compose -f deploy/docker-compose/docker-compose.yaml up --build -d
    # 服务都启动成功后,使用此命令行可清除none镜像
    docker system prune

5.4 kubernetes

01.使用声明
    上云需要手动初始化数据库,不支持在线初始化操作 (/deployment/server/gva-server-configmap.yaml)
    自定义初始化数据库后,将配置写入/deployment/server/gva-server-configmap.yaml

02.选择访问方式
    a.域名访问
        如:http://demo.gin-vue-admin.com
        修改代码第9行为自己的域名 deploy/kubernetes/web/gva-web-ingress.yaml
    b.service访问
        如:http://127.0.0.1:30180
        修改代码第12行以后为如下配置即可 deploy/kubernetes/web/gva-web-service.yaml
        spec:
          type: NodePort
          ports:
            - name: http
              port: 8080
              targetPort: 8080
              nodePort: 30180
          selector:
            app: gva-web
            version: gva-vue3

03.选择镜像
    a.使用gin-vue-admin官方镜像
        前端如:image: registry.cn-hangzhou.aliyuncs.com/gva/web:latest
        修改代码第26行为需要部署的镜像 deploy/kubernetes/web/gva-web-deploymemt.yaml
        -----------------------------------------------------------------------------------------------------
        后端如:image: registry.cn-hangzhou.aliyuncs.com/gva/server:latest
        修改代码第26行为需要部署的镜像 deploy/kubernetes/server/gva-server-deployment.yaml
    b.使用自定义镜像
        可参考docker页面制作自定义镜像,上传至镜像仓库后,按照步骤一修改yaml文件即可。

04.开始部署
# 使用默认namespace
kubectl apply  -f deploy/kubernetes/server/ -f deploy/kubernetes/web/

# 指定namespace
kubectl apply  -f deploy/kubernetes/server/ -f deploy/kubernetes/web/ -n namespace

# 重启server服务
kubectl -n namespace rollout restart deployment gva-server

# 扩容server服务
kubectl -n namespace scale deployment gva-server --replicas 2

# 清除gva服务
kubectl delete  -f deploy/kubernetes/server/ -f deploy/kubernetes/web/ -n namespace