[
  {
    "path": ".aone_copilot/rules/project_rules.md",
    "content": "### 功能描述以及必要性描述\n\n---\nname: gin-vue-admin\ndescription: |\n  gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。\n  \n  前端技术栈：\n  - Vue 3.5.7 + Composition API\n  - Vite 6.2.3 构建工具\n  - Pinia 2.2.2 状态管理\n  - Element Plus 2.10.2 UI组件库\n  - UnoCSS 66.4.2 原子化CSS框架\n  - Vue Router 4.4.3 路由管理\n  - Axios 1.8.2 HTTP客户端\n  - ECharts 5.5.1 数据可视化\n  - @vueuse/core Vue组合式API工具集\n  \n  后端技术栈：\n  - Go 1.23 + Gin 1.10.0 Web框架\n  - GORM 1.25.12 ORM框架\n  - Casbin 2.103.0 权限管理\n  - Viper 1.19.0 配置管理\n  - Zap 1.27.0 日志系统\n  - Redis 9.7.0 缓存\n  - JWT 5.2.2 认证授权\n  - 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库\n  - 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务\n  \n  核心特性：\n  - 完整的RBAC权限控制系统\n  - 代码自动生成功能\n  - 丰富的中间件支持\n  - 插件化架构设计\n  - Swagger API文档\n---\n\n#### **角色与目标**\n\n你是一名资深的全栈开发专家，**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**，熟练使用Golang、Vue3、Gin、GORM等技术栈。\n\n你的核心任务是，根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n---\n\n### **🚀 重要提示：GVA Helper MCP 支持**\n\n**在开始任何GVA开发工作之前，请务必注意以下重要工作流程：**\n\n1. **MCP支持**: GVA框架本身支持MCP（Model Context Protocol），提供了强大的开发辅助能力\n\n2. **GVA Helper**: 通常会有一个名为 \"**GVA Helper**\" 的MCP助手，专门为GVA框架开发提供支持\n\n3. **开发流程**: \n   - **第一步**: 在开发任何新功能之前，**必须先通过GVA Helper获得支持和指导**\n   - **第二步**: 在获得GVA Helper的专业建议和代码示例后，再进行具体的开发操作\n   - **第三步**: 遵循GVA Helper提供的最佳实践和代码规范\n\n4. **优势**: 通过GVA Helper可以获得：\n   - 最新的GVA框架特性和最佳实践\n   - 符合项目规范的代码模板\n   - 避免常见的开发陷阱和错误\n   - 确保代码质量和一致性\n\n**请始终记住：GVA Helper → 获得支持 → 开始开发**\n\n---\n\n### **核心开发指令：绝不可违背的原则**\n\n\n## **项目结构说明**\n\n### **整体架构**\n\ngin-vue-admin 采用前后端分离架构：\n- **后端 (server/)**：基于 Go + Gin 的 RESTful API 服务\n- **前端 (web/)**：基于 Vue 3 + Vite 的单页面应用\n- **部署 (deploy/)**：Docker、Kubernetes 等部署配置\n\n### **后端目录结构 (server/)**\n\n```\nserver/\n├── api/                    # API控制器层\n│   └── v1/                # API版本控制\n│       ├── enter.go       # API组入口文件\n│       ├── system/        # 系统模块API\n│       └──example/       # 示例模块API\n├── config/                # 配置结构体定义\n├── core/                  # 核心启动文件\n├── docs/                  # Swagger文档\n├── global/                # 全局变量和模型\n├── initialize/            # 初始化模块\n├── middleware/            # 中间件\n├── model/                 # 数据模型层\n│   ├── system/           # 系统模块模型\n│   ├── example/          # 示例模块模型\n│   └── common/           # 通用模型\n├── plugin/               # 插件目录\n│   ├── announcement/     # 公告插件\n│   └── email/           # 邮件插件\n├── router/               # 路由层\n│   ├── enter.go         # 路由组入口\n│   ├── system/          # 系统路由\n│   └──example/         # 示例路由\n├── service/              # 服务层\n│   ├── enter.go         # 服务组入口\n│   ├── system/          # 系统服务\n│   └──  example/         # 示例服务\n├── source/               # 数据初始化\n├── utils/                # 工具包\n├── config.yaml          # 配置文件\n└── main.go              # 程序入口\n```\n\n### **前端目录结构 (web/)**\n\n```\nweb/\n├── public/               # 静态资源\n├── src/\n│   ├── api/             # API接口定义\n│   │   ├── user.js      # 用户相关API\n│   │   ├── menu.js      # 菜单相关API\n│   │   └── cattery/     # 业务模块API\n│   ├── assets/          # 资源文件\n│   │   ├── icons/       # 图标\n│   │   └── images/      # 图片\n│   ├── core/            # 核心配置\n│   ├── directive/       # 自定义指令\n│   ├── hooks/           # 组合式API钩子\n│   ├── pinia/           # 状态管理\n│   │   ├── index.js     # Pinia入口\n│   │   └── modules/     # 状态模块\n│   ├── plugin/          # 前端插件\n│   │   ├── announcement/ # 公告插件\n│   │   └── email/       # 邮件插件\n│   ├── router/          # 路由配置\n│   ├── style/           # 样式文件\n│   ├── utils/           # 工具函数\n│   ├── view/            # 页面组件\n│   │   ├── dashboard/   # 仪表盘\n│   │   ├── layout/      # 布局组件\n│   │   ├── login/       # 登录页\n│   │   ├── superAdmin/  # 超级管理员\n│   │   ├── systemTools/ # 系统工具\n│   │   └── cattery/     # 业务页面\n│   ├── App.vue          # 根组件\n│   └── main.js          # 程序入口\n├── package.json         # 依赖配置\n├── vite.config.js       # Vite配置\n└── uno.config.js        # UnoCSS配置\n```\n\n---\n\n#### 后端规则\n\n在编写任何代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的分层架构**:\n    \n    - **职责单一**: 每个层（Model, Service, API, Router）都有其唯一职责，**严禁跨层调用**。例如，API层绝不能直接操作数据库，必须通过Service层。Service层绝不能直接处理`gin.Context`。\n        \n    - **依赖关系**: 依赖链条必须是单向的：`Router -> API -> Service -> Model`。\n        \n2. **`enter.go` 组管理模式**:\n    \n    - 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。\n        \n    - 全局实例变量（如 `service.ServiceGroupApp`）是模块间通信的唯一入口，以此来避免循环引用。\n        \n3. **详尽的 Swagger 注释 (API层强制要求)**:\n    \n    - **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源，也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。\n        \n4. **统一的响应与错误处理**:\n    \n    - Service 层函数遇到业务错误时，应返回 `error` 对象。\n        \n    - API 层负责捕获 Service 层的 `error`，并使用项目统一的 `response` 包（如 `response.OkWithDetailed` 或 `response.FailWithMessage`）将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。\n        \n\n---\n\n### **各层级代码实现规范**\n\n#### **1. 模型层 (`model/`)**\n\n- **数据模型 (`model/xxx.go`)**:\n\n   - 用于定义与数据库表映射的 GORM 结构体。\n\n   - 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。\n\n   - 以上三个字段返回给前端并未做驼峰处理，json内依然是  `ID`, `CreatedAt`, `UpdatedAt`\n\n   - 必须为字段添加清晰的 `json` 和 `gorm` 标签。\n\n   - **⚠️ 重要提醒：数据类型一致性**\n      - **必须确保**同一字段在不同模型文件中的数据类型保持严格一致\n      - 例如：如果某字段在数据模型中定义为特定类型，那么在请求模型、响应模型中也必须使用相同的数据类型\n      - **常见错误**：数据模型与请求模型中同一字段使用了不同的数据类型，这会导致类型转换错误和运行时异常\n      - **解决方案**：在设计阶段统一确定字段类型，并在所有相关模型中保持一致\n      - **检查要点**：特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段\n      - **⚠️ 指针类型处理**：\n         - 当数据模型中使用指针类型（如 `*string`、`*int`）而请求/响应模型中使用非指针类型时，**必须**在服务层进行正确的指针转换\n         - **转换规则**：从指针到非指针需要检查nil值，从非指针到指针需要取地址\n         - **示例**：数据模型 `Name *string` 转换为请求模型 `Name string` 时，需要处理 `if model.Name != nil { request.Name = *model.Name }`\n\n- **请求模型 (`model/request/xxx.go`)**:\n    \n    - 用于定义接收前端请求参数的结构体（DTOs）。\n        \n    - **必须**为字段添加 `json` 和 `form` 标签，以便 Gin 进行参数绑定。\n        \n    - 对于列表查询请求，应创建一个 `XxxSearch` 结构体，并内嵌通用的 `request.PageInfo` 分页结构体。\n        \n\n#### **2. 服务层 (`service/`)**\n\n- **职责**: 封装所有核心业务逻辑，进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码（如 `gin.Context`）**。\n    \n- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件，并在 `service/enter.go` 中注册。\n    \n- **函数签名**: 函数应接收具体的业务参数（如 `model.Xxx` 或 `request.XxxSearch`），并返回处理结果和 `error`。\n\n- **⚠️ 数据类型处理注意事项**:\n   - 在进行数据模型转换时，**必须确保**字段类型的一致性\n   - 避免在服务层进行不必要的类型转换，应在模型设计阶段统一类型\n   - 如果必须进行类型转换，**必须**添加详细的注释说明转换原因和逻辑\n\n\n#### **3. API层 (`api/`)**\n\n- **职责**: 作为HTTP请求的入口，负责参数校验、调用Service层方法、并返回格式化的JSON响应。\n    \n- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件，并在 `api/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。\n    \n- **Swagger 示例 (必须遵循)**:\n    \n    Go\n    \n    ```\n    // CreateXxx 创建XXX\n    // @Tags     XxxModule\n    // @Summary  创建一个新的XXX\n    // @Security ApiKeyAuth\n    // @accept   application/json\n    // @Produce  application/json\n    // @Param    data body request.CreateXxxRequest true \"XXX的名称和描述\"\n    // @Success  200  {object} response.Response{msg=string} \"创建成功\"\n    // @Router   /xxx/createXxx [post]\n    func (a *XxxApi) CreateXxx(c *gin.Context) {\n        // ...\n    }\n    ```\n    \n\n#### **4. 路由层 (`router/`)**\n\n- **职责**: 定义API路由规则，并将HTTP请求路径映射到具体的API处理函数上，同时配置中间件。\n    \n- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件，并在 `router/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。\n    \n- **路由分组**: 应根据业务需求和权限，合理使用路由组 (`Router.Group()`)，并挂载不同的中间件（如鉴权、操作记录等）。\n\n#### **5. 初始化层 (`initialize/`)**\n\n- **职责**: 提供插件资源（数据库、路由、菜单等）的初始化入口，供主程序调用。\n    \n- **`gorm.go`**: 实现 `InitializeDB` 函数，**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。\n    \n- **`router.go`**: 实现 `InitializeRouter` 函数，**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法，注册所有API路由。\n    \n- **`menu.go`**: 实现 `InitializeMenu` 函数，负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。\n- viper.go: 加载插件配置文件\n-  api.go: 注册API到系统\n    \n\n#### **6. 插件入口 (`plugin.go`) \n\n- **职责**: 作为插件的唯一入口，实现 GVA 的插件接口，让框架能够识别和加载本插件。\n    \n- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。\n\n- **插件注册**: **必须**调用 ```\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n```\n方法，让插件自动注册到本体中\n    \n- **`Register`方法**: 实现 `Register` 方法，该方法接收一个 `*gin.RouterGroup` 参数，其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。\n    \n- **`RouterPath`方法**: 实现 `RouterPath` 方法，返回该插件所有API的根路径，例如 `\"/myPlugin\"`。\n\n### 模块间引用关系：\n- API层引用Service层：在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`\n- Router层引用API层：在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`\n- Initialize/Router引用Router层：通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`\n- 各模块通过enter.go文件组织和暴露功能，避免循环引用\n\n### 插件默认注册功能\n\n`plugin/register.go` 文件下用 `\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin/插件\"\n` 的方式匿名引用用于激活插件本体的init\n\n### 代码组织示例：\n\n1. Service入口 (service/enter.go):\n```go\npackage service\n\ntype ServiceGroup struct {\n    XxxService\n    YyyService\n    // 其他服务...\n}\n\nvar ServiceGroupApp = new(ServiceGroup)\n```\n\n2. API入口 (api/enter.go):\n```go\npackage api\n\ntype ApiGroup struct {\n    XxxApi\n    YyyApi\n    // 其他API...\n}\n\nvar ApiGroupApp = new(ApiGroup)\n```\n\n3. Router入口 (router/enter.go):\n```go\npackage router\n\ntype RouterGroup struct {\n    XxxRouter\n    YyyRouter\n    // 其他路由...\n}\n\nvar RouterGroupApp = new(RouterGroup)\n```\n\n### Swagger注释规范：\n- @Tags: 接口所属的分组\n- @Summary: 接口功能简述\n- @Security: 安全认证方式（如需认证则添加）\n- @accept/@Produce: 请求/响应格式\n- @Param: 请求参数，包括名称、来源、类型、是否必须、描述\n- @Success: 成功响应，包括状态码、返回类型、描述\n- @Router: 接口路径和HTTP方法\n\nAPI函数的Swagger注释不仅用于生成API文档，也是前端开发的重要参考，请确保注释的完整性和准确性。\n\n\n---\n\n### **开发工作流**\n\n1. **接收任务**: 我会向你下达一个具体的功能插件开发任务，例如：“请为项目创建一个‘商品管理 (Product)’插件”。\n    \n2. **【第一步】模型设计 (奠定基础)**:\n    \n    - 你的**首要行动**是分析需求，设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。\n        \n3. **【第二步】自下而上，分层实现**:\n    - 具体项目结构可以参考：server/plugin/announcement 这个插件，非常经典！\n\n    - 在模型确认后，你将按照 `Service -> API -> Router` 的顺序，逐层生成代码。\n        \n    - 确保每一层的代码都完整、健壮，并严格遵守上述规范。\n        \n4. **【第三步】插件初始化与注册**:\n    \n    - 在完成核心功能层的代码后，你将生成 `initialize/` 目录下的相关初始化文件（如 `db.go`, `router.go`）以及插件的主入口文件 `plugin.go`。\n        \n5. **【第四步】提供完整代码**:\n    \n    - 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码，并对每个文件的**相对路径**（例如 `server/plugin/product/api/product_api.go`）和用途进行清晰的说明。\n\n\n---\n\n## **前端开发规范**\n\n### **角色与目标**\n\n你是一名资深的 Vue.js 前端开发专家，**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。\n\n你的核心任务是，根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n### **核心开发指令：绝不可违背的原则**\n\n#### 前端规则\n\n在编写任何前端代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的模块化架构**:\n   - **职责单一**: 每个模块（API、组件、页面、状态）都有其唯一职责，**严禁跨模块直接调用**\n   - **依赖关系**: 依赖链条必须是单向的：`页面组件 -> API服务 -> 后端接口`\n\n2. **统一的API调用模式**:\n   - 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装\n   - **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求\n   - API函数**必须**包含完整的JSDoc注释，描述接口功能、参数和返回值\n\n3. **组件化开发原则**:\n   - **每一个**可复用的UI元素都**必须**封装为组件\n   - 组件**必须**遵循单一职责原则，功能明确\n   - **必须**为组件添加完整的props定义和事件说明\n\n4. **统一的状态管理**:\n   - 全局状态**必须**使用Pinia进行管理\n   - 状态模块**必须**按业务功能进行划分\n   - **严禁**在组件中直接修改全局状态，必须通过actions\n\n### **各层级代码实现规范**\n\n#### **1. API层 (`src/api/`)**\n\n- **职责**: 封装所有后端API调用，提供统一的接口服务\n- **结构**: 按业务模块创建API文件，如 `user.js`、`menu.js`\n- **规范**:\n  ```javascript\n  import service from '@/utils/request'\n  \n  /**\n   * 获取用户列表\n   * @param {Object} data 查询参数\n   * @param {number} data.page 页码\n   * @param {number} data.pageSize 每页数量\n   * @returns {Promise} 用户列表数据\n   */\n  export const getUserList = (data) => {\n    return service({\n      url: '/user/getUserList',\n      method: 'post',\n      data: data\n    })\n  }\n  ```\n\n#### **2. 组件层 (`src/components/`)**\n\n- **职责**: 提供可复用的UI组件\n- **结构**: 按功能分类组织，每个组件一个文件夹\n- **规范**:\n  ```vue\n  <template>\n    <div class=\"gva-table\">\n      <!-- 组件内容 -->\n    </div>\n  </template>\n  \n  <script setup>\n  /**\n   * 通用表格组件\n   * @component GvaTable\n   * @description 提供统一的表格展示功能\n   */\n  \n  // Props定义\n  const props = defineProps({\n    data: {\n      type: Array,\n      required: true,\n      default: () => []\n    },\n    loading: {\n      type: Boolean,\n      default: false\n    }\n  })\n  \n  // 事件定义\n  const emit = defineEmits(['refresh', 'edit', 'delete'])\n  </script>\n  ```\n\n#### **3. 页面层 (`src/view/`)**\n\n- **职责**: 实现具体的业务页面\n- **结构**: 按业务模块组织，每个页面一个Vue文件\n- **规范**:\n  - **必须**使用Composition API\n  - **必须**进行响应式数据管理\n  - **必须**处理加载状态和错误状态\n  - **必须**遵循Element Plus组件规范\n  - **必须**优先使用UnoCSS原子化类名进行样式设计\n  - **必须**优先el-drawer组件进行编辑，新增，步骤等操作\n  - **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性，确保组件销毁，避免内存泄漏和状态污染\n\n#### **4. 状态管理 (`src/pinia/`)**\n\n- **职责**: 管理全局状态和业务逻辑\n- **结构**: 按业务模块创建store文件\n- **规范**:\n  ```javascript\n  import { defineStore } from 'pinia'\n  import { ref, computed } from 'vue'\n  import { useStorage } from '@vueuse/core'\n  \n  export const useUserStore = defineStore('user', () => {\n    // 状态定义 - 使用 ref() 创建响应式状态\n    const userInfo = ref({\n      uuid: '',\n      nickName: '',\n      headerImg: '',\n      authority: {}\n    })\n    const token = useStorage('token', '')\n    \n    // 计算属性 - 使用 computed() 定义\n    const isLogin = computed(() => !!token.value)\n    \n    // 方法定义 - 直接定义函数作为 actions\n    const setUserInfo = (val) => {\n      userInfo.value = val\n    }\n    \n    const setToken = (val) => {\n      token.value = val\n    }\n    \n    const login = async (loginForm) => {\n      // 登录逻辑\n      try {\n        const res = await loginApi(loginForm)\n        if (res.code === 0) {\n          setUserInfo(res.data.user)\n          setToken(res.data.token)\n          return true\n        }\n        return false\n      } catch (error) {\n        console.error('Login error:', error)\n        return false\n      }\n    }\n    \n    const logout = async () => {\n      // 登出逻辑\n      token.value = ''\n      userInfo.value = {}\n    }\n    \n    // 返回所有需要暴露的状态和方法\n    return {\n      userInfo,\n      token,\n      isLogin,\n      setUserInfo,\n      setToken,\n      login,\n      logout\n    }\n  })\n  ```\n\n#### **5. 路由管理 (`src/router/`)**\n\n- **职责**: 管理页面路由和权限控制\n- **规范**:\n  - **必须**配置路由元信息\n  - **必须**实现权限验证\n  - **必须**支持动态路由\n\n### **前端插件开发规范**\n\n#### **插件目录结构**\n\n```\nsrc/plugin/[插件名]/\n├── api/                # 插件API接口\n│   └── [模块].js\n├── components/         # 插件组件（可选）\n│   └── [组件名].vue\n├── view/              # 插件页面\n│   └── [页面名].vue\n├── form/              # 插件表单（可选）\n│   └── [表单名].vue\n└── index.js           # 插件入口文件（可选）\n```\n\n#### **插件开发原则**\n\n1. **独立性**: 插件应该是自包含的，不依赖其他业务模块\n2. **可配置性**: 插件应该支持配置化，便于定制\n3. **可扩展性**: 插件应该预留扩展接口\n4. **一致性**: 插件UI风格应与主系统保持一致\n\n### **代码质量要求**\n\n1. **命名规范**:\n   - 文件名：kebab-case（短横线命名）\n   - 组件名：PascalCase（大驼峰）\n   - 变量名：camelCase（小驼峰）\n   - 常量名：UPPER_SNAKE_CASE（大写下划线）\n\n2. **注释规范**:\n   - **必须**为所有API函数添加JSDoc注释\n   - **必须**为复杂组件添加功能说明\n   - **必须**为关键业务逻辑添加行内注释\n\n3. **样式规范**:\n   - **优先**使用UnoCSS原子化类名\n   - **必须**遵循Element Plus设计规范\n   - **禁止**使用内联样式\n   - **必须**使用CSS变量进行主题定制\n\n4. **性能要求**:\n   - **必须**使用懒加载优化路由\n   - **必须**对大列表进行虚拟滚动优化\n   - **必须**合理使用缓存机制\n   - **必须**优化图片和资源加载\n\n---\n\n## **⚠️ 前端工具库使用规范（强制）**\n\n> **核心原则：在开发任何前端功能时，必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数，严禁重复造轮子。**\n\n`src/utils/` 目录提供了项目级别的通用工具集，涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明：\n\n### **工具文件清单**\n\n#### `request.js` — HTTP 请求封装（核心）\n- 基于 Axios 封装的统一 HTTP 请求实例，内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截\n- **所有 API 请求必须且只能通过此模块发送，禁止直接使用 axios**\n- 用法：`import service from '@/utils/request'`\n\n#### `date.js` — 日期格式化\n- 扩展了 `Date.prototype.Format` 方法，支持自定义格式如 `yyyy-MM-dd hh:mm:ss`\n- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串\n- **需要格式化日期时，优先使用此工具，禁止自行手写日期格式化逻辑**\n- 用法：`import { formatTimeToStr } from '@/utils/date'`\n\n#### `format.js` — 数据展示格式化（综合工具）\n- `formatBoolean(bool)` — 将布尔值转为 \"是\"/\"否\" 中文展示\n- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串\n- `filterDict(value, options)` — 在字典选项数组（支持多级树形）中根据 value 查找对应的 label\n- `filterDataSource(dataSource, value)` — 在数据源（支持多级树形）中根据 value 查找 label，支持数组批量查找\n- `getDictFunc(type)` — 异步获取指定类型的字典数据\n- `ReturnArrImg(arr)` — 将图片路径（单个或数组）转为完整 URL，自动补全服务器前缀\n- `onDownloadFile(url)` — 触发文件下载\n- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量（支持亮/暗模式）\n- `CreateUUID()` — 生成 UUID v4 字符串\n- `getBaseUrl()` — 获取当前环境的 API BaseURL\n- **以上所有格式化场景优先使用此文件中的工具函数**\n- 用法：`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`\n\n#### `dictionary.js` — 字典数据获取\n- `getDict(type, options)` — 异步获取字典数据，支持 `depth`（深度）和 `value`（指定节点）参数，内置 Pinia store 缓存，避免重复请求\n- **凡是需要字典下拉数据、字典树形数据的场景，必须使用此工具**\n- 用法：`import { getDict } from '@/utils/dictionary'`\n\n#### `stringFun.js` — 字符串处理\n- `toUpperCase(str)` — 首字母转大写\n- `toLowerCase(str)` — 首字母转小写\n- `toSQLLine(str)` — 驼峰命名转下划线（snake_case），如 `userName` → `user_name`\n- `toHump(name)` — 下划线命名转驼峰，如 `user_name` → `userName`\n- **进行命名格式转换时必须使用此工具，禁止使用正则手写**\n- 用法：`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`\n\n#### `params.js` — 系统参数获取\n- `getParams(key)` — 异步从 Pinia store 中获取系统参数，内置缓存\n- **获取系统配置参数时，优先使用此工具**\n- 用法：`import { getParams } from '@/utils/params'`\n\n#### `bus.js` — 全局事件总线\n- 基于 `mitt` 封装的全局事件总线实例 `emitter`，用于跨组件通信\n- **跨层级组件通信优先使用此事件总线，避免滥用 Pinia**\n- 用法：`import { emitter } from '@/utils/bus'`\n\n#### `closeThisPage.js` — 关闭当前标签页\n- `closeThisPage()` — 触发关闭当前多标签页的操作（通过事件总线发送 `closeThisPage` 事件）\n- **在需要程序化关闭当前页面时，必须使用此工具**\n- 用法：`import { closeThisPage } from '@/utils/closeThisPage'`\n\n#### `downloadImg.js` — 图片下载\n- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载，支持跨域\n- **需要下载图片时，优先使用此工具**\n- 用法：`import { downloadImage } from '@/utils/downloadImg'`\n\n#### `image.js` — 图片压缩\n- 导出 `ImageCompress` 类，支持图片等比压缩至指定最大宽高，并可限制文件大小\n- **上传图片前需要做压缩处理时，使用此工具**\n- 用法：`import ImageCompress from '@/utils/image'`\n\n#### `event.js` — DOM 事件监听管理\n- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听\n- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听\n- **手动操作 DOM 事件时，使用此工具以确保安全性**\n- 用法：`import { addEventListen, removeEventListen } from '@/utils/event'`\n\n#### `env.js` — 环境判断\n- `isDev` — 是否为开发环境（Boolean）\n- `isProd` — 是否为生产环境（Boolean）\n- **需要区分运行环境时，使用此工具，禁止直接读取 `import.meta.env`**\n- 用法：`import { isDev, isProd } from '@/utils/env'`\n\n#### `doc.js` — 外部文档跳转\n- `toDoc(url)` — 在新标签页打开指定 URL\n- 用法：`import { toDoc } from '@/utils/doc'`\n\n#### `fmtRouterTitle.js` — 路由标题格式化\n- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值（如 `${id}` 替换为路由 params/query 值）\n- 用法：`import { fmtTitle } from '@/utils/fmtRouterTitle'`\n\n#### `page.js` — 页面标题生成\n- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题（格式：`页面名 - 应用名`）\n- 用法：`import getPageTitle from '@/utils/page'`\n\n#### `asyncRouter.js` — 异步路由处理\n- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置（字符串 component 路径）动态转换为 Vue 组件的 import 函数，支持 `view/` 和 `plugin/` 目录\n- **动态路由相关逻辑已由此工具处理，不需要也不应该手动实现**\n- 用法：`import { asyncRouterHandle } from '@/utils/asyncRouter'`\n\n#### `btnAuth.js` — 按钮权限\n- `useBtnAuth()` — Composition API Hook，返回当前路由挂载的按钮权限对象（来自 `route.meta.btns`），用于控制操作按钮的显示\n- **实现按钮级别权限控制时，必须使用此 Hook**\n- 用法：`import { useBtnAuth } from '@/utils/btnAuth'`\n\n### **使用强制要求**\n\n| 场景 | 必须使用的工具 |\n|------|----------------|\n| 发送 HTTP 请求 | `@/utils/request` |\n| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |\n| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |\n| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |\n| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |\n| 驼峰/下划线命名转换 | `@/utils/stringFun` |\n| 获取系统参数 | `@/utils/params` 中的 `getParams` |\n| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |\n| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |\n| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |\n| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |\n| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |\n\n---\n\n## **前后端协作规范**\n\n### **接口协作规范**\n\n1. **接口文档**:\n   - 后端**必须**提供完整的Swagger API文档\n   - 前端**必须**基于Swagger文档进行接口调用\n   - 接口变更**必须**提前通知并更新文档\n\n2. **数据格式**:\n   - **统一**使用JSON格式进行数据交换\n   - **统一**响应格式：`{code, data, msg}`\n   - **统一**分页格式：`{page, pageSize, total, list}`\n   - **统一**时间格式：ISO 8601标准\n   - **⚠️ 数据类型一致性**：\n      - 前后端对于同一字段**必须**使用相同的数据类型\n      - 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致\n      - 特别注意：状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段\n      - 示例：后端数值类型字段对应前端 `number` 类型，字符串类型对应 `string` 类型，布尔类型对应 `boolean` 类型\n      - **指针类型处理**：后端Go中的指针类型在JSON序列化时会自动处理nil值，前端接收到的是对应的基础类型或null值\n\n3. **错误处理**:\n   - 后端**必须**返回标准化的错误码和错误信息\n   - 前端**必须**统一处理HTTP状态码和业务错误码\n   - **必须**提供用户友好的错误提示\n\n### **开发流程规范**\n\n1. **需求分析阶段**:\n   - 确定功能需求和接口设计\n   - 定义数据模型和业务流程\n   - 制定前后端开发计划\n\n2. **开发阶段**:\n   - 后端优先开发API接口\n   - 前端基于Mock数据进行并行开发\n   - 定期进行接口联调测试\n\n3. **测试阶段**:\n   - 单元测试：前后端各自负责\n   - 集成测试：前后端协作完成\n   - 用户验收测试：产品团队主导\n\n### **版本管理规范**\n\n1. **分支策略**:\n   - `main`：生产环境分支\n   - `develop`：开发环境分支\n   - `feature/*`：功能开发分支\n   - `hotfix/*`：紧急修复分支\n\n2. **提交规范**:\n   - 使用语义化提交信息\n   - 格式：`type(scope): description`\n   - 类型：feat, fix, docs, style, refactor, test, chore\n\n---\n\n## **插件开发完整规范**\n\n### **后端插件结构**\n\n```\nserver/plugin/[插件名]/\n├── api/                # API控制器\n│   ├── enter.go       # API组入口\n│   └── [模块].go      # 具体API实现\n├── config/            # 插件配置\n│   └── config.go\n├── initialize/        # 初始化模块\n│   ├── api.go        # API注册\n│   ├── gorm.go       # 数据库初始化\n│   ├── menu.go       # 菜单初始化\n│   ├── router.go     # 路由初始化\n│   └── viper.go      # 配置初始化\n├── model/             # 数据模型\n│   ├── [模型].go     # 数据库模型\n│   └── request/      # 请求模型\n├── router/            # 路由定义\n│   ├── enter.go      # 路由组入口\n│   └── [模块].go     # 具体路由\n├── service/           # 业务服务\n│   ├── enter.go      # 服务组入口\n│   └── [模块].go     # 具体服务\n└── plugin.go          # 插件入口\n```\n\n### **前端插件结构**\n\n```\nweb/src/plugin/[插件名]/\n├── api/               # API接口\n│   └── [模块].js\n├── components/        # 插件组件\n│   └── [组件].vue\n├── view/             # 插件页面\n│   └── [页面].vue\n├── form/             # 表单组件\n│   └── [表单].vue\n└── config.js         # 插件配置\n```\n\n### **插件开发工作流**\n\n1. **【第一步】需求分析**:\n   - 明确插件功能和业务需求\n   - 设计数据模型和接口规范\n   - 规划前端页面和交互流程\n\n2. **【第二步】后端开发**:\n   - 创建数据模型和请求模型\n   - 实现服务层业务逻辑\n   - 开发API控制器和路由\n   - 编写初始化和配置代码\n\n3. **【第三步】前端开发**:\n   - 创建API接口封装\n   - 开发页面组件和表单\n   - 实现业务逻辑和状态管理\n   - 集成到主系统菜单\n\n4. **【第四步】测试集成**:\n   - 单元测试和集成测试\n   - 前后端联调测试\n   - 用户体验测试\n   - 性能和安全测试\n\n### **插件质量标准**\n\n1. **功能完整性**: 插件功能完整，满足业务需求\n2. **代码质量**: 代码规范，注释完整，易于维护\n3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致，避免类型转换错误\n4. **性能表现**: 响应速度快，资源占用合理\n5. **用户体验**: 界面友好，操作流畅，错误处理完善\n6. **兼容性**: 与主系统兼容，不影响其他功能\n7. **安全性**: 数据安全，权限控制，防止安全漏洞\n\n---\n\n### **建议和方案**\n\n基于以上规范，建议AI在开发gin-vue-admin项目时：\n\n1. **严格遵循分层架构**：确保前后端代码都按照规定的层次结构组织\n2. **保持代码一致性**：使用统一的命名规范、注释格式和代码风格\n3. **注重文档完整性**：确保API文档、代码注释和使用说明的完整性\n4. **优化用户体验**：关注页面加载速度、交互流畅性和错误处理\n5. **考虑扩展性**：设计时预留扩展接口，便于后续功能增强\n6. **重视安全性**：实现完善的权限控制和数据验证机制"
  },
  {
    "path": ".claude/rules/project_rules.md",
    "content": "### 功能描述以及必要性描述\n\n---\nname: gin-vue-admin\ndescription: |\n  gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。\n  \n  前端技术栈：\n  - Vue 3.5.7 + Composition API\n  - Vite 6.2.3 构建工具\n  - Pinia 2.2.2 状态管理\n  - Element Plus 2.10.2 UI组件库\n  - UnoCSS 66.4.2 原子化CSS框架\n  - Vue Router 4.4.3 路由管理\n  - Axios 1.8.2 HTTP客户端\n  - ECharts 5.5.1 数据可视化\n  - @vueuse/core Vue组合式API工具集\n  \n  后端技术栈：\n  - Go 1.23 + Gin 1.10.0 Web框架\n  - GORM 1.25.12 ORM框架\n  - Casbin 2.103.0 权限管理\n  - Viper 1.19.0 配置管理\n  - Zap 1.27.0 日志系统\n  - Redis 9.7.0 缓存\n  - JWT 5.2.2 认证授权\n  - 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库\n  - 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务\n  \n  核心特性：\n  - 完整的RBAC权限控制系统\n  - 代码自动生成功能\n  - 丰富的中间件支持\n  - 插件化架构设计\n  - Swagger API文档\n---\n\n#### **角色与目标**\n\n你是一名资深的全栈开发专家，**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**，熟练使用Golang、Vue3、Gin、GORM等技术栈。\n\n你的核心任务是，根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n---\n\n### **🚀 重要提示：GVA Helper MCP 支持**\n\n**在开始任何GVA开发工作之前，请务必注意以下重要工作流程：**\n\n1. **MCP支持**: GVA框架本身支持MCP（Model Context Protocol），提供了强大的开发辅助能力\n\n2. **GVA Helper**: 通常会有一个名为 \"**GVA Helper**\" 的MCP助手，专门为GVA框架开发提供支持\n\n3. **开发流程**: \n   - **第一步**: 在开发任何新功能之前，**必须先通过GVA Helper获得支持和指导**\n   - **第二步**: 在获得GVA Helper的专业建议和代码示例后，再进行具体的开发操作\n   - **第三步**: 遵循GVA Helper提供的最佳实践和代码规范\n\n4. **优势**: 通过GVA Helper可以获得：\n   - 最新的GVA框架特性和最佳实践\n   - 符合项目规范的代码模板\n   - 避免常见的开发陷阱和错误\n   - 确保代码质量和一致性\n\n**请始终记住：GVA Helper → 获得支持 → 开始开发**\n\n---\n\n### **核心开发指令：绝不可违背的原则**\n\n\n## **项目结构说明**\n\n### **整体架构**\n\ngin-vue-admin 采用前后端分离架构：\n- **后端 (server/)**：基于 Go + Gin 的 RESTful API 服务\n- **前端 (web/)**：基于 Vue 3 + Vite 的单页面应用\n- **部署 (deploy/)**：Docker、Kubernetes 等部署配置\n\n### **后端目录结构 (server/)**\n\n```\nserver/\n├── api/                    # API控制器层\n│   └── v1/                # API版本控制\n│       ├── enter.go       # API组入口文件\n│       ├── system/        # 系统模块API\n│       └──example/       # 示例模块API\n├── config/                # 配置结构体定义\n├── core/                  # 核心启动文件\n├── docs/                  # Swagger文档\n├── global/                # 全局变量和模型\n├── initialize/            # 初始化模块\n├── middleware/            # 中间件\n├── model/                 # 数据模型层\n│   ├── system/           # 系统模块模型\n│   ├── example/          # 示例模块模型\n│   └── common/           # 通用模型\n├── plugin/               # 插件目录\n│   ├── announcement/     # 公告插件\n│   └── email/           # 邮件插件\n├── router/               # 路由层\n│   ├── enter.go         # 路由组入口\n│   ├── system/          # 系统路由\n│   └──example/         # 示例路由\n├── service/              # 服务层\n│   ├── enter.go         # 服务组入口\n│   ├── system/          # 系统服务\n│   └──  example/         # 示例服务\n├── source/               # 数据初始化\n├── utils/                # 工具包\n├── config.yaml          # 配置文件\n└── main.go              # 程序入口\n```\n\n### **前端目录结构 (web/)**\n\n```\nweb/\n├── public/               # 静态资源\n├── src/\n│   ├── api/             # API接口定义\n│   │   ├── user.js      # 用户相关API\n│   │   ├── menu.js      # 菜单相关API\n│   │   └── cattery/     # 业务模块API\n│   ├── assets/          # 资源文件\n│   │   ├── icons/       # 图标\n│   │   └── images/      # 图片\n│   ├── core/            # 核心配置\n│   ├── directive/       # 自定义指令\n│   ├── hooks/           # 组合式API钩子\n│   ├── pinia/           # 状态管理\n│   │   ├── index.js     # Pinia入口\n│   │   └── modules/     # 状态模块\n│   ├── plugin/          # 前端插件\n│   │   ├── announcement/ # 公告插件\n│   │   └── email/       # 邮件插件\n│   ├── router/          # 路由配置\n│   ├── style/           # 样式文件\n│   ├── utils/           # 工具函数\n│   ├── view/            # 页面组件\n│   │   ├── dashboard/   # 仪表盘\n│   │   ├── layout/      # 布局组件\n│   │   ├── login/       # 登录页\n│   │   ├── superAdmin/  # 超级管理员\n│   │   ├── systemTools/ # 系统工具\n│   │   └── cattery/     # 业务页面\n│   ├── App.vue          # 根组件\n│   └── main.js          # 程序入口\n├── package.json         # 依赖配置\n├── vite.config.js       # Vite配置\n└── uno.config.js        # UnoCSS配置\n```\n\n---\n\n#### 后端规则\n\n在编写任何代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的分层架构**:\n    \n    - **职责单一**: 每个层（Model, Service, API, Router）都有其唯一职责，**严禁跨层调用**。例如，API层绝不能直接操作数据库，必须通过Service层。Service层绝不能直接处理`gin.Context`。\n        \n    - **依赖关系**: 依赖链条必须是单向的：`Router -> API -> Service -> Model`。\n        \n2. **`enter.go` 组管理模式**:\n    \n    - 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。\n        \n    - 全局实例变量（如 `service.ServiceGroupApp`）是模块间通信的唯一入口，以此来避免循环引用。\n        \n3. **详尽的 Swagger 注释 (API层强制要求)**:\n    \n    - **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源，也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。\n        \n4. **统一的响应与错误处理**:\n    \n    - Service 层函数遇到业务错误时，应返回 `error` 对象。\n        \n    - API 层负责捕获 Service 层的 `error`，并使用项目统一的 `response` 包（如 `response.OkWithDetailed` 或 `response.FailWithMessage`）将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。\n        \n\n---\n\n### **各层级代码实现规范**\n\n#### **1. 模型层 (`model/`)**\n\n- **数据模型 (`model/xxx.go`)**:\n\n   - 用于定义与数据库表映射的 GORM 结构体。\n\n   - 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。\n\n   - 以上三个字段返回给前端并未做驼峰处理，json内依然是  `ID`, `CreatedAt`, `UpdatedAt`\n\n   - 必须为字段添加清晰的 `json` 和 `gorm` 标签。\n\n   - **⚠️ 重要提醒：数据类型一致性**\n      - **必须确保**同一字段在不同模型文件中的数据类型保持严格一致\n      - 例如：如果某字段在数据模型中定义为特定类型，那么在请求模型、响应模型中也必须使用相同的数据类型\n      - **常见错误**：数据模型与请求模型中同一字段使用了不同的数据类型，这会导致类型转换错误和运行时异常\n      - **解决方案**：在设计阶段统一确定字段类型，并在所有相关模型中保持一致\n      - **检查要点**：特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段\n      - **⚠️ 指针类型处理**：\n         - 当数据模型中使用指针类型（如 `*string`、`*int`）而请求/响应模型中使用非指针类型时，**必须**在服务层进行正确的指针转换\n         - **转换规则**：从指针到非指针需要检查nil值，从非指针到指针需要取地址\n         - **示例**：数据模型 `Name *string` 转换为请求模型 `Name string` 时，需要处理 `if model.Name != nil { request.Name = *model.Name }`\n\n- **请求模型 (`model/request/xxx.go`)**:\n    \n    - 用于定义接收前端请求参数的结构体（DTOs）。\n        \n    - **必须**为字段添加 `json` 和 `form` 标签，以便 Gin 进行参数绑定。\n        \n    - 对于列表查询请求，应创建一个 `XxxSearch` 结构体，并内嵌通用的 `request.PageInfo` 分页结构体。\n        \n\n#### **2. 服务层 (`service/`)**\n\n- **职责**: 封装所有核心业务逻辑，进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码（如 `gin.Context`）**。\n    \n- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件，并在 `service/enter.go` 中注册。\n    \n- **函数签名**: 函数应接收具体的业务参数（如 `model.Xxx` 或 `request.XxxSearch`），并返回处理结果和 `error`。\n\n- **⚠️ 数据类型处理注意事项**:\n   - 在进行数据模型转换时，**必须确保**字段类型的一致性\n   - 避免在服务层进行不必要的类型转换，应在模型设计阶段统一类型\n   - 如果必须进行类型转换，**必须**添加详细的注释说明转换原因和逻辑\n\n\n#### **3. API层 (`api/`)**\n\n- **职责**: 作为HTTP请求的入口，负责参数校验、调用Service层方法、并返回格式化的JSON响应。\n    \n- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件，并在 `api/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。\n    \n- **Swagger 示例 (必须遵循)**:\n    \n    Go\n    \n    ```\n    // CreateXxx 创建XXX\n    // @Tags     XxxModule\n    // @Summary  创建一个新的XXX\n    // @Security ApiKeyAuth\n    // @accept   application/json\n    // @Produce  application/json\n    // @Param    data body request.CreateXxxRequest true \"XXX的名称和描述\"\n    // @Success  200  {object} response.Response{msg=string} \"创建成功\"\n    // @Router   /xxx/createXxx [post]\n    func (a *XxxApi) CreateXxx(c *gin.Context) {\n        // ...\n    }\n    ```\n    \n\n#### **4. 路由层 (`router/`)**\n\n- **职责**: 定义API路由规则，并将HTTP请求路径映射到具体的API处理函数上，同时配置中间件。\n    \n- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件，并在 `router/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。\n    \n- **路由分组**: 应根据业务需求和权限，合理使用路由组 (`Router.Group()`)，并挂载不同的中间件（如鉴权、操作记录等）。\n\n#### **5. 初始化层 (`initialize/`)**\n\n- **职责**: 提供插件资源（数据库、路由、菜单等）的初始化入口，供主程序调用。\n    \n- **`gorm.go`**: 实现 `InitializeDB` 函数，**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。\n    \n- **`router.go`**: 实现 `InitializeRouter` 函数，**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法，注册所有API路由。\n    \n- **`menu.go`**: 实现 `InitializeMenu` 函数，负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。\n- viper.go: 加载插件配置文件\n-  api.go: 注册API到系统\n    \n\n#### **6. 插件入口 (`plugin.go`) \n\n- **职责**: 作为插件的唯一入口，实现 GVA 的插件接口，让框架能够识别和加载本插件。\n    \n- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。\n\n- **插件注册**: **必须**调用 ```\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n```\n方法，让插件自动注册到本体中\n    \n- **`Register`方法**: 实现 `Register` 方法，该方法接收一个 `*gin.RouterGroup` 参数，其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。\n    \n- **`RouterPath`方法**: 实现 `RouterPath` 方法，返回该插件所有API的根路径，例如 `\"/myPlugin\"`。\n\n### 模块间引用关系：\n- API层引用Service层：在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`\n- Router层引用API层：在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`\n- Initialize/Router引用Router层：通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`\n- 各模块通过enter.go文件组织和暴露功能，避免循环引用\n\n### 插件默认注册功能\n\n`plugin/register.go` 文件下用 `\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin/插件\"\n` 的方式匿名引用用于激活插件本体的init\n\n### 代码组织示例：\n\n1. Service入口 (service/enter.go):\n```go\npackage service\n\ntype ServiceGroup struct {\n    XxxService\n    YyyService\n    // 其他服务...\n}\n\nvar ServiceGroupApp = new(ServiceGroup)\n```\n\n2. API入口 (api/enter.go):\n```go\npackage api\n\ntype ApiGroup struct {\n    XxxApi\n    YyyApi\n    // 其他API...\n}\n\nvar ApiGroupApp = new(ApiGroup)\n```\n\n3. Router入口 (router/enter.go):\n```go\npackage router\n\ntype RouterGroup struct {\n    XxxRouter\n    YyyRouter\n    // 其他路由...\n}\n\nvar RouterGroupApp = new(RouterGroup)\n```\n\n### Swagger注释规范：\n- @Tags: 接口所属的分组\n- @Summary: 接口功能简述\n- @Security: 安全认证方式（如需认证则添加）\n- @accept/@Produce: 请求/响应格式\n- @Param: 请求参数，包括名称、来源、类型、是否必须、描述\n- @Success: 成功响应，包括状态码、返回类型、描述\n- @Router: 接口路径和HTTP方法\n\nAPI函数的Swagger注释不仅用于生成API文档，也是前端开发的重要参考，请确保注释的完整性和准确性。\n\n\n---\n\n### **开发工作流**\n\n1. **接收任务**: 我会向你下达一个具体的功能插件开发任务，例如：“请为项目创建一个‘商品管理 (Product)’插件”。\n    \n2. **【第一步】模型设计 (奠定基础)**:\n    \n    - 你的**首要行动**是分析需求，设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。\n        \n3. **【第二步】自下而上，分层实现**:\n    - 具体项目结构可以参考：server/plugin/announcement 这个插件，非常经典！\n\n    - 在模型确认后，你将按照 `Service -> API -> Router` 的顺序，逐层生成代码。\n        \n    - 确保每一层的代码都完整、健壮，并严格遵守上述规范。\n        \n4. **【第三步】插件初始化与注册**:\n    \n    - 在完成核心功能层的代码后，你将生成 `initialize/` 目录下的相关初始化文件（如 `db.go`, `router.go`）以及插件的主入口文件 `plugin.go`。\n        \n5. **【第四步】提供完整代码**:\n    \n    - 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码，并对每个文件的**相对路径**（例如 `server/plugin/product/api/product_api.go`）和用途进行清晰的说明。\n\n\n---\n\n## **前端开发规范**\n\n### **角色与目标**\n\n你是一名资深的 Vue.js 前端开发专家，**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。\n\n你的核心任务是，根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n### **核心开发指令：绝不可违背的原则**\n\n#### 前端规则\n\n在编写任何前端代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的模块化架构**:\n   - **职责单一**: 每个模块（API、组件、页面、状态）都有其唯一职责，**严禁跨模块直接调用**\n   - **依赖关系**: 依赖链条必须是单向的：`页面组件 -> API服务 -> 后端接口`\n\n2. **统一的API调用模式**:\n   - 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装\n   - **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求\n   - API函数**必须**包含完整的JSDoc注释，描述接口功能、参数和返回值\n\n3. **组件化开发原则**:\n   - **每一个**可复用的UI元素都**必须**封装为组件\n   - 组件**必须**遵循单一职责原则，功能明确\n   - **必须**为组件添加完整的props定义和事件说明\n\n4. **统一的状态管理**:\n   - 全局状态**必须**使用Pinia进行管理\n   - 状态模块**必须**按业务功能进行划分\n   - **严禁**在组件中直接修改全局状态，必须通过actions\n\n### **各层级代码实现规范**\n\n#### **1. API层 (`src/api/`)**\n\n- **职责**: 封装所有后端API调用，提供统一的接口服务\n- **结构**: 按业务模块创建API文件，如 `user.js`、`menu.js`\n- **规范**:\n  ```javascript\n  import service from '@/utils/request'\n  \n  /**\n   * 获取用户列表\n   * @param {Object} data 查询参数\n   * @param {number} data.page 页码\n   * @param {number} data.pageSize 每页数量\n   * @returns {Promise} 用户列表数据\n   */\n  export const getUserList = (data) => {\n    return service({\n      url: '/user/getUserList',\n      method: 'post',\n      data: data\n    })\n  }\n  ```\n\n#### **2. 组件层 (`src/components/`)**\n\n- **职责**: 提供可复用的UI组件\n- **结构**: 按功能分类组织，每个组件一个文件夹\n- **规范**:\n  ```vue\n  <template>\n    <div class=\"gva-table\">\n      <!-- 组件内容 -->\n    </div>\n  </template>\n  \n  <script setup>\n  /**\n   * 通用表格组件\n   * @component GvaTable\n   * @description 提供统一的表格展示功能\n   */\n  \n  // Props定义\n  const props = defineProps({\n    data: {\n      type: Array,\n      required: true,\n      default: () => []\n    },\n    loading: {\n      type: Boolean,\n      default: false\n    }\n  })\n  \n  // 事件定义\n  const emit = defineEmits(['refresh', 'edit', 'delete'])\n  </script>\n  ```\n\n#### **3. 页面层 (`src/view/`)**\n\n- **职责**: 实现具体的业务页面\n- **结构**: 按业务模块组织，每个页面一个Vue文件\n- **规范**:\n  - **必须**使用Composition API\n  - **必须**进行响应式数据管理\n  - **必须**处理加载状态和错误状态\n  - **必须**遵循Element Plus组件规范\n  - **必须**优先使用UnoCSS原子化类名进行样式设计\n  - **必须**优先el-drawer组件进行编辑，新增，步骤等操作\n  - **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性，确保组件销毁，避免内存泄漏和状态污染\n\n#### **4. 状态管理 (`src/pinia/`)**\n\n- **职责**: 管理全局状态和业务逻辑\n- **结构**: 按业务模块创建store文件\n- **规范**:\n  ```javascript\n  import { defineStore } from 'pinia'\n  import { ref, computed } from 'vue'\n  import { useStorage } from '@vueuse/core'\n  \n  export const useUserStore = defineStore('user', () => {\n    // 状态定义 - 使用 ref() 创建响应式状态\n    const userInfo = ref({\n      uuid: '',\n      nickName: '',\n      headerImg: '',\n      authority: {}\n    })\n    const token = useStorage('token', '')\n    \n    // 计算属性 - 使用 computed() 定义\n    const isLogin = computed(() => !!token.value)\n    \n    // 方法定义 - 直接定义函数作为 actions\n    const setUserInfo = (val) => {\n      userInfo.value = val\n    }\n    \n    const setToken = (val) => {\n      token.value = val\n    }\n    \n    const login = async (loginForm) => {\n      // 登录逻辑\n      try {\n        const res = await loginApi(loginForm)\n        if (res.code === 0) {\n          setUserInfo(res.data.user)\n          setToken(res.data.token)\n          return true\n        }\n        return false\n      } catch (error) {\n        console.error('Login error:', error)\n        return false\n      }\n    }\n    \n    const logout = async () => {\n      // 登出逻辑\n      token.value = ''\n      userInfo.value = {}\n    }\n    \n    // 返回所有需要暴露的状态和方法\n    return {\n      userInfo,\n      token,\n      isLogin,\n      setUserInfo,\n      setToken,\n      login,\n      logout\n    }\n  })\n  ```\n\n#### **5. 路由管理 (`src/router/`)**\n\n- **职责**: 管理页面路由和权限控制\n- **规范**:\n  - **必须**配置路由元信息\n  - **必须**实现权限验证\n  - **必须**支持动态路由\n\n### **前端插件开发规范**\n\n#### **插件目录结构**\n\n```\nsrc/plugin/[插件名]/\n├── api/                # 插件API接口\n│   └── [模块].js\n├── components/         # 插件组件（可选）\n│   └── [组件名].vue\n├── view/              # 插件页面\n│   └── [页面名].vue\n├── form/              # 插件表单（可选）\n│   └── [表单名].vue\n└── index.js           # 插件入口文件（可选）\n```\n\n#### **插件开发原则**\n\n1. **独立性**: 插件应该是自包含的，不依赖其他业务模块\n2. **可配置性**: 插件应该支持配置化，便于定制\n3. **可扩展性**: 插件应该预留扩展接口\n4. **一致性**: 插件UI风格应与主系统保持一致\n\n### **代码质量要求**\n\n1. **命名规范**:\n   - 文件名：kebab-case（短横线命名）\n   - 组件名：PascalCase（大驼峰）\n   - 变量名：camelCase（小驼峰）\n   - 常量名：UPPER_SNAKE_CASE（大写下划线）\n\n2. **注释规范**:\n   - **必须**为所有API函数添加JSDoc注释\n   - **必须**为复杂组件添加功能说明\n   - **必须**为关键业务逻辑添加行内注释\n\n3. **样式规范**:\n   - **优先**使用UnoCSS原子化类名\n   - **必须**遵循Element Plus设计规范\n   - **禁止**使用内联样式\n   - **必须**使用CSS变量进行主题定制\n\n4. **性能要求**:\n   - **必须**使用懒加载优化路由\n   - **必须**对大列表进行虚拟滚动优化\n   - **必须**合理使用缓存机制\n   - **必须**优化图片和资源加载\n\n---\n\n## **⚠️ 前端工具库使用规范（强制）**\n\n> **核心原则：在开发任何前端功能时，必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数，严禁重复造轮子。**\n\n`src/utils/` 目录提供了项目级别的通用工具集，涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明：\n\n### **工具文件清单**\n\n#### `request.js` — HTTP 请求封装（核心）\n- 基于 Axios 封装的统一 HTTP 请求实例，内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截\n- **所有 API 请求必须且只能通过此模块发送，禁止直接使用 axios**\n- 用法：`import service from '@/utils/request'`\n\n#### `date.js` — 日期格式化\n- 扩展了 `Date.prototype.Format` 方法，支持自定义格式如 `yyyy-MM-dd hh:mm:ss`\n- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串\n- **需要格式化日期时，优先使用此工具，禁止自行手写日期格式化逻辑**\n- 用法：`import { formatTimeToStr } from '@/utils/date'`\n\n#### `format.js` — 数据展示格式化（综合工具）\n- `formatBoolean(bool)` — 将布尔值转为 \"是\"/\"否\" 中文展示\n- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串\n- `filterDict(value, options)` — 在字典选项数组（支持多级树形）中根据 value 查找对应的 label\n- `filterDataSource(dataSource, value)` — 在数据源（支持多级树形）中根据 value 查找 label，支持数组批量查找\n- `getDictFunc(type)` — 异步获取指定类型的字典数据\n- `ReturnArrImg(arr)` — 将图片路径（单个或数组）转为完整 URL，自动补全服务器前缀\n- `onDownloadFile(url)` — 触发文件下载\n- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量（支持亮/暗模式）\n- `CreateUUID()` — 生成 UUID v4 字符串\n- `getBaseUrl()` — 获取当前环境的 API BaseURL\n- **以上所有格式化场景优先使用此文件中的工具函数**\n- 用法：`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`\n\n#### `dictionary.js` — 字典数据获取\n- `getDict(type, options)` — 异步获取字典数据，支持 `depth`（深度）和 `value`（指定节点）参数，内置 Pinia store 缓存，避免重复请求\n- **凡是需要字典下拉数据、字典树形数据的场景，必须使用此工具**\n- 用法：`import { getDict } from '@/utils/dictionary'`\n\n#### `stringFun.js` — 字符串处理\n- `toUpperCase(str)` — 首字母转大写\n- `toLowerCase(str)` — 首字母转小写\n- `toSQLLine(str)` — 驼峰命名转下划线（snake_case），如 `userName` → `user_name`\n- `toHump(name)` — 下划线命名转驼峰，如 `user_name` → `userName`\n- **进行命名格式转换时必须使用此工具，禁止使用正则手写**\n- 用法：`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`\n\n#### `params.js` — 系统参数获取\n- `getParams(key)` — 异步从 Pinia store 中获取系统参数，内置缓存\n- **获取系统配置参数时，优先使用此工具**\n- 用法：`import { getParams } from '@/utils/params'`\n\n#### `bus.js` — 全局事件总线\n- 基于 `mitt` 封装的全局事件总线实例 `emitter`，用于跨组件通信\n- **跨层级组件通信优先使用此事件总线，避免滥用 Pinia**\n- 用法：`import { emitter } from '@/utils/bus'`\n\n#### `closeThisPage.js` — 关闭当前标签页\n- `closeThisPage()` — 触发关闭当前多标签页的操作（通过事件总线发送 `closeThisPage` 事件）\n- **在需要程序化关闭当前页面时，必须使用此工具**\n- 用法：`import { closeThisPage } from '@/utils/closeThisPage'`\n\n#### `downloadImg.js` — 图片下载\n- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载，支持跨域\n- **需要下载图片时，优先使用此工具**\n- 用法：`import { downloadImage } from '@/utils/downloadImg'`\n\n#### `image.js` — 图片压缩\n- 导出 `ImageCompress` 类，支持图片等比压缩至指定最大宽高，并可限制文件大小\n- **上传图片前需要做压缩处理时，使用此工具**\n- 用法：`import ImageCompress from '@/utils/image'`\n\n#### `event.js` — DOM 事件监听管理\n- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听\n- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听\n- **手动操作 DOM 事件时，使用此工具以确保安全性**\n- 用法：`import { addEventListen, removeEventListen } from '@/utils/event'`\n\n#### `env.js` — 环境判断\n- `isDev` — 是否为开发环境（Boolean）\n- `isProd` — 是否为生产环境（Boolean）\n- **需要区分运行环境时，使用此工具，禁止直接读取 `import.meta.env`**\n- 用法：`import { isDev, isProd } from '@/utils/env'`\n\n#### `doc.js` — 外部文档跳转\n- `toDoc(url)` — 在新标签页打开指定 URL\n- 用法：`import { toDoc } from '@/utils/doc'`\n\n#### `fmtRouterTitle.js` — 路由标题格式化\n- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值（如 `${id}` 替换为路由 params/query 值）\n- 用法：`import { fmtTitle } from '@/utils/fmtRouterTitle'`\n\n#### `page.js` — 页面标题生成\n- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题（格式：`页面名 - 应用名`）\n- 用法：`import getPageTitle from '@/utils/page'`\n\n#### `asyncRouter.js` — 异步路由处理\n- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置（字符串 component 路径）动态转换为 Vue 组件的 import 函数，支持 `view/` 和 `plugin/` 目录\n- **动态路由相关逻辑已由此工具处理，不需要也不应该手动实现**\n- 用法：`import { asyncRouterHandle } from '@/utils/asyncRouter'`\n\n#### `btnAuth.js` — 按钮权限\n- `useBtnAuth()` — Composition API Hook，返回当前路由挂载的按钮权限对象（来自 `route.meta.btns`），用于控制操作按钮的显示\n- **实现按钮级别权限控制时，必须使用此 Hook**\n- 用法：`import { useBtnAuth } from '@/utils/btnAuth'`\n\n### **使用强制要求**\n\n| 场景 | 必须使用的工具 |\n|------|----------------|\n| 发送 HTTP 请求 | `@/utils/request` |\n| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |\n| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |\n| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |\n| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |\n| 驼峰/下划线命名转换 | `@/utils/stringFun` |\n| 获取系统参数 | `@/utils/params` 中的 `getParams` |\n| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |\n| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |\n| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |\n| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |\n| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |\n\n---\n\n## **前后端协作规范**\n\n### **接口协作规范**\n\n1. **接口文档**:\n   - 后端**必须**提供完整的Swagger API文档\n   - 前端**必须**基于Swagger文档进行接口调用\n   - 接口变更**必须**提前通知并更新文档\n\n2. **数据格式**:\n   - **统一**使用JSON格式进行数据交换\n   - **统一**响应格式：`{code, data, msg}`\n   - **统一**分页格式：`{page, pageSize, total, list}`\n   - **统一**时间格式：ISO 8601标准\n   - **⚠️ 数据类型一致性**：\n      - 前后端对于同一字段**必须**使用相同的数据类型\n      - 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致\n      - 特别注意：状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段\n      - 示例：后端数值类型字段对应前端 `number` 类型，字符串类型对应 `string` 类型，布尔类型对应 `boolean` 类型\n      - **指针类型处理**：后端Go中的指针类型在JSON序列化时会自动处理nil值，前端接收到的是对应的基础类型或null值\n\n3. **错误处理**:\n   - 后端**必须**返回标准化的错误码和错误信息\n   - 前端**必须**统一处理HTTP状态码和业务错误码\n   - **必须**提供用户友好的错误提示\n\n### **开发流程规范**\n\n1. **需求分析阶段**:\n   - 确定功能需求和接口设计\n   - 定义数据模型和业务流程\n   - 制定前后端开发计划\n\n2. **开发阶段**:\n   - 后端优先开发API接口\n   - 前端基于Mock数据进行并行开发\n   - 定期进行接口联调测试\n\n3. **测试阶段**:\n   - 单元测试：前后端各自负责\n   - 集成测试：前后端协作完成\n   - 用户验收测试：产品团队主导\n\n### **版本管理规范**\n\n1. **分支策略**:\n   - `main`：生产环境分支\n   - `develop`：开发环境分支\n   - `feature/*`：功能开发分支\n   - `hotfix/*`：紧急修复分支\n\n2. **提交规范**:\n   - 使用语义化提交信息\n   - 格式：`type(scope): description`\n   - 类型：feat, fix, docs, style, refactor, test, chore\n\n---\n\n## **插件开发完整规范**\n\n### **后端插件结构**\n\n```\nserver/plugin/[插件名]/\n├── api/                # API控制器\n│   ├── enter.go       # API组入口\n│   └── [模块].go      # 具体API实现\n├── config/            # 插件配置\n│   └── config.go\n├── initialize/        # 初始化模块\n│   ├── api.go        # API注册\n│   ├── gorm.go       # 数据库初始化\n│   ├── menu.go       # 菜单初始化\n│   ├── router.go     # 路由初始化\n│   └── viper.go      # 配置初始化\n├── model/             # 数据模型\n│   ├── [模型].go     # 数据库模型\n│   └── request/      # 请求模型\n├── router/            # 路由定义\n│   ├── enter.go      # 路由组入口\n│   └── [模块].go     # 具体路由\n├── service/           # 业务服务\n│   ├── enter.go      # 服务组入口\n│   └── [模块].go     # 具体服务\n└── plugin.go          # 插件入口\n```\n\n### **前端插件结构**\n\n```\nweb/src/plugin/[插件名]/\n├── api/               # API接口\n│   └── [模块].js\n├── components/        # 插件组件\n│   └── [组件].vue\n├── view/             # 插件页面\n│   └── [页面].vue\n├── form/             # 表单组件\n│   └── [表单].vue\n└── config.js         # 插件配置\n```\n\n### **插件开发工作流**\n\n1. **【第一步】需求分析**:\n   - 明确插件功能和业务需求\n   - 设计数据模型和接口规范\n   - 规划前端页面和交互流程\n\n2. **【第二步】后端开发**:\n   - 创建数据模型和请求模型\n   - 实现服务层业务逻辑\n   - 开发API控制器和路由\n   - 编写初始化和配置代码\n\n3. **【第三步】前端开发**:\n   - 创建API接口封装\n   - 开发页面组件和表单\n   - 实现业务逻辑和状态管理\n   - 集成到主系统菜单\n\n4. **【第四步】测试集成**:\n   - 单元测试和集成测试\n   - 前后端联调测试\n   - 用户体验测试\n   - 性能和安全测试\n\n### **插件质量标准**\n\n1. **功能完整性**: 插件功能完整，满足业务需求\n2. **代码质量**: 代码规范，注释完整，易于维护\n3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致，避免类型转换错误\n4. **性能表现**: 响应速度快，资源占用合理\n5. **用户体验**: 界面友好，操作流畅，错误处理完善\n6. **兼容性**: 与主系统兼容，不影响其他功能\n7. **安全性**: 数据安全，权限控制，防止安全漏洞\n\n---\n\n### **建议和方案**\n\n基于以上规范，建议AI在开发gin-vue-admin项目时：\n\n1. **严格遵循分层架构**：确保前后端代码都按照规定的层次结构组织\n2. **保持代码一致性**：使用统一的命名规范、注释格式和代码风格\n3. **注重文档完整性**：确保API文档、代码注释和使用说明的完整性\n4. **优化用户体验**：关注页面加载速度、交互流畅性和错误处理\n5. **考虑扩展性**：设计时预留扩展接口，便于后续功能增强\n6. **重视安全性**：实现完善的权限控制和数据验证机制"
  },
  {
    "path": ".codex/rules/project_rules.md",
    "content": "### 功能描述以及必要性描述\n\n---\nname: gin-vue-admin\ndescription: |\n  gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。\n  \n  前端技术栈：\n  - Vue 3.5.7 + Composition API\n  - Vite 6.2.3 构建工具\n  - Pinia 2.2.2 状态管理\n  - Element Plus 2.10.2 UI组件库\n  - UnoCSS 66.4.2 原子化CSS框架\n  - Vue Router 4.4.3 路由管理\n  - Axios 1.8.2 HTTP客户端\n  - ECharts 5.5.1 数据可视化\n  - @vueuse/core Vue组合式API工具集\n  \n  后端技术栈：\n  - Go 1.23 + Gin 1.10.0 Web框架\n  - GORM 1.25.12 ORM框架\n  - Casbin 2.103.0 权限管理\n  - Viper 1.19.0 配置管理\n  - Zap 1.27.0 日志系统\n  - Redis 9.7.0 缓存\n  - JWT 5.2.2 认证授权\n  - 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库\n  - 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务\n  \n  核心特性：\n  - 完整的RBAC权限控制系统\n  - 代码自动生成功能\n  - 丰富的中间件支持\n  - 插件化架构设计\n  - Swagger API文档\n---\n\n#### **角色与目标**\n\n你是一名资深的全栈开发专家，**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**，熟练使用Golang、Vue3、Gin、GORM等技术栈。\n\n你的核心任务是，根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n---\n\n### **🚀 重要提示：GVA Helper MCP 支持**\n\n**在开始任何GVA开发工作之前，请务必注意以下重要工作流程：**\n\n1. **MCP支持**: GVA框架本身支持MCP（Model Context Protocol），提供了强大的开发辅助能力\n\n2. **GVA Helper**: 通常会有一个名为 \"**GVA Helper**\" 的MCP助手，专门为GVA框架开发提供支持\n\n3. **开发流程**: \n   - **第一步**: 在开发任何新功能之前，**必须先通过GVA Helper获得支持和指导**\n   - **第二步**: 在获得GVA Helper的专业建议和代码示例后，再进行具体的开发操作\n   - **第三步**: 遵循GVA Helper提供的最佳实践和代码规范\n\n4. **优势**: 通过GVA Helper可以获得：\n   - 最新的GVA框架特性和最佳实践\n   - 符合项目规范的代码模板\n   - 避免常见的开发陷阱和错误\n   - 确保代码质量和一致性\n\n**请始终记住：GVA Helper → 获得支持 → 开始开发**\n\n---\n\n### **核心开发指令：绝不可违背的原则**\n\n\n## **项目结构说明**\n\n### **整体架构**\n\ngin-vue-admin 采用前后端分离架构：\n- **后端 (server/)**：基于 Go + Gin 的 RESTful API 服务\n- **前端 (web/)**：基于 Vue 3 + Vite 的单页面应用\n- **部署 (deploy/)**：Docker、Kubernetes 等部署配置\n\n### **后端目录结构 (server/)**\n\n```\nserver/\n├── api/                    # API控制器层\n│   └── v1/                # API版本控制\n│       ├── enter.go       # API组入口文件\n│       ├── system/        # 系统模块API\n│       └──example/       # 示例模块API\n├── config/                # 配置结构体定义\n├── core/                  # 核心启动文件\n├── docs/                  # Swagger文档\n├── global/                # 全局变量和模型\n├── initialize/            # 初始化模块\n├── middleware/            # 中间件\n├── model/                 # 数据模型层\n│   ├── system/           # 系统模块模型\n│   ├── example/          # 示例模块模型\n│   └── common/           # 通用模型\n├── plugin/               # 插件目录\n│   ├── announcement/     # 公告插件\n│   └── email/           # 邮件插件\n├── router/               # 路由层\n│   ├── enter.go         # 路由组入口\n│   ├── system/          # 系统路由\n│   └──example/         # 示例路由\n├── service/              # 服务层\n│   ├── enter.go         # 服务组入口\n│   ├── system/          # 系统服务\n│   └──  example/         # 示例服务\n├── source/               # 数据初始化\n├── utils/                # 工具包\n├── config.yaml          # 配置文件\n└── main.go              # 程序入口\n```\n\n### **前端目录结构 (web/)**\n\n```\nweb/\n├── public/               # 静态资源\n├── src/\n│   ├── api/             # API接口定义\n│   │   ├── user.js      # 用户相关API\n│   │   ├── menu.js      # 菜单相关API\n│   │   └── cattery/     # 业务模块API\n│   ├── assets/          # 资源文件\n│   │   ├── icons/       # 图标\n│   │   └── images/      # 图片\n│   ├── core/            # 核心配置\n│   ├── directive/       # 自定义指令\n│   ├── hooks/           # 组合式API钩子\n│   ├── pinia/           # 状态管理\n│   │   ├── index.js     # Pinia入口\n│   │   └── modules/     # 状态模块\n│   ├── plugin/          # 前端插件\n│   │   ├── announcement/ # 公告插件\n│   │   └── email/       # 邮件插件\n│   ├── router/          # 路由配置\n│   ├── style/           # 样式文件\n│   ├── utils/           # 工具函数\n│   ├── view/            # 页面组件\n│   │   ├── dashboard/   # 仪表盘\n│   │   ├── layout/      # 布局组件\n│   │   ├── login/       # 登录页\n│   │   ├── superAdmin/  # 超级管理员\n│   │   ├── systemTools/ # 系统工具\n│   │   └── cattery/     # 业务页面\n│   ├── App.vue          # 根组件\n│   └── main.js          # 程序入口\n├── package.json         # 依赖配置\n├── vite.config.js       # Vite配置\n└── uno.config.js        # UnoCSS配置\n```\n\n---\n\n#### 后端规则\n\n在编写任何代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的分层架构**:\n    \n    - **职责单一**: 每个层（Model, Service, API, Router）都有其唯一职责，**严禁跨层调用**。例如，API层绝不能直接操作数据库，必须通过Service层。Service层绝不能直接处理`gin.Context`。\n        \n    - **依赖关系**: 依赖链条必须是单向的：`Router -> API -> Service -> Model`。\n        \n2. **`enter.go` 组管理模式**:\n    \n    - 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。\n        \n    - 全局实例变量（如 `service.ServiceGroupApp`）是模块间通信的唯一入口，以此来避免循环引用。\n        \n3. **详尽的 Swagger 注释 (API层强制要求)**:\n    \n    - **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源，也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。\n        \n4. **统一的响应与错误处理**:\n    \n    - Service 层函数遇到业务错误时，应返回 `error` 对象。\n        \n    - API 层负责捕获 Service 层的 `error`，并使用项目统一的 `response` 包（如 `response.OkWithDetailed` 或 `response.FailWithMessage`）将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。\n        \n\n---\n\n### **各层级代码实现规范**\n\n#### **1. 模型层 (`model/`)**\n\n- **数据模型 (`model/xxx.go`)**:\n\n   - 用于定义与数据库表映射的 GORM 结构体。\n\n   - 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。\n\n   - 以上三个字段返回给前端并未做驼峰处理，json内依然是  `ID`, `CreatedAt`, `UpdatedAt`\n\n   - 必须为字段添加清晰的 `json` 和 `gorm` 标签。\n\n   - **⚠️ 重要提醒：数据类型一致性**\n      - **必须确保**同一字段在不同模型文件中的数据类型保持严格一致\n      - 例如：如果某字段在数据模型中定义为特定类型，那么在请求模型、响应模型中也必须使用相同的数据类型\n      - **常见错误**：数据模型与请求模型中同一字段使用了不同的数据类型，这会导致类型转换错误和运行时异常\n      - **解决方案**：在设计阶段统一确定字段类型，并在所有相关模型中保持一致\n      - **检查要点**：特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段\n      - **⚠️ 指针类型处理**：\n         - 当数据模型中使用指针类型（如 `*string`、`*int`）而请求/响应模型中使用非指针类型时，**必须**在服务层进行正确的指针转换\n         - **转换规则**：从指针到非指针需要检查nil值，从非指针到指针需要取地址\n         - **示例**：数据模型 `Name *string` 转换为请求模型 `Name string` 时，需要处理 `if model.Name != nil { request.Name = *model.Name }`\n\n- **请求模型 (`model/request/xxx.go`)**:\n    \n    - 用于定义接收前端请求参数的结构体（DTOs）。\n        \n    - **必须**为字段添加 `json` 和 `form` 标签，以便 Gin 进行参数绑定。\n        \n    - 对于列表查询请求，应创建一个 `XxxSearch` 结构体，并内嵌通用的 `request.PageInfo` 分页结构体。\n        \n\n#### **2. 服务层 (`service/`)**\n\n- **职责**: 封装所有核心业务逻辑，进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码（如 `gin.Context`）**。\n    \n- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件，并在 `service/enter.go` 中注册。\n    \n- **函数签名**: 函数应接收具体的业务参数（如 `model.Xxx` 或 `request.XxxSearch`），并返回处理结果和 `error`。\n\n- **⚠️ 数据类型处理注意事项**:\n   - 在进行数据模型转换时，**必须确保**字段类型的一致性\n   - 避免在服务层进行不必要的类型转换，应在模型设计阶段统一类型\n   - 如果必须进行类型转换，**必须**添加详细的注释说明转换原因和逻辑\n\n\n#### **3. API层 (`api/`)**\n\n- **职责**: 作为HTTP请求的入口，负责参数校验、调用Service层方法、并返回格式化的JSON响应。\n    \n- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件，并在 `api/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。\n    \n- **Swagger 示例 (必须遵循)**:\n    \n    Go\n    \n    ```\n    // CreateXxx 创建XXX\n    // @Tags     XxxModule\n    // @Summary  创建一个新的XXX\n    // @Security ApiKeyAuth\n    // @accept   application/json\n    // @Produce  application/json\n    // @Param    data body request.CreateXxxRequest true \"XXX的名称和描述\"\n    // @Success  200  {object} response.Response{msg=string} \"创建成功\"\n    // @Router   /xxx/createXxx [post]\n    func (a *XxxApi) CreateXxx(c *gin.Context) {\n        // ...\n    }\n    ```\n    \n\n#### **4. 路由层 (`router/`)**\n\n- **职责**: 定义API路由规则，并将HTTP请求路径映射到具体的API处理函数上，同时配置中间件。\n    \n- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件，并在 `router/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。\n    \n- **路由分组**: 应根据业务需求和权限，合理使用路由组 (`Router.Group()`)，并挂载不同的中间件（如鉴权、操作记录等）。\n\n#### **5. 初始化层 (`initialize/`)**\n\n- **职责**: 提供插件资源（数据库、路由、菜单等）的初始化入口，供主程序调用。\n    \n- **`gorm.go`**: 实现 `InitializeDB` 函数，**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。\n    \n- **`router.go`**: 实现 `InitializeRouter` 函数，**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法，注册所有API路由。\n    \n- **`menu.go`**: 实现 `InitializeMenu` 函数，负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。\n- viper.go: 加载插件配置文件\n-  api.go: 注册API到系统\n    \n\n#### **6. 插件入口 (`plugin.go`) \n\n- **职责**: 作为插件的唯一入口，实现 GVA 的插件接口，让框架能够识别和加载本插件。\n    \n- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。\n\n- **插件注册**: **必须**调用 ```\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n```\n方法，让插件自动注册到本体中\n    \n- **`Register`方法**: 实现 `Register` 方法，该方法接收一个 `*gin.RouterGroup` 参数，其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。\n    \n- **`RouterPath`方法**: 实现 `RouterPath` 方法，返回该插件所有API的根路径，例如 `\"/myPlugin\"`。\n\n### 模块间引用关系：\n- API层引用Service层：在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`\n- Router层引用API层：在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`\n- Initialize/Router引用Router层：通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`\n- 各模块通过enter.go文件组织和暴露功能，避免循环引用\n\n### 插件默认注册功能\n\n`plugin/register.go` 文件下用 `\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin/插件\"\n` 的方式匿名引用用于激活插件本体的init\n\n### 代码组织示例：\n\n1. Service入口 (service/enter.go):\n```go\npackage service\n\ntype ServiceGroup struct {\n    XxxService\n    YyyService\n    // 其他服务...\n}\n\nvar ServiceGroupApp = new(ServiceGroup)\n```\n\n2. API入口 (api/enter.go):\n```go\npackage api\n\ntype ApiGroup struct {\n    XxxApi\n    YyyApi\n    // 其他API...\n}\n\nvar ApiGroupApp = new(ApiGroup)\n```\n\n3. Router入口 (router/enter.go):\n```go\npackage router\n\ntype RouterGroup struct {\n    XxxRouter\n    YyyRouter\n    // 其他路由...\n}\n\nvar RouterGroupApp = new(RouterGroup)\n```\n\n### Swagger注释规范：\n- @Tags: 接口所属的分组\n- @Summary: 接口功能简述\n- @Security: 安全认证方式（如需认证则添加）\n- @accept/@Produce: 请求/响应格式\n- @Param: 请求参数，包括名称、来源、类型、是否必须、描述\n- @Success: 成功响应，包括状态码、返回类型、描述\n- @Router: 接口路径和HTTP方法\n\nAPI函数的Swagger注释不仅用于生成API文档，也是前端开发的重要参考，请确保注释的完整性和准确性。\n\n\n---\n\n### **开发工作流**\n\n1. **接收任务**: 我会向你下达一个具体的功能插件开发任务，例如：“请为项目创建一个‘商品管理 (Product)’插件”。\n    \n2. **【第一步】模型设计 (奠定基础)**:\n    \n    - 你的**首要行动**是分析需求，设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。\n        \n3. **【第二步】自下而上，分层实现**:\n    - 具体项目结构可以参考：server/plugin/announcement 这个插件，非常经典！\n\n    - 在模型确认后，你将按照 `Service -> API -> Router` 的顺序，逐层生成代码。\n        \n    - 确保每一层的代码都完整、健壮，并严格遵守上述规范。\n        \n4. **【第三步】插件初始化与注册**:\n    \n    - 在完成核心功能层的代码后，你将生成 `initialize/` 目录下的相关初始化文件（如 `db.go`, `router.go`）以及插件的主入口文件 `plugin.go`。\n        \n5. **【第四步】提供完整代码**:\n    \n    - 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码，并对每个文件的**相对路径**（例如 `server/plugin/product/api/product_api.go`）和用途进行清晰的说明。\n\n\n---\n\n## **前端开发规范**\n\n### **角色与目标**\n\n你是一名资深的 Vue.js 前端开发专家，**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。\n\n你的核心任务是，根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n### **核心开发指令：绝不可违背的原则**\n\n#### 前端规则\n\n在编写任何前端代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的模块化架构**:\n   - **职责单一**: 每个模块（API、组件、页面、状态）都有其唯一职责，**严禁跨模块直接调用**\n   - **依赖关系**: 依赖链条必须是单向的：`页面组件 -> API服务 -> 后端接口`\n\n2. **统一的API调用模式**:\n   - 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装\n   - **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求\n   - API函数**必须**包含完整的JSDoc注释，描述接口功能、参数和返回值\n\n3. **组件化开发原则**:\n   - **每一个**可复用的UI元素都**必须**封装为组件\n   - 组件**必须**遵循单一职责原则，功能明确\n   - **必须**为组件添加完整的props定义和事件说明\n\n4. **统一的状态管理**:\n   - 全局状态**必须**使用Pinia进行管理\n   - 状态模块**必须**按业务功能进行划分\n   - **严禁**在组件中直接修改全局状态，必须通过actions\n\n### **各层级代码实现规范**\n\n#### **1. API层 (`src/api/`)**\n\n- **职责**: 封装所有后端API调用，提供统一的接口服务\n- **结构**: 按业务模块创建API文件，如 `user.js`、`menu.js`\n- **规范**:\n  ```javascript\n  import service from '@/utils/request'\n  \n  /**\n   * 获取用户列表\n   * @param {Object} data 查询参数\n   * @param {number} data.page 页码\n   * @param {number} data.pageSize 每页数量\n   * @returns {Promise} 用户列表数据\n   */\n  export const getUserList = (data) => {\n    return service({\n      url: '/user/getUserList',\n      method: 'post',\n      data: data\n    })\n  }\n  ```\n\n#### **2. 组件层 (`src/components/`)**\n\n- **职责**: 提供可复用的UI组件\n- **结构**: 按功能分类组织，每个组件一个文件夹\n- **规范**:\n  ```vue\n  <template>\n    <div class=\"gva-table\">\n      <!-- 组件内容 -->\n    </div>\n  </template>\n  \n  <script setup>\n  /**\n   * 通用表格组件\n   * @component GvaTable\n   * @description 提供统一的表格展示功能\n   */\n  \n  // Props定义\n  const props = defineProps({\n    data: {\n      type: Array,\n      required: true,\n      default: () => []\n    },\n    loading: {\n      type: Boolean,\n      default: false\n    }\n  })\n  \n  // 事件定义\n  const emit = defineEmits(['refresh', 'edit', 'delete'])\n  </script>\n  ```\n\n#### **3. 页面层 (`src/view/`)**\n\n- **职责**: 实现具体的业务页面\n- **结构**: 按业务模块组织，每个页面一个Vue文件\n- **规范**:\n  - **必须**使用Composition API\n  - **必须**进行响应式数据管理\n  - **必须**处理加载状态和错误状态\n  - **必须**遵循Element Plus组件规范\n  - **必须**优先使用UnoCSS原子化类名进行样式设计\n  - **必须**优先el-drawer组件进行编辑，新增，步骤等操作\n  - **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性，确保组件销毁，避免内存泄漏和状态污染\n\n#### **4. 状态管理 (`src/pinia/`)**\n\n- **职责**: 管理全局状态和业务逻辑\n- **结构**: 按业务模块创建store文件\n- **规范**:\n  ```javascript\n  import { defineStore } from 'pinia'\n  import { ref, computed } from 'vue'\n  import { useStorage } from '@vueuse/core'\n  \n  export const useUserStore = defineStore('user', () => {\n    // 状态定义 - 使用 ref() 创建响应式状态\n    const userInfo = ref({\n      uuid: '',\n      nickName: '',\n      headerImg: '',\n      authority: {}\n    })\n    const token = useStorage('token', '')\n    \n    // 计算属性 - 使用 computed() 定义\n    const isLogin = computed(() => !!token.value)\n    \n    // 方法定义 - 直接定义函数作为 actions\n    const setUserInfo = (val) => {\n      userInfo.value = val\n    }\n    \n    const setToken = (val) => {\n      token.value = val\n    }\n    \n    const login = async (loginForm) => {\n      // 登录逻辑\n      try {\n        const res = await loginApi(loginForm)\n        if (res.code === 0) {\n          setUserInfo(res.data.user)\n          setToken(res.data.token)\n          return true\n        }\n        return false\n      } catch (error) {\n        console.error('Login error:', error)\n        return false\n      }\n    }\n    \n    const logout = async () => {\n      // 登出逻辑\n      token.value = ''\n      userInfo.value = {}\n    }\n    \n    // 返回所有需要暴露的状态和方法\n    return {\n      userInfo,\n      token,\n      isLogin,\n      setUserInfo,\n      setToken,\n      login,\n      logout\n    }\n  })\n  ```\n\n#### **5. 路由管理 (`src/router/`)**\n\n- **职责**: 管理页面路由和权限控制\n- **规范**:\n  - **必须**配置路由元信息\n  - **必须**实现权限验证\n  - **必须**支持动态路由\n\n### **前端插件开发规范**\n\n#### **插件目录结构**\n\n```\nsrc/plugin/[插件名]/\n├── api/                # 插件API接口\n│   └── [模块].js\n├── components/         # 插件组件（可选）\n│   └── [组件名].vue\n├── view/              # 插件页面\n│   └── [页面名].vue\n├── form/              # 插件表单（可选）\n│   └── [表单名].vue\n└── index.js           # 插件入口文件（可选）\n```\n\n#### **插件开发原则**\n\n1. **独立性**: 插件应该是自包含的，不依赖其他业务模块\n2. **可配置性**: 插件应该支持配置化，便于定制\n3. **可扩展性**: 插件应该预留扩展接口\n4. **一致性**: 插件UI风格应与主系统保持一致\n\n### **代码质量要求**\n\n1. **命名规范**:\n   - 文件名：kebab-case（短横线命名）\n   - 组件名：PascalCase（大驼峰）\n   - 变量名：camelCase（小驼峰）\n   - 常量名：UPPER_SNAKE_CASE（大写下划线）\n\n2. **注释规范**:\n   - **必须**为所有API函数添加JSDoc注释\n   - **必须**为复杂组件添加功能说明\n   - **必须**为关键业务逻辑添加行内注释\n\n3. **样式规范**:\n   - **优先**使用UnoCSS原子化类名\n   - **必须**遵循Element Plus设计规范\n   - **禁止**使用内联样式\n   - **必须**使用CSS变量进行主题定制\n\n4. **性能要求**:\n   - **必须**使用懒加载优化路由\n   - **必须**对大列表进行虚拟滚动优化\n   - **必须**合理使用缓存机制\n   - **必须**优化图片和资源加载\n\n---\n\n## **⚠️ 前端工具库使用规范（强制）**\n\n> **核心原则：在开发任何前端功能时，必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数，严禁重复造轮子。**\n\n`src/utils/` 目录提供了项目级别的通用工具集，涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明：\n\n### **工具文件清单**\n\n#### `request.js` — HTTP 请求封装（核心）\n- 基于 Axios 封装的统一 HTTP 请求实例，内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截\n- **所有 API 请求必须且只能通过此模块发送，禁止直接使用 axios**\n- 用法：`import service from '@/utils/request'`\n\n#### `date.js` — 日期格式化\n- 扩展了 `Date.prototype.Format` 方法，支持自定义格式如 `yyyy-MM-dd hh:mm:ss`\n- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串\n- **需要格式化日期时，优先使用此工具，禁止自行手写日期格式化逻辑**\n- 用法：`import { formatTimeToStr } from '@/utils/date'`\n\n#### `format.js` — 数据展示格式化（综合工具）\n- `formatBoolean(bool)` — 将布尔值转为 \"是\"/\"否\" 中文展示\n- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串\n- `filterDict(value, options)` — 在字典选项数组（支持多级树形）中根据 value 查找对应的 label\n- `filterDataSource(dataSource, value)` — 在数据源（支持多级树形）中根据 value 查找 label，支持数组批量查找\n- `getDictFunc(type)` — 异步获取指定类型的字典数据\n- `ReturnArrImg(arr)` — 将图片路径（单个或数组）转为完整 URL，自动补全服务器前缀\n- `onDownloadFile(url)` — 触发文件下载\n- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量（支持亮/暗模式）\n- `CreateUUID()` — 生成 UUID v4 字符串\n- `getBaseUrl()` — 获取当前环境的 API BaseURL\n- **以上所有格式化场景优先使用此文件中的工具函数**\n- 用法：`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`\n\n#### `dictionary.js` — 字典数据获取\n- `getDict(type, options)` — 异步获取字典数据，支持 `depth`（深度）和 `value`（指定节点）参数，内置 Pinia store 缓存，避免重复请求\n- **凡是需要字典下拉数据、字典树形数据的场景，必须使用此工具**\n- 用法：`import { getDict } from '@/utils/dictionary'`\n\n#### `stringFun.js` — 字符串处理\n- `toUpperCase(str)` — 首字母转大写\n- `toLowerCase(str)` — 首字母转小写\n- `toSQLLine(str)` — 驼峰命名转下划线（snake_case），如 `userName` → `user_name`\n- `toHump(name)` — 下划线命名转驼峰，如 `user_name` → `userName`\n- **进行命名格式转换时必须使用此工具，禁止使用正则手写**\n- 用法：`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`\n\n#### `params.js` — 系统参数获取\n- `getParams(key)` — 异步从 Pinia store 中获取系统参数，内置缓存\n- **获取系统配置参数时，优先使用此工具**\n- 用法：`import { getParams } from '@/utils/params'`\n\n#### `bus.js` — 全局事件总线\n- 基于 `mitt` 封装的全局事件总线实例 `emitter`，用于跨组件通信\n- **跨层级组件通信优先使用此事件总线，避免滥用 Pinia**\n- 用法：`import { emitter } from '@/utils/bus'`\n\n#### `closeThisPage.js` — 关闭当前标签页\n- `closeThisPage()` — 触发关闭当前多标签页的操作（通过事件总线发送 `closeThisPage` 事件）\n- **在需要程序化关闭当前页面时，必须使用此工具**\n- 用法：`import { closeThisPage } from '@/utils/closeThisPage'`\n\n#### `downloadImg.js` — 图片下载\n- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载，支持跨域\n- **需要下载图片时，优先使用此工具**\n- 用法：`import { downloadImage } from '@/utils/downloadImg'`\n\n#### `image.js` — 图片压缩\n- 导出 `ImageCompress` 类，支持图片等比压缩至指定最大宽高，并可限制文件大小\n- **上传图片前需要做压缩处理时，使用此工具**\n- 用法：`import ImageCompress from '@/utils/image'`\n\n#### `event.js` — DOM 事件监听管理\n- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听\n- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听\n- **手动操作 DOM 事件时，使用此工具以确保安全性**\n- 用法：`import { addEventListen, removeEventListen } from '@/utils/event'`\n\n#### `env.js` — 环境判断\n- `isDev` — 是否为开发环境（Boolean）\n- `isProd` — 是否为生产环境（Boolean）\n- **需要区分运行环境时，使用此工具，禁止直接读取 `import.meta.env`**\n- 用法：`import { isDev, isProd } from '@/utils/env'`\n\n#### `doc.js` — 外部文档跳转\n- `toDoc(url)` — 在新标签页打开指定 URL\n- 用法：`import { toDoc } from '@/utils/doc'`\n\n#### `fmtRouterTitle.js` — 路由标题格式化\n- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值（如 `${id}` 替换为路由 params/query 值）\n- 用法：`import { fmtTitle } from '@/utils/fmtRouterTitle'`\n\n#### `page.js` — 页面标题生成\n- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题（格式：`页面名 - 应用名`）\n- 用法：`import getPageTitle from '@/utils/page'`\n\n#### `asyncRouter.js` — 异步路由处理\n- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置（字符串 component 路径）动态转换为 Vue 组件的 import 函数，支持 `view/` 和 `plugin/` 目录\n- **动态路由相关逻辑已由此工具处理，不需要也不应该手动实现**\n- 用法：`import { asyncRouterHandle } from '@/utils/asyncRouter'`\n\n#### `btnAuth.js` — 按钮权限\n- `useBtnAuth()` — Composition API Hook，返回当前路由挂载的按钮权限对象（来自 `route.meta.btns`），用于控制操作按钮的显示\n- **实现按钮级别权限控制时，必须使用此 Hook**\n- 用法：`import { useBtnAuth } from '@/utils/btnAuth'`\n\n### **使用强制要求**\n\n| 场景 | 必须使用的工具 |\n|------|----------------|\n| 发送 HTTP 请求 | `@/utils/request` |\n| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |\n| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |\n| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |\n| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |\n| 驼峰/下划线命名转换 | `@/utils/stringFun` |\n| 获取系统参数 | `@/utils/params` 中的 `getParams` |\n| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |\n| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |\n| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |\n| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |\n| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |\n\n---\n\n## **前后端协作规范**\n\n### **接口协作规范**\n\n1. **接口文档**:\n   - 后端**必须**提供完整的Swagger API文档\n   - 前端**必须**基于Swagger文档进行接口调用\n   - 接口变更**必须**提前通知并更新文档\n\n2. **数据格式**:\n   - **统一**使用JSON格式进行数据交换\n   - **统一**响应格式：`{code, data, msg}`\n   - **统一**分页格式：`{page, pageSize, total, list}`\n   - **统一**时间格式：ISO 8601标准\n   - **⚠️ 数据类型一致性**：\n      - 前后端对于同一字段**必须**使用相同的数据类型\n      - 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致\n      - 特别注意：状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段\n      - 示例：后端数值类型字段对应前端 `number` 类型，字符串类型对应 `string` 类型，布尔类型对应 `boolean` 类型\n      - **指针类型处理**：后端Go中的指针类型在JSON序列化时会自动处理nil值，前端接收到的是对应的基础类型或null值\n\n3. **错误处理**:\n   - 后端**必须**返回标准化的错误码和错误信息\n   - 前端**必须**统一处理HTTP状态码和业务错误码\n   - **必须**提供用户友好的错误提示\n\n### **开发流程规范**\n\n1. **需求分析阶段**:\n   - 确定功能需求和接口设计\n   - 定义数据模型和业务流程\n   - 制定前后端开发计划\n\n2. **开发阶段**:\n   - 后端优先开发API接口\n   - 前端基于Mock数据进行并行开发\n   - 定期进行接口联调测试\n\n3. **测试阶段**:\n   - 单元测试：前后端各自负责\n   - 集成测试：前后端协作完成\n   - 用户验收测试：产品团队主导\n\n### **版本管理规范**\n\n1. **分支策略**:\n   - `main`：生产环境分支\n   - `develop`：开发环境分支\n   - `feature/*`：功能开发分支\n   - `hotfix/*`：紧急修复分支\n\n2. **提交规范**:\n   - 使用语义化提交信息\n   - 格式：`type(scope): description`\n   - 类型：feat, fix, docs, style, refactor, test, chore\n\n---\n\n## **插件开发完整规范**\n\n### **后端插件结构**\n\n```\nserver/plugin/[插件名]/\n├── api/                # API控制器\n│   ├── enter.go       # API组入口\n│   └── [模块].go      # 具体API实现\n├── config/            # 插件配置\n│   └── config.go\n├── initialize/        # 初始化模块\n│   ├── api.go        # API注册\n│   ├── gorm.go       # 数据库初始化\n│   ├── menu.go       # 菜单初始化\n│   ├── router.go     # 路由初始化\n│   └── viper.go      # 配置初始化\n├── model/             # 数据模型\n│   ├── [模型].go     # 数据库模型\n│   └── request/      # 请求模型\n├── router/            # 路由定义\n│   ├── enter.go      # 路由组入口\n│   └── [模块].go     # 具体路由\n├── service/           # 业务服务\n│   ├── enter.go      # 服务组入口\n│   └── [模块].go     # 具体服务\n└── plugin.go          # 插件入口\n```\n\n### **前端插件结构**\n\n```\nweb/src/plugin/[插件名]/\n├── api/               # API接口\n│   └── [模块].js\n├── components/        # 插件组件\n│   └── [组件].vue\n├── view/             # 插件页面\n│   └── [页面].vue\n├── form/             # 表单组件\n│   └── [表单].vue\n└── config.js         # 插件配置\n```\n\n### **插件开发工作流**\n\n1. **【第一步】需求分析**:\n   - 明确插件功能和业务需求\n   - 设计数据模型和接口规范\n   - 规划前端页面和交互流程\n\n2. **【第二步】后端开发**:\n   - 创建数据模型和请求模型\n   - 实现服务层业务逻辑\n   - 开发API控制器和路由\n   - 编写初始化和配置代码\n\n3. **【第三步】前端开发**:\n   - 创建API接口封装\n   - 开发页面组件和表单\n   - 实现业务逻辑和状态管理\n   - 集成到主系统菜单\n\n4. **【第四步】测试集成**:\n   - 单元测试和集成测试\n   - 前后端联调测试\n   - 用户体验测试\n   - 性能和安全测试\n\n### **插件质量标准**\n\n1. **功能完整性**: 插件功能完整，满足业务需求\n2. **代码质量**: 代码规范，注释完整，易于维护\n3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致，避免类型转换错误\n4. **性能表现**: 响应速度快，资源占用合理\n5. **用户体验**: 界面友好，操作流畅，错误处理完善\n6. **兼容性**: 与主系统兼容，不影响其他功能\n7. **安全性**: 数据安全，权限控制，防止安全漏洞\n\n---\n\n### **建议和方案**\n\n基于以上规范，建议AI在开发gin-vue-admin项目时：\n\n1. **严格遵循分层架构**：确保前后端代码都按照规定的层次结构组织\n2. **保持代码一致性**：使用统一的命名规范、注释格式和代码风格\n3. **注重文档完整性**：确保API文档、代码注释和使用说明的完整性\n4. **优化用户体验**：关注页面加载速度、交互流畅性和错误处理\n5. **考虑扩展性**：设计时预留扩展接口，便于后续功能增强\n6. **重视安全性**：实现完善的权限控制和数据验证机制"
  },
  {
    "path": ".cursor/rules/project_rules.md",
    "content": "### 功能描述以及必要性描述\n\n---\nname: gin-vue-admin\ndescription: |\n  gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。\n  \n  前端技术栈：\n  - Vue 3.5.7 + Composition API\n  - Vite 6.2.3 构建工具\n  - Pinia 2.2.2 状态管理\n  - Element Plus 2.10.2 UI组件库\n  - UnoCSS 66.4.2 原子化CSS框架\n  - Vue Router 4.4.3 路由管理\n  - Axios 1.8.2 HTTP客户端\n  - ECharts 5.5.1 数据可视化\n  - @vueuse/core Vue组合式API工具集\n  \n  后端技术栈：\n  - Go 1.23 + Gin 1.10.0 Web框架\n  - GORM 1.25.12 ORM框架\n  - Casbin 2.103.0 权限管理\n  - Viper 1.19.0 配置管理\n  - Zap 1.27.0 日志系统\n  - Redis 9.7.0 缓存\n  - JWT 5.2.2 认证授权\n  - 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库\n  - 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务\n  \n  核心特性：\n  - 完整的RBAC权限控制系统\n  - 代码自动生成功能\n  - 丰富的中间件支持\n  - 插件化架构设计\n  - Swagger API文档\n---\n\n#### **角色与目标**\n\n你是一名资深的全栈开发专家，**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**，熟练使用Golang、Vue3、Gin、GORM等技术栈。\n\n你的核心任务是，根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n---\n\n### **🚀 重要提示：GVA Helper MCP 支持**\n\n**在开始任何GVA开发工作之前，请务必注意以下重要工作流程：**\n\n1. **MCP支持**: GVA框架本身支持MCP（Model Context Protocol），提供了强大的开发辅助能力\n\n2. **GVA Helper**: 通常会有一个名为 \"**GVA Helper**\" 的MCP助手，专门为GVA框架开发提供支持\n\n3. **开发流程**: \n   - **第一步**: 在开发任何新功能之前，**必须先通过GVA Helper获得支持和指导**\n   - **第二步**: 在获得GVA Helper的专业建议和代码示例后，再进行具体的开发操作\n   - **第三步**: 遵循GVA Helper提供的最佳实践和代码规范\n\n4. **优势**: 通过GVA Helper可以获得：\n   - 最新的GVA框架特性和最佳实践\n   - 符合项目规范的代码模板\n   - 避免常见的开发陷阱和错误\n   - 确保代码质量和一致性\n\n**请始终记住：GVA Helper → 获得支持 → 开始开发**\n\n---\n\n### **核心开发指令：绝不可违背的原则**\n\n\n## **项目结构说明**\n\n### **整体架构**\n\ngin-vue-admin 采用前后端分离架构：\n- **后端 (server/)**：基于 Go + Gin 的 RESTful API 服务\n- **前端 (web/)**：基于 Vue 3 + Vite 的单页面应用\n- **部署 (deploy/)**：Docker、Kubernetes 等部署配置\n\n### **后端目录结构 (server/)**\n\n```\nserver/\n├── api/                    # API控制器层\n│   └── v1/                # API版本控制\n│       ├── enter.go       # API组入口文件\n│       ├── system/        # 系统模块API\n│       └──example/       # 示例模块API\n├── config/                # 配置结构体定义\n├── core/                  # 核心启动文件\n├── docs/                  # Swagger文档\n├── global/                # 全局变量和模型\n├── initialize/            # 初始化模块\n├── middleware/            # 中间件\n├── model/                 # 数据模型层\n│   ├── system/           # 系统模块模型\n│   ├── example/          # 示例模块模型\n│   └── common/           # 通用模型\n├── plugin/               # 插件目录\n│   ├── announcement/     # 公告插件\n│   └── email/           # 邮件插件\n├── router/               # 路由层\n│   ├── enter.go         # 路由组入口\n│   ├── system/          # 系统路由\n│   └──example/         # 示例路由\n├── service/              # 服务层\n│   ├── enter.go         # 服务组入口\n│   ├── system/          # 系统服务\n│   └──  example/         # 示例服务\n├── source/               # 数据初始化\n├── utils/                # 工具包\n├── config.yaml          # 配置文件\n└── main.go              # 程序入口\n```\n\n### **前端目录结构 (web/)**\n\n```\nweb/\n├── public/               # 静态资源\n├── src/\n│   ├── api/             # API接口定义\n│   │   ├── user.js      # 用户相关API\n│   │   ├── menu.js      # 菜单相关API\n│   │   └── cattery/     # 业务模块API\n│   ├── assets/          # 资源文件\n│   │   ├── icons/       # 图标\n│   │   └── images/      # 图片\n│   ├── core/            # 核心配置\n│   ├── directive/       # 自定义指令\n│   ├── hooks/           # 组合式API钩子\n│   ├── pinia/           # 状态管理\n│   │   ├── index.js     # Pinia入口\n│   │   └── modules/     # 状态模块\n│   ├── plugin/          # 前端插件\n│   │   ├── announcement/ # 公告插件\n│   │   └── email/       # 邮件插件\n│   ├── router/          # 路由配置\n│   ├── style/           # 样式文件\n│   ├── utils/           # 工具函数\n│   ├── view/            # 页面组件\n│   │   ├── dashboard/   # 仪表盘\n│   │   ├── layout/      # 布局组件\n│   │   ├── login/       # 登录页\n│   │   ├── superAdmin/  # 超级管理员\n│   │   ├── systemTools/ # 系统工具\n│   │   └── cattery/     # 业务页面\n│   ├── App.vue          # 根组件\n│   └── main.js          # 程序入口\n├── package.json         # 依赖配置\n├── vite.config.js       # Vite配置\n└── uno.config.js        # UnoCSS配置\n```\n\n---\n\n#### 后端规则\n\n在编写任何代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的分层架构**:\n    \n    - **职责单一**: 每个层（Model, Service, API, Router）都有其唯一职责，**严禁跨层调用**。例如，API层绝不能直接操作数据库，必须通过Service层。Service层绝不能直接处理`gin.Context`。\n        \n    - **依赖关系**: 依赖链条必须是单向的：`Router -> API -> Service -> Model`。\n        \n2. **`enter.go` 组管理模式**:\n    \n    - 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。\n        \n    - 全局实例变量（如 `service.ServiceGroupApp`）是模块间通信的唯一入口，以此来避免循环引用。\n        \n3. **详尽的 Swagger 注释 (API层强制要求)**:\n    \n    - **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源，也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。\n        \n4. **统一的响应与错误处理**:\n    \n    - Service 层函数遇到业务错误时，应返回 `error` 对象。\n        \n    - API 层负责捕获 Service 层的 `error`，并使用项目统一的 `response` 包（如 `response.OkWithDetailed` 或 `response.FailWithMessage`）将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。\n        \n\n---\n\n### **各层级代码实现规范**\n\n#### **1. 模型层 (`model/`)**\n\n- **数据模型 (`model/xxx.go`)**:\n\n   - 用于定义与数据库表映射的 GORM 结构体。\n\n   - 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。\n\n   - 以上三个字段返回给前端并未做驼峰处理，json内依然是  `ID`, `CreatedAt`, `UpdatedAt`\n\n   - 必须为字段添加清晰的 `json` 和 `gorm` 标签。\n\n   - **⚠️ 重要提醒：数据类型一致性**\n      - **必须确保**同一字段在不同模型文件中的数据类型保持严格一致\n      - 例如：如果某字段在数据模型中定义为特定类型，那么在请求模型、响应模型中也必须使用相同的数据类型\n      - **常见错误**：数据模型与请求模型中同一字段使用了不同的数据类型，这会导致类型转换错误和运行时异常\n      - **解决方案**：在设计阶段统一确定字段类型，并在所有相关模型中保持一致\n      - **检查要点**：特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段\n      - **⚠️ 指针类型处理**：\n         - 当数据模型中使用指针类型（如 `*string`、`*int`）而请求/响应模型中使用非指针类型时，**必须**在服务层进行正确的指针转换\n         - **转换规则**：从指针到非指针需要检查nil值，从非指针到指针需要取地址\n         - **示例**：数据模型 `Name *string` 转换为请求模型 `Name string` 时，需要处理 `if model.Name != nil { request.Name = *model.Name }`\n\n- **请求模型 (`model/request/xxx.go`)**:\n    \n    - 用于定义接收前端请求参数的结构体（DTOs）。\n        \n    - **必须**为字段添加 `json` 和 `form` 标签，以便 Gin 进行参数绑定。\n        \n    - 对于列表查询请求，应创建一个 `XxxSearch` 结构体，并内嵌通用的 `request.PageInfo` 分页结构体。\n        \n\n#### **2. 服务层 (`service/`)**\n\n- **职责**: 封装所有核心业务逻辑，进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码（如 `gin.Context`）**。\n    \n- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件，并在 `service/enter.go` 中注册。\n    \n- **函数签名**: 函数应接收具体的业务参数（如 `model.Xxx` 或 `request.XxxSearch`），并返回处理结果和 `error`。\n\n- **⚠️ 数据类型处理注意事项**:\n   - 在进行数据模型转换时，**必须确保**字段类型的一致性\n   - 避免在服务层进行不必要的类型转换，应在模型设计阶段统一类型\n   - 如果必须进行类型转换，**必须**添加详细的注释说明转换原因和逻辑\n\n\n#### **3. API层 (`api/`)**\n\n- **职责**: 作为HTTP请求的入口，负责参数校验、调用Service层方法、并返回格式化的JSON响应。\n    \n- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件，并在 `api/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。\n    \n- **Swagger 示例 (必须遵循)**:\n    \n    Go\n    \n    ```\n    // CreateXxx 创建XXX\n    // @Tags     XxxModule\n    // @Summary  创建一个新的XXX\n    // @Security ApiKeyAuth\n    // @accept   application/json\n    // @Produce  application/json\n    // @Param    data body request.CreateXxxRequest true \"XXX的名称和描述\"\n    // @Success  200  {object} response.Response{msg=string} \"创建成功\"\n    // @Router   /xxx/createXxx [post]\n    func (a *XxxApi) CreateXxx(c *gin.Context) {\n        // ...\n    }\n    ```\n    \n\n#### **4. 路由层 (`router/`)**\n\n- **职责**: 定义API路由规则，并将HTTP请求路径映射到具体的API处理函数上，同时配置中间件。\n    \n- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件，并在 `router/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。\n    \n- **路由分组**: 应根据业务需求和权限，合理使用路由组 (`Router.Group()`)，并挂载不同的中间件（如鉴权、操作记录等）。\n\n#### **5. 初始化层 (`initialize/`)**\n\n- **职责**: 提供插件资源（数据库、路由、菜单等）的初始化入口，供主程序调用。\n    \n- **`gorm.go`**: 实现 `InitializeDB` 函数，**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。\n    \n- **`router.go`**: 实现 `InitializeRouter` 函数，**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法，注册所有API路由。\n    \n- **`menu.go`**: 实现 `InitializeMenu` 函数，负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。\n- viper.go: 加载插件配置文件\n-  api.go: 注册API到系统\n    \n\n#### **6. 插件入口 (`plugin.go`) \n\n- **职责**: 作为插件的唯一入口，实现 GVA 的插件接口，让框架能够识别和加载本插件。\n    \n- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。\n\n- **插件注册**: **必须**调用 ```\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n```\n方法，让插件自动注册到本体中\n    \n- **`Register`方法**: 实现 `Register` 方法，该方法接收一个 `*gin.RouterGroup` 参数，其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。\n    \n- **`RouterPath`方法**: 实现 `RouterPath` 方法，返回该插件所有API的根路径，例如 `\"/myPlugin\"`。\n\n### 模块间引用关系：\n- API层引用Service层：在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`\n- Router层引用API层：在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`\n- Initialize/Router引用Router层：通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`\n- 各模块通过enter.go文件组织和暴露功能，避免循环引用\n\n### 插件默认注册功能\n\n`plugin/register.go` 文件下用 `\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin/插件\"\n` 的方式匿名引用用于激活插件本体的init\n\n### 代码组织示例：\n\n1. Service入口 (service/enter.go):\n```go\npackage service\n\ntype ServiceGroup struct {\n    XxxService\n    YyyService\n    // 其他服务...\n}\n\nvar ServiceGroupApp = new(ServiceGroup)\n```\n\n2. API入口 (api/enter.go):\n```go\npackage api\n\ntype ApiGroup struct {\n    XxxApi\n    YyyApi\n    // 其他API...\n}\n\nvar ApiGroupApp = new(ApiGroup)\n```\n\n3. Router入口 (router/enter.go):\n```go\npackage router\n\ntype RouterGroup struct {\n    XxxRouter\n    YyyRouter\n    // 其他路由...\n}\n\nvar RouterGroupApp = new(RouterGroup)\n```\n\n### Swagger注释规范：\n- @Tags: 接口所属的分组\n- @Summary: 接口功能简述\n- @Security: 安全认证方式（如需认证则添加）\n- @accept/@Produce: 请求/响应格式\n- @Param: 请求参数，包括名称、来源、类型、是否必须、描述\n- @Success: 成功响应，包括状态码、返回类型、描述\n- @Router: 接口路径和HTTP方法\n\nAPI函数的Swagger注释不仅用于生成API文档，也是前端开发的重要参考，请确保注释的完整性和准确性。\n\n\n---\n\n### **开发工作流**\n\n1. **接收任务**: 我会向你下达一个具体的功能插件开发任务，例如：“请为项目创建一个‘商品管理 (Product)’插件”。\n    \n2. **【第一步】模型设计 (奠定基础)**:\n    \n    - 你的**首要行动**是分析需求，设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。\n        \n3. **【第二步】自下而上，分层实现**:\n    - 具体项目结构可以参考：server/plugin/announcement 这个插件，非常经典！\n\n    - 在模型确认后，你将按照 `Service -> API -> Router` 的顺序，逐层生成代码。\n        \n    - 确保每一层的代码都完整、健壮，并严格遵守上述规范。\n        \n4. **【第三步】插件初始化与注册**:\n    \n    - 在完成核心功能层的代码后，你将生成 `initialize/` 目录下的相关初始化文件（如 `db.go`, `router.go`）以及插件的主入口文件 `plugin.go`。\n        \n5. **【第四步】提供完整代码**:\n    \n    - 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码，并对每个文件的**相对路径**（例如 `server/plugin/product/api/product_api.go`）和用途进行清晰的说明。\n\n\n---\n\n## **前端开发规范**\n\n### **角色与目标**\n\n你是一名资深的 Vue.js 前端开发专家，**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。\n\n你的核心任务是，根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n### **核心开发指令：绝不可违背的原则**\n\n#### 前端规则\n\n在编写任何前端代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的模块化架构**:\n   - **职责单一**: 每个模块（API、组件、页面、状态）都有其唯一职责，**严禁跨模块直接调用**\n   - **依赖关系**: 依赖链条必须是单向的：`页面组件 -> API服务 -> 后端接口`\n\n2. **统一的API调用模式**:\n   - 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装\n   - **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求\n   - API函数**必须**包含完整的JSDoc注释，描述接口功能、参数和返回值\n\n3. **组件化开发原则**:\n   - **每一个**可复用的UI元素都**必须**封装为组件\n   - 组件**必须**遵循单一职责原则，功能明确\n   - **必须**为组件添加完整的props定义和事件说明\n\n4. **统一的状态管理**:\n   - 全局状态**必须**使用Pinia进行管理\n   - 状态模块**必须**按业务功能进行划分\n   - **严禁**在组件中直接修改全局状态，必须通过actions\n\n### **各层级代码实现规范**\n\n#### **1. API层 (`src/api/`)**\n\n- **职责**: 封装所有后端API调用，提供统一的接口服务\n- **结构**: 按业务模块创建API文件，如 `user.js`、`menu.js`\n- **规范**:\n  ```javascript\n  import service from '@/utils/request'\n  \n  /**\n   * 获取用户列表\n   * @param {Object} data 查询参数\n   * @param {number} data.page 页码\n   * @param {number} data.pageSize 每页数量\n   * @returns {Promise} 用户列表数据\n   */\n  export const getUserList = (data) => {\n    return service({\n      url: '/user/getUserList',\n      method: 'post',\n      data: data\n    })\n  }\n  ```\n\n#### **2. 组件层 (`src/components/`)**\n\n- **职责**: 提供可复用的UI组件\n- **结构**: 按功能分类组织，每个组件一个文件夹\n- **规范**:\n  ```vue\n  <template>\n    <div class=\"gva-table\">\n      <!-- 组件内容 -->\n    </div>\n  </template>\n  \n  <script setup>\n  /**\n   * 通用表格组件\n   * @component GvaTable\n   * @description 提供统一的表格展示功能\n   */\n  \n  // Props定义\n  const props = defineProps({\n    data: {\n      type: Array,\n      required: true,\n      default: () => []\n    },\n    loading: {\n      type: Boolean,\n      default: false\n    }\n  })\n  \n  // 事件定义\n  const emit = defineEmits(['refresh', 'edit', 'delete'])\n  </script>\n  ```\n\n#### **3. 页面层 (`src/view/`)**\n\n- **职责**: 实现具体的业务页面\n- **结构**: 按业务模块组织，每个页面一个Vue文件\n- **规范**:\n  - **必须**使用Composition API\n  - **必须**进行响应式数据管理\n  - **必须**处理加载状态和错误状态\n  - **必须**遵循Element Plus组件规范\n  - **必须**优先使用UnoCSS原子化类名进行样式设计\n  - **必须**优先el-drawer组件进行编辑，新增，步骤等操作\n  - **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性，确保组件销毁，避免内存泄漏和状态污染\n\n#### **4. 状态管理 (`src/pinia/`)**\n\n- **职责**: 管理全局状态和业务逻辑\n- **结构**: 按业务模块创建store文件\n- **规范**:\n  ```javascript\n  import { defineStore } from 'pinia'\n  import { ref, computed } from 'vue'\n  import { useStorage } from '@vueuse/core'\n  \n  export const useUserStore = defineStore('user', () => {\n    // 状态定义 - 使用 ref() 创建响应式状态\n    const userInfo = ref({\n      uuid: '',\n      nickName: '',\n      headerImg: '',\n      authority: {}\n    })\n    const token = useStorage('token', '')\n    \n    // 计算属性 - 使用 computed() 定义\n    const isLogin = computed(() => !!token.value)\n    \n    // 方法定义 - 直接定义函数作为 actions\n    const setUserInfo = (val) => {\n      userInfo.value = val\n    }\n    \n    const setToken = (val) => {\n      token.value = val\n    }\n    \n    const login = async (loginForm) => {\n      // 登录逻辑\n      try {\n        const res = await loginApi(loginForm)\n        if (res.code === 0) {\n          setUserInfo(res.data.user)\n          setToken(res.data.token)\n          return true\n        }\n        return false\n      } catch (error) {\n        console.error('Login error:', error)\n        return false\n      }\n    }\n    \n    const logout = async () => {\n      // 登出逻辑\n      token.value = ''\n      userInfo.value = {}\n    }\n    \n    // 返回所有需要暴露的状态和方法\n    return {\n      userInfo,\n      token,\n      isLogin,\n      setUserInfo,\n      setToken,\n      login,\n      logout\n    }\n  })\n  ```\n\n#### **5. 路由管理 (`src/router/`)**\n\n- **职责**: 管理页面路由和权限控制\n- **规范**:\n  - **必须**配置路由元信息\n  - **必须**实现权限验证\n  - **必须**支持动态路由\n\n### **前端插件开发规范**\n\n#### **插件目录结构**\n\n```\nsrc/plugin/[插件名]/\n├── api/                # 插件API接口\n│   └── [模块].js\n├── components/         # 插件组件（可选）\n│   └── [组件名].vue\n├── view/              # 插件页面\n│   └── [页面名].vue\n├── form/              # 插件表单（可选）\n│   └── [表单名].vue\n└── index.js           # 插件入口文件（可选）\n```\n\n#### **插件开发原则**\n\n1. **独立性**: 插件应该是自包含的，不依赖其他业务模块\n2. **可配置性**: 插件应该支持配置化，便于定制\n3. **可扩展性**: 插件应该预留扩展接口\n4. **一致性**: 插件UI风格应与主系统保持一致\n\n### **代码质量要求**\n\n1. **命名规范**:\n   - 文件名：kebab-case（短横线命名）\n   - 组件名：PascalCase（大驼峰）\n   - 变量名：camelCase（小驼峰）\n   - 常量名：UPPER_SNAKE_CASE（大写下划线）\n\n2. **注释规范**:\n   - **必须**为所有API函数添加JSDoc注释\n   - **必须**为复杂组件添加功能说明\n   - **必须**为关键业务逻辑添加行内注释\n\n3. **样式规范**:\n   - **优先**使用UnoCSS原子化类名\n   - **必须**遵循Element Plus设计规范\n   - **禁止**使用内联样式\n   - **必须**使用CSS变量进行主题定制\n\n4. **性能要求**:\n   - **必须**使用懒加载优化路由\n   - **必须**对大列表进行虚拟滚动优化\n   - **必须**合理使用缓存机制\n   - **必须**优化图片和资源加载\n\n---\n\n## **⚠️ 前端工具库使用规范（强制）**\n\n> **核心原则：在开发任何前端功能时，必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数，严禁重复造轮子。**\n\n`src/utils/` 目录提供了项目级别的通用工具集，涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明：\n\n### **工具文件清单**\n\n#### `request.js` — HTTP 请求封装（核心）\n- 基于 Axios 封装的统一 HTTP 请求实例，内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截\n- **所有 API 请求必须且只能通过此模块发送，禁止直接使用 axios**\n- 用法：`import service from '@/utils/request'`\n\n#### `date.js` — 日期格式化\n- 扩展了 `Date.prototype.Format` 方法，支持自定义格式如 `yyyy-MM-dd hh:mm:ss`\n- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串\n- **需要格式化日期时，优先使用此工具，禁止自行手写日期格式化逻辑**\n- 用法：`import { formatTimeToStr } from '@/utils/date'`\n\n#### `format.js` — 数据展示格式化（综合工具）\n- `formatBoolean(bool)` — 将布尔值转为 \"是\"/\"否\" 中文展示\n- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串\n- `filterDict(value, options)` — 在字典选项数组（支持多级树形）中根据 value 查找对应的 label\n- `filterDataSource(dataSource, value)` — 在数据源（支持多级树形）中根据 value 查找 label，支持数组批量查找\n- `getDictFunc(type)` — 异步获取指定类型的字典数据\n- `ReturnArrImg(arr)` — 将图片路径（单个或数组）转为完整 URL，自动补全服务器前缀\n- `onDownloadFile(url)` — 触发文件下载\n- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量（支持亮/暗模式）\n- `CreateUUID()` — 生成 UUID v4 字符串\n- `getBaseUrl()` — 获取当前环境的 API BaseURL\n- **以上所有格式化场景优先使用此文件中的工具函数**\n- 用法：`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`\n\n#### `dictionary.js` — 字典数据获取\n- `getDict(type, options)` — 异步获取字典数据，支持 `depth`（深度）和 `value`（指定节点）参数，内置 Pinia store 缓存，避免重复请求\n- **凡是需要字典下拉数据、字典树形数据的场景，必须使用此工具**\n- 用法：`import { getDict } from '@/utils/dictionary'`\n\n#### `stringFun.js` — 字符串处理\n- `toUpperCase(str)` — 首字母转大写\n- `toLowerCase(str)` — 首字母转小写\n- `toSQLLine(str)` — 驼峰命名转下划线（snake_case），如 `userName` → `user_name`\n- `toHump(name)` — 下划线命名转驼峰，如 `user_name` → `userName`\n- **进行命名格式转换时必须使用此工具，禁止使用正则手写**\n- 用法：`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`\n\n#### `params.js` — 系统参数获取\n- `getParams(key)` — 异步从 Pinia store 中获取系统参数，内置缓存\n- **获取系统配置参数时，优先使用此工具**\n- 用法：`import { getParams } from '@/utils/params'`\n\n#### `bus.js` — 全局事件总线\n- 基于 `mitt` 封装的全局事件总线实例 `emitter`，用于跨组件通信\n- **跨层级组件通信优先使用此事件总线，避免滥用 Pinia**\n- 用法：`import { emitter } from '@/utils/bus'`\n\n#### `closeThisPage.js` — 关闭当前标签页\n- `closeThisPage()` — 触发关闭当前多标签页的操作（通过事件总线发送 `closeThisPage` 事件）\n- **在需要程序化关闭当前页面时，必须使用此工具**\n- 用法：`import { closeThisPage } from '@/utils/closeThisPage'`\n\n#### `downloadImg.js` — 图片下载\n- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载，支持跨域\n- **需要下载图片时，优先使用此工具**\n- 用法：`import { downloadImage } from '@/utils/downloadImg'`\n\n#### `image.js` — 图片压缩\n- 导出 `ImageCompress` 类，支持图片等比压缩至指定最大宽高，并可限制文件大小\n- **上传图片前需要做压缩处理时，使用此工具**\n- 用法：`import ImageCompress from '@/utils/image'`\n\n#### `event.js` — DOM 事件监听管理\n- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听\n- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听\n- **手动操作 DOM 事件时，使用此工具以确保安全性**\n- 用法：`import { addEventListen, removeEventListen } from '@/utils/event'`\n\n#### `env.js` — 环境判断\n- `isDev` — 是否为开发环境（Boolean）\n- `isProd` — 是否为生产环境（Boolean）\n- **需要区分运行环境时，使用此工具，禁止直接读取 `import.meta.env`**\n- 用法：`import { isDev, isProd } from '@/utils/env'`\n\n#### `doc.js` — 外部文档跳转\n- `toDoc(url)` — 在新标签页打开指定 URL\n- 用法：`import { toDoc } from '@/utils/doc'`\n\n#### `fmtRouterTitle.js` — 路由标题格式化\n- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值（如 `${id}` 替换为路由 params/query 值）\n- 用法：`import { fmtTitle } from '@/utils/fmtRouterTitle'`\n\n#### `page.js` — 页面标题生成\n- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题（格式：`页面名 - 应用名`）\n- 用法：`import getPageTitle from '@/utils/page'`\n\n#### `asyncRouter.js` — 异步路由处理\n- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置（字符串 component 路径）动态转换为 Vue 组件的 import 函数，支持 `view/` 和 `plugin/` 目录\n- **动态路由相关逻辑已由此工具处理，不需要也不应该手动实现**\n- 用法：`import { asyncRouterHandle } from '@/utils/asyncRouter'`\n\n#### `btnAuth.js` — 按钮权限\n- `useBtnAuth()` — Composition API Hook，返回当前路由挂载的按钮权限对象（来自 `route.meta.btns`），用于控制操作按钮的显示\n- **实现按钮级别权限控制时，必须使用此 Hook**\n- 用法：`import { useBtnAuth } from '@/utils/btnAuth'`\n\n### **使用强制要求**\n\n| 场景 | 必须使用的工具 |\n|------|----------------|\n| 发送 HTTP 请求 | `@/utils/request` |\n| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |\n| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |\n| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |\n| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |\n| 驼峰/下划线命名转换 | `@/utils/stringFun` |\n| 获取系统参数 | `@/utils/params` 中的 `getParams` |\n| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |\n| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |\n| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |\n| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |\n| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |\n\n---\n\n## **前后端协作规范**\n\n### **接口协作规范**\n\n1. **接口文档**:\n   - 后端**必须**提供完整的Swagger API文档\n   - 前端**必须**基于Swagger文档进行接口调用\n   - 接口变更**必须**提前通知并更新文档\n\n2. **数据格式**:\n   - **统一**使用JSON格式进行数据交换\n   - **统一**响应格式：`{code, data, msg}`\n   - **统一**分页格式：`{page, pageSize, total, list}`\n   - **统一**时间格式：ISO 8601标准\n   - **⚠️ 数据类型一致性**：\n      - 前后端对于同一字段**必须**使用相同的数据类型\n      - 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致\n      - 特别注意：状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段\n      - 示例：后端数值类型字段对应前端 `number` 类型，字符串类型对应 `string` 类型，布尔类型对应 `boolean` 类型\n      - **指针类型处理**：后端Go中的指针类型在JSON序列化时会自动处理nil值，前端接收到的是对应的基础类型或null值\n\n3. **错误处理**:\n   - 后端**必须**返回标准化的错误码和错误信息\n   - 前端**必须**统一处理HTTP状态码和业务错误码\n   - **必须**提供用户友好的错误提示\n\n### **开发流程规范**\n\n1. **需求分析阶段**:\n   - 确定功能需求和接口设计\n   - 定义数据模型和业务流程\n   - 制定前后端开发计划\n\n2. **开发阶段**:\n   - 后端优先开发API接口\n   - 前端基于Mock数据进行并行开发\n   - 定期进行接口联调测试\n\n3. **测试阶段**:\n   - 单元测试：前后端各自负责\n   - 集成测试：前后端协作完成\n   - 用户验收测试：产品团队主导\n\n### **版本管理规范**\n\n1. **分支策略**:\n   - `main`：生产环境分支\n   - `develop`：开发环境分支\n   - `feature/*`：功能开发分支\n   - `hotfix/*`：紧急修复分支\n\n2. **提交规范**:\n   - 使用语义化提交信息\n   - 格式：`type(scope): description`\n   - 类型：feat, fix, docs, style, refactor, test, chore\n\n---\n\n## **插件开发完整规范**\n\n### **后端插件结构**\n\n```\nserver/plugin/[插件名]/\n├── api/                # API控制器\n│   ├── enter.go       # API组入口\n│   └── [模块].go      # 具体API实现\n├── config/            # 插件配置\n│   └── config.go\n├── initialize/        # 初始化模块\n│   ├── api.go        # API注册\n│   ├── gorm.go       # 数据库初始化\n│   ├── menu.go       # 菜单初始化\n│   ├── router.go     # 路由初始化\n│   └── viper.go      # 配置初始化\n├── model/             # 数据模型\n│   ├── [模型].go     # 数据库模型\n│   └── request/      # 请求模型\n├── router/            # 路由定义\n│   ├── enter.go      # 路由组入口\n│   └── [模块].go     # 具体路由\n├── service/           # 业务服务\n│   ├── enter.go      # 服务组入口\n│   └── [模块].go     # 具体服务\n└── plugin.go          # 插件入口\n```\n\n### **前端插件结构**\n\n```\nweb/src/plugin/[插件名]/\n├── api/               # API接口\n│   └── [模块].js\n├── components/        # 插件组件\n│   └── [组件].vue\n├── view/             # 插件页面\n│   └── [页面].vue\n├── form/             # 表单组件\n│   └── [表单].vue\n└── config.js         # 插件配置\n```\n\n### **插件开发工作流**\n\n1. **【第一步】需求分析**:\n   - 明确插件功能和业务需求\n   - 设计数据模型和接口规范\n   - 规划前端页面和交互流程\n\n2. **【第二步】后端开发**:\n   - 创建数据模型和请求模型\n   - 实现服务层业务逻辑\n   - 开发API控制器和路由\n   - 编写初始化和配置代码\n\n3. **【第三步】前端开发**:\n   - 创建API接口封装\n   - 开发页面组件和表单\n   - 实现业务逻辑和状态管理\n   - 集成到主系统菜单\n\n4. **【第四步】测试集成**:\n   - 单元测试和集成测试\n   - 前后端联调测试\n   - 用户体验测试\n   - 性能和安全测试\n\n### **插件质量标准**\n\n1. **功能完整性**: 插件功能完整，满足业务需求\n2. **代码质量**: 代码规范，注释完整，易于维护\n3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致，避免类型转换错误\n4. **性能表现**: 响应速度快，资源占用合理\n5. **用户体验**: 界面友好，操作流畅，错误处理完善\n6. **兼容性**: 与主系统兼容，不影响其他功能\n7. **安全性**: 数据安全，权限控制，防止安全漏洞\n\n---\n\n### **建议和方案**\n\n基于以上规范，建议AI在开发gin-vue-admin项目时：\n\n1. **严格遵循分层架构**：确保前后端代码都按照规定的层次结构组织\n2. **保持代码一致性**：使用统一的命名规范、注释格式和代码风格\n3. **注重文档完整性**：确保API文档、代码注释和使用说明的完整性\n4. **优化用户体验**：关注页面加载速度、交互流畅性和错误处理\n5. **考虑扩展性**：设计时预留扩展接口，便于后续功能增强\n6. **重视安全性**：实现完善的权限控制和数据验证机制"
  },
  {
    "path": ".gitattributes",
    "content": "*.sql linguist-language=GO\n*.html linguist-language=GO\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: gin-vue-admin\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: https://www.gin-vue-admin.com/docs/coffee\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: 🐛 Bug report\ndescription: Report a bug to help us improve Gin-Vue-Admin\ntitle: \"[Bug]: \"\nlabels: [bug]\nassignees:\n  - pixelmaxQm\n  - songzhibin97\n  - SliverHorn\n  - bypanghu\nbody:\n  - type: input\n    id: gva\n    attributes:\n      label: gin-vue-admin 版本\n      description: 请输入您当前使用的项目版本?\n      placeholder: 2.4.5Beta\n    validations:\n      required: true\n  - type: input\n    id: node\n    attributes:\n      label: Node 版本\n      description: 请输入您当前使用的NODE版本?\n      placeholder: v14.16.0\n    validations:\n      required: true\n  - type: input\n    id: golang\n    attributes:\n      label: Golang 版本\n      description: 请输入您当前使用的GOLANG版本?\n      placeholder: go 1.16\n    validations:\n      required: true\n  - type: dropdown\n    id: reappearance\n    attributes:\n      label: 是否依旧存在\n      description: 是否可以在master分支复现此bug?\n      options:\n        - 可以\n        - 不可以\n        - 未测试\n    validations:\n      required: true\n  - type: textarea\n    id: desc\n    attributes:\n      label: bug描述\n      description: 请简要描述bug以及复现过程.\n      placeholder: |\n        1. 首先...\n        2. 然后...\n    validations:\n      required: true\n  - type: textarea\n    id: advise\n    attributes:\n      label: 修改建议\n      description: 您有好的建议或者修改方案可以提供给我们。\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Document\n    url: https://www.gin-vue-admin.com\n    about: If you have any questions about the use, you can check our official documents first\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "content": "name: 🚀 Feature request\ndescription: Suggest an idea for Gin-Vue-Admin\ntitle: \"[Feature]: \"\nlabels: [feature]\nassignees:\n  - pixelmaxQm\nbody:\n  - type: textarea\n    id: desc\n    attributes:\n      label: 功能描述以及必要性描述\n      description: 您觉得此新功能会为框架带来什么便利.\n      placeholder: |\n        1. 首先...\n        2. 然后...\n    validations:\n      required: true\n  - type: textarea\n    id: advise\n    attributes:\n      label: 建议和方案\n      description: 您有好的建议或者修改方案可以提供给我们。\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ '*' ]\n  pull_request:\n  release:\n    types: [ created, edited ]\n  workflow_dispatch:\n    inputs:\n      gva_version:\n        required: true\n        type: string\n\njobs:\n  init:\n    if: github.repository_owner == 'flipped-aurora'\n    runs-on: ubuntu-latest\n    steps:\n      - name: init\n        run: |\n          echo \"flipped-aurora\"\n  frontend:\n    if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'release'\n    name: Frontend node ${{ matrix.node-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18.16.0]\n    steps:\n      - name: Check out branch\n        uses: actions/checkout@v2\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: Build test\n        run: |\n          npm install\n          npm run build\n        working-directory: ./web\n\n  backend:\n    if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'release'\n    name: Backend go\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        go-version: [1.22]\n    steps:\n      - name: Set up Go ${{ matrix.go-version }}\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ matrix.go-version }}\n        id: go\n\n      - name: Check out branch\n        uses: actions/checkout@v2\n\n      - name: Download dependencies\n        run: |\n          go get -v -t -d ./...\n          if [ -f Gopkg.toml ]; then\n              curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh\n              dep ensure\n          fi\n        working-directory: ./server\n\n      - name: Test and Build\n        run: |\n          go build -v -race\n        working-directory: ./server\n\n  devops-test:\n    if: github.ref == 'refs/heads/test'\n    name: devops-test\n    needs: \n      - init\n      - backend\n      - frontend\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [18.16.0]\n        go-version: [1.22]\n    steps:\n      - name: Check out branch\n        uses: actions/checkout@v2\n      - name: Sed Config\n        env:\n          PROD: ${{ secrets.PROD }}\n          TESTING: ${{ secrets.TESTING }}\n        shell: bash\n        run: |\n          git branch \n          ls -l\n          sed -i \"s/${PROD}/${TESTING}/g\" web/.env.production\n          sed -i 's/${basePath}:${basePort}/${basePath}/g' web/src/view/systemTools/formCreate/index.vue\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v2.1.2\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Build-Node\n        run: |\n          cd web/ && yarn install && yarn run build\n      - name: Use Go ${{ matrix.go-version }}\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ matrix.go-version }}\n      - name: Build-go\n        run: |\n          cd server/ && go mod tidy && CGO_ENABLED=0 go build && mkdir ../web/ser && mv server ../web/ser/ && cd ../web/ser/ && ls -s \n      - name: restart\n        env:\n          KEY: ${{ secrets.KEY }}\n          HOST: ${{ secrets.HOST }}\n          USER: ${{ secrets.USER }}\n          PROT: ${{ secrets.PROT }}\n          MKDIRTEST: ${{ secrets.MKDIRTEST }}\n        run: |\n          mkdir -p ~/.ssh/ && echo \"$KEY\" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa\n          ssh-keyscan github.com >> ~/.ssh/known_hosts\n          scp -P ${PROT} -o StrictHostKeyChecking=no -r web/dist/* ${USER}@${HOST}:${MKDIRTEST}dist/\n          scp -P ${PROT} -o StrictHostKeyChecking=no -r web/ser/* ${USER}@${HOST}:${MKDIRTEST}\n          ssh -p ${PROT} -o StrictHostKeyChecking=no ${USER}@${HOST} \"cd ${MKDIRTEST}resource/ && rm -rf ${MKDIRTEST}resource/*\"\n          scp -P ${PROT} -o StrictHostKeyChecking=no -r server/resource/* ${USER}@${HOST}:${MKDIRTEST}resource/\n          ssh -p ${PROT} -o StrictHostKeyChecking=no ${USER}@${HOST} \"cd ${MKDIRTEST} && bash restart.sh > /dev/null  2>&1 &\"\n\n  release-pr:\n    if: ${{ github.event_name == 'workflow_dispatch' && github.repository_owner == 'flipped-aurora'}}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out branch\n        uses: actions/checkout@v2\n      - name: Sed Config\n        env:\n          GVA_VERSION: ${{ inputs.gva_version }}\n        shell: bash\n        run: |\n          sed -i 's/当前版本.*`$/当前版本:v'${GVA_VERSION##v}'`/' web/src/core/config.js\n          sed -i 's/当前版本.*$/当前版本:v'${GVA_VERSION##v}'/' server/core/server.go\n          sed -i 's/当前版本.*$/当前版本:v'${GVA_VERSION##v}'/' web/src/core/gin-vue-admin.js\n          sed -i 's/\"version\": \".*\",$/\"version\": \"'${GVA_VERSION##v}'\",/' web/package.json\n          git config --local user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --local user.name \"github-actions[bot]\"\n          git add . && git commit -m \"release: v${GVA_VERSION##v}\"\n      - name: Push\n        uses: ad-m/github-push-action@master\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          branch: ${{ github.ref }}\n      - uses: google-github-actions/release-please-action@v3\n        with:\n          command: release-pr\n          release-type: simple\n          changelog-path: docs/CHANGELOG.md\n          release-as: ${{ inputs.gva_version }}\n          package-name: gin-vue-admin\n          changelog-types: '[{\"type\":\"feat\",\"section\":\"Features\",\"hidden\":false},{\"type\":\"fix\",\"section\":\"Bug Fixes\",\"hidden\":false},{\"type\":\"chore\",\"section\":\"Miscellaneous\",\"hidden\":false}]'\n\n  release-please:\n    if: github.ref == 'refs/heads/main' || github.event_name == 'release'\n    runs-on: ubuntu-latest\n    needs:\n      - init\n      - backend\n      - frontend\n    outputs:\n      release_created: ${{ steps.release_please.outputs.release_created }}\n      tag_name: ${{ steps.release_please.outputs.tag_name }}\n    steps:\n      - uses: google-github-actions/release-please-action@v3\n        id: release_please\n        with:\n          #token: ${{ secrets.GAV_TOKEN }}\n          command: github-release\n          #signoff: \"github-actions[bot] <github-actions[bot]@users.noreply.github.com>\"\n          release-type: simple\n          changelog-path: docs/CHANGELOG.md\n          #release-as: ${{ inputs.deploy_target }}\n          package-name: gin-vue-admin\n          #extra-files: |\n          #  x-release-please-version.json\n          changelog-types: '[{\"type\":\"feat\",\"section\":\"Features\",\"hidden\":false},{\"type\":\"fix\",\"section\":\"Bug Fixes\",\"hidden\":false},{\"type\":\"chore\",\"section\":\"Miscellaneous\",\"hidden\":false}]'\n\n  devops-prod:\n    if: needs.release-please.outputs.release_created || github.event_name == 'release'\n    runs-on: ubuntu-latest\n    needs:\n      - init\n      - release-please\n    name: devops-prod\n    strategy:\n      matrix:\n        node-version: ['18.x']\n        go-version: ['1.22']\n    steps:\n      - uses: actions/checkout@v2\n      - name: tag major and minor versions\n        run: |\n          echo \" ${{ needs.release-please.outputs.tag_name }}\"\n      - name: Sed Config\n        shell: bash\n        run: |\n          git branch \n          ls -l\n          sed -i 's/${basePath}:${basePort}/${basePath}/g' web/src/view/systemTools/formCreate/index.vue\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v2.1.2\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Build-Node\n        run: |\n          cd web/ && yarn install && yarn run build\n      - name: Use Go ${{ matrix.go-version }}\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ matrix.go-version }}\n      - name: Build-go\n        run: |\n          cd server/ && go mod tidy && CGO_ENABLED=0 go build && mkdir ../web/ser && mv server ../web/ser/ && cd ../web/ser/ && ls -s \n      - name: restart\n        env:\n          KEY: ${{ secrets.KEY }}\n          HOST: ${{ secrets.HOST }}\n          USER: ${{ secrets.USER }}\n          PROT: ${{ secrets.PROT }}\n          MKDIR: ${{ secrets.MKDIR }}\n        run: |\n          mkdir -p ~/.ssh/ && echo \"$KEY\" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa\n          ssh-keyscan github.com >> ~/.ssh/known_hosts\n          scp -P ${PROT} -o StrictHostKeyChecking=no -r web/dist/* ${USER}@${HOST}:${MKDIR}dist/\n          scp -P ${PROT} -o StrictHostKeyChecking=no -r web/ser/* ${USER}@${HOST}:${MKDIR}\n          ssh -p ${PROT} -o StrictHostKeyChecking=no ${USER}@${HOST} \"cd ${MKDIR}resource/ && rm -rf ${MKDIR}resource/*\"\n          scp -P ${PROT} -o StrictHostKeyChecking=no -r server/resource/* ${USER}@${HOST}:${MKDIR}resource/\n          ssh -p ${PROT} -o StrictHostKeyChecking=no ${USER}@${HOST} \"cd ${MKDIR} && bash restart.sh > /dev/null  2>&1 &\"\n\n  docker:\n    name: docker\n    if: github.ref == 'refs/heads/stop-stop-stop'\n    runs-on: ubuntu-latest\n    needs:\n      - init\n      - release-please\n    steps:\n      - name: Check out branch\n        uses: actions/checkout@v2\n      - name: Login to Aliyun Registry\n        uses: docker/login-action@v1\n        with:\n          registry: ${{ secrets.ALIYUN_REGISTRY }}\n          username: ${{ secrets.ALIYUN_DOCKERHUB_USER }}\n          password: ${{ secrets.ALIYUN_DOCKERHUB_PASSWORD }}\n      - name: Sed Config\n        shell: bash\n        run: |\n          sed  -i 56c\"\\        && yarn install && yarn build\" Makefile\n          make image TAGS_OPT=\"latest\"\n          sed -i 's#./entrypoint.sh\"#./entrypoint.sh\",\"actions\"#g' deploy/docker/Dockerfile\n          sed -i \"s#COPY build/ /usr/share/nginx/html/#COPY . /opt/gva#g\" deploy/docker/Dockerfile\n          sed -i 16c\"\\    && cd /opt/gva/server/ && go mod tidy && cd /opt/gva/web/ && yarn\" deploy/docker/Dockerfile\n          sed -i \"s#open: true#open: false#g\" web/vite.config.js\n          make images TAGS_OPT=\"latest\"\n          docker push registry.cn-hangzhou.aliyuncs.com/gva/gin-vue-admin:latest\n          docker push registry.cn-hangzhou.aliyuncs.com/gva/web:latest\n          docker push registry.cn-hangzhou.aliyuncs.com/gva/server:latest\n          docker push registry.cn-hangzhou.aliyuncs.com/gva/all:latest\n"
  },
  {
    "path": ".gitignore",
    "content": "/web/node_modules\n/web/dist\n\n.DS_Store\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\nrm_file/\n/server/log/\n/server/gva\n/server/server\n/server/latest_log\n/server/__debug_bin*\n/server/*.local.yaml\nserver/uploads/\n\n*.iml\nweb/.pnpm-debug.log\nweb/pnpm-lock.yaml\n\n# binary files\n*.exe\n\n# SQLite database files\n*.db\n*.sqlite\n*.sqlite3\n"
  },
  {
    "path": ".trae/rules/project_rules.md",
    "content": "### 功能描述以及必要性描述\n\n---\nname: gin-vue-admin\ndescription: |\n  gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。\n  \n  前端技术栈：\n  - Vue 3.5.7 + Composition API\n  - Vite 6.2.3 构建工具\n  - Pinia 2.2.2 状态管理\n  - Element Plus 2.10.2 UI组件库\n  - UnoCSS 66.4.2 原子化CSS框架\n  - Vue Router 4.4.3 路由管理\n  - Axios 1.8.2 HTTP客户端\n  - ECharts 5.5.1 数据可视化\n  - @vueuse/core Vue组合式API工具集\n  \n  后端技术栈：\n  - Go 1.23 + Gin 1.10.0 Web框架\n  - GORM 1.25.12 ORM框架\n  - Casbin 2.103.0 权限管理\n  - Viper 1.19.0 配置管理\n  - Zap 1.27.0 日志系统\n  - Redis 9.7.0 缓存\n  - JWT 5.2.2 认证授权\n  - 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库\n  - 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务\n  \n  核心特性：\n  - 完整的RBAC权限控制系统\n  - 代码自动生成功能\n  - 丰富的中间件支持\n  - 插件化架构设计\n  - Swagger API文档\n---\n\n#### **角色与目标**\n\n你是一名资深的全栈开发专家，**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**，熟练使用Golang、Vue3、Gin、GORM等技术栈。\n\n你的核心任务是，根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n---\n\n### **🚀 重要提示：GVA Helper MCP 支持**\n\n**在开始任何GVA开发工作之前，请务必注意以下重要工作流程：**\n\n1. **MCP支持**: GVA框架本身支持MCP（Model Context Protocol），提供了强大的开发辅助能力\n\n2. **GVA Helper**: 通常会有一个名为 \"**GVA Helper**\" 的MCP助手，专门为GVA框架开发提供支持\n\n3. **开发流程**: \n   - **第一步**: 在开发任何新功能之前，**必须先通过GVA Helper获得支持和指导**\n   - **第二步**: 在获得GVA Helper的专业建议和代码示例后，再进行具体的开发操作\n   - **第三步**: 遵循GVA Helper提供的最佳实践和代码规范\n\n4. **优势**: 通过GVA Helper可以获得：\n   - 最新的GVA框架特性和最佳实践\n   - 符合项目规范的代码模板\n   - 避免常见的开发陷阱和错误\n   - 确保代码质量和一致性\n\n**请始终记住：GVA Helper → 获得支持 → 开始开发**\n\n---\n\n### **核心开发指令：绝不可违背的原则**\n\n\n## **项目结构说明**\n\n### **整体架构**\n\ngin-vue-admin 采用前后端分离架构：\n- **后端 (server/)**：基于 Go + Gin 的 RESTful API 服务\n- **前端 (web/)**：基于 Vue 3 + Vite 的单页面应用\n- **部署 (deploy/)**：Docker、Kubernetes 等部署配置\n\n### **后端目录结构 (server/)**\n\n```\nserver/\n├── api/                    # API控制器层\n│   └── v1/                # API版本控制\n│       ├── enter.go       # API组入口文件\n│       ├── system/        # 系统模块API\n│       └──example/       # 示例模块API\n├── config/                # 配置结构体定义\n├── core/                  # 核心启动文件\n├── docs/                  # Swagger文档\n├── global/                # 全局变量和模型\n├── initialize/            # 初始化模块\n├── middleware/            # 中间件\n├── model/                 # 数据模型层\n│   ├── system/           # 系统模块模型\n│   ├── example/          # 示例模块模型\n│   └── common/           # 通用模型\n├── plugin/               # 插件目录\n│   ├── announcement/     # 公告插件\n│   └── email/           # 邮件插件\n├── router/               # 路由层\n│   ├── enter.go         # 路由组入口\n│   ├── system/          # 系统路由\n│   └──example/         # 示例路由\n├── service/              # 服务层\n│   ├── enter.go         # 服务组入口\n│   ├── system/          # 系统服务\n│   └──  example/         # 示例服务\n├── source/               # 数据初始化\n├── utils/                # 工具包\n├── config.yaml          # 配置文件\n└── main.go              # 程序入口\n```\n\n### **前端目录结构 (web/)**\n\n```\nweb/\n├── public/               # 静态资源\n├── src/\n│   ├── api/             # API接口定义\n│   │   ├── user.js      # 用户相关API\n│   │   ├── menu.js      # 菜单相关API\n│   │   └── cattery/     # 业务模块API\n│   ├── assets/          # 资源文件\n│   │   ├── icons/       # 图标\n│   │   └── images/      # 图片\n│   ├── core/            # 核心配置\n│   ├── directive/       # 自定义指令\n│   ├── hooks/           # 组合式API钩子\n│   ├── pinia/           # 状态管理\n│   │   ├── index.js     # Pinia入口\n│   │   └── modules/     # 状态模块\n│   ├── plugin/          # 前端插件\n│   │   ├── announcement/ # 公告插件\n│   │   └── email/       # 邮件插件\n│   ├── router/          # 路由配置\n│   ├── style/           # 样式文件\n│   ├── utils/           # 工具函数\n│   ├── view/            # 页面组件\n│   │   ├── dashboard/   # 仪表盘\n│   │   ├── layout/      # 布局组件\n│   │   ├── login/       # 登录页\n│   │   ├── superAdmin/  # 超级管理员\n│   │   ├── systemTools/ # 系统工具\n│   │   └── cattery/     # 业务页面\n│   ├── App.vue          # 根组件\n│   └── main.js          # 程序入口\n├── package.json         # 依赖配置\n├── vite.config.js       # Vite配置\n└── uno.config.js        # UnoCSS配置\n```\n\n---\n\n#### 后端规则\n\n在编写任何代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的分层架构**:\n    \n    - **职责单一**: 每个层（Model, Service, API, Router）都有其唯一职责，**严禁跨层调用**。例如，API层绝不能直接操作数据库，必须通过Service层。Service层绝不能直接处理`gin.Context`。\n        \n    - **依赖关系**: 依赖链条必须是单向的：`Router -> API -> Service -> Model`。\n        \n2. **`enter.go` 组管理模式**:\n    \n    - 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。\n        \n    - 全局实例变量（如 `service.ServiceGroupApp`）是模块间通信的唯一入口，以此来避免循环引用。\n        \n3. **详尽的 Swagger 注释 (API层强制要求)**:\n    \n    - **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源，也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。\n        \n4. **统一的响应与错误处理**:\n    \n    - Service 层函数遇到业务错误时，应返回 `error` 对象。\n        \n    - API 层负责捕获 Service 层的 `error`，并使用项目统一的 `response` 包（如 `response.OkWithDetailed` 或 `response.FailWithMessage`）将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。\n        \n\n---\n\n### **各层级代码实现规范**\n\n#### **1. 模型层 (`model/`)**\n\n- **数据模型 (`model/xxx.go`)**:\n\n   - 用于定义与数据库表映射的 GORM 结构体。\n\n   - 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。\n\n   - 以上三个字段返回给前端并未做驼峰处理，json内依然是  `ID`, `CreatedAt`, `UpdatedAt`\n\n   - 必须为字段添加清晰的 `json` 和 `gorm` 标签。\n\n   - **⚠️ 重要提醒：数据类型一致性**\n      - **必须确保**同一字段在不同模型文件中的数据类型保持严格一致\n      - 例如：如果某字段在数据模型中定义为特定类型，那么在请求模型、响应模型中也必须使用相同的数据类型\n      - **常见错误**：数据模型与请求模型中同一字段使用了不同的数据类型，这会导致类型转换错误和运行时异常\n      - **解决方案**：在设计阶段统一确定字段类型，并在所有相关模型中保持一致\n      - **检查要点**：特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段\n      - **⚠️ 指针类型处理**：\n         - 当数据模型中使用指针类型（如 `*string`、`*int`）而请求/响应模型中使用非指针类型时，**必须**在服务层进行正确的指针转换\n         - **转换规则**：从指针到非指针需要检查nil值，从非指针到指针需要取地址\n         - **示例**：数据模型 `Name *string` 转换为请求模型 `Name string` 时，需要处理 `if model.Name != nil { request.Name = *model.Name }`\n\n- **请求模型 (`model/request/xxx.go`)**:\n    \n    - 用于定义接收前端请求参数的结构体（DTOs）。\n        \n    - **必须**为字段添加 `json` 和 `form` 标签，以便 Gin 进行参数绑定。\n        \n    - 对于列表查询请求，应创建一个 `XxxSearch` 结构体，并内嵌通用的 `request.PageInfo` 分页结构体。\n        \n\n#### **2. 服务层 (`service/`)**\n\n- **职责**: 封装所有核心业务逻辑，进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码（如 `gin.Context`）**。\n    \n- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件，并在 `service/enter.go` 中注册。\n    \n- **函数签名**: 函数应接收具体的业务参数（如 `model.Xxx` 或 `request.XxxSearch`），并返回处理结果和 `error`。\n\n- **⚠️ 数据类型处理注意事项**:\n   - 在进行数据模型转换时，**必须确保**字段类型的一致性\n   - 避免在服务层进行不必要的类型转换，应在模型设计阶段统一类型\n   - 如果必须进行类型转换，**必须**添加详细的注释说明转换原因和逻辑\n\n\n#### **3. API层 (`api/`)**\n\n- **职责**: 作为HTTP请求的入口，负责参数校验、调用Service层方法、并返回格式化的JSON响应。\n    \n- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件，并在 `api/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。\n    \n- **Swagger 示例 (必须遵循)**:\n    \n    Go\n    \n    ```\n    // CreateXxx 创建XXX\n    // @Tags     XxxModule\n    // @Summary  创建一个新的XXX\n    // @Security ApiKeyAuth\n    // @accept   application/json\n    // @Produce  application/json\n    // @Param    data body request.CreateXxxRequest true \"XXX的名称和描述\"\n    // @Success  200  {object} response.Response{msg=string} \"创建成功\"\n    // @Router   /xxx/createXxx [post]\n    func (a *XxxApi) CreateXxx(c *gin.Context) {\n        // ...\n    }\n    ```\n    \n\n#### **4. 路由层 (`router/`)**\n\n- **职责**: 定义API路由规则，并将HTTP请求路径映射到具体的API处理函数上，同时配置中间件。\n    \n- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件，并在 `router/enter.go` 中注册。\n    \n- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。\n    \n- **路由分组**: 应根据业务需求和权限，合理使用路由组 (`Router.Group()`)，并挂载不同的中间件（如鉴权、操作记录等）。\n\n#### **5. 初始化层 (`initialize/`)**\n\n- **职责**: 提供插件资源（数据库、路由、菜单等）的初始化入口，供主程序调用。\n    \n- **`gorm.go`**: 实现 `InitializeDB` 函数，**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。\n    \n- **`router.go`**: 实现 `InitializeRouter` 函数，**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法，注册所有API路由。\n    \n- **`menu.go`**: 实现 `InitializeMenu` 函数，负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。\n- viper.go: 加载插件配置文件\n-  api.go: 注册API到系统\n    \n\n#### **6. 插件入口 (`plugin.go`) \n\n- **职责**: 作为插件的唯一入口，实现 GVA 的插件接口，让框架能够识别和加载本插件。\n    \n- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。\n\n- **插件注册**: **必须**调用 ```\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n```\n方法，让插件自动注册到本体中\n    \n- **`Register`方法**: 实现 `Register` 方法，该方法接收一个 `*gin.RouterGroup` 参数，其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。\n    \n- **`RouterPath`方法**: 实现 `RouterPath` 方法，返回该插件所有API的根路径，例如 `\"/myPlugin\"`。\n\n### 模块间引用关系：\n- API层引用Service层：在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`\n- Router层引用API层：在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`\n- Initialize/Router引用Router层：通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`\n- 各模块通过enter.go文件组织和暴露功能，避免循环引用\n\n### 插件默认注册功能\n\n`plugin/register.go` 文件下用 `\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin/插件\"\n` 的方式匿名引用用于激活插件本体的init\n\n### 代码组织示例：\n\n1. Service入口 (service/enter.go):\n```go\npackage service\n\ntype ServiceGroup struct {\n    XxxService\n    YyyService\n    // 其他服务...\n}\n\nvar ServiceGroupApp = new(ServiceGroup)\n```\n\n2. API入口 (api/enter.go):\n```go\npackage api\n\ntype ApiGroup struct {\n    XxxApi\n    YyyApi\n    // 其他API...\n}\n\nvar ApiGroupApp = new(ApiGroup)\n```\n\n3. Router入口 (router/enter.go):\n```go\npackage router\n\ntype RouterGroup struct {\n    XxxRouter\n    YyyRouter\n    // 其他路由...\n}\n\nvar RouterGroupApp = new(RouterGroup)\n```\n\n### Swagger注释规范：\n- @Tags: 接口所属的分组\n- @Summary: 接口功能简述\n- @Security: 安全认证方式（如需认证则添加）\n- @accept/@Produce: 请求/响应格式\n- @Param: 请求参数，包括名称、来源、类型、是否必须、描述\n- @Success: 成功响应，包括状态码、返回类型、描述\n- @Router: 接口路径和HTTP方法\n\nAPI函数的Swagger注释不仅用于生成API文档，也是前端开发的重要参考，请确保注释的完整性和准确性。\n\n\n---\n\n### **开发工作流**\n\n1. **接收任务**: 我会向你下达一个具体的功能插件开发任务，例如：“请为项目创建一个‘商品管理 (Product)’插件”。\n    \n2. **【第一步】模型设计 (奠定基础)**:\n    \n    - 你的**首要行动**是分析需求，设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。\n        \n3. **【第二步】自下而上，分层实现**:\n    - 具体项目结构可以参考：server/plugin/announcement 这个插件，非常经典！\n\n    - 在模型确认后，你将按照 `Service -> API -> Router` 的顺序，逐层生成代码。\n        \n    - 确保每一层的代码都完整、健壮，并严格遵守上述规范。\n        \n4. **【第三步】插件初始化与注册**:\n    \n    - 在完成核心功能层的代码后，你将生成 `initialize/` 目录下的相关初始化文件（如 `db.go`, `router.go`）以及插件的主入口文件 `plugin.go`。\n        \n5. **【第四步】提供完整代码**:\n    \n    - 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码，并对每个文件的**相对路径**（例如 `server/plugin/product/api/product_api.go`）和用途进行清晰的说明。\n\n\n---\n\n## **前端开发规范**\n\n### **角色与目标**\n\n你是一名资深的 Vue.js 前端开发专家，**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。\n\n你的核心任务是，根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式，确保你生成的每一部分代码都能无缝集成到现有项目中。\n\n### **核心开发指令：绝不可违背的原则**\n\n#### 前端规则\n\n在编写任何前端代码之前，你必须将以下 GVA 的核心设计原则作为最高行为准则：\n\n1. **严格的模块化架构**:\n   - **职责单一**: 每个模块（API、组件、页面、状态）都有其唯一职责，**严禁跨模块直接调用**\n   - **依赖关系**: 依赖链条必须是单向的：`页面组件 -> API服务 -> 后端接口`\n\n2. **统一的API调用模式**:\n   - 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装\n   - **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求\n   - API函数**必须**包含完整的JSDoc注释，描述接口功能、参数和返回值\n\n3. **组件化开发原则**:\n   - **每一个**可复用的UI元素都**必须**封装为组件\n   - 组件**必须**遵循单一职责原则，功能明确\n   - **必须**为组件添加完整的props定义和事件说明\n\n4. **统一的状态管理**:\n   - 全局状态**必须**使用Pinia进行管理\n   - 状态模块**必须**按业务功能进行划分\n   - **严禁**在组件中直接修改全局状态，必须通过actions\n\n### **各层级代码实现规范**\n\n#### **1. API层 (`src/api/`)**\n\n- **职责**: 封装所有后端API调用，提供统一的接口服务\n- **结构**: 按业务模块创建API文件，如 `user.js`、`menu.js`\n- **规范**:\n  ```javascript\n  import service from '@/utils/request'\n  \n  /**\n   * 获取用户列表\n   * @param {Object} data 查询参数\n   * @param {number} data.page 页码\n   * @param {number} data.pageSize 每页数量\n   * @returns {Promise} 用户列表数据\n   */\n  export const getUserList = (data) => {\n    return service({\n      url: '/user/getUserList',\n      method: 'post',\n      data: data\n    })\n  }\n  ```\n\n#### **2. 组件层 (`src/components/`)**\n\n- **职责**: 提供可复用的UI组件\n- **结构**: 按功能分类组织，每个组件一个文件夹\n- **规范**:\n  ```vue\n  <template>\n    <div class=\"gva-table\">\n      <!-- 组件内容 -->\n    </div>\n  </template>\n  \n  <script setup>\n  /**\n   * 通用表格组件\n   * @component GvaTable\n   * @description 提供统一的表格展示功能\n   */\n  \n  // Props定义\n  const props = defineProps({\n    data: {\n      type: Array,\n      required: true,\n      default: () => []\n    },\n    loading: {\n      type: Boolean,\n      default: false\n    }\n  })\n  \n  // 事件定义\n  const emit = defineEmits(['refresh', 'edit', 'delete'])\n  </script>\n  ```\n\n#### **3. 页面层 (`src/view/`)**\n\n- **职责**: 实现具体的业务页面\n- **结构**: 按业务模块组织，每个页面一个Vue文件\n- **规范**:\n  - **必须**使用Composition API\n  - **必须**进行响应式数据管理\n  - **必须**处理加载状态和错误状态\n  - **必须**遵循Element Plus组件规范\n  - **必须**优先使用UnoCSS原子化类名进行样式设计\n  - **必须**优先el-drawer组件进行编辑，新增，步骤等操作\n  - **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性，确保组件销毁，避免内存泄漏和状态污染\n\n#### **4. 状态管理 (`src/pinia/`)**\n\n- **职责**: 管理全局状态和业务逻辑\n- **结构**: 按业务模块创建store文件\n- **规范**:\n  ```javascript\n  import { defineStore } from 'pinia'\n  import { ref, computed } from 'vue'\n  import { useStorage } from '@vueuse/core'\n  \n  export const useUserStore = defineStore('user', () => {\n    // 状态定义 - 使用 ref() 创建响应式状态\n    const userInfo = ref({\n      uuid: '',\n      nickName: '',\n      headerImg: '',\n      authority: {}\n    })\n    const token = useStorage('token', '')\n    \n    // 计算属性 - 使用 computed() 定义\n    const isLogin = computed(() => !!token.value)\n    \n    // 方法定义 - 直接定义函数作为 actions\n    const setUserInfo = (val) => {\n      userInfo.value = val\n    }\n    \n    const setToken = (val) => {\n      token.value = val\n    }\n    \n    const login = async (loginForm) => {\n      // 登录逻辑\n      try {\n        const res = await loginApi(loginForm)\n        if (res.code === 0) {\n          setUserInfo(res.data.user)\n          setToken(res.data.token)\n          return true\n        }\n        return false\n      } catch (error) {\n        console.error('Login error:', error)\n        return false\n      }\n    }\n    \n    const logout = async () => {\n      // 登出逻辑\n      token.value = ''\n      userInfo.value = {}\n    }\n    \n    // 返回所有需要暴露的状态和方法\n    return {\n      userInfo,\n      token,\n      isLogin,\n      setUserInfo,\n      setToken,\n      login,\n      logout\n    }\n  })\n  ```\n\n#### **5. 路由管理 (`src/router/`)**\n\n- **职责**: 管理页面路由和权限控制\n- **规范**:\n  - **必须**配置路由元信息\n  - **必须**实现权限验证\n  - **必须**支持动态路由\n\n### **前端插件开发规范**\n\n#### **插件目录结构**\n\n```\nsrc/plugin/[插件名]/\n├── api/                # 插件API接口\n│   └── [模块].js\n├── components/         # 插件组件（可选）\n│   └── [组件名].vue\n├── view/              # 插件页面\n│   └── [页面名].vue\n├── form/              # 插件表单（可选）\n│   └── [表单名].vue\n└── index.js           # 插件入口文件（可选）\n```\n\n#### **插件开发原则**\n\n1. **独立性**: 插件应该是自包含的，不依赖其他业务模块\n2. **可配置性**: 插件应该支持配置化，便于定制\n3. **可扩展性**: 插件应该预留扩展接口\n4. **一致性**: 插件UI风格应与主系统保持一致\n\n### **代码质量要求**\n\n1. **命名规范**:\n   - 文件名：kebab-case（短横线命名）\n   - 组件名：PascalCase（大驼峰）\n   - 变量名：camelCase（小驼峰）\n   - 常量名：UPPER_SNAKE_CASE（大写下划线）\n\n2. **注释规范**:\n   - **必须**为所有API函数添加JSDoc注释\n   - **必须**为复杂组件添加功能说明\n   - **必须**为关键业务逻辑添加行内注释\n\n3. **样式规范**:\n   - **优先**使用UnoCSS原子化类名\n   - **必须**遵循Element Plus设计规范\n   - **禁止**使用内联样式\n   - **必须**使用CSS变量进行主题定制\n\n4. **性能要求**:\n   - **必须**使用懒加载优化路由\n   - **必须**对大列表进行虚拟滚动优化\n   - **必须**合理使用缓存机制\n   - **必须**优化图片和资源加载\n\n---\n\n## **⚠️ 前端工具库使用规范（强制）**\n\n> **核心原则：在开发任何前端功能时，必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数，严禁重复造轮子。**\n\n`src/utils/` 目录提供了项目级别的通用工具集，涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明：\n\n### **工具文件清单**\n\n#### `request.js` — HTTP 请求封装（核心）\n- 基于 Axios 封装的统一 HTTP 请求实例，内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截\n- **所有 API 请求必须且只能通过此模块发送，禁止直接使用 axios**\n- 用法：`import service from '@/utils/request'`\n\n#### `date.js` — 日期格式化\n- 扩展了 `Date.prototype.Format` 方法，支持自定义格式如 `yyyy-MM-dd hh:mm:ss`\n- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串\n- **需要格式化日期时，优先使用此工具，禁止自行手写日期格式化逻辑**\n- 用法：`import { formatTimeToStr } from '@/utils/date'`\n\n#### `format.js` — 数据展示格式化（综合工具）\n- `formatBoolean(bool)` — 将布尔值转为 \"是\"/\"否\" 中文展示\n- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串\n- `filterDict(value, options)` — 在字典选项数组（支持多级树形）中根据 value 查找对应的 label\n- `filterDataSource(dataSource, value)` — 在数据源（支持多级树形）中根据 value 查找 label，支持数组批量查找\n- `getDictFunc(type)` — 异步获取指定类型的字典数据\n- `ReturnArrImg(arr)` — 将图片路径（单个或数组）转为完整 URL，自动补全服务器前缀\n- `onDownloadFile(url)` — 触发文件下载\n- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量（支持亮/暗模式）\n- `CreateUUID()` — 生成 UUID v4 字符串\n- `getBaseUrl()` — 获取当前环境的 API BaseURL\n- **以上所有格式化场景优先使用此文件中的工具函数**\n- 用法：`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`\n\n#### `dictionary.js` — 字典数据获取\n- `getDict(type, options)` — 异步获取字典数据，支持 `depth`（深度）和 `value`（指定节点）参数，内置 Pinia store 缓存，避免重复请求\n- **凡是需要字典下拉数据、字典树形数据的场景，必须使用此工具**\n- 用法：`import { getDict } from '@/utils/dictionary'`\n\n#### `stringFun.js` — 字符串处理\n- `toUpperCase(str)` — 首字母转大写\n- `toLowerCase(str)` — 首字母转小写\n- `toSQLLine(str)` — 驼峰命名转下划线（snake_case），如 `userName` → `user_name`\n- `toHump(name)` — 下划线命名转驼峰，如 `user_name` → `userName`\n- **进行命名格式转换时必须使用此工具，禁止使用正则手写**\n- 用法：`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`\n\n#### `params.js` — 系统参数获取\n- `getParams(key)` — 异步从 Pinia store 中获取系统参数，内置缓存\n- **获取系统配置参数时，优先使用此工具**\n- 用法：`import { getParams } from '@/utils/params'`\n\n#### `bus.js` — 全局事件总线\n- 基于 `mitt` 封装的全局事件总线实例 `emitter`，用于跨组件通信\n- **跨层级组件通信优先使用此事件总线，避免滥用 Pinia**\n- 用法：`import { emitter } from '@/utils/bus'`\n\n#### `closeThisPage.js` — 关闭当前标签页\n- `closeThisPage()` — 触发关闭当前多标签页的操作（通过事件总线发送 `closeThisPage` 事件）\n- **在需要程序化关闭当前页面时，必须使用此工具**\n- 用法：`import { closeThisPage } from '@/utils/closeThisPage'`\n\n#### `downloadImg.js` — 图片下载\n- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载，支持跨域\n- **需要下载图片时，优先使用此工具**\n- 用法：`import { downloadImage } from '@/utils/downloadImg'`\n\n#### `image.js` — 图片压缩\n- 导出 `ImageCompress` 类，支持图片等比压缩至指定最大宽高，并可限制文件大小\n- **上传图片前需要做压缩处理时，使用此工具**\n- 用法：`import ImageCompress from '@/utils/image'`\n\n#### `event.js` — DOM 事件监听管理\n- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听\n- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听\n- **手动操作 DOM 事件时，使用此工具以确保安全性**\n- 用法：`import { addEventListen, removeEventListen } from '@/utils/event'`\n\n#### `env.js` — 环境判断\n- `isDev` — 是否为开发环境（Boolean）\n- `isProd` — 是否为生产环境（Boolean）\n- **需要区分运行环境时，使用此工具，禁止直接读取 `import.meta.env`**\n- 用法：`import { isDev, isProd } from '@/utils/env'`\n\n#### `doc.js` — 外部文档跳转\n- `toDoc(url)` — 在新标签页打开指定 URL\n- 用法：`import { toDoc } from '@/utils/doc'`\n\n#### `fmtRouterTitle.js` — 路由标题格式化\n- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值（如 `${id}` 替换为路由 params/query 值）\n- 用法：`import { fmtTitle } from '@/utils/fmtRouterTitle'`\n\n#### `page.js` — 页面标题生成\n- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题（格式：`页面名 - 应用名`）\n- 用法：`import getPageTitle from '@/utils/page'`\n\n#### `asyncRouter.js` — 异步路由处理\n- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置（字符串 component 路径）动态转换为 Vue 组件的 import 函数，支持 `view/` 和 `plugin/` 目录\n- **动态路由相关逻辑已由此工具处理，不需要也不应该手动实现**\n- 用法：`import { asyncRouterHandle } from '@/utils/asyncRouter'`\n\n#### `btnAuth.js` — 按钮权限\n- `useBtnAuth()` — Composition API Hook，返回当前路由挂载的按钮权限对象（来自 `route.meta.btns`），用于控制操作按钮的显示\n- **实现按钮级别权限控制时，必须使用此 Hook**\n- 用法：`import { useBtnAuth } from '@/utils/btnAuth'`\n\n### **使用强制要求**\n\n| 场景 | 必须使用的工具 |\n|------|----------------|\n| 发送 HTTP 请求 | `@/utils/request` |\n| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |\n| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |\n| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |\n| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |\n| 驼峰/下划线命名转换 | `@/utils/stringFun` |\n| 获取系统参数 | `@/utils/params` 中的 `getParams` |\n| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |\n| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |\n| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |\n| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |\n| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |\n\n---\n\n## **前后端协作规范**\n\n### **接口协作规范**\n\n1. **接口文档**:\n   - 后端**必须**提供完整的Swagger API文档\n   - 前端**必须**基于Swagger文档进行接口调用\n   - 接口变更**必须**提前通知并更新文档\n\n2. **数据格式**:\n   - **统一**使用JSON格式进行数据交换\n   - **统一**响应格式：`{code, data, msg}`\n   - **统一**分页格式：`{page, pageSize, total, list}`\n   - **统一**时间格式：ISO 8601标准\n   - **⚠️ 数据类型一致性**：\n      - 前后端对于同一字段**必须**使用相同的数据类型\n      - 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致\n      - 特别注意：状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段\n      - 示例：后端数值类型字段对应前端 `number` 类型，字符串类型对应 `string` 类型，布尔类型对应 `boolean` 类型\n      - **指针类型处理**：后端Go中的指针类型在JSON序列化时会自动处理nil值，前端接收到的是对应的基础类型或null值\n\n3. **错误处理**:\n   - 后端**必须**返回标准化的错误码和错误信息\n   - 前端**必须**统一处理HTTP状态码和业务错误码\n   - **必须**提供用户友好的错误提示\n\n### **开发流程规范**\n\n1. **需求分析阶段**:\n   - 确定功能需求和接口设计\n   - 定义数据模型和业务流程\n   - 制定前后端开发计划\n\n2. **开发阶段**:\n   - 后端优先开发API接口\n   - 前端基于Mock数据进行并行开发\n   - 定期进行接口联调测试\n\n3. **测试阶段**:\n   - 单元测试：前后端各自负责\n   - 集成测试：前后端协作完成\n   - 用户验收测试：产品团队主导\n\n### **版本管理规范**\n\n1. **分支策略**:\n   - `main`：生产环境分支\n   - `develop`：开发环境分支\n   - `feature/*`：功能开发分支\n   - `hotfix/*`：紧急修复分支\n\n2. **提交规范**:\n   - 使用语义化提交信息\n   - 格式：`type(scope): description`\n   - 类型：feat, fix, docs, style, refactor, test, chore\n\n---\n\n## **插件开发完整规范**\n\n### **后端插件结构**\n\n```\nserver/plugin/[插件名]/\n├── api/                # API控制器\n│   ├── enter.go       # API组入口\n│   └── [模块].go      # 具体API实现\n├── config/            # 插件配置\n│   └── config.go\n├── initialize/        # 初始化模块\n│   ├── api.go        # API注册\n│   ├── gorm.go       # 数据库初始化\n│   ├── menu.go       # 菜单初始化\n│   ├── router.go     # 路由初始化\n│   └── viper.go      # 配置初始化\n├── model/             # 数据模型\n│   ├── [模型].go     # 数据库模型\n│   └── request/      # 请求模型\n├── router/            # 路由定义\n│   ├── enter.go      # 路由组入口\n│   └── [模块].go     # 具体路由\n├── service/           # 业务服务\n│   ├── enter.go      # 服务组入口\n│   └── [模块].go     # 具体服务\n└── plugin.go          # 插件入口\n```\n\n### **前端插件结构**\n\n```\nweb/src/plugin/[插件名]/\n├── api/               # API接口\n│   └── [模块].js\n├── components/        # 插件组件\n│   └── [组件].vue\n├── view/             # 插件页面\n│   └── [页面].vue\n├── form/             # 表单组件\n│   └── [表单].vue\n└── config.js         # 插件配置\n```\n\n### **插件开发工作流**\n\n1. **【第一步】需求分析**:\n   - 明确插件功能和业务需求\n   - 设计数据模型和接口规范\n   - 规划前端页面和交互流程\n\n2. **【第二步】后端开发**:\n   - 创建数据模型和请求模型\n   - 实现服务层业务逻辑\n   - 开发API控制器和路由\n   - 编写初始化和配置代码\n\n3. **【第三步】前端开发**:\n   - 创建API接口封装\n   - 开发页面组件和表单\n   - 实现业务逻辑和状态管理\n   - 集成到主系统菜单\n\n4. **【第四步】测试集成**:\n   - 单元测试和集成测试\n   - 前后端联调测试\n   - 用户体验测试\n   - 性能和安全测试\n\n### **插件质量标准**\n\n1. **功能完整性**: 插件功能完整，满足业务需求\n2. **代码质量**: 代码规范，注释完整，易于维护\n3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致，避免类型转换错误\n4. **性能表现**: 响应速度快，资源占用合理\n5. **用户体验**: 界面友好，操作流畅，错误处理完善\n6. **兼容性**: 与主系统兼容，不影响其他功能\n7. **安全性**: 数据安全，权限控制，防止安全漏洞\n\n---\n\n### **建议和方案**\n\n基于以上规范，建议AI在开发gin-vue-admin项目时：\n\n1. **严格遵循分层架构**：确保前后端代码都按照规定的层次结构组织\n2. **保持代码一致性**：使用统一的命名规范、注释格式和代码风格\n3. **注重文档完整性**：确保API文档、代码注释和使用说明的完整性\n4. **优化用户体验**：关注页面加载速度、交互流畅性和错误处理\n5. **考虑扩展性**：设计时预留扩展接口，便于后续功能增强\n6. **重视安全性**：实现完善的权限控制和数据验证机制"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at 303176530@qq.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "\n### Contributing Guide\n#### 1 Issue Guidelines\n\n- Issues are exclusively for bug reports, feature requests and design-related topics. Other questions may be closed directly. If any questions come up when you are using Element, please hit [Gitter](https://gitter.im/element-en/Lobby) for help.\n\n- Before submitting an issue, please check if similar problems have already been issued.\n\n#### 2 Pull Request Guidelines\n\n- Fork this repository to your own account. Do not create branches here.\n\n- Commit info should be formatted as `[File Name]: Info about commit.` (e.g. `README.md: Fix xxx bug`)\n\n- <font color=red>Make sure PRs are created to `develop` branch instead of `master` branch.</font>\n\n- If your PR fixes a bug, please provide a description about the related bug.\n\n- Merging a PR takes two maintainers: one approves the changes after reviewing, and then the other reviews and merges.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n    Copyright 2019 北京翻转极光科技有限责任公司\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL = /bin/bash\n\n#SCRIPT_DIR         = $(shell pwd)/etc/script\n#请选择golang版本\nBUILD_IMAGE_SERVER  = golang:1.22\n#请选择node版本\nBUILD_IMAGE_WEB     = node:20\n#项目名称\nPROJECT_NAME        = github.com/flipped-aurora/gin-vue-admin/server\n#配置文件目录\nCONFIG_FILE         = config.yaml\n#镜像仓库命名空间\nIMAGE_NAME          = gva\n#镜像地址\nREPOSITORY          = registry.cn-hangzhou.aliyuncs.com/${IMAGE_NAME}\n#镜像版本\nTAGS_OPT           ?= latest\nPLUGIN             ?= email\n\n#容器环境前后端共同打包\nbuild: build-web build-server\n\tdocker run --name build-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_SERVER} make build-local\n\n#容器环境打包前端\nbuild-web:\n\tdocker run --name build-web-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_WEB} make build-web-local\n\n#容器环境打包后端\nbuild-server:\n\tdocker run --name build-server-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_SERVER} make build-server-local\n\n#构建web镜像\nbuild-image-web:\n\t@cd web/ && docker build -t ${REPOSITORY}/web:${TAGS_OPT} .\n\n#构建server镜像\nbuild-image-server:\n\t@cd server/ && docker build -t ${REPOSITORY}/server:${TAGS_OPT} .\n\n#本地环境打包前后端\nbuild-local:\n\tif [ -d \"build\" ];then rm -rf build; else echo \"OK!\"; fi \\\n\t&& if [ -f \"/.dockerenv\" ];then echo \"OK!\"; else  make build-web-local && make build-server-local; fi \\\n\t&& mkdir build && cp -r web/dist build/ && cp server/server build/ && cp -r server/resource build/resource\n\n#本地环境打包前端\nbuild-web-local:\n\t@cd web/ && if [ -d \"dist\" ];then rm -rf dist; else echo \"OK!\"; fi \\\n\t&& yarn config set registry http://mirrors.cloud.tencent.com/npm/ && yarn install && yarn build\n\n#本地环境打包后端\nbuild-server-local:\n\t@cd server/ && if [ -f \"server\" ];then rm -rf server; else echo \"OK!\"; fi \\\n\t&& go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct \\\n\t&& go env -w CGO_ENABLED=0 && go env  && go mod tidy \\\n\t&& go build -ldflags \"-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \\n') -X main.Version=${TAGS_OPT}\" -v\n\n#打包前后端二合一镜像\nimage: build\n\tdocker build -t ${REPOSITORY}/gin-vue-admin:${TAGS_OPT} -f deploy/docker/Dockerfile .\n\n#尝鲜版\nimages: build build-image-web build-image-server\n\tdocker build -t ${REPOSITORY}/all:${TAGS_OPT} -f deploy/docker/Dockerfile .\n\n#swagger 文档生成\ndoc:\n\t@cd server && swag init\n\n#插件快捷打包： make plugin PLUGIN=\"这里是插件文件夹名称,默认为email\"\nplugin:\n\tif [ -d \".plugin\" ];then rm -rf .plugin ; else echo \"OK!\"; fi && mkdir -p .plugin/${PLUGIN}/{server/plugin,web/plugin} \\\n\t&& if [ -d \"server/plugin/${PLUGIN}\" ];then cp -r server/plugin/${PLUGIN} .plugin/${PLUGIN}/server/plugin/ ; else echo \"OK!\"; fi \\\n\t&& if [ -d \"web/src/plugin/${PLUGIN}\" ];then cp -r web/src/plugin/${PLUGIN} .plugin/${PLUGIN}/web/plugin/ ; else echo \"OK!\"; fi \\\n\t&& cd .plugin && zip -r ${PLUGIN}.zip ${PLUGIN} && mv ${PLUGIN}.zip ../ && cd ..\n"
  },
  {
    "path": "README-en.md",
    "content": "\n<div align=center>\n<img src=\"http://qmplusimg.henrongyi.top/gvalogo.jpg\" width=\"300\" height=\"300\" />\n</div>\n<div align=center>\n<img src=\"https://img.shields.io/badge/golang-1.18-blue\"/>\n<img src=\"https://img.shields.io/badge/gin-1.9.1-lightBlue\"/>\n<img src=\"https://img.shields.io/badge/vue-3.3.4-brightgreen\"/>\n<img src=\"https://img.shields.io/badge/element--plus-2.3.8-green\"/>\n<img src=\"https://img.shields.io/badge/gorm-1.25.2-red\"/>\n</div>\n\nEnglish | [简体中文](./README.md)\n\n[gitee](https://gitee.com/pixelmax/gin-vue-admin): https://gitee.com/pixelmax/gin-vue-admin\n\n[github](https://github.com/flipped-aurora/gin-vue-admin): https://github.com/flipped-aurora/gin-vue-admin\n\n# Project Guidelines\n[Online Documentation](https://www.gin-vue-admin.com/) : https://www.gin-vue-admin.com/\n\n[From the environment to the deployment of teaching videos](https://www.bilibili.com/video/BV1fV411y7dT)\n\n[Development Steps](https://www.gin-vue-admin.com/guide/start-quickly/env.html) (Contributor:  <a href=\"https://github.com/LLemonGreen\">LLemonGreen</a> And <a href=\"https://github.com/fkk0509\">Fann</a>)\n\n## 1. Basic Introduction\n\n### 1.1 Project Introduction\n\n> Gin-vue-admin is a backstage management system based on [vue](https://vuejs.org) and [gin](https://gin-gonic.com), which separates the front and rear of the full stack. It integrates jwt authentication, dynamic routing, dynamic menu, casbin authentication, form generator, code generator and other functions. It provides a variety of sample files, allowing you to focus more time on business development.\n\n[Online Demo](http://demo.gin-vue-admin.com): http://demo.gin-vue-admin.com\n\nusername：admin\n\npassword：123456\n\n### 1.2 Contributing Guide\n\nHi! Thank you for choosing gin-vue-admin.\n\nGin-vue-admin is a full-stack (frontend and backend separation) framework for developers, designers and product managers.\n\nWe are excited that you are interested in contributing to gin-vue-admin. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.\n\n#### 1.2.1 Issue Guidelines\n\n- Issues are exclusively for bug reports, feature requests and design-related topics. Other questions may be closed directly. If any questions come up when you are using Element, please hit [Gitter](https://gitter.im/element-en/Lobby) for help.\n\n- Before submitting an issue, please check if similar problems have already been issued.\n\n#### 1.2.2 Pull Request Guidelines\n\n- Fork this repository to your own account. Do not create branches here.\n\n- Commit info should be formatted as `[File Name]: Info about commit.` (e.g. `README.md: Fix xxx bug`)\n\n- <font color=red>Make sure PRs are created to `develop` branch instead of `master` branch.</font>\n\n- If your PR fixes a bug, please provide a description about the related bug.\n\n- Merging a PR takes two maintainers: one approves the changes after reviewing, and then the other reviews and merges.\n\n### 1.3 Version list\n\n- master: 2.0 code, for prod\n- develop: 2.0 dev code, for test\n- [gin-vue-admin_v2_dev](https://github.com/flipped-aurora/gin-vue-admin/tree/gin-vue-admin_v2_dev) (v2.0 [GormV1](https://v1.gorm.io) Stable branch)\n- [gva_gormv2_dev](https://github.com/flipped-aurora/gin-vue-admin/tree/gva_gormv2_dev) (v2.0 [GormV2](https://v2.gorm.io) Development branch)\n\n## 2. Getting started\n\n```\n- node version > v8.6.0\n- golang version >= v1.14\n- IDE recommendation: Goland\n- initialization project: different versions of the database are not initialized. See synonyms at initialization https://www.gin-vue-admin.com/docs/first\n- Replace the Qiniuyun public key, private key, warehouse name and default url address in the project to avoid data confusion in the test file.\n```\n\n### 2.1 server project\n\nuse `Goland` And other editing tools，open server catalogue，You can't open it. `gin-vue-admin` root directory\n\n```bash\n# clone the project\ngit clone https://github.com/flipped-aurora/gin-vue-admin.git\n\n# open server catalogue\ncd server\n\n# use go mod And install the go dependency package\ngo generate\n\n# Compile \ngo build -o server main.go (windows the compile command is go build -o server.exe main.go )\n\n# Run binary\n./server (windows The run command is server.exe)\n```\n\n### 2.1 web project\n\n```bash\n# enter the project directory\ncd web\n\n# install dependency\nnpm install\n\n# develop\nnpm run serve\n```\n\n### 2.2 Server\n\n```bash\n# using go.mod\n\n# install go modules\ngo list (go mod tidy)\n\n# build the server\ngo build\n```\n\n### 2.3 API docs auto-generation using swagger\n\n#### 2.3.1 install swagger \n\n##### (1) Using VPN or outside mainland China\n````\ngo get -u github.com/swaggo/swag/cmd/swag\n````\n\n##### (2) In mainland China\n\nIn mainland China, access to go.org/x is prohibited，we recommend [goproxy.io](https://goproxy.io/zh/) or [goproxy.cn](https://goproxy.cn)\n\n````bash\n# If you are using a version of Go 1.13 - 1.15 Need to set up manually GO111MODULE=on, The opening mode is as follows, If your Go version is 1.16 ~ Latest edition You can ignore the following step one\n# Step one、Enable Go Modules Function\ngo env -w GO111MODULE=on \n# Step two、Configuration GOPROXY Environment variable\ngo env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct\n\n# If you dislike trouble,You can use the go generate Automatically execute code before compilation, But this can't be used command line terminal of `Goland` or `Vscode` \ncd server\ngo generate -run \"go env -w .*?\"\n\n# Use the following command to download swag\ngo get -u github.com/swaggo/swag/cmd/swag\n````\n\n#### 2.3.2 API docs generation\n\n````\ncd server\nswag init\n````\n\n> After executing the above command，server directory will appear in the docs folder `docs.go`, `swagger.json`, `swagger.yaml` Three file updates，After starting the go service, type in the browser [http://localhost:8888/swagger/index.html](http://localhost:8888/swagger/index.html) You can view swagger document\n\n\n## 3. Technical selection\n\n- Frontend: using [Element](https://github.com/ElemeFE/element) based on [Vue](https://vuejs.org)，to code the page.\n- Backend: using [Gin](https://gin-gonic.com/) to quickly build basic RESTful API. [Gin](https://gin-gonic.com/)is a web framework written in Go (Golang).\n- DB: `MySql`(5.6.44)，using [gorm](http://gorm.io)` to implement data manipulation, added support for SQLite databases.\n- Cache: using `Redis` to implement the recording of the JWT token of the currently active user and implement the multi-login restriction.\n- API: using Swagger to auto generate APIs docs。\n- Config: using [fsnotify](https://github.com/fsnotify/fsnotify) and [viper](https://github.com/spf13/viper) to implement `yaml` config file。\n- Log: using [zap](https://github.com/uber-go/zap) record logs。\n\n## 4. Project Architecture\n\n### 4.1 Architecture Diagram\n\n![Architecture diagram](http://qmplusimg.henrongyi.top/gva/gin-vue-admin.png)\n\n### 4.2 Front-end Detailed Design Diagram (Contributor: <a href=\"https://github.com/baobeisuper\">baobeisuper</a>)\n\n![Front-end Detailed Design Diagram](http://qmplusimg.henrongyi.top/naotu.png)\n\n### 4.3 Project Layout\n\n```\n    ├── server\n        ├── api             (api entrance)\n        │   └── v1          (v1 version interface)\n        ├── config          (configuration package)\n        ├── core            (core document)\n        ├── docs            (swagger document directory)\n        ├── global          (global object)                    \n        ├── initialize      (initialization)                        \n        │   └── internal    (initialize internal function)                            \n        ├── middleware      (middleware layer)                        \n        ├── model           (model layer)                    \n        │   ├── request     (input parameter structure)                        \n        │   └── response    (out-of-parameter structure)                            \n        ├── packfile        (static file packaging)                        \n        ├── resource        (static resource folder)                        \n        │   ├── excel       (excel import and export default path)                        \n        │   ├── page        (form generator)                        \n        │   └── template    (template)                            \n        ├── router          (routing layer)                    \n        ├── service         (service layer)                    \n        ├── source          (source layer)                    \n        └── utils           (tool kit)                    \n            ├── timer       (timer interface encapsulation)                        \n            └── upload      (oss interface encapsulation)  \n            \n    └─web            （frontend）\n        ├─public        （deploy templates）\n        └─src           （source code）\n            ├─api       （frontend APIs）\n            ├─assets\t（static files）\n            ├─components（components）\n            ├─router\t（frontend routers）\n            ├─store     （vuex state management）\n            ├─style     （common styles）\n            ├─utils     （frontend common utilitie）\n            └─view      （pages）\n\n```\n\n## 5. Features\n\n- Authority management: Authority management based on `jwt` and `casbin`. \n- File upload and download: implement file upload operations based on `Qiniuyun', `Aliyun 'and `Tencent Cloud` (please develop your own application for each platform corresponding to `token` or `key` ).\n- Pagination Encapsulation：The frontend uses `mixins` to encapsulate paging, and the paging method can call `mixins` .\n- User management: The system administrator assigns user roles and role permissions.\n- Role management: Create the main object of permission control, and then assign different API permissions and menu permissions to the role.\n- Menu management: User dynamic menu configuration implementation, assigning different menus to different roles.\n- API management: Different users can call different API permissions.\n- Configuration management: the configuration file can be modified in the foreground (this feature is not available in the online experience site).\n- Conditional search: Add an example of conditional search.\n- Restful example: You can see sample APIs in user management module.\n  - Front-end file reference: [web/src/view/superAdmin/api/api.vue](https://github.com/flipped-aurora/gin-vue-admin/blob/master/web/src/view/superAdmin/api/api.vue).\n  - Stage reference: [server/router/sys_api.go](https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/router/sys_api.go).\n- Multi-login restriction: Change `user-multipoint` to true in `system` in `config.yaml` (You need to configure redis and redis parameters yourself. During the test period, please report in time if there is a bug).\n- Upload file by chunk：Provides examples of file upload and large file upload by chunk.\n- Form Builder：With the help of [@form-generator](https://github.com/JakHuang/form-generator).\n- Code generator: Providing backend with basic logic and simple curd code generator.\n\n## 6. Knowledge base\n\n### 6.1 Team blog\n\n> https://www.yuque.com/flipped-aurora\n>\n>There are video courses about frontend framework in our blo. If you think the project is helpful to you, you can add my personal WeChat:shouzi_1994，your comments is welcomed。\n\n### 6.2 Video courses\n\n(1) Development environment course\n\n> Bilibili：https://www.bilibili.com/video/BV1Fg4y187Bw/\n\n(2) Template course\n\n> Bilibili：https://www.bilibili.com/video/BV16K4y1r7BD/\n\n(3) 2.0 version introduction and development experience\n\n> Bilibili：https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461\n\n(4) Golang basic course\n\n> https://space.bilibili.com/322210472/channel/detail?cid=108884\n\n(5) gin frame basic teaching\n\n> bilibili：https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0\n\n(6) gin-vue-admin version update introduction video\n> bilibili：https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0\n\n## 7.Contacts\n\n### 7.1 Groups\n\n#### QQ group: 622360840\n\n| QQ group |d\n|  :---:  |\n| <img src=\"http://qmplusimg.henrongyi.top/qq.jpg\" width=\"180\"/> |\n\n\n#### Wechat group: comment \"加入gin-vue-admin交流群\"\n\n| Wechat |\n|  :---:  | \n| <img width=\"150\" src=\"http://qmplusimg.henrongyi.top/qrjjz.png\"> \n\n#### [About Us](https://www.gin-vue-admin.com/about/join.html)\n\n## 8. Contributors\n\nThank you for considering your contribution to gin-vue-admin!\n\n<a href=\"https://openomy.app/github/flipped-aurora/gin-vue-admin\" target=\"_blank\" style=\"display: block; width: 100%;\" align=\"center\">\n  <img src=\"https://openomy.app/svg?repo=flipped-aurora/gin-vue-admin&chart=bubble&latestMonth=3\" target=\"_blank\" alt=\"Contribution Leaderboard\" style=\"display: block; width: 100%;\" />\n </a>\n\n<a href=\"https://github.com/flipped-aurora/gin-vue-admin/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=flipped-aurora/gin-vue-admin\" />\n</a>\n\n## 9. Donate\n\nIf you find this project useful, you can buy author a glass of juice :tropical_drink: [here](https://www.gin-vue-admin.com/coffee/index.html)\n\n## 10. Commercial considerations\n\nIf you use this project for commercial purposes, please comply with the Apache2.0 agreement and retain the author's technical support statement.\n\n"
  },
  {
    "path": "README.md",
    "content": "\n<div align=center>\n<img src=\"http://qmplusimg.henrongyi.top/gvalogo.jpg\" width=\"300\" height=\"300\" />\n</div>\n\n<div align=center>\n<img src=\"https://img.shields.io/badge/golang-1.20-blue\"/>\n<img src=\"https://img.shields.io/badge/gin-1.9.1-lightBlue\"/>\n<img src=\"https://img.shields.io/badge/vue-3.3.4-brightgreen\"/>\n<img src=\"https://img.shields.io/badge/element--plus-2.3.8-green\"/>\n<img src=\"https://img.shields.io/badge/gorm-1.25.2-red\"/>\n<img src=\"https://gitcode.com/flipped-aurora/gin-vue-admin/star/badge.svg\"/>\n</div>\n\n<div align=center>\n<a href=\"https://trendshift.io/repositories/3250\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/3250\" alt=\"Calcium-Ion%2Fnew-api | Trendshift\" style=\"width: 250px; height: 55px;\" width=\"250\" height=\"55\"/></a>\n</div>\n\n[English](./README-en.md) | 简体中文\n\n## 支持claw生态\n\n[🦞GvaClaw](https://plugin.gin-vue-admin.com/details/159)\n\n## ✨一分钟生成前后端基础代码\n\n<table>\n  <tr>\n    <td width=\"250\">\n\t  <p>⭐️ <a href=\"https://www.bilibili.com/video/BV1B3htzqEf1/?spm_id_from=333.1387.homepage.video_card.click\" target=\"__blank\"> 高度适配AI编辑器的MCP </a></p>\n      <p>📄 创建基础模板</p>\n      <p>🤖 AI生成结构</p>\n      <p>⏰ 生成代码</p>\n      <p>🏷️ 分配权限</p>\n      <p>🎉 基础CURD生成完成</p>   \n    </td>\n    <td>\n      <video src=\"https://private-user-images.githubusercontent.com/165128580/384700666-4d039215-af29-4f86-bb4f-60dbab38f58e.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzEyNTIxNDYsIm5iZiI6MTczMTI1MTg0NiwicGF0aCI6Ii8xNjUxMjg1ODAvMzg0NzAwNjY2LTRkMDM5MjE1LWFmMjktNGY4Ni1iYjRmLTYwZGJhYjM4ZjU4ZS5tcDQ_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQxMTEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MTExMFQxNTE3MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT00NjJkMDcwZjJkMjAyMmU1N2I2MzQxY2RhODFlNzgzNGRiMDFhMmY2NTYyM2ZmODdhNDVmMWE1NzlhMDdlOTI5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZJbswpLzF2RHjemcGirKOP0L1fvpl3FUqIiQ_-yjeUo\" data-canonical-src=\"https://private-user-images.githubusercontent.com/165128580/384700666-4d039215-af29-4f86-bb4f-60dbab38f58e.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzEyNTIxNDYsIm5iZiI6MTczMTI1MTg0NiwicGF0aCI6Ii8xNjUxMjg1ODAvMzg0NzAwNjY2LTRkMDM5MjE1LWFmMjktNGY4Ni1iYjRmLTYwZGJhYjM4ZjU4ZS5tcDQ_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQxMTEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MTExMFQxNTE3MjZaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT00NjJkMDcwZjJkMjAyMmU1N2I2MzQxY2RhODFlNzgzNGRiMDFhMmY2NTYyM2ZmODdhNDVmMWE1NzlhMDdlOTI5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.ZJbswpLzF2RHjemcGirKOP0L1fvpl3FUqIiQ_-yjeUo\" controls=\"controls\" muted=\"muted\" class=\"d-block rounded-bottom-2 border-top width-fit\" style=\"max-height:640px; min-height: 200px\">\n</video>\n    </td>\n  </tr>\n</table>\n\n\n# 项目文档\n[在线文档](https://www.gin-vue-admin.com) : https://www.gin-vue-admin.com\n\n[初始化](https://www.gin-vue-admin.com/guide/start-quickly/initialization.html)\n\t\t\t\t\t\t       \n[从环境到部署教学视频](https://www.bilibili.com/video/BV1Rg411u7xH)\n\n[开发教学](https://www.gin-vue-admin.com/guide/start-quickly/env.html) (贡献者:  <a href=\"https://github.com/LLemonGreen\">LLemonGreen</a> And <a href=\"https://github.com/fkk0509\">Fann</a>)\n\n[交流社区](https://support.qq.com/products/371961)\n\n[插件市场](https://plugin.gin-vue-admin.com/)\n\n[软件著作权证书](https://www.gin-vue-admin.com/copyright.pdf)\n\n# 重要提示\n\n1.本项目从起步到开发到部署均有文档和详细视频教程\n\n2.本项目需要您有一定的golang和vue基础\n\n3.您完全可以通过我们的教程和文档完成一切操作，因此我们不再提供免费的技术服务，如需服务请进行[付费支持](https://www.gin-vue-admin.com/coffee/payment.html)\n\n4.如果您将此项目用于商业用途，请遵守Apache2.0协议并保留作者技术支持声明。您需保留如下版权声明信息，以及日志和代码中所包含的版权声明信息。所需保留信息均为文案性质，不会影响任何业务内容，如决定商用【产生收益的商业行为均在商用行列】或者必须剔除请[购买授权](https://plugin.gin-vue-admin.com/licenseindex.html)\n\\\n<img src=\"https://qmplusimg.henrongyi.top/openSource/login.jpg\" width=\"1000\">\n\n<img src=\"https://qmplusimg.henrongyi.top/openSource/dashboard.jpg\" width=\"1000\">\n\n## 1. 基本介绍\n\n### 1.1 项目介绍\n\n> Gin-vue-admin是一个基于 [vue](https://vuejs.org) 和 [gin](https://gin-gonic.com) 开发的全栈前后端分离的开发基础平台，集成jwt鉴权，动态路由，动态菜单，casbin鉴权，表单生成器，代码生成器等功能，提供多种示例文件，让您把更多时间专注在业务开发上。\n\n[在线预览](http://demo.gin-vue-admin.com): http://demo.gin-vue-admin.com\n\n测试用户名：admin\n\n测试密码：123456\n\n### 1.2 贡献指南\nHi! 首先感谢你使用 gin-vue-admin。\n\nGin-vue-admin 是一套为快速研发准备的一整套前后端分离架构式的开源框架，旨在快速搭建中小型项目。\n\nGin-vue-admin 的成长离不开大家的支持，如果你愿意为 gin-vue-admin 贡献代码或提供建议，请阅读以下内容。\n\n#### 1.2.1 Issue 规范\n- issue 仅用于提交 Bug 或 Feature 以及设计相关的内容，其它内容可能会被直接关闭。\n\t\t\t\t\t\t\t\t\t      \n- 在提交 issue 之前，请搜索相关内容是否已被提出。\n\n#### 1.2.2 Pull Request 规范\n- 请先 fork 一份到自己的项目下，不要直接在仓库下建分支。\n\n- commit 信息要以`[文件名]: 描述信息` 的形式填写，例如 `README.md: fix xxx bug`。\n\n- 如果是修复 bug，请在 PR 中给出描述信息。\n\n- 合并代码需要两名维护人员参与：一人进行 review 后 approve，另一人再次 review，通过后即可合并。\n\n## 2. 使用说明\n\n```\n- node版本 > v18.16.0\n- golang版本 >= v1.22\n- IDE推荐：Goland\n```\n\n### 2.1 server项目\n\n使用 `Goland` 等编辑工具，打开server目录，不可以打开 gin-vue-admin 根目录\n\n```bash\n\n# 克隆项目\ngit clone https://github.com/flipped-aurora/gin-vue-admin.git\n# 进入server文件夹\ncd server\n\n# 使用 go mod 并安装go依赖包\ngo generate\n\n# 运行\ngo run . \n\n```\n\n### 2.2 web项目\n\n```bash\n# 进入web文件夹\ncd web\n\n# 安装依赖\nnpm install\n\n# 启动web项目\nnpm run serve\n```\n\n### 2.3 swagger自动化API文档\n\n#### 2.3.1 安装 swagger\n\n``` shell\ngo install github.com/swaggo/swag/cmd/swag@latest\n```\n\n#### 2.3.2 生成API文档\n\n```` shell\ncd server\nswag init\n````\n\n> 执行上面的命令后，server目录下会出现docs文件夹里的 `docs.go`, `swagger.json`, `swagger.yaml` 三个文件更新，启动go服务之后, 在浏览器输入 [http://localhost:8888/swagger/index.html](http://localhost:8888/swagger/index.html) 即可查看swagger文档\n\n### 2.4 VSCode工作区\n\n#### 2.4.1 开发\n\n使用`VSCode`打开根目录下的工作区文件`gin-vue-admin.code-workspace`，在边栏可以看到三个虚拟目录：`backend`、`frontend`、`root`。\n\n#### 2.4.2 运行/调试\n\n在运行和调试中也可以看到三个task：`Backend`、`Frontend`、`Both (Backend & Frontend)`。运行`Both (Backend & Frontend)`可以同时启动前后端项目。\n\n#### 2.4.3 settings\n\n在工作区配置文件中有`go.toolsEnvVars`字段，是用于`VSCode`自身的go工具环境变量。此外在多go版本的系统中，可以通过`gopath`、`go.goroot`指定运行版本。\n\n```json\n    \"go.gopath\": null,\n    \"go.goroot\": null,\n```\n\n## 3. 技术选型\n\n- 前端：用基于 [Vue](https://vuejs.org) 的 [Element](https://github.com/ElemeFE/element) 构建基础页面。\n- 后端：用 [Gin](https://gin-gonic.com/) 快速搭建基础restful风格API，[Gin](https://gin-gonic.com/) 是一个go语言编写的Web框架。\n- 数据库：采用`MySql` > (5.7) 版本 数据库引擎 InnoDB，使用 [gorm](http://gorm.cn) 实现对数据库的基本操作。\n- 缓存：使用`Redis`实现记录当前活跃用户的`jwt`令牌并实现多点登录限制。\n- API文档：使用`Swagger`构建自动化文档。\n- 配置文件：使用 [fsnotify](https://github.com/fsnotify/fsnotify) 和 [viper](https://github.com/spf13/viper) 实现`yaml`格式的配置文件。\n- 日志：使用 [zap](https://github.com/uber-go/zap) 实现日志记录。\n\n## 4. 项目架构\n\n### 4.1 系统架构图\n\n![系统架构图](http://qmplusimg.henrongyi.top/gva/gin-vue-admin.png)\n\n### 4.2 前端详细设计图 （提供者:<a href=\"https://github.com/baobeisuper\">baobeisuper</a>）\n\n![前端详细设计图](http://qmplusimg.henrongyi.top/naotu.png)\n\n### 4.3 目录结构\n\n```\n    ├── server\n        ├── api             (api层)\n        │   └── v1          (v1版本接口)\n        ├── config          (配置包)\n        ├── core            (核心文件)\n        ├── docs            (swagger文档目录)\n        ├── global          (全局对象)                    \n        ├── initialize      (初始化)                        \n        │   └── internal    (初始化内部函数)                            \n        ├── middleware      (中间件层)                        \n        ├── model           (模型层)                    \n        │   ├── request     (入参结构体)                        \n        │   └── response    (出参结构体)                            \n        ├── packfile        (静态文件打包)                        \n        ├── resource        (静态资源文件夹)                        \n        │   ├── excel       (excel导入导出默认路径)                        \n        │   ├── page        (表单生成器)                        \n        │   └── template    (模板)                            \n        ├── router          (路由层)                    \n        ├── service         (service层)                    \n        ├── source          (source层)                    \n        └── utils           (工具包)                    \n            ├── timer       (定时器接口封装)                        \n            └── upload      (oss接口封装)                        \n    \n            web\n        ├── babel.config.js\n        ├── Dockerfile\n        ├── favicon.ico\n        ├── index.html                 -- 主页面\n        ├── limit.js                   -- 助手代码\n        ├── package.json               -- 包管理器代码\n        ├── src                        -- 源代码\n        │   ├── api                    -- api 组\n        │   ├── App.vue                -- 主页面\n        │   ├── assets                 -- 静态资源\n        │   ├── components             -- 全局组件\n        │   ├── core                   -- gva 组件包\n        │   │   ├── config.js          -- gva网站配置文件\n        │   │   ├── gin-vue-admin.js   -- 注册欢迎文件\n        │   │   └── global.js          -- 统一导入文件\n        │   ├── directive              -- v-auth 注册文件\n        │   ├── main.js                -- 主文件\n        │   ├── permission.js          -- 路由中间件\n        │   ├── pinia                  -- pinia 状态管理器，取代vuex\n        │   │   ├── index.js           -- 入口文件\n        │   │   └── modules            -- modules\n        │   │       ├── dictionary.js\n        │   │       ├── router.js\n        │   │       └── user.js\n        │   ├── router                 -- 路由声明文件\n        │   │   └── index.js\n        │   ├── style                  -- 全局样式\n        │   │   ├── base.scss\n        │   │   ├── basics.scss\n        │   │   ├── element_visiable.scss  -- 此处可以全局覆盖 element-plus 样式\n        │   │   ├── iconfont.css           -- 顶部几个icon的样式文件\n        │   │   ├── main.scss\n        │   │   ├── mobile.scss\n        │   │   └── newLogin.scss\n        │   ├── utils                  -- 方法包库\n        │   │   ├── asyncRouter.js     -- 动态路由相关\n        │   │   ├── btnAuth.js         -- 动态权限按钮相关\n        │   │   ├── bus.js             -- 全局mitt声明文件\n        │   │   ├── date.js            -- 日期相关\n        │   │   ├── dictionary.js      -- 获取字典方法 \n        │   │   ├── downloadImg.js     -- 下载图片方法\n        │   │   ├── format.js          -- 格式整理相关\n        │   │   ├── image.js           -- 图片相关方法\n        │   │   ├── page.js            -- 设置页面标题\n        │   │   ├── request.js         -- 请求\n        │   │   └── stringFun.js       -- 字符串文件\n        |   ├── view -- 主要view代码\n        |   |   ├── about -- 关于我们\n        |   |   ├── dashboard -- 面板\n        |   |   ├── error -- 错误\n        |   |   ├── example --上传案例\n        |   |   ├── iconList -- icon列表\n        |   |   ├── init -- 初始化数据  \n        |   |   |   ├── index -- 新版本\n        |   |   |   ├── init -- 旧版本\n        |   |   ├── layout  --  layout约束页面 \n        |   |   |   ├── aside \n        |   |   |   ├── bottomInfo     -- bottomInfo\n        |   |   |   ├── screenfull     -- 全屏设置\n        |   |   |   ├── setting        -- 系统设置\n        |   |   |   └── index.vue      -- base 约束\n        |   |   ├── login              --登录 \n        |   |   ├── person             --个人中心 \n        |   |   ├── superAdmin         -- 超级管理员操作\n        |   |   ├── system             -- 系统检测页面\n        |   |   ├── systemTools        -- 系统配置相关页面\n        |   |   └── routerHolder.vue   -- page 入口页面 \n        ├── vite.config.js             -- vite 配置文件\n        └── yarn.lock\n\n```\n\n## 5. 主要功能\n\n- 权限管理：基于`jwt`和`casbin`实现的权限管理。\n- 文件上传下载：实现基于`七牛云`, `阿里云`, `腾讯云` 的文件上传操作(请开发自己去各个平台的申请对应 `token` 或者对应`key`)。\n- 分页封装：前端使用 `mixins` 封装分页，分页方法调用 `mixins` 即可。\n- 用户管理：系统管理员分配用户角色和角色权限。\n- 角色管理：创建权限控制的主要对象，可以给角色分配不同api权限和菜单权限。\n- 菜单管理：实现用户动态菜单配置，实现不同角色不同菜单。\n- api管理：不同用户可调用的api接口的权限不同。\n- 配置管理：配置文件可前台修改(在线体验站点不开放此功能)。\n- 条件搜索：增加条件搜索示例。\n- restful示例：可以参考用户管理模块中的示例API。\n\t- 前端文件参考: [web/src/view/superAdmin/api/api.vue](https://github.com/flipped-aurora/gin-vue-admin/blob/master/web/src/view/superAdmin/api/api.vue)\n    - 后台文件参考: [server/router/sys_api.go](https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/router/sys_api.go)\n- 多点登录限制：需要在`config.yaml`中把`system`中的`use-multipoint`修改为true(需要自行配置Redis和Config中的Redis参数，测试阶段，有bug请及时反馈)。\n- 分片上传：提供文件分片上传和大文件分片上传功能示例。\n- 表单生成器：表单生成器借助 [@Variant Form](https://github.com/vform666/variant-form) 。\n- 代码生成器：后台基础逻辑以及简单curd的代码生成器。\n\n## 6. 知识库 \n\n## 6.1 团队博客\n\n> https://www.yuque.com/flipped-aurora\n>\n>内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994，欢迎您提出宝贵的需求。\n\n## 6.2 教学视频\n\n（1）手把手教学视频\n\n> https://www.bilibili.com/video/BV1Rg411u7xH/\n\n（2）后端目录结构调整介绍以及使用方法\n\n> https://www.bilibili.com/video/BV1x44y117TT/\n\n（3）golang基础教学视频\n\n> bilibili：https://space.bilibili.com/322210472/channel/detail?cid=108884\n\n（4）gin框架基础教学\n\n> bilibili：https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0\n\n（5）gin-vue-admin 版本更新介绍视频\n\n> bilibili：https://www.bilibili.com/video/BV1kv4y1g7nT\n\n## 7. 联系方式\n\n### 7.1 技术群\n\n### QQ交流群：971857775\n\n### 微信交流群\n| 微信 |\n|  :---:  | \n| <img width=\"150\" src=\"http://qmplusimg.henrongyi.top/qrjjz.png\"> \n\n防止广告进群，添加微信，输入以下代码执行结果（请勿转码为string）\n\n```\nstr := \"5Yqg5YWlR1ZB5Lqk5rWB576k\"\ndecodeBytes, err := base64.StdEncoding.DecodeString(str)\nfmt.Println(decodeBytes, err)\n```\n\n### [关于我们](https://www.gin-vue-admin.com/about/join.html)\n\n## 8. 贡献者\n\n感谢您对gin-vue-admin的贡献!\n\n<a href=\"https://openomy.app/github/flipped-aurora/gin-vue-admin\" target=\"_blank\" style=\"display: block; width: 100%;\" align=\"center\">\n  <img src=\"https://openomy.app/svg?repo=flipped-aurora/gin-vue-admin&chart=bubble&latestMonth=3\" target=\"_blank\" alt=\"Contribution Leaderboard\" style=\"display: block; width: 100%;\" />\n </a>\n\n## 9. 捐赠\n\n如果你觉得这个项目对你有帮助，你可以请作者喝饮料 :tropical_drink: [点我](https://www.gin-vue-admin.com/coffee/index.html)\n\n## 10. 注意事项\n\n请严格遵守Apache 2.0协议并保留作品声明，去除版权信息请务必[获取授权](https://plugin.gin-vue-admin.com/license)  \n未授权去除版权信息将依法追究法律责任\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report security issues to qimiaojiangjizhao@gmail.com\n"
  },
  {
    "path": "deploy/docker/Dockerfile",
    "content": "FROM centos:7\nWORKDIR /opt\nENV LANG=en_US.utf8\nCOPY deploy/docker/entrypoint.sh .\nCOPY build/ /usr/share/nginx/html/\nCOPY server/config.yaml /usr/share/nginx/html/config.yaml\nCOPY web/.docker-compose/nginx/conf.d/nginx.conf /etc/nginx/conf.d/nginx.conf\nRUN set -ex \\\n    && echo \"LANG=en_US.utf8\" > /etc/locale.conf \\\n    && echo \"net.core.somaxconn = 1024\" >> /etc/sysctl.conf \\\n    && echo \"vm.overcommit_memory = 1\" >> /etc/sysctl.conf \\\n    && yum -y install epel-release \\\n    && yum -y localinstall http://mirrors.ustc.edu.cn/mysql-repo/mysql57-community-release-el7.rpm \\\n    && yum -y install mysql-community-server git redis nginx go npm --nogpgcheck && chmod +x ./entrypoint.sh \\\n    && npm install -g yarn && go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct \\\n    && echo \"start\" > /dev/null\nEXPOSE 80\nENTRYPOINT [\"./entrypoint.sh\"]"
  },
  {
    "path": "deploy/docker/entrypoint.sh",
    "content": "#!/bin/bash\nif [ ! -d \"/var/lib/mysql/gva\" ]; then\n    mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql\n    mysqld --daemonize --user=mysql\n    sleep 5s\n    mysql -uroot -e \"create database gva default charset 'utf8' collate 'utf8_bin'; grant all on gva.* to 'root'@'127.0.0.1' identified by '123456'; flush privileges;\"\nelse\n    mysqld --daemonize --user=mysql\nfi\nredis-server &\nif [ \"$1\" = \"actions\" ]; then\n    cd /opt/gva/server && go run main.go &\n    cd /opt/gva/web/ && yarn serve &\nelse \n    /usr/sbin/nginx &\n    cd /usr/share/nginx/html/ && ./server &\nfi\necho \"gva ALL start!!!\"\ntail -f /dev/null"
  },
  {
    "path": "deploy/docker-compose/docker-compose.yaml",
    "content": "version: \"3\"\n\n# 声明一个名为network的networks,subnet为network的子网地址,默认网关是177.7.0.1\nnetworks:\n  network:\n    ipam:\n      driver: default\n      config:\n        - subnet: '177.7.0.0/16'\n        \n# 设置mysql，redis持久化保存\nvolumes:\n  mysql:\n  redis:\n  \nservices:\n  web:\n    build:\n      context: ../../web\n      dockerfile: ./Dockerfile\n    container_name: gva-web\n    restart: always\n    ports:\n      - '8080:8080'\n    depends_on:\n      - server\n    command: [ 'nginx-debug', '-g', 'daemon off;' ]\n    networks:\n      network:\n        ipv4_address: 177.7.0.11\n\n  server:\n    build:\n      context: ../../server\n      dockerfile: ./Dockerfile\n    container_name: gva-server\n    restart: always\n    ports:\n      - '8888:8888'\n    depends_on:\n      mysql:\n        condition: service_healthy\n      redis:\n        condition: service_healthy\n    links:\n      - mysql\n      - redis\n    networks:\n      network:\n        ipv4_address: 177.7.0.12\n\n  mysql:\n    image: mysql:8.0.21       # 如果您是 arm64 架构：如 MacOS 的 M1，请修改镜像为 image: mysql/mysql-server:8.0.21\n    container_name: gva-mysql\n    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #设置utf8字符集\n    restart: always\n    ports:\n      - \"13306:3306\"  # host物理直接映射端口为13306\n    environment:\n      #MYSQL_ROOT_PASSWORD: 'Aa@6447985' # root管理员用户密码\n      MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称\n      MYSQL_USER: 'gva'\n      MYSQL_PASSWORD: 'Aa@6447985'\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\", \"-u\", \"gva\", \"-pAa@6447985\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n    volumes:\n      - mysql:/var/lib/mysql\n    networks:\n      network:\n        ipv4_address: 177.7.0.13\n\n  redis:\n    image: redis:6.0.6\n    container_name: gva-redis # 容器名\n    restart: always\n    ports:\n      - '16379:6379'\n    healthcheck:\n      test: [\"CMD-SHELL\", \"redis-cli ping | grep PONG || exit 1\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n    volumes:\n      - redis:/data\n    networks:\n      network:\n        ipv4_address: 177.7.0.14\n"
  },
  {
    "path": "deploy/kubernetes/server/gva-server-configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: config.yaml\n  annotations:\n    flipped-aurora/gin-vue-admin: backend\n    github: \"https://github.com/flipped-aurora/gin-vue-admin.git\"\n    app.kubernetes.io/version: 0.0.1\n  labels:\n    app: gva-server\n    version: gva-vue3\ndata:\n  config.yaml: |\n    # github.com/flipped-aurora/gin-vue-admin/server Global Configuration\n\n    # jwt configuration\n    jwt:\n      signing-key: 'qmPlus'\n      expires-time: 604800\n      buffer-time: 86400\n\n    # zap logger configuration\n    zap:\n      level: 'info'\n      format: 'console'\n      prefix: '[github.com/flipped-aurora/gin-vue-admin/server]'\n      director: 'log'\n      link-name: 'latest_log'\n      show-line: true\n      encode-level: 'LowercaseColorLevelEncoder'\n      stacktrace-key: 'stacktrace'\n      log-in-console: true\n\n    # redis configuration\n    redis:\n      db: 0\n      addr: '127.0.0.1:6379'\n      password: ''\n\n    # email configuration\n    email:\n      to: 'xxx@qq.com'\n      port: 465\n      from: 'xxx@163.com'\n      host: 'smtp.163.com'\n      is-ssl: true\n      secret: 'xxx'\n      nickname: 'test'\n\n    # casbin configuration\n    casbin:\n      model-path: './resource/rbac_model.conf'\n\n    # system configuration\n    system:\n      env: 'develop'  # Change to \"develop\" to skip authentication for development mode\n      addr: 8888\n      db-type: 'mysql'\n      oss-type: 'local'    # 控制oss选择走本期还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置\n      use-multipoint: false\n\n    # captcha configuration\n    captcha:\n      key-long: 6\n      img-width: 240\n      img-height: 80\n\n    # mysql connect configuration\n    # 未初始化之前请勿手动修改数据库信息！！！如果一定要手动初始化请看（https://www.github.com/flipped-aurora/gin-vue-admin/server.com/docs/first）\n    mysql:\n      path: ''\n      config: ''\n      db-name: ''\n      username: ''\n      password: ''\n      max-idle-conns: 10\n      max-open-conns: 100\n      log-mode: false\n      log-zap: \"\"\n\n    # local configuration\n    local:\n      path: 'uploads/file'\n\n    # autocode configuration\n    autocode:\n      transfer-restart: true\n      root: \"\"\n      server: /server\n      server-api: /api/v1/autocode\n      server-initialize: /initialize\n      server-model: /model/autocode\n      server-request: /model/autocode/request/\n      server-router: /router/autocode\n      server-service: /service/autocode\n      web: /web/src\n      web-api: /api\n      web-flow: /view\n      web-form: /view\n      web-table: /view\n\n    # qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)\n    qiniu:\n      zone: 'ZoneHuaDong'\n      bucket: ''\n      img-path: ''\n      use-https: false\n      access-key: ''\n      secret-key: ''\n      use-cdn-domains: false\n\n\n    # aliyun oss configuration\n    aliyun-oss:\n      endpoint: 'yourEndpoint'\n      access-key-id: 'yourAccessKeyId'\n      access-key-secret: 'yourAccessKeySecret'\n      bucket-name: 'yourBucketName'\n      bucket-url: 'yourBucketUrl'\n      base-path: 'yourBasePath'\n\n    # tencent cos configuration\n    tencent-cos:\n      bucket: 'xxxxx-10005608'\n      region: 'ap-shanghai'\n      secret-id: 'xxxxxxxx'\n      secret-key: 'xxxxxxxx'\n      base-url: 'https://gin.vue.admin'\n      path-prefix: 'github.com/flipped-aurora/gin-vue-admin/server'\n\n    # excel configuration\n    excel:\n      dir: './resource/excel/'\n\n\n    # timer task db clear table\n    Timer:\n      start: true\n      spec: \"@daily\"  # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3\n      detail: [\n        # tableName: 需要清理的表名\n        # compareField: 需要比较时间的字段\n        # interval: 时间间隔, 具体配置详看 time.ParseDuration() 中字符串表示 且不能为负数\n        # 2160h = 24 * 30 * 3 -> 三个月\n        { tableName: \"sys_operation_records\" , compareField: \"created_at\", interval: \"2160h\" },\n        { tableName: \"jwt_blacklists\" , compareField: \"created_at\", interval: \"168h\" }\n        #{ tableName: \"log2\" , compareField: \"created_at\", interval: \"2160h\" }\n      ]\n"
  },
  {
    "path": "deploy/kubernetes/server/gva-server-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gva-server\n  annotations:\n    flipped-aurora/gin-vue-admin: backend\n    github: \"https://github.com/flipped-aurora/gin-vue-admin.git\"\n    app.kubernetes.io/version: 0.0.1\n  labels:\n    app: gva-server\n    version: gva-vue3\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gva-server\n      version: gva-vue3\n  template:\n    metadata:\n      labels:\n        app: gva-server\n        version: gva-vue3\n    spec:\n      containers:\n        - name: gin-vue-admin-container\n          image: registry.cn-hangzhou.aliyuncs.com/gva/server:latest\n          imagePullPolicy: Always\n          ports:\n            - containerPort: 8888\n              name: http\n          volumeMounts:\n            - mountPath: /go/src/github.com/flipped-aurora/gin-vue-admin/server/config.docker.yaml\n              name: config\n              subPath: config.yaml\n            - mountPath: /etc/localtime\n              name: localtime\n          resources:\n            limits:\n              cpu: 1000m\n              memory: 2000Mi\n            requests:\n              cpu: 100m\n              memory: 200Mi\n          livenessProbe:\n            failureThreshold: 1\n            periodSeconds: 5\n            successThreshold: 1\n            tcpSocket:\n              port: 8888\n            timeoutSeconds: 1\n          readinessProbe:\n            failureThreshold: 3\n            initialDelaySeconds: 30\n            periodSeconds: 5\n            successThreshold: 1\n            tcpSocket:\n              port: 8888\n            timeoutSeconds: 1\n          startupProbe:\n            failureThreshold: 40\n            periodSeconds: 5\n            successThreshold: 1\n            tcpSocket:\n              port: 8888\n            timeoutSeconds: 1\n      #imagePullSecrets:\n      #  - name: docker-registry\n      volumes:\n        - name: localtime\n          hostPath:\n            path: /etc/localtime\n        - name: config\n          configMap:\n            name: config.yaml"
  },
  {
    "path": "deploy/kubernetes/server/gva-server-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: gva-server\n  annotations:\n    flipped-aurora/gin-vue-admin: backend\n    github: \"https://github.com/flipped-aurora/gin-vue-admin.git\"\n    app.kubernetes.io/version: 0.0.1\n  labels:\n    app: gva-server\n    version: gva-vue3\nspec:\n  selector:\n    app: gva-server\n    version: gva-vue3\n  ports:\n    - port: 8888\n      name: http\n      targetPort: 8888\n  type: ClusterIP\n#  type: NodePort\n"
  },
  {
    "path": "deploy/kubernetes/web/gva-web-configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: my.conf\ndata:\n  my.conf: |\n    server {\n        listen       8080;\n        server_name localhost;\n\n        #charset koi8-r;\n        #access_log  logs/host.access.log  main;\n\n        location / {\n            root /usr/share/nginx/html;\n            add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';\n            try_files $uri $uri/ /index.html;\n        }\n\n        location /api {\n            proxy_set_header Host $http_host;\n            proxy_set_header  X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header X-Forwarded-Proto $scheme;\n            rewrite ^/api/(.*)$ /$1 break;         #重写\n            proxy_pass http://gva-server:8888;     # 设置代理服务器的协议和地址\n         }\n\n        location /api/swagger/index.html {\n            proxy_pass http://gva-server:8888/swagger/index.html;\n         }\n     }"
  },
  {
    "path": "deploy/kubernetes/web/gva-web-deploymemt.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gva-web\n  annotations:\n    flipped-aurora/gin-vue-admin: ui\n    github: \"https://github.com/flipped-aurora/gin-vue-admin.git\"\n    app.kubernetes.io/version: 0.0.1\n  labels:\n    app: gva-web\n    version: gva-vue3\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gva-web\n      version: gva-vue3\n  template:\n    metadata:\n      labels:\n        app: gva-web\n        version: gva-vue3\n    spec:\n      containers:\n        - name: gin-vue-admin-nginx-container\n          image: registry.cn-hangzhou.aliyuncs.com/gva/web:latest\n          imagePullPolicy: Always\n          ports:\n            - containerPort: 8080\n              name: http\n          readinessProbe:\n            tcpSocket:\n              port: 8080\n            initialDelaySeconds: 10\n            periodSeconds: 10\n            successThreshold: 1\n            failureThreshold: 3\n          resources:\n            limits:\n              cpu: 500m\n              memory: 1000Mi\n            requests:\n              cpu: 100m\n              memory: 100Mi\n          volumeMounts:\n            - mountPath: /etc/nginx/conf.d/\n              name: nginx-config\n      volumes:\n        - name: nginx-config\n          configMap:\n            name: my.conf\n"
  },
  {
    "path": "deploy/kubernetes/web/gva-web-ingress.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: gva-ingress\n  annotations:\n    kubernetes.io/ingress.class: \"nginx\"\nspec:\n  rules:\n  - host: demo.gin-vue-admin.com\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: gva-web\n            port:\n              number: 8080"
  },
  {
    "path": "deploy/kubernetes/web/gva-web-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: gva-web\n  annotations:\n    flipped-aurora/gin-vue-admin: ui\n    github: \"https://github.com/flipped-aurora/gin-vue-admin.git\"\n    app.kubernetes.io/version: 0.0.1\n  labels:\n    app: gva-web\n    version: gva-vue3\nspec:\n#  type: NodePort\n  type: ClusterIP\n  ports:\n    - name: http\n      port: 8080\n      targetPort: 8080\n  selector:\n    app: gva-web\n    version: gva-vue3\n"
  },
  {
    "path": "gin-vue-admin.code-workspace",
    "content": "{\n  \"folders\": [\n    {\n      \"path\": \"server\",\n      \"name\": \"backend\"\n    },\n    {\n      \"path\": \"web\",\n      \"name\": \"frontend\"\n    },\n    {\n      \"path\": \".\",\n      \"name\": \"root\"\n    }\n  ],\n  \"settings\": {\n    \"go.toolsEnvVars\": {\n      \"GOPROXY\": \"https://goproxy.cn,direct\",\n      \"GONOPROXY\": \"none;\"\n    }\n  },\n  \"launch\": {\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n      {\n        \"type\": \"go\",\n        \"request\": \"launch\",\n        \"name\": \"Backend\",\n        \"cwd\": \"${workspaceFolder:backend}\",\n        \"program\": \"${workspaceFolder:backend}/\"\n      },\n      {\n        \"type\": \"node\",\n        \"request\": \"launch\",\n        \"cwd\": \"${workspaceFolder:frontend}\",\n        \"name\": \"Frontend\",\n        \"runtimeExecutable\": \"npm\",\n        \"runtimeArgs\": [\"run-script\", \"serve\"]\n      }\n    ],\n    \"compounds\": [\n      {\n        \"name\": \"Both (Backend & Frontend)\",\n        \"configurations\": [\"Backend\", \"Frontend\"],\n        \"stopAll\": true\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "server/Dockerfile",
    "content": "FROM golang:alpine as builder\n\nWORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/server\nCOPY . .\n\nRUN go env -w GO111MODULE=on \\\n    && go env -w GOPROXY=https://goproxy.cn,direct \\\n    && go env -w CGO_ENABLED=0 \\\n    && go env \\\n    && go mod tidy \\\n    && go build -o server .\n\nFROM alpine:latest\n\nLABEL MAINTAINER=\"SliverHorn@sliver_horn@qq.com\"\n# 设置时区\nENV TZ=Asia/Shanghai\nRUN apk update && apk add --no-cache tzdata openntpd \\\n    && ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone\n\nWORKDIR /go/src/github.com/flipped-aurora/gin-vue-admin/server\n\nCOPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/server ./\nCOPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/resource ./resource/\nCOPY --from=0 /go/src/github.com/flipped-aurora/gin-vue-admin/server/config.docker.yaml ./\n\n# 挂载目录：如果使用了sqlite数据库，容器命令示例：docker run -d -v /宿主机路径/gva.db:/go/src/github.com/flipped-aurora/gin-vue-admin/server/gva.db -p 8888:8888 --name gva-server-v1 gva-server:1.0\n# VOLUME [\"/go/src/github.com/flipped-aurora/gin-vue-admin/server\"]\n\nEXPOSE 8888\nENTRYPOINT ./server -c config.docker.yaml\n"
  },
  {
    "path": "server/README.md",
    "content": "## server项目结构\n\n```shell\n├── api\n│   └── v1\n├── config\n├── core\n├── docs\n├── global\n├── initialize\n│   └── internal\n├── middleware\n├── model\n│   ├── request\n│   └── response\n├── packfile\n├── resource\n│   ├── excel\n│   ├── page\n│   └── template\n├── router\n├── service\n├── source\n└── utils\n    ├── timer\n    └── upload\n```\n\n| 文件夹       | 说明                    | 描述                        |\n| ------------ | ----------------------- | --------------------------- |\n| `api`        | api层                   | api层 |\n| `--v1`       | v1版本接口              | v1版本接口                  |\n| `config`     | 配置包                  | config.yaml对应的配置结构体 |\n| `core`       | 核心文件                | 核心组件(zap, viper, server)的初始化 |\n| `docs`       | swagger文档目录         | swagger文档目录 |\n| `global`     | 全局对象                | 全局对象 |\n| `initialize` | 初始化 | router,redis,gorm,validator, timer的初始化 |\n| `--internal` | 初始化内部函数 | gorm 的 longger 自定义,在此文件夹的函数只能由 `initialize` 层进行调用 |\n| `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |\n| `model`      | 模型层                  | 模型对应数据表              |\n| `--request`  | 入参结构体              | 接收前端发送到后端的数据。  |\n| `--response` | 出参结构体              | 返回给前端的数据结构体      |\n| `packfile`   | 静态文件打包            | 静态文件打包 |\n| `resource`   | 静态资源文件夹          | 负责存放静态文件                |\n| `--excel` | excel导入导出默认路径 | excel导入导出默认路径 |\n| `--page` | 表单生成器 | 表单生成器 打包后的dist |\n| `--template` | 模板 | 模板文件夹,存放的是代码生成器的模板 |\n| `router`     | 路由层                  | 路由层 |\n| `service`    | service层               | 存放业务逻辑问题 |\n| `source` | source层 | 存放初始化数据的函数 |\n| `utils`      | 工具包                  | 工具函数封装            |\n| `--timer` | timer | 定时器接口封装 |\n| `--upload`      | oss                  | oss接口封装        |\n\n"
  },
  {
    "path": "server/api/v1/enter.go",
    "content": "package v1\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/api/v1/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/api/v1/system\"\n)\n\nvar ApiGroupApp = new(ApiGroup)\n\ntype ApiGroup struct {\n\tSystemApiGroup  system.ApiGroup\n\tExampleApiGroup example.ApiGroup\n}\n"
  },
  {
    "path": "server/api/v1/example/enter.go",
    "content": "package example\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\ntype ApiGroup struct {\n\tCustomerApi\n\tFileUploadAndDownloadApi\n\tAttachmentCategoryApi\n}\n\nvar (\n\tcustomerService              = service.ServiceGroupApp.ExampleServiceGroup.CustomerService\n\tfileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService\n\tattachmentCategoryService    = service.ServiceGroupApp.ExampleServiceGroup.AttachmentCategoryService\n)\n"
  },
  {
    "path": "server/api/v1/example/exa_attachment_category.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AttachmentCategoryApi struct{}\n\n// GetCategoryList\n// @Tags      GetCategoryList\n// @Summary   媒体库分类列表\n// @Security  AttachmentCategory\n// @Produce   application/json\n// @Success   200   {object}  response.Response{data=example.ExaAttachmentCategory,msg=string}  \"媒体库分类列表\"\n// @Router    /attachmentCategory/getCategoryList [get]\nfunc (a *AttachmentCategoryApi) GetCategoryList(c *gin.Context) {\n\tres, err := attachmentCategoryService.GetCategoryList()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取分类列表失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取分类列表失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithData(res, c)\n}\n\n// AddCategory\n// @Tags      AddCategory\n// @Summary   添加媒体库分类\n// @Security  AttachmentCategory\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      example.ExaAttachmentCategory  true  \"媒体库分类数据\"// @Success   200   {object}  response.Response{msg=string}   \"添加媒体库分类\"\n// @Router    /attachmentCategory/addCategory [post]\nfunc (a *AttachmentCategoryApi) AddCategory(c *gin.Context) {\n\tvar req example.ExaAttachmentCategory\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"参数错误!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"参数错误\", c)\n\t\treturn\n\t}\n\n\tif err := attachmentCategoryService.AddCategory(&req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建/更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建/更新失败：\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建/更新成功\", c)\n}\n\n// DeleteCategory\n// @Tags      DeleteCategory\n// @Summary   删除分类\n// @Security  AttachmentCategory\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      common.GetById                true  \"分类id\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除分类\"\n// @Router    /attachmentCategory/deleteCategory [post]\nfunc (a *AttachmentCategoryApi) DeleteCategory(c *gin.Context) {\n\tvar req common.GetById\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\tresponse.FailWithMessage(\"参数错误\", c)\n\t\treturn\n\t}\n\n\tif req.ID == 0 {\n\t\tresponse.FailWithMessage(\"参数错误\", c)\n\t\treturn\n\t}\n\n\tif err := attachmentCategoryService.DeleteCategory(&req.ID); err != nil {\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/example/exa_breakpoint_continue.go",
    "content": "package example\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\texampleRes \"github.com/flipped-aurora/gin-vue-admin/server/model/example/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\n// BreakpointContinue\n// @Tags      ExaFileUploadAndDownload\n// @Summary   断点续传到服务器\n// @Security  ApiKeyAuth\n// @accept    multipart/form-data\n// @Produce   application/json\n// @Param     file  formData  file                           true  \"an example for breakpoint resume, 断点续传示例\"\n// @Success   200   {object}  response.Response{msg=string}  \"断点续传到服务器\"\n// @Router    /fileUploadAndDownload/breakpointContinue [post]\nfunc (b *FileUploadAndDownloadApi) BreakpointContinue(c *gin.Context) {\n\tfileMd5 := c.Request.FormValue(\"fileMd5\")\n\tfileName := c.Request.FormValue(\"fileName\")\n\tchunkMd5 := c.Request.FormValue(\"chunkMd5\")\n\tchunkNumber, _ := strconv.Atoi(c.Request.FormValue(\"chunkNumber\"))\n\tchunkTotal, _ := strconv.Atoi(c.Request.FormValue(\"chunkTotal\"))\n\t_, FileHeader, err := c.Request.FormFile(\"file\")\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"接收文件失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"接收文件失败\", c)\n\t\treturn\n\t}\n\tf, err := FileHeader.Open()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"文件读取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"文件读取失败\", c)\n\t\treturn\n\t}\n\tdefer func(f multipart.File) {\n\t\terr := f.Close()\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}(f)\n\tcen, _ := io.ReadAll(f)\n\tif !utils.CheckMd5(cen, chunkMd5) {\n\t\tglobal.GVA_LOG.Error(\"检查md5失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"检查md5失败\", c)\n\t\treturn\n\t}\n\tfile, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查找或创建记录失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查找或创建记录失败\", c)\n\t\treturn\n\t}\n\tpathC, err := utils.BreakPointContinue(cen, fileName, chunkNumber, chunkTotal, fileMd5)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"断点续传失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"断点续传失败\", c)\n\t\treturn\n\t}\n\n\tif err = fileUploadAndDownloadService.CreateFileChunk(file.ID, pathC, chunkNumber); err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建文件记录失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建文件记录失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"切片创建成功\", c)\n}\n\n// FindFile\n// @Tags      ExaFileUploadAndDownload\n// @Summary   查找文件\n// @Security  ApiKeyAuth\n// @accept    multipart/form-data\n// @Produce   application/json\n// @Param     file  formData  file                                                        true  \"Find the file, 查找文件\"\n// @Success   200   {object}  response.Response{data=exampleRes.FileResponse,msg=string}  \"查找文件,返回包括文件详情\"\n// @Router    /fileUploadAndDownload/findFile [get]\nfunc (b *FileUploadAndDownloadApi) FindFile(c *gin.Context) {\n\tfileMd5 := c.Query(\"fileMd5\")\n\tfileName := c.Query(\"fileName\")\n\tchunkTotal, _ := strconv.Atoi(c.Query(\"chunkTotal\"))\n\tfile, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查找失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查找失败\", c)\n\t} else {\n\t\tresponse.OkWithDetailed(exampleRes.FileResponse{File: file}, \"查找成功\", c)\n\t}\n}\n\n// BreakpointContinueFinish\n// @Tags      ExaFileUploadAndDownload\n// @Summary   创建文件\n// @Security  ApiKeyAuth\n// @accept    multipart/form-data\n// @Produce   application/json\n// @Param     file  formData  file                                                            true  \"上传文件完成\"\n// @Success   200   {object}  response.Response{data=exampleRes.FilePathResponse,msg=string}  \"创建文件,返回包括文件路径\"\n// @Router    /fileUploadAndDownload/findFile [post]\nfunc (b *FileUploadAndDownloadApi) BreakpointContinueFinish(c *gin.Context) {\n\tfileMd5 := c.Query(\"fileMd5\")\n\tfileName := c.Query(\"fileName\")\n\tfilePath, err := utils.MakeFile(fileName, fileMd5)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"文件创建失败!\", zap.Error(err))\n\t\tresponse.FailWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, \"文件创建失败\", c)\n\t} else {\n\t\tresponse.OkWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, \"文件创建成功\", c)\n\t}\n}\n\n// RemoveChunk\n// @Tags      ExaFileUploadAndDownload\n// @Summary   删除切片\n// @Security  ApiKeyAuth\n// @accept    multipart/form-data\n// @Produce   application/json\n// @Param     file  formData  file                           true  \"删除缓存切片\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除切片\"\n// @Router    /fileUploadAndDownload/removeChunk [post]\nfunc (b *FileUploadAndDownloadApi) RemoveChunk(c *gin.Context) {\n\tvar file example.ExaFile\n\terr := c.ShouldBindJSON(&file)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\t// 路径穿越拦截\n\tif strings.Contains(file.FilePath, \"..\") || strings.Contains(file.FilePath, \"../\") || strings.Contains(file.FilePath, \"./\") || strings.Contains(file.FilePath, \".\\\\\") {\n\t\tresponse.FailWithMessage(\"非法路径，禁止删除\", c)\n\t\treturn\n\t}\n\terr = utils.RemoveChunk(file.FileMd5)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"缓存切片删除失败!\", zap.Error(err))\n\t\treturn\n\t}\n\terr = fileUploadAndDownloadService.DeleteFileChunk(file.FileMd5, file.FilePath)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(err.Error(), zap.Error(err))\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"缓存切片删除成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/example/exa_customer.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\texampleRes \"github.com/flipped-aurora/gin-vue-admin/server/model/example/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype CustomerApi struct{}\n\n// CreateExaCustomer\n// @Tags      ExaCustomer\n// @Summary   创建客户\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      example.ExaCustomer            true  \"客户用户名, 客户手机号码\"\n// @Success   200   {object}  response.Response{msg=string}  \"创建客户\"\n// @Router    /customer/customer [post]\nfunc (e *CustomerApi) CreateExaCustomer(c *gin.Context) {\n\tvar customer example.ExaCustomer\n\terr := c.ShouldBindJSON(&customer)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(customer, utils.CustomerVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tcustomer.SysUserID = utils.GetUserID(c)\n\tcustomer.SysUserAuthorityID = utils.GetUserAuthorityId(c)\n\terr = customerService.CreateExaCustomer(customer)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// DeleteExaCustomer\n// @Tags      ExaCustomer\n// @Summary   删除客户\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      example.ExaCustomer            true  \"客户ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除客户\"\n// @Router    /customer/customer [delete]\nfunc (e *CustomerApi) DeleteExaCustomer(c *gin.Context) {\n\tvar customer example.ExaCustomer\n\terr := c.ShouldBindJSON(&customer)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(customer.GVA_MODEL, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = customerService.DeleteExaCustomer(customer)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// UpdateExaCustomer\n// @Tags      ExaCustomer\n// @Summary   更新客户信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      example.ExaCustomer            true  \"客户ID, 客户信息\"\n// @Success   200   {object}  response.Response{msg=string}  \"更新客户信息\"\n// @Router    /customer/customer [put]\nfunc (e *CustomerApi) UpdateExaCustomer(c *gin.Context) {\n\tvar customer example.ExaCustomer\n\terr := c.ShouldBindJSON(&customer)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(customer.GVA_MODEL, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(customer, utils.CustomerVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = customerService.UpdateExaCustomer(&customer)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// GetExaCustomer\n// @Tags      ExaCustomer\n// @Summary   获取单一客户信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     example.ExaCustomer                                                true  \"客户ID\"\n// @Success   200   {object}  response.Response{data=exampleRes.ExaCustomerResponse,msg=string}  \"获取单一客户信息,返回包括客户详情\"\n// @Router    /customer/customer [get]\nfunc (e *CustomerApi) GetExaCustomer(c *gin.Context) {\n\tvar customer example.ExaCustomer\n\terr := c.ShouldBindQuery(&customer)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(customer.GVA_MODEL, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tdata, err := customerService.GetExaCustomer(customer.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(exampleRes.ExaCustomerResponse{Customer: data}, \"获取成功\", c)\n}\n\n// GetExaCustomerList\n// @Tags      ExaCustomer\n// @Summary   分页获取权限客户列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     request.PageInfo                                        true  \"页码, 每页大小\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取权限客户列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /customer/customerList [get]\nfunc (e *CustomerApi) GetExaCustomerList(c *gin.Context) {\n\tvar pageInfo request.PageInfo\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(pageInfo, utils.PageInfoVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tcustomerList, total, err := customerService.GetCustomerInfoList(utils.GetUserAuthorityId(c), pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     customerList,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/example/exa_file_upload_download.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example/request\"\n\texampleRes \"github.com/flipped-aurora/gin-vue-admin/server/model/example/response\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n\t\"strconv\"\n)\n\ntype FileUploadAndDownloadApi struct{}\n\n// UploadFile\n// @Tags      ExaFileUploadAndDownload\n// @Summary   上传文件示例\n// @Security  ApiKeyAuth\n// @accept    multipart/form-data\n// @Produce   application/json\n// @Param     file  formData  file                                                           true  \"上传文件示例\"\n// @Success   200   {object}  response.Response{data=exampleRes.ExaFileResponse,msg=string}  \"上传文件示例,返回包括文件详情\"\n// @Router    /fileUploadAndDownload/upload [post]\nfunc (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) {\n\tvar file example.ExaFileUploadAndDownload\n\tnoSave := c.DefaultQuery(\"noSave\", \"0\")\n\t_, header, err := c.Request.FormFile(\"file\")\n\tclassId, _ := strconv.Atoi(c.DefaultPostForm(\"classId\", \"0\"))\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"接收文件失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"接收文件失败\", c)\n\t\treturn\n\t}\n\tfile, err = fileUploadAndDownloadService.UploadFile(header, noSave, classId) // 文件上传后拿到文件路径\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"上传文件失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"上传文件失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(exampleRes.ExaFileResponse{File: file}, \"上传成功\", c)\n}\n\n// EditFileName 编辑文件名或者备注\nfunc (b *FileUploadAndDownloadApi) EditFileName(c *gin.Context) {\n\tvar file example.ExaFileUploadAndDownload\n\terr := c.ShouldBindJSON(&file)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = fileUploadAndDownloadService.EditFileName(file)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"编辑失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"编辑失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"编辑成功\", c)\n}\n\n// DeleteFile\n// @Tags      ExaFileUploadAndDownload\n// @Summary   删除文件\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     data  body      example.ExaFileUploadAndDownload  true  \"传入文件里面id即可\"\n// @Success   200   {object}  response.Response{msg=string}     \"删除文件\"\n// @Router    /fileUploadAndDownload/deleteFile [post]\nfunc (b *FileUploadAndDownloadApi) DeleteFile(c *gin.Context) {\n\tvar file example.ExaFileUploadAndDownload\n\terr := c.ShouldBindJSON(&file)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := fileUploadAndDownloadService.DeleteFile(file); err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// GetFileList\n// @Tags      ExaFileUploadAndDownload\n// @Summary   分页文件列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.ExaAttachmentCategorySearch                                        true  \"页码, 每页大小, 分类id\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页文件列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /fileUploadAndDownload/getFileList [post]\nfunc (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) {\n\tvar pageInfo request.ExaAttachmentCategorySearch\n\terr := c.ShouldBindJSON(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := fileUploadAndDownloadService.GetFileRecordInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// ImportURL\n// @Tags      ExaFileUploadAndDownload\n// @Summary   导入URL\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     data  body      example.ExaFileUploadAndDownload  true  \"对象\"\n// @Success   200   {object}  response.Response{msg=string}     \"导入URL\"\n// @Router    /fileUploadAndDownload/importURL [post]\nfunc (b *FileUploadAndDownloadApi) ImportURL(c *gin.Context) {\n\tvar file []example.ExaFileUploadAndDownload\n\terr := c.ShouldBindJSON(&file)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := fileUploadAndDownloadService.ImportURL(&file); err != nil {\n\t\tglobal.GVA_LOG.Error(\"导入URL失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"导入URL失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"导入URL成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/auto_code_history.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\trequest \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AutoCodeHistoryApi struct{}\n\n// First\n// @Tags      AutoCode\n// @Summary   获取meta信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetById                                            true  \"请求参数\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"获取meta信息\"\n// @Router    /autoCode/getMeta [post]\nfunc (a *AutoCodeHistoryApi) First(c *gin.Context) {\n\tvar info common.GetById\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tdata, err := autoCodeHistoryService.First(c.Request.Context(), info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"meta\": data}, \"获取成功\", c)\n}\n\n// Delete\n// @Tags      AutoCode\n// @Summary   删除回滚记录\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetById                true  \"请求参数\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除回滚记录\"\n// @Router    /autoCode/delSysHistory [post]\nfunc (a *AutoCodeHistoryApi) Delete(c *gin.Context) {\n\tvar info common.GetById\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = autoCodeHistoryService.Delete(c.Request.Context(), info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// RollBack\n// @Tags      AutoCode\n// @Summary   回滚自动生成代码\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.SysAutoHistoryRollBack             true  \"请求参数\"\n// @Success   200   {object}  response.Response{msg=string}  \"回滚自动生成代码\"\n// @Router    /autoCode/rollback [post]\nfunc (a *AutoCodeHistoryApi) RollBack(c *gin.Context) {\n\tvar info request.SysAutoHistoryRollBack\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = autoCodeHistoryService.RollBack(c.Request.Context(), info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"回滚成功\", c)\n}\n\n// GetList\n// @Tags      AutoCode\n// @Summary   查询回滚记录\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      common.PageInfo                                true  \"请求参数\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"查询回滚记录,返回包括列表,总数,页码,每页数量\"\n// @Router    /autoCode/getSysHistory [post]\nfunc (a *AutoCodeHistoryApi) GetList(c *gin.Context) {\n\tvar info common.PageInfo\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := autoCodeHistoryService.GetList(c.Request.Context(), info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     info.Page,\n\t\tPageSize: info.PageSize,\n\t}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/auto_code_mcp.go",
    "content": "package system\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/mcp/client\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\n// Create\n// @Tags      mcp\n// @Summary   自动McpTool\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.AutoMcpTool  true  \"创建自动代码\"\n// @Success   200   {string}  string                 \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router    /autoCode/mcp [post]\nfunc (a *AutoCodeTemplateApi) MCP(c *gin.Context) {\n\tvar info request.AutoMcpTool\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\ttoolFilePath, err := autoCodeTemplateService.CreateMcp(c.Request.Context(), info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\tglobal.GVA_LOG.Error(err.Error())\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功,MCP Tool路径:\"+toolFilePath, c)\n}\n\n// Create\n// @Tags      mcp\n// @Summary   自动McpTool\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.AutoMcpTool  true  \"创建自动代码\"\n// @Success   200   {string}  string                 \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router    /autoCode/mcpList [post]\nfunc (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {\n\n\tbaseUrl := fmt.Sprintf(\"http://127.0.0.1:%d%s\", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)\n\n\ttestClient, err := client.NewClient(baseUrl, \"testClient\", \"v1.0.0\", global.GVA_CONFIG.MCP.Name)\n\tdefer testClient.Close()\n\ttoolsRequest := mcp.ListToolsRequest{}\n\n\tlist, err := testClient.ListTools(c.Request.Context(), toolsRequest)\n\n\tif err != nil {\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\tglobal.GVA_LOG.Error(err.Error())\n\t\treturn\n\t}\n\n\tmcpServerConfig := map[string]interface{}{\n\t\t\"mcpServers\": map[string]interface{}{\n\t\t\tglobal.GVA_CONFIG.MCP.Name: map[string]string{\n\t\t\t\t\"url\": baseUrl,\n\t\t\t},\n\t\t},\n\t}\n\tresponse.OkWithData(gin.H{\n\t\t\"mcpServerConfig\": mcpServerConfig,\n\t\t\"list\":            list,\n\t}, c)\n}\n\n// Create\n// @Tags      mcp\n// @Summary   测试McpTool\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      object  true  \"调用MCP Tool的参数\"\n// @Success   200   {object}  response.Response  \"{\"success\":true,\"data\":{},\"msg\":\"测试成功\"}\"\n// @Router    /autoCode/mcpTest [post]\nfunc (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {\n\t// 定义接口请求结构\n\tvar testRequest struct {\n\t\tName      string                 `json:\"name\" binding:\"required\"`      // 工具名称\n\t\tArguments map[string]interface{} `json:\"arguments\" binding:\"required\"` // 工具参数\n\t}\n\n\t// 绑定JSON请求体\n\tif err := c.ShouldBindJSON(&testRequest); err != nil {\n\t\tresponse.FailWithMessage(\"参数解析失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\t// 创建MCP客户端\n\tbaseUrl := fmt.Sprintf(\"http://127.0.0.1:%d%s\", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)\n\ttestClient, err := client.NewClient(baseUrl, \"testClient\", \"v1.0.0\", global.GVA_CONFIG.MCP.Name)\n\tif err != nil {\n\t\tresponse.FailWithMessage(\"创建MCP客户端失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tdefer testClient.Close()\n\n\tctx := c.Request.Context()\n\n\t// 初始化MCP连接\n\tinitRequest := mcp.InitializeRequest{}\n\tinitRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION\n\tinitRequest.Params.ClientInfo = mcp.Implementation{\n\t\tName:    \"testClient\",\n\t\tVersion: \"v1.0.0\",\n\t}\n\n\t_, err = testClient.Initialize(ctx, initRequest)\n\tif err != nil {\n\t\tresponse.FailWithMessage(\"初始化MCP连接失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\t// 构建工具调用请求\n\trequest := mcp.CallToolRequest{}\n\trequest.Params.Name = testRequest.Name\n\trequest.Params.Arguments = testRequest.Arguments\n\n\t// 调用工具\n\tresult, err := testClient.CallTool(ctx, request)\n\tif err != nil {\n\t\tresponse.FailWithMessage(\"工具调用失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\t// 处理响应结果\n\tif len(result.Content) == 0 {\n\t\tresponse.FailWithMessage(\"工具未返回任何内容\", c)\n\t\treturn\n\t}\n\n\t// 返回结果\n\tresponse.OkWithData(result.Content, c)\n}\n"
  },
  {
    "path": "server/api/v1/system/auto_code_package.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n\t\"strings\"\n)\n\ntype AutoCodePackageApi struct{}\n\n// Create\n// @Tags      AutoCodePackage\n// @Summary   创建package\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.SysAutoCodePackageCreate                                         true  \"创建package\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"创建package成功\"\n// @Router    /autoCode/createPackage [post]\nfunc (a *AutoCodePackageApi) Create(c *gin.Context) {\n\tvar info request.SysAutoCodePackageCreate\n\t_ = c.ShouldBindJSON(&info)\n\tif err := utils.Verify(info, utils.AutoPackageVerify); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif strings.Contains(info.PackageName, \"\\\\\") || strings.Contains(info.PackageName, \"/\") || strings.Contains(info.PackageName, \"..\") {\n\t\tresponse.FailWithMessage(\"包名不合法\", c)\n\t\treturn\n\t} // PackageName可能导致路径穿越的问题 / 和 \\ 都要防止\n\terr := autoCodePackageService.Create(c.Request.Context(), &info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// Delete\n// @Tags      AutoCode\n// @Summary   删除package\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      common.GetById                                         true  \"创建package\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"删除package成功\"\n// @Router    /autoCode/delPackage [post]\nfunc (a *AutoCodePackageApi) Delete(c *gin.Context) {\n\tvar info common.GetById\n\t_ = c.ShouldBindJSON(&info)\n\terr := autoCodePackageService.Delete(c.Request.Context(), info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// All\n// @Tags      AutoCodePackage\n// @Summary   获取package\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"创建package成功\"\n// @Router    /autoCode/getPackage [post]\nfunc (a *AutoCodePackageApi) All(c *gin.Context) {\n\tdata, err := autoCodePackageService.All(c.Request.Context())\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"pkgs\": data}, \"获取成功\", c)\n}\n\n// Templates\n// @Tags      AutoCodePackage\n// @Summary   获取package\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"创建package成功\"\n// @Router    /autoCode/getTemplates [get]\nfunc (a *AutoCodePackageApi) Templates(c *gin.Context) {\n\tdata, err := autoCodePackageService.Templates(c.Request.Context())\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(data, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/auto_code_plugin.go",
    "content": "package system\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AutoCodePluginApi struct{}\n\n// Install\n// @Tags      AutoCodePlugin\n// @Summary   安装插件\n// @Security  ApiKeyAuth\n// @accept    multipart/form-data\n// @Produce   application/json\n// @Param     plug  formData  file                                              true  \"this is a test file\"\n// @Success   200   {object}  response.Response{data=[]interface{},msg=string}  \"安装插件成功\"\n// @Router    /autoCode/installPlugin [post]\nfunc (a *AutoCodePluginApi) Install(c *gin.Context) {\n\theader, err := c.FormFile(\"plug\")\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tweb, server, err := autoCodePluginService.Install(header)\n\twebStr := \"web插件安装成功\"\n\tserverStr := \"server插件安装成功\"\n\tif web == -1 {\n\t\twebStr = \"web端插件未成功安装，请按照文档自行解压安装，如果为纯后端插件请忽略此条提示\"\n\t}\n\tif server == -1 {\n\t\tserverStr = \"server端插件未成功安装，请按照文档自行解压安装，如果为纯前端插件请忽略此条提示\"\n\t}\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithData([]interface{}{\n\t\tgin.H{\n\t\t\t\"code\": web,\n\t\t\t\"msg\":  webStr,\n\t\t},\n\t\tgin.H{\n\t\t\t\"code\": server,\n\t\t\t\"msg\":  serverStr,\n\t\t}}, c)\n}\n\n// Packaged\n// @Tags      AutoCodePlugin\n// @Summary   打包插件\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     plugName  query    string  true  \"插件名称\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"打包插件成功\"\n// @Router    /autoCode/pubPlug [post]\nfunc (a *AutoCodePluginApi) Packaged(c *gin.Context) {\n\tplugName := c.Query(\"plugName\")\n\tzipPath, err := autoCodePluginService.PubPlug(plugName)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"打包失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"打包失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(fmt.Sprintf(\"打包成功,文件路径为:%s\", zipPath), c)\n}\n\n// InitMenu\n// @Tags      AutoCodePlugin\n// @Summary   打包插件\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"打包插件成功\"\n// @Router    /autoCode/initMenu [post]\nfunc (a *AutoCodePluginApi) InitMenu(c *gin.Context) {\n\tvar menuInfo request.InitMenu\n\terr := c.ShouldBindJSON(&menuInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = autoCodePluginService.InitMenu(menuInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建初始化Menu失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建初始化Menu失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"文件变更成功\", c)\n}\n\n// InitAPI\n// @Tags      AutoCodePlugin\n// @Summary   打包插件\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"打包插件成功\"\n// @Router    /autoCode/initAPI [post]\nfunc (a *AutoCodePluginApi) InitAPI(c *gin.Context) {\n\tvar apiInfo request.InitApi\n\terr := c.ShouldBindJSON(&apiInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = autoCodePluginService.InitAPI(apiInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建初始化API失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建初始化API失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"文件变更成功\", c)\n}\n\n// InitDictionary\n// @Tags      AutoCodePlugin\n// @Summary   打包插件\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"打包插件成功\"\n// @Router    /autoCode/initDictionary [post]\nfunc (a *AutoCodePluginApi) InitDictionary(c *gin.Context) {\n\tvar dictInfo request.InitDictionary\n\terr := c.ShouldBindJSON(&dictInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = autoCodePluginService.InitDictionary(dictInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建初始化Dictionary失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建初始化Dictionary失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"文件变更成功\", c)\n}\n\n// GetPluginList\n// @Tags      AutoCodePlugin\n// @Summary   获取插件列表\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Success   200   {object}  response.Response{data=[]systemRes.PluginInfo}  \"获取插件列表成功\"\n// @Router    /autoCode/getPluginList [get]\nfunc (a *AutoCodePluginApi) GetPluginList(c *gin.Context) {\n\tserverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\")\n\twebDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, \"plugin\")\n\n\tserverEntries, _ := os.ReadDir(serverDir)\n\twebEntries, _ := os.ReadDir(webDir)\n\n\tconfigMap := make(map[string]string)\n\n\tfor _, entry := range serverEntries {\n\t\tif entry.IsDir() {\n\t\t\tconfigMap[entry.Name()] = \"server\"\n\t\t}\n\t}\n\n\tfor _, entry := range webEntries {\n\t\tif entry.IsDir() {\n\t\t\tif val, ok := configMap[entry.Name()]; ok {\n\t\t\t\tif val == \"server\" {\n\t\t\t\t\tconfigMap[entry.Name()] = \"full\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconfigMap[entry.Name()] = \"web\"\n\t\t\t}\n\t\t}\n\t}\n\n\tvar list []systemRes.PluginInfo\n\tfor k, v := range configMap {\n\t\tapis, menus, dicts := utils.GetPluginData(k)\n\t\tlist = append(list, systemRes.PluginInfo{\n\t\t\tPluginName:   k,\n\t\t\tPluginType:   v,\n\t\t\tApis:         apis,\n\t\t\tMenus:        menus,\n\t\t\tDictionaries: dicts,\n\t\t})\n\t}\n\n\tresponse.OkWithDetailed(list, \"获取成功\", c)\n}\n\n// Remove\n// @Tags      AutoCodePlugin\n// @Summary   删除插件\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     pluginName  query    string  true  \"插件名称\"\n// @Param     pluginType  query    string  true  \"插件类型\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除插件成功\"\n// @Router    /autoCode/removePlugin [post]\nfunc (a *AutoCodePluginApi) Remove(c *gin.Context) {\n\tpluginName := c.Query(\"pluginName\")\n\tpluginType := c.Query(\"pluginType\")\n\terr := autoCodePluginService.Remove(pluginName, pluginType)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/auto_code_template.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AutoCodeTemplateApi struct{}\n\n// Preview\n// @Tags      AutoCodeTemplate\n// @Summary   预览创建后的代码\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.AutoCode                                      true  \"预览创建代码\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"预览创建后的代码\"\n// @Router    /autoCode/preview [post]\nfunc (a *AutoCodeTemplateApi) Preview(c *gin.Context) {\n\tvar info request.AutoCode\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(info, utils.AutoCodeVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = info.Pretreatment()\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tinfo.PackageT = utils.FirstUpper(info.Package)\n\tautoCode, err := autoCodeTemplateService.Preview(c.Request.Context(), info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(err.Error(), zap.Error(err))\n\t\tresponse.FailWithMessage(\"预览失败:\"+err.Error(), c)\n\t} else {\n\t\tresponse.OkWithDetailed(gin.H{\"autoCode\": autoCode}, \"预览成功\", c)\n\t}\n}\n\n// Create\n// @Tags      AutoCodeTemplate\n// @Summary   自动代码模板\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.AutoCode  true  \"创建自动代码\"\n// @Success   200   {string}  string                 \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router    /autoCode/createTemp [post]\nfunc (a *AutoCodeTemplateApi) Create(c *gin.Context) {\n\tvar info request.AutoCode\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(info, utils.AutoCodeVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = info.Pretreatment()\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = autoCodeTemplateService.Create(c.Request.Context(), info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t} else {\n\t\tresponse.OkWithMessage(\"创建成功\", c)\n\t}\n}\n\n// AddFunc\n// @Tags      AddFunc\n// @Summary   增加方法\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.AutoCode  true  \"增加方法\"\n// @Success   200   {string}  string                 \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router    /autoCode/addFunc [post]\nfunc (a *AutoCodeTemplateApi) AddFunc(c *gin.Context) {\n\tvar info request.AutoFunc\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tvar tempMap map[string]string\n\tif info.IsPreview {\n\t\tinfo.Router = \"填充router\"\n\t\tinfo.FuncName = \"填充funcName\"\n\t\tinfo.Method = \"填充method\"\n\t\tinfo.Description = \"填充description\"\n\t\ttempMap, err = autoCodeTemplateService.GetApiAndServer(info)\n\t} else {\n\t\terr = autoCodeTemplateService.AddFunc(info)\n\t}\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"注入失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"注入失败\", c)\n\t} else {\n\t\tif info.IsPreview {\n\t\t\tresponse.OkWithDetailed(tempMap, \"注入成功\", c)\n\t\t\treturn\n\t\t}\n\t\tresponse.OkWithMessage(\"注入成功\", c)\n\t}\n}\n"
  },
  {
    "path": "server/api/v1/system/enter.go",
    "content": "package system\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\ntype ApiGroup struct {\n\tDBApi\n\tJwtApi\n\tBaseApi\n\tSystemApi\n\tCasbinApi\n\tAutoCodeApi\n\tSystemApiApi\n\tAuthorityApi\n\tDictionaryApi\n\tAuthorityMenuApi\n\tOperationRecordApi\n\tDictionaryDetailApi\n\tAuthorityBtnApi\n\tSysExportTemplateApi\n\tAutoCodePluginApi\n\tAutoCodePackageApi\n\tAutoCodeHistoryApi\n\tAutoCodeTemplateApi\n\tSysParamsApi\n\tSysVersionApi\n\tSysErrorApi\n\tLoginLogApi\n\tApiTokenApi\n\tSkillsApi\n}\n\nvar (\n\tapiService              = service.ServiceGroupApp.SystemServiceGroup.ApiService\n\tjwtService              = service.ServiceGroupApp.SystemServiceGroup.JwtService\n\tmenuService             = service.ServiceGroupApp.SystemServiceGroup.MenuService\n\tuserService             = service.ServiceGroupApp.SystemServiceGroup.UserService\n\tinitDBService           = service.ServiceGroupApp.SystemServiceGroup.InitDBService\n\tcasbinService           = service.ServiceGroupApp.SystemServiceGroup.CasbinService\n\tbaseMenuService         = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService\n\tauthorityService        = service.ServiceGroupApp.SystemServiceGroup.AuthorityService\n\tdictionaryService       = service.ServiceGroupApp.SystemServiceGroup.DictionaryService\n\tauthorityBtnService     = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService\n\tsystemConfigService     = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService\n\tsysParamsService        = service.ServiceGroupApp.SystemServiceGroup.SysParamsService\n\toperationRecordService  = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService\n\tdictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService\n\tautoCodeService         = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService\n\tautoCodePluginService   = service.ServiceGroupApp.SystemServiceGroup.AutoCodePlugin\n\tautoCodePackageService  = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage\n\tautoCodeHistoryService  = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory\n\tautoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate\n\tsysVersionService       = service.ServiceGroupApp.SystemServiceGroup.SysVersionService\n\tsysErrorService         = service.ServiceGroupApp.SystemServiceGroup.SysErrorService\n\tloginLogService         = service.ServiceGroupApp.SystemServiceGroup.LoginLogService\n\tapiTokenService         = service.ServiceGroupApp.SystemServiceGroup.ApiTokenService\n\tskillsService           = service.ServiceGroupApp.SystemServiceGroup.SkillsService\n)\n"
  },
  {
    "path": "server/api/v1/system/sys_api.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype SystemApiApi struct{}\n\n// CreateApi\n// @Tags      SysApi\n// @Summary   创建基础api\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysApi                  true  \"api路径, api中文描述, api组, 方法\"\n// @Success   200   {object}  response.Response{msg=string}  \"创建基础api\"\n// @Router    /api/createApi [post]\nfunc (s *SystemApiApi) CreateApi(c *gin.Context) {\n\tvar api system.SysApi\n\terr := c.ShouldBindJSON(&api)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(api, utils.ApiVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiService.CreateApi(api)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// SyncApi\n// @Tags      SysApi\n// @Summary   同步API\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{msg=string}  \"同步API\"\n// @Router    /api/syncApi [get]\nfunc (s *SystemApiApi) SyncApi(c *gin.Context) {\n\tnewApis, deleteApis, ignoreApis, err := apiService.SyncApi()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"同步失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"同步失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithData(gin.H{\n\t\t\"newApis\":    newApis,\n\t\t\"deleteApis\": deleteApis,\n\t\t\"ignoreApis\": ignoreApis,\n\t}, c)\n}\n\n// GetApiGroups\n// @Tags      SysApi\n// @Summary   获取API分组\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{msg=string}  \"获取API分组\"\n// @Router    /api/getApiGroups [get]\nfunc (s *SystemApiApi) GetApiGroups(c *gin.Context) {\n\tgroups, apiGroupMap, err := apiService.GetApiGroups()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithData(gin.H{\n\t\t\"groups\":      groups,\n\t\t\"apiGroupMap\": apiGroupMap,\n\t}, c)\n}\n\n// IgnoreApi\n// @Tags      IgnoreApi\n// @Summary   忽略API\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{msg=string}  \"同步API\"\n// @Router    /api/ignoreApi [post]\nfunc (s *SystemApiApi) IgnoreApi(c *gin.Context) {\n\tvar ignoreApi system.SysIgnoreApi\n\terr := c.ShouldBindJSON(&ignoreApi)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiService.IgnoreApi(ignoreApi)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"忽略失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"忽略失败\", c)\n\t\treturn\n\t}\n\tresponse.Ok(c)\n}\n\n// EnterSyncApi\n// @Tags      SysApi\n// @Summary   确认同步API\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{msg=string}  \"确认同步API\"\n// @Router    /api/enterSyncApi [post]\nfunc (s *SystemApiApi) EnterSyncApi(c *gin.Context) {\n\tvar syncApi systemRes.SysSyncApis\n\terr := c.ShouldBindJSON(&syncApi)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiService.EnterSyncApi(syncApi)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"忽略失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"忽略失败\", c)\n\t\treturn\n\t}\n\tresponse.Ok(c)\n}\n\n// DeleteApi\n// @Tags      SysApi\n// @Summary   删除api\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysApi                  true  \"ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除api\"\n// @Router    /api/deleteApi [post]\nfunc (s *SystemApiApi) DeleteApi(c *gin.Context) {\n\tvar api system.SysApi\n\terr := c.ShouldBindJSON(&api)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(api.GVA_MODEL, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiService.DeleteApi(api)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// GetApiList\n// @Tags      SysApi\n// @Summary   分页获取API列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.SearchApiParams                               true  \"分页获取API列表\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取API列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /api/getApiList [post]\nfunc (s *SystemApiApi) GetApiList(c *gin.Context) {\n\tvar pageInfo systemReq.SearchApiParams\n\terr := c.ShouldBindJSON(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(pageInfo.PageInfo, utils.PageInfoVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := apiService.GetAPIInfoList(pageInfo.SysApi, pageInfo.PageInfo, pageInfo.OrderKey, pageInfo.Desc)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// GetApiById\n// @Tags      SysApi\n// @Summary   根据id获取api\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetById                                   true  \"根据id获取api\"\n// @Success   200   {object}  response.Response{data=systemRes.SysAPIResponse}  \"根据id获取api,返回包括api详情\"\n// @Router    /api/getApiById [post]\nfunc (s *SystemApiApi) GetApiById(c *gin.Context) {\n\tvar idInfo request.GetById\n\terr := c.ShouldBindJSON(&idInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(idInfo, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tapi, err := apiService.GetApiById(idInfo.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysAPIResponse{Api: api}, \"获取成功\", c)\n}\n\n// UpdateApi\n// @Tags      SysApi\n// @Summary   修改基础api\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysApi                  true  \"api路径, api中文描述, api组, 方法\"\n// @Success   200   {object}  response.Response{msg=string}  \"修改基础api\"\n// @Router    /api/updateApi [post]\nfunc (s *SystemApiApi) UpdateApi(c *gin.Context) {\n\tvar api system.SysApi\n\terr := c.ShouldBindJSON(&api)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(api, utils.ApiVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiService.UpdateApi(api)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"修改失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"修改失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"修改成功\", c)\n}\n\n// GetAllApis\n// @Tags      SysApi\n// @Summary   获取所有的Api 不分页\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=systemRes.SysAPIListResponse,msg=string}  \"获取所有的Api 不分页,返回包括api列表\"\n// @Router    /api/getAllApis [post]\nfunc (s *SystemApiApi) GetAllApis(c *gin.Context) {\n\tauthorityID := utils.GetUserAuthorityId(c)\n\tapis, err := apiService.GetAllApis(authorityID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysAPIListResponse{Apis: apis}, \"获取成功\", c)\n}\n\n// DeleteApisByIds\n// @Tags      SysApi\n// @Summary   删除选中Api\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.IdsReq                 true  \"ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除选中Api\"\n// @Router    /api/deleteApisByIds [delete]\nfunc (s *SystemApiApi) DeleteApisByIds(c *gin.Context) {\n\tvar ids request.IdsReq\n\terr := c.ShouldBindJSON(&ids)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiService.DeleteApisByIds(ids)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// FreshCasbin\n// @Tags      SysApi\n// @Summary   刷新casbin缓存\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{msg=string}  \"刷新成功\"\n// @Router    /api/freshCasbin [get]\nfunc (s *SystemApiApi) FreshCasbin(c *gin.Context) {\n\terr := casbinService.FreshCasbin()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"刷新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"刷新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"刷新成功\", c)\n}\n\n// GetApiRoles\n// @Tags      SysApi\n// @Summary   获取拥有指定API权限的角色ID列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     path    query     string                                                    true  \"API路径\"\n// @Param     method  query     string                                                    true  \"请求方法\"\n// @Success   200     {object}  response.Response{data=map[string]interface{},msg=string}  \"获取成功\"\n// @Router    /api/getApiRoles [get]\nfunc (s *SystemApiApi) GetApiRoles(c *gin.Context) {\n\tpath := c.Query(\"path\")\n\tmethod := c.Query(\"method\")\n\tif path == \"\" || method == \"\" {\n\t\tresponse.FailWithMessage(\"API路径和请求方法不能为空\", c)\n\t\treturn\n\t}\n\tauthorityIds, err := casbinService.GetAuthoritiesByApi(path, method)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tif authorityIds == nil {\n\t\tauthorityIds = []uint{}\n\t}\n\tresponse.OkWithDetailed(authorityIds, \"获取成功\", c)\n}\n\n// SetApiRoles\n// @Tags      SysApi\n// @Summary   全量覆盖某API关联的角色列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.SetApiAuthorities    true  \"API路径、请求方法和角色ID列表\"\n// @Success   200   {object}  response.Response{msg=string}  \"设置成功\"\n// @Router    /api/setApiRoles [post]\nfunc (s *SystemApiApi) SetApiRoles(c *gin.Context) {\n\tvar req systemReq.SetApiAuthorities\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif req.Path == \"\" || req.Method == \"\" {\n\t\tresponse.FailWithMessage(\"API路径和请求方法不能为空\", c)\n\t\treturn\n\t}\n\tif err := casbinService.SetApiAuthorities(req.Path, req.Method, req.AuthorityIds); err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\"+err.Error(), c)\n\t\treturn\n\t}\n\t// 刷新casbin缓存使策略立即生效\n\t_ = casbinService.FreshCasbin()\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_api_token.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsysReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype ApiTokenApi struct{}\n\n// CreateApiToken 签发Token\nfunc (s *ApiTokenApi) CreateApiToken(c *gin.Context) {\n\tvar req struct {\n\t\tUserID      uint   `json:\"userId\"`\n\t\tAuthorityID uint   `json:\"authorityId\"`\n\t\tDays        int    `json:\"days\"` // -1为永久, 其他为天数\n\t\tRemark      string `json:\"remark\"`\n\t}\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\ttoken := system.SysApiToken{\n\t\tUserID:      req.UserID,\n\t\tAuthorityID: req.AuthorityID,\n\t\tRemark:      req.Remark,\n\t}\n\n\tjwtStr, err := apiTokenService.CreateApiToken(token, req.Days)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"签发失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"签发失败: \"+err.Error(), c)\n\t\treturn\n\t}\n\n\tresponse.OkWithDetailed(gin.H{\"token\": jwtStr}, \"签发成功\", c)\n}\n\n// GetApiTokenList 获取列表\nfunc (s *ApiTokenApi) GetApiTokenList(c *gin.Context) {\n\tvar pageInfo sysReq.SysApiTokenSearch\n\terr := c.ShouldBindJSON(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := apiTokenService.GetApiTokenList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// DeleteApiToken 作废Token\nfunc (s *ApiTokenApi) DeleteApiToken(c *gin.Context) {\n\tvar req system.SysApiToken\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = apiTokenService.DeleteApiToken(req.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"作废失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"作废失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"作废成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_authority.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AuthorityApi struct{}\n\n// CreateAuthority\n// @Tags      Authority\n// @Summary   创建角色\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysAuthority                                                true  \"权限id, 权限名, 父角色id\"\n// @Success   200   {object}  response.Response{data=systemRes.SysAuthorityResponse,msg=string}  \"创建角色,返回包括系统角色详情\"\n// @Router    /authority/createAuthority [post]\nfunc (a *AuthorityApi) CreateAuthority(c *gin.Context) {\n\tvar authority, authBack system.SysAuthority\n\tvar err error\n\n\tif err = c.ShouldBindJSON(&authority); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\tif err = utils.Verify(authority, utils.AuthorityVerify); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\tif *authority.ParentId == 0 && global.GVA_CONFIG.System.UseStrictAuth {\n\t\tauthority.ParentId = utils.Pointer(utils.GetUserAuthorityId(c))\n\t}\n\n\tif authBack, err = authorityService.CreateAuthority(authority); err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\"+err.Error(), c)\n\t\treturn\n\t}\n\terr = casbinService.FreshCasbin()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建成功，权限刷新失败。\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建成功，权限刷新失败。\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, \"创建成功\", c)\n}\n\n// CopyAuthority\n// @Tags      Authority\n// @Summary   拷贝角色\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      response.SysAuthorityCopyResponse                                  true  \"旧角色id, 新权限id, 新权限名, 新父角色id\"\n// @Success   200   {object}  response.Response{data=systemRes.SysAuthorityResponse,msg=string}  \"拷贝角色,返回包括系统角色详情\"\n// @Router    /authority/copyAuthority [post]\nfunc (a *AuthorityApi) CopyAuthority(c *gin.Context) {\n\tvar copyInfo systemRes.SysAuthorityCopyResponse\n\terr := c.ShouldBindJSON(&copyInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(copyInfo, utils.OldAuthorityVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(copyInfo.Authority, utils.AuthorityVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tadminAuthorityID := utils.GetUserAuthorityId(c)\n\tauthBack, err := authorityService.CopyAuthority(adminAuthorityID, copyInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"拷贝失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"拷贝失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, \"拷贝成功\", c)\n}\n\n// DeleteAuthority\n// @Tags      Authority\n// @Summary   删除角色\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysAuthority            true  \"删除角色\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除角色\"\n// @Router    /authority/deleteAuthority [post]\nfunc (a *AuthorityApi) DeleteAuthority(c *gin.Context) {\n\tvar authority system.SysAuthority\n\tvar err error\n\tif err = c.ShouldBindJSON(&authority); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err = utils.Verify(authority, utils.AuthorityIdVerify); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\t// 删除角色之前需要判断是否有用户正在使用此角色\n\tif err = authorityService.DeleteAuthority(&authority); err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\"+err.Error(), c)\n\t\treturn\n\t}\n\t_ = casbinService.FreshCasbin()\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// UpdateAuthority\n// @Tags      Authority\n// @Summary   更新角色信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysAuthority                                                true  \"权限id, 权限名, 父角色id\"\n// @Success   200   {object}  response.Response{data=systemRes.SysAuthorityResponse,msg=string}  \"更新角色信息,返回包括系统角色详情\"\n// @Router    /authority/updateAuthority [put]\nfunc (a *AuthorityApi) UpdateAuthority(c *gin.Context) {\n\tvar auth system.SysAuthority\n\terr := c.ShouldBindJSON(&auth)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(auth, utils.AuthorityVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tauthority, err := authorityService.UpdateAuthority(auth)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authority}, \"更新成功\", c)\n}\n\n// GetAuthorityList\n// @Tags      Authority\n// @Summary   分页获取角色列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.PageInfo                                        true  \"页码, 每页大小\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取角色列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /authority/getAuthorityList [post]\nfunc (a *AuthorityApi) GetAuthorityList(c *gin.Context) {\n\tauthorityID := utils.GetUserAuthorityId(c)\n\tlist, err := authorityService.GetAuthorityInfoList(authorityID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(list, \"获取成功\", c)\n}\n\n// SetDataAuthority\n// @Tags      Authority\n// @Summary   设置角色资源权限\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysAuthority            true  \"设置角色资源权限\"\n// @Success   200   {object}  response.Response{msg=string}  \"设置角色资源权限\"\n// @Router    /authority/setDataAuthority [post]\nfunc (a *AuthorityApi) SetDataAuthority(c *gin.Context) {\n\tvar auth system.SysAuthority\n\terr := c.ShouldBindJSON(&auth)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(auth, utils.AuthorityIdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tadminAuthorityID := utils.GetUserAuthorityId(c)\n\terr = authorityService.SetDataAuthority(adminAuthorityID, auth)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n\n// GetUsersByAuthority\n// @Tags      Authority\n// @Summary   获取拥有指定角色的用户ID列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     authorityId  query     uint                                                        true  \"角色ID\"\n// @Success   200          {object}  response.Response{data=[]uint,msg=string}                   \"获取成功\"\n// @Router    /authority/getUsersByAuthority [get]\nfunc (a *AuthorityApi) GetUsersByAuthority(c *gin.Context) {\n\tvar req systemReq.SetRoleUsers\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tuserIds, err := authorityService.GetUserIdsByAuthorityId(req.AuthorityId)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tif userIds == nil {\n\t\tuserIds = []uint{}\n\t}\n\tresponse.OkWithDetailed(userIds, \"获取成功\", c)\n}\n\n// SetRoleUsers\n// @Tags      Authority\n// @Summary   全量覆盖某角色关联的用户列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.SetRoleUsers         true  \"角色ID和用户ID列表\"\n// @Success   200   {object}  response.Response{msg=string}  \"设置成功\"\n// @Router    /authority/setRoleUsers [post]\nfunc (a *AuthorityApi) SetRoleUsers(c *gin.Context) {\n\tvar req systemReq.SetRoleUsers\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif req.AuthorityId == 0 {\n\t\tresponse.FailWithMessage(\"角色ID不能为空\", c)\n\t\treturn\n\t}\n\tif err := authorityService.SetRoleUsers(req.AuthorityId, req.UserIds); err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_authority_btn.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AuthorityBtnApi struct{}\n\n// GetAuthorityBtn\n// @Tags      AuthorityBtn\n// @Summary   获取权限按钮\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.SysAuthorityBtnReq                                      true  \"菜单id, 角色id, 选中的按钮id\"\n// @Success   200   {object}  response.Response{data=response.SysAuthorityBtnRes,msg=string}  \"返回列表成功\"\n// @Router    /authorityBtn/getAuthorityBtn [post]\nfunc (a *AuthorityBtnApi) GetAuthorityBtn(c *gin.Context) {\n\tvar req request.SysAuthorityBtnReq\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tres, err := authorityBtnService.GetAuthorityBtn(req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(res, \"查询成功\", c)\n}\n\n// SetAuthorityBtn\n// @Tags      AuthorityBtn\n// @Summary   设置权限按钮\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.SysAuthorityBtnReq     true  \"菜单id, 角色id, 选中的按钮id\"\n// @Success   200   {object}  response.Response{msg=string}  \"返回列表成功\"\n// @Router    /authorityBtn/setAuthorityBtn [post]\nfunc (a *AuthorityBtnApi) SetAuthorityBtn(c *gin.Context) {\n\tvar req request.SysAuthorityBtnReq\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = authorityBtnService.SetAuthorityBtn(req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"分配失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"分配失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"分配成功\", c)\n}\n\n// CanRemoveAuthorityBtn\n// @Tags      AuthorityBtn\n// @Summary   设置权限按钮\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{msg=string}  \"删除成功\"\n// @Router    /authorityBtn/canRemoveAuthorityBtn [post]\nfunc (a *AuthorityBtnApi) CanRemoveAuthorityBtn(c *gin.Context) {\n\tid := c.Query(\"id\")\n\terr := authorityBtnService.CanRemoveAuthorityBtn(id)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_auto_code.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AutoCodeApi struct{}\n\n// GetDB\n// @Tags      AutoCode\n// @Summary   获取当前所有数据库\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"获取当前所有数据库\"\n// @Router    /autoCode/getDB [get]\nfunc (autoApi *AutoCodeApi) GetDB(c *gin.Context) {\n\tbusinessDB := c.Query(\"businessDB\")\n\tdbs, err := autoCodeService.Database(businessDB).GetDB(businessDB)\n\tvar dbList []map[string]interface{}\n\tfor _, db := range global.GVA_CONFIG.DBList {\n\t\tvar item = make(map[string]interface{})\n\t\titem[\"aliasName\"] = db.AliasName\n\t\titem[\"dbName\"] = db.Dbname\n\t\titem[\"disable\"] = db.Disable\n\t\titem[\"dbtype\"] = db.Type\n\t\tdbList = append(dbList, item)\n\t}\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t} else {\n\t\tresponse.OkWithDetailed(gin.H{\"dbs\": dbs, \"dbList\": dbList}, \"获取成功\", c)\n\t}\n}\n\n// GetTables\n// @Tags      AutoCode\n// @Summary   获取当前数据库所有表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"获取当前数据库所有表\"\n// @Router    /autoCode/getTables [get]\nfunc (autoApi *AutoCodeApi) GetTables(c *gin.Context) {\n\tdbName := c.Query(\"dbName\")\n\tbusinessDB := c.Query(\"businessDB\")\n\tif dbName == \"\" {\n\t\tdbName = *global.GVA_ACTIVE_DBNAME\n\t\tif businessDB != \"\" {\n\t\t\tfor _, db := range global.GVA_CONFIG.DBList {\n\t\t\t\tif db.AliasName == businessDB {\n\t\t\t\t\tdbName = db.Dbname\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\ttables, err := autoCodeService.Database(businessDB).GetTables(businessDB, dbName)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询table失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询table失败\", c)\n\t} else {\n\t\tresponse.OkWithDetailed(gin.H{\"tables\": tables}, \"获取成功\", c)\n\t}\n}\n\n// GetColumn\n// @Tags      AutoCode\n// @Summary   获取当前表所有字段\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"获取当前表所有字段\"\n// @Router    /autoCode/getColumn [get]\nfunc (autoApi *AutoCodeApi) GetColumn(c *gin.Context) {\n\tbusinessDB := c.Query(\"businessDB\")\n\tdbName := c.Query(\"dbName\")\n\tif dbName == \"\" {\n\t\tdbName = *global.GVA_ACTIVE_DBNAME\n\t\tif businessDB != \"\" {\n\t\t\tfor _, db := range global.GVA_CONFIG.DBList {\n\t\t\t\tif db.AliasName == businessDB {\n\t\t\t\t\tdbName = db.Dbname\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\ttableName := c.Query(\"tableName\")\n\tcolumns, err := autoCodeService.Database(businessDB).GetColumn(businessDB, tableName, dbName)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t} else {\n\t\tresponse.OkWithDetailed(gin.H{\"columns\": columns}, \"获取成功\", c)\n\t}\n}\n\nfunc (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) {\n\tvar llm common.JSONMap\n\tif err := c.ShouldBindJSON(&llm); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tdata, err := autoCodeService.LLMAuto(c.Request.Context(), llm)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"大模型生成失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"大模型生成失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithData(data, c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_captcha.go",
    "content": "package system\n\nimport (\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/mojocn/base64Captcha\"\n\t\"go.uber.org/zap\"\n)\n\n// 当开启多服务器部署时，替换下面的配置，使用redis共享存储验证码\n// var store = captcha.NewDefaultRedisStore()\nvar store = base64Captcha.DefaultMemStore\n\ntype BaseApi struct{}\n\n// Captcha\n// @Tags      Base\n// @Summary   生成验证码\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=systemRes.SysCaptchaResponse,msg=string}  \"生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码\"\n// @Router    /base/captcha [post]\nfunc (b *BaseApi) Captcha(c *gin.Context) {\n\t// 判断验证码是否开启\n\topenCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha               // 是否开启防爆次数\n\topenCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间\n\tkey := c.ClientIP()\n\tv, ok := global.BlackCache.Get(key)\n\tif !ok {\n\t\tglobal.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))\n\t}\n\n\tvar oc bool\n\tif openCaptcha == 0 || openCaptcha < interfaceToInt(v) {\n\t\toc = true\n\t}\n\t// 字符,公式,验证码配置\n\t// 生成默认数字的driver\n\tdriver := base64Captcha.NewDriverDigit(global.GVA_CONFIG.Captcha.ImgHeight, global.GVA_CONFIG.Captcha.ImgWidth, global.GVA_CONFIG.Captcha.KeyLong, 0.7, 80)\n\t// cp := base64Captcha.NewCaptcha(driver, store.UseWithCtx(c))   // v8下使用redis\n\tcp := base64Captcha.NewCaptcha(driver, store)\n\tid, b64s, _, err := cp.Generate()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"验证码获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"验证码获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysCaptchaResponse{\n\t\tCaptchaId:     id,\n\t\tPicPath:       b64s,\n\t\tCaptchaLength: global.GVA_CONFIG.Captcha.KeyLong,\n\t\tOpenCaptcha:   oc,\n\t}, \"验证码获取成功\", c)\n}\n\n// 类型转换\nfunc interfaceToInt(v interface{}) (i int) {\n\tswitch v := v.(type) {\n\tcase int:\n\t\ti = v\n\tdefault:\n\t\ti = 0\n\t}\n\treturn\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_casbin.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype CasbinApi struct{}\n\n// UpdateCasbin\n// @Tags      Casbin\n// @Summary   更新角色api权限\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.CasbinInReceive        true  \"权限id, 权限模型列表\"\n// @Success   200   {object}  response.Response{msg=string}  \"更新角色api权限\"\n// @Router    /casbin/UpdateCasbin [post]\nfunc (cas *CasbinApi) UpdateCasbin(c *gin.Context) {\n\tvar cmr request.CasbinInReceive\n\terr := c.ShouldBindJSON(&cmr)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(cmr, utils.AuthorityIdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tadminAuthorityID := utils.GetUserAuthorityId(c)\n\terr = casbinService.UpdateCasbin(adminAuthorityID, cmr.AuthorityId, cmr.CasbinInfos)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// GetPolicyPathByAuthorityId\n// @Tags      Casbin\n// @Summary   获取权限列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.CasbinInReceive                                          true  \"权限id, 权限模型列表\"\n// @Success   200   {object}  response.Response{data=systemRes.PolicyPathResponse,msg=string}  \"获取权限列表,返回包括casbin详情列表\"\n// @Router    /casbin/getPolicyPathByAuthorityId [post]\nfunc (cas *CasbinApi) GetPolicyPathByAuthorityId(c *gin.Context) {\n\tvar casbin request.CasbinInReceive\n\terr := c.ShouldBindJSON(&casbin)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(casbin, utils.AuthorityIdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tpaths := casbinService.GetPolicyPathByAuthorityId(casbin.AuthorityId)\n\tresponse.OkWithDetailed(systemRes.PolicyPathResponse{Paths: paths}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_dictionary.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype DictionaryApi struct{}\n\n// CreateSysDictionary\n// @Tags      SysDictionary\n// @Summary   创建SysDictionary\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysDictionary           true  \"SysDictionary模型\"\n// @Success   200   {object}  response.Response{msg=string}  \"创建SysDictionary\"\n// @Router    /sysDictionary/createSysDictionary [post]\nfunc (s *DictionaryApi) CreateSysDictionary(c *gin.Context) {\n\tvar dictionary system.SysDictionary\n\terr := c.ShouldBindJSON(&dictionary)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryService.CreateSysDictionary(dictionary)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// DeleteSysDictionary\n// @Tags      SysDictionary\n// @Summary   删除SysDictionary\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysDictionary           true  \"SysDictionary模型\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除SysDictionary\"\n// @Router    /sysDictionary/deleteSysDictionary [delete]\nfunc (s *DictionaryApi) DeleteSysDictionary(c *gin.Context) {\n\tvar dictionary system.SysDictionary\n\terr := c.ShouldBindJSON(&dictionary)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryService.DeleteSysDictionary(dictionary)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// UpdateSysDictionary\n// @Tags      SysDictionary\n// @Summary   更新SysDictionary\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysDictionary           true  \"SysDictionary模型\"\n// @Success   200   {object}  response.Response{msg=string}  \"更新SysDictionary\"\n// @Router    /sysDictionary/updateSysDictionary [put]\nfunc (s *DictionaryApi) UpdateSysDictionary(c *gin.Context) {\n\tvar dictionary system.SysDictionary\n\terr := c.ShouldBindJSON(&dictionary)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryService.UpdateSysDictionary(&dictionary)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// FindSysDictionary\n// @Tags      SysDictionary\n// @Summary   用id查询SysDictionary\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     system.SysDictionary                                       true  \"ID或字典英名\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"用id查询SysDictionary\"\n// @Router    /sysDictionary/findSysDictionary [get]\nfunc (s *DictionaryApi) FindSysDictionary(c *gin.Context) {\n\tvar dictionary system.SysDictionary\n\terr := c.ShouldBindQuery(&dictionary)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tsysDictionary, err := dictionaryService.GetSysDictionary(dictionary.Type, dictionary.ID, dictionary.Status)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"字典未创建或未开启!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"字典未创建或未开启\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"resysDictionary\": sysDictionary}, \"查询成功\", c)\n}\n\n// GetSysDictionaryList\n// @Tags      SysDictionary\n// @Summary   分页获取SysDictionary列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     request.SysDictionarySearch                                    true  \"字典 name 或者 type\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /sysDictionary/getSysDictionaryList [get]\nfunc (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) {\n\tvar dictionary request.SysDictionarySearch\n\terr := c.ShouldBindQuery(&dictionary)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, err := dictionaryService.GetSysDictionaryInfoList(c, dictionary)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(list, \"获取成功\", c)\n}\n\n// ExportSysDictionary\n// @Tags      SysDictionary\n// @Summary   导出字典JSON（包含字典详情）\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     system.SysDictionary                                       true  \"字典ID\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"导出字典JSON\"\n// @Router    /sysDictionary/exportSysDictionary [get]\nfunc (s *DictionaryApi) ExportSysDictionary(c *gin.Context) {\n\tvar dictionary system.SysDictionary\n\terr := c.ShouldBindQuery(&dictionary)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif dictionary.ID == 0 {\n\t\tresponse.FailWithMessage(\"字典ID不能为空\", c)\n\t\treturn\n\t}\n\texportData, err := dictionaryService.ExportSysDictionary(dictionary.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"导出失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"导出失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(exportData, \"导出成功\", c)\n}\n\n// ImportSysDictionary\n// @Tags      SysDictionary\n// @Summary   导入字典JSON（包含字典详情）\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.ImportSysDictionaryRequest     true  \"字典JSON数据\"\n// @Success   200   {object}  response.Response{msg=string}          \"导入字典\"\n// @Router    /sysDictionary/importSysDictionary [post]\nfunc (s *DictionaryApi) ImportSysDictionary(c *gin.Context) {\n\tvar req request.ImportSysDictionaryRequest\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryService.ImportSysDictionary(req.Json)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"导入失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"导入失败: \"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"导入成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_dictionary_detail.go",
    "content": "package system\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype DictionaryDetailApi struct{}\n\n// CreateSysDictionaryDetail\n// @Tags      SysDictionaryDetail\n// @Summary   创建SysDictionaryDetail\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysDictionaryDetail     true  \"SysDictionaryDetail模型\"\n// @Success   200   {object}  response.Response{msg=string}  \"创建SysDictionaryDetail\"\n// @Router    /sysDictionaryDetail/createSysDictionaryDetail [post]\nfunc (s *DictionaryDetailApi) CreateSysDictionaryDetail(c *gin.Context) {\n\tvar detail system.SysDictionaryDetail\n\terr := c.ShouldBindJSON(&detail)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryDetailService.CreateSysDictionaryDetail(detail)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// DeleteSysDictionaryDetail\n// @Tags      SysDictionaryDetail\n// @Summary   删除SysDictionaryDetail\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysDictionaryDetail     true  \"SysDictionaryDetail模型\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除SysDictionaryDetail\"\n// @Router    /sysDictionaryDetail/deleteSysDictionaryDetail [delete]\nfunc (s *DictionaryDetailApi) DeleteSysDictionaryDetail(c *gin.Context) {\n\tvar detail system.SysDictionaryDetail\n\terr := c.ShouldBindJSON(&detail)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryDetailService.DeleteSysDictionaryDetail(detail)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// UpdateSysDictionaryDetail\n// @Tags      SysDictionaryDetail\n// @Summary   更新SysDictionaryDetail\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysDictionaryDetail     true  \"更新SysDictionaryDetail\"\n// @Success   200   {object}  response.Response{msg=string}  \"更新SysDictionaryDetail\"\n// @Router    /sysDictionaryDetail/updateSysDictionaryDetail [put]\nfunc (s *DictionaryDetailApi) UpdateSysDictionaryDetail(c *gin.Context) {\n\tvar detail system.SysDictionaryDetail\n\terr := c.ShouldBindJSON(&detail)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = dictionaryDetailService.UpdateSysDictionaryDetail(&detail)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// FindSysDictionaryDetail\n// @Tags      SysDictionaryDetail\n// @Summary   用id查询SysDictionaryDetail\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     system.SysDictionaryDetail                                 true  \"用id查询SysDictionaryDetail\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"用id查询SysDictionaryDetail\"\n// @Router    /sysDictionaryDetail/findSysDictionaryDetail [get]\nfunc (s *DictionaryDetailApi) FindSysDictionaryDetail(c *gin.Context) {\n\tvar detail system.SysDictionaryDetail\n\terr := c.ShouldBindQuery(&detail)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(detail, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\treSysDictionaryDetail, err := dictionaryDetailService.GetSysDictionaryDetail(detail.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"reSysDictionaryDetail\": reSysDictionaryDetail}, \"查询成功\", c)\n}\n\n// GetSysDictionaryDetailList\n// @Tags      SysDictionaryDetail\n// @Summary   分页获取SysDictionaryDetail列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     request.SysDictionaryDetailSearch                       true  \"页码, 每页大小, 搜索条件\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /sysDictionaryDetail/getSysDictionaryDetailList [get]\nfunc (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) {\n\tvar pageInfo request.SysDictionaryDetailSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := dictionaryDetailService.GetSysDictionaryDetailInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// GetDictionaryTreeList\n// @Tags      SysDictionaryDetail\n// @Summary   获取字典详情树形结构\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     sysDictionaryID  query     int                                                true  \"字典ID\"\n// @Success   200              {object}  response.Response{data=[]system.SysDictionaryDetail,msg=string}  \"获取字典详情树形结构\"\n// @Router    /sysDictionaryDetail/getDictionaryTreeList [get]\nfunc (s *DictionaryDetailApi) GetDictionaryTreeList(c *gin.Context) {\n\tsysDictionaryID := c.Query(\"sysDictionaryID\")\n\tif sysDictionaryID == \"\" {\n\t\tresponse.FailWithMessage(\"字典ID不能为空\", c)\n\t\treturn\n\t}\n\n\tvar id uint\n\tif idUint64, err := strconv.ParseUint(sysDictionaryID, 10, 32); err != nil {\n\t\tresponse.FailWithMessage(\"字典ID格式错误\", c)\n\t\treturn\n\t} else {\n\t\tid = uint(idUint64)\n\t}\n\t\n\tlist, err := dictionaryDetailService.GetDictionaryTreeList(id)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"list\": list}, \"获取成功\", c)\n}\n\n// GetDictionaryTreeListByType\n// @Tags      SysDictionaryDetail\n// @Summary   根据字典类型获取字典详情树形结构\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     type  query     string                                                true  \"字典类型\"\n// @Success   200   {object}  response.Response{data=[]system.SysDictionaryDetail,msg=string}  \"获取字典详情树形结构\"\n// @Router    /sysDictionaryDetail/getDictionaryTreeListByType [get]\nfunc (s *DictionaryDetailApi) GetDictionaryTreeListByType(c *gin.Context) {\n\tdictType := c.Query(\"type\")\n\tif dictType == \"\" {\n\t\tresponse.FailWithMessage(\"字典类型不能为空\", c)\n\t\treturn\n\t}\n\t\n\tlist, err := dictionaryDetailService.GetDictionaryTreeListByType(dictType)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"list\": list}, \"获取成功\", c)\n}\n\n// GetDictionaryDetailsByParent\n// @Tags      SysDictionaryDetail\n// @Summary   根据父级ID获取字典详情\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     request.GetDictionaryDetailsByParentRequest                true  \"查询参数\"\n// @Success   200   {object}  response.Response{data=[]system.SysDictionaryDetail,msg=string}  \"获取字典详情列表\"\n// @Router    /sysDictionaryDetail/getDictionaryDetailsByParent [get]\nfunc (s *DictionaryDetailApi) GetDictionaryDetailsByParent(c *gin.Context) {\n\tvar req request.GetDictionaryDetailsByParentRequest\n\terr := c.ShouldBindQuery(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\t\n\tlist, err := dictionaryDetailService.GetDictionaryDetailsByParent(req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"list\": list}, \"获取成功\", c)\n}\n\n// GetDictionaryPath\n// @Tags      SysDictionaryDetail\n// @Summary   获取字典详情的完整路径\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     id  query     uint                                                true  \"字典详情ID\"\n// @Success   200 {object}  response.Response{data=[]system.SysDictionaryDetail,msg=string}  \"获取字典详情路径\"\n// @Router    /sysDictionaryDetail/getDictionaryPath [get]\nfunc (s *DictionaryDetailApi) GetDictionaryPath(c *gin.Context) {\n\tidStr := c.Query(\"id\")\n\tif idStr == \"\" {\n\t\tresponse.FailWithMessage(\"字典详情ID不能为空\", c)\n\t\treturn\n\t}\n\t\n\tvar id uint\n\tif idUint64, err := strconv.ParseUint(idStr, 10, 32); err != nil {\n\t\tresponse.FailWithMessage(\"字典详情ID格式错误\", c)\n\t\treturn\n\t} else {\n\t\tid = uint(idUint64)\n\t}\n\t\n\tpath, err := dictionaryDetailService.GetDictionaryPath(id)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"path\": path}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_error.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype SysErrorApi struct{}\n\n// CreateSysError 创建错误日志\n// @Tags SysError\n// @Summary 创建错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body system.SysError true \"创建错误日志\"\n// @Success 200 {object} response.Response{msg=string} \"创建成功\"\n// @Router /sysError/createSysError [post]\nfunc (sysErrorApi *SysErrorApi) CreateSysError(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tvar sysError system.SysError\n\terr := c.ShouldBindJSON(&sysError)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = sysErrorService.CreateSysError(ctx, &sysError)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// DeleteSysError 删除错误日志\n// @Tags SysError\n// @Summary 删除错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body system.SysError true \"删除错误日志\"\n// @Success 200 {object} response.Response{msg=string} \"删除成功\"\n// @Router /sysError/deleteSysError [delete]\nfunc (sysErrorApi *SysErrorApi) DeleteSysError(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tID := c.Query(\"ID\")\n\terr := sysErrorService.DeleteSysError(ctx, ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// DeleteSysErrorByIds 批量删除错误日志\n// @Tags SysError\n// @Summary 批量删除错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{msg=string} \"批量删除成功\"\n// @Router /sysError/deleteSysErrorByIds [delete]\nfunc (sysErrorApi *SysErrorApi) DeleteSysErrorByIds(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tIDs := c.QueryArray(\"IDs[]\")\n\terr := sysErrorService.DeleteSysErrorByIds(ctx, IDs)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\n// UpdateSysError 更新错误日志\n// @Tags SysError\n// @Summary 更新错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body system.SysError true \"更新错误日志\"\n// @Success 200 {object} response.Response{msg=string} \"更新成功\"\n// @Router /sysError/updateSysError [put]\nfunc (sysErrorApi *SysErrorApi) UpdateSysError(c *gin.Context) {\n\t// 从ctx获取标准context进行业务行为\n\tctx := c.Request.Context()\n\n\tvar sysError system.SysError\n\terr := c.ShouldBindJSON(&sysError)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = sysErrorService.UpdateSysError(ctx, sysError)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// FindSysError 用id查询错误日志\n// @Tags SysError\n// @Summary 用id查询错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param ID query uint true \"用id查询错误日志\"\n// @Success 200 {object} response.Response{data=system.SysError,msg=string} \"查询成功\"\n// @Router /sysError/findSysError [get]\nfunc (sysErrorApi *SysErrorApi) FindSysError(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tID := c.Query(\"ID\")\n\tresysError, err := sysErrorService.GetSysError(ctx, ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithData(resysError, c)\n}\n\n// GetSysErrorList 分页获取错误日志列表\n// @Tags SysError\n// @Summary 分页获取错误日志列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query systemReq.SysErrorSearch true \"分页获取错误日志列表\"\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /sysError/getSysErrorList [get]\nfunc (sysErrorApi *SysErrorApi) GetSysErrorList(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tvar pageInfo systemReq.SysErrorSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := sysErrorService.GetSysErrorInfoList(ctx, pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// GetSysErrorSolution 触发错误日志的异步处理\n// @Tags SysError\n// @Summary 根据ID触发处理：标记为处理中，1分钟后自动改为处理完成\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param id query string true \"错误日志ID\"\n// @Success 200 {object} response.Response{msg=string} \"处理已提交\"\n// @Router /sysError/getSysErrorSolution [get]\nfunc (sysErrorApi *SysErrorApi) GetSysErrorSolution(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\t// 兼容 id 与 ID 两种参数\n\tID := c.Query(\"id\")\n\tif ID == \"\" {\n\t\tresponse.FailWithMessage(\"缺少参数: id\", c)\n\t\treturn\n\t}\n\n\terr := sysErrorService.GetSysErrorSolution(ctx, ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"处理触发失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"处理触发失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\tresponse.OkWithMessage(\"已提交至AI处理\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_export_template.go",
    "content": "package system\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\n// 用于token一次性存储\nvar (\n\texportTokenCache      = make(map[string]interface{})\n\texportTokenExpiration = make(map[string]time.Time)\n\ttokenMutex            sync.RWMutex\n)\n\n// 五分钟检测窗口过期\nfunc cleanupExpiredTokens() {\n\tfor {\n\t\ttime.Sleep(5 * time.Minute)\n\t\ttokenMutex.Lock()\n\t\tnow := time.Now()\n\t\tfor token, expiry := range exportTokenExpiration {\n\t\t\tif now.After(expiry) {\n\t\t\t\tdelete(exportTokenCache, token)\n\t\t\t\tdelete(exportTokenExpiration, token)\n\t\t\t}\n\t\t}\n\t\ttokenMutex.Unlock()\n\t}\n}\n\nfunc init() {\n\tgo cleanupExpiredTokens()\n}\n\ntype SysExportTemplateApi struct {\n}\n\nvar sysExportTemplateService = service.ServiceGroupApp.SystemServiceGroup.SysExportTemplateService\n\n// PreviewSQL 预览最终生成的SQL\n// @Tags     SysExportTemplate\n// @Summary  预览最终生成的SQL（不执行查询，仅返回SQL字符串）\n// @Security ApiKeyAuth\n// @accept   application/json\n// @Produce  application/json\n// @Param    templateID query string true  \"导出模板ID\"\n// @Param    params     query string false \"查询参数编码字符串，参考 ExportExcel 组件\"\n// @Success  200  {object}  response.Response{data=map[string]string} \"获取成功\"\n// @Router   /sysExportTemplate/previewSQL [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) PreviewSQL(c *gin.Context) {\n    templateID := c.Query(\"templateID\")\n    if templateID == \"\" {\n        response.FailWithMessage(\"模板ID不能为空\", c)\n        return\n    }\n\n    // 直接复用导出接口的参数组织方式：使用 URL Query，其中 params 为内部编码的查询字符串\n    queryParams := c.Request.URL.Query()\n\n    if sqlPreview, err := sysExportTemplateService.PreviewSQL(templateID, queryParams); err != nil {\n        global.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n        response.FailWithMessage(\"获取失败\", c)\n    } else {\n        response.OkWithData(gin.H{\"sql\": sqlPreview}, c)\n    }\n}\n\n// CreateSysExportTemplate 创建导出模板\n// @Tags SysExportTemplate\n// @Summary 创建导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body system.SysExportTemplate true \"创建导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /sysExportTemplate/createSysExportTemplate [post]\nfunc (sysExportTemplateApi *SysExportTemplateApi) CreateSysExportTemplate(c *gin.Context) {\n\tvar sysExportTemplate system.SysExportTemplate\n\terr := c.ShouldBindJSON(&sysExportTemplate)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tverify := utils.Rules{\n\t\t\"Name\": {utils.NotEmpty()},\n\t}\n\tif err := utils.Verify(sysExportTemplate, verify); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := sysExportTemplateService.CreateSysExportTemplate(&sysExportTemplate); err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t} else {\n\t\tresponse.OkWithMessage(\"创建成功\", c)\n\t}\n}\n\n// DeleteSysExportTemplate 删除导出模板\n// @Tags SysExportTemplate\n// @Summary 删除导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body system.SysExportTemplate true \"删除导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysExportTemplate/deleteSysExportTemplate [delete]\nfunc (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplate(c *gin.Context) {\n\tvar sysExportTemplate system.SysExportTemplate\n\terr := c.ShouldBindJSON(&sysExportTemplate)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := sysExportTemplateService.DeleteSysExportTemplate(sysExportTemplate); err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t} else {\n\t\tresponse.OkWithMessage(\"删除成功\", c)\n\t}\n}\n\n// DeleteSysExportTemplateByIds 批量删除导出模板\n// @Tags SysExportTemplate\n// @Summary 批量删除导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"批量删除成功\"}\"\n// @Router /sysExportTemplate/deleteSysExportTemplateByIds [delete]\nfunc (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplateByIds(c *gin.Context) {\n\tvar IDS request.IdsReq\n\terr := c.ShouldBindJSON(&IDS)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := sysExportTemplateService.DeleteSysExportTemplateByIds(IDS); err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败\", c)\n\t} else {\n\t\tresponse.OkWithMessage(\"批量删除成功\", c)\n\t}\n}\n\n// UpdateSysExportTemplate 更新导出模板\n// @Tags SysExportTemplate\n// @Summary 更新导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body system.SysExportTemplate true \"更新导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /sysExportTemplate/updateSysExportTemplate [put]\nfunc (sysExportTemplateApi *SysExportTemplateApi) UpdateSysExportTemplate(c *gin.Context) {\n\tvar sysExportTemplate system.SysExportTemplate\n\terr := c.ShouldBindJSON(&sysExportTemplate)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tverify := utils.Rules{\n\t\t\"Name\": {utils.NotEmpty()},\n\t}\n\tif err := utils.Verify(sysExportTemplate, verify); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := sysExportTemplateService.UpdateSysExportTemplate(sysExportTemplate); err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t} else {\n\t\tresponse.OkWithMessage(\"更新成功\", c)\n\t}\n}\n\n// FindSysExportTemplate 用id查询导出模板\n// @Tags SysExportTemplate\n// @Summary 用id查询导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query system.SysExportTemplate true \"用id查询导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysExportTemplate/findSysExportTemplate [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) FindSysExportTemplate(c *gin.Context) {\n\tvar sysExportTemplate system.SysExportTemplate\n\terr := c.ShouldBindQuery(&sysExportTemplate)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif resysExportTemplate, err := sysExportTemplateService.GetSysExportTemplate(sysExportTemplate.ID); err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t} else {\n\t\tresponse.OkWithData(gin.H{\"resysExportTemplate\": resysExportTemplate}, c)\n\t}\n}\n\n// GetSysExportTemplateList 分页获取导出模板列表\n// @Tags SysExportTemplate\n// @Summary 分页获取导出模板列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query systemReq.SysExportTemplateSearch true \"分页获取导出模板列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysExportTemplate/getSysExportTemplateList [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gin.Context) {\n\tvar pageInfo systemReq.SysExportTemplateSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif list, total, err := sysExportTemplateService.GetSysExportTemplateInfoList(pageInfo); err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t} else {\n\t\tresponse.OkWithDetailed(response.PageResult{\n\t\t\tList:     list,\n\t\t\tTotal:    total,\n\t\t\tPage:     pageInfo.Page,\n\t\t\tPageSize: pageInfo.PageSize,\n\t\t}, \"获取成功\", c)\n\t}\n}\n\n// ExportExcel 导出表格token\n// @Tags SysExportTemplate\n// @Summary 导出表格\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/exportExcel [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {\n\ttemplateID := c.Query(\"templateID\")\n\tif templateID == \"\" {\n\t\tresponse.FailWithMessage(\"模板ID不能为空\", c)\n\t\treturn\n\t}\n\n\tqueryParams := c.Request.URL.Query()\n\n\t//创造一次性token\n\ttoken := utils.RandomString(32) // 随机32位\n\n\t// 记录本次请求参数\n\texportParams := map[string]interface{}{\n\t\t\"templateID\":  templateID,\n\t\t\"queryParams\": queryParams,\n\t}\n\n\t// 参数保留记录完成鉴权\n\ttokenMutex.Lock()\n\texportTokenCache[token] = exportParams\n\texportTokenExpiration[token] = time.Now().Add(30 * time.Minute)\n\ttokenMutex.Unlock()\n\n\t// 生成一次性链接\n\texportUrl := fmt.Sprintf(\"/sysExportTemplate/exportExcelByToken?token=%s\", token)\n\tresponse.OkWithData(exportUrl, c)\n}\n\n// ExportExcelByToken 导出表格\n// @Tags ExportExcelByToken\n// @Summary 导出表格\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/exportExcelByToken [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) ExportExcelByToken(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tif token == \"\" {\n\t\tresponse.FailWithMessage(\"导出token不能为空\", c)\n\t\treturn\n\t}\n\n\t// 获取token并且从缓存中剔除\n\ttokenMutex.RLock()\n\texportParamsRaw, exists := exportTokenCache[token]\n\texpiry, _ := exportTokenExpiration[token]\n\ttokenMutex.RUnlock()\n\n\tif !exists || time.Now().After(expiry) {\n\t\tglobal.GVA_LOG.Error(\"导出token无效或已过期!\")\n\t\tresponse.FailWithMessage(\"导出token无效或已过期\", c)\n\t\treturn\n\t}\n\n\t// 从token获取参数\n\texportParams, ok := exportParamsRaw.(map[string]interface{})\n\tif !ok {\n\t\tglobal.GVA_LOG.Error(\"解析导出参数失败!\")\n\t\tresponse.FailWithMessage(\"解析导出参数失败\", c)\n\t\treturn\n\t}\n\n\t// 获取导出参数\n\ttemplateID := exportParams[\"templateID\"].(string)\n\tqueryParams := exportParams[\"queryParams\"].(url.Values)\n\n\t// 清理一次性token\n\ttokenMutex.Lock()\n\tdelete(exportTokenCache, token)\n\tdelete(exportTokenExpiration, token)\n\ttokenMutex.Unlock()\n\n\t// 导出\n\tif file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t} else {\n\t\tc.Header(\"Content-Disposition\", fmt.Sprintf(\"attachment; filename=%s\", name+utils.RandomString(6)+\".xlsx\"))\n\t\tc.Header(\"success\", \"true\")\n\t\tc.Data(http.StatusOK, \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\", file.Bytes())\n\t}\n}\n\n// ExportTemplate 导出表格模板\n// @Tags SysExportTemplate\n// @Summary 导出表格模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/exportTemplate [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) ExportTemplate(c *gin.Context) {\n\ttemplateID := c.Query(\"templateID\")\n\tif templateID == \"\" {\n\t\tresponse.FailWithMessage(\"模板ID不能为空\", c)\n\t\treturn\n\t}\n\n\t// 创造一次性token\n\ttoken := utils.RandomString(32) // 随机32位\n\n\t// 记录本次请求参数\n\texportParams := map[string]interface{}{\n\t\t\"templateID\": templateID,\n\t\t\"isTemplate\": true,\n\t}\n\n\t// 参数保留记录完成鉴权\n\ttokenMutex.Lock()\n\texportTokenCache[token] = exportParams\n\texportTokenExpiration[token] = time.Now().Add(30 * time.Minute)\n\ttokenMutex.Unlock()\n\n\t// 生成一次性链接\n\texportUrl := fmt.Sprintf(\"/sysExportTemplate/exportTemplateByToken?token=%s\", token)\n\tresponse.OkWithData(exportUrl, c)\n}\n\n// ExportTemplateByToken 通过token导出表格模板\n// @Tags ExportTemplateByToken\n// @Summary 通过token导出表格模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/exportTemplateByToken [get]\nfunc (sysExportTemplateApi *SysExportTemplateApi) ExportTemplateByToken(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tif token == \"\" {\n\t\tresponse.FailWithMessage(\"导出token不能为空\", c)\n\t\treturn\n\t}\n\n\t// 获取token并且从缓存中剔除\n\ttokenMutex.RLock()\n\texportParamsRaw, exists := exportTokenCache[token]\n\texpiry, _ := exportTokenExpiration[token]\n\ttokenMutex.RUnlock()\n\n\tif !exists || time.Now().After(expiry) {\n\t\tglobal.GVA_LOG.Error(\"导出token无效或已过期!\")\n\t\tresponse.FailWithMessage(\"导出token无效或已过期\", c)\n\t\treturn\n\t}\n\n\t// 从token获取参数\n\texportParams, ok := exportParamsRaw.(map[string]interface{})\n\tif !ok {\n\t\tglobal.GVA_LOG.Error(\"解析导出参数失败!\")\n\t\tresponse.FailWithMessage(\"解析导出参数失败\", c)\n\t\treturn\n\t}\n\n\t// 检查是否为模板导出\n\tisTemplate, _ := exportParams[\"isTemplate\"].(bool)\n\tif !isTemplate {\n\t\tglobal.GVA_LOG.Error(\"token类型错误!\")\n\t\tresponse.FailWithMessage(\"token类型错误\", c)\n\t\treturn\n\t}\n\n\t// 获取导出参数\n\ttemplateID := exportParams[\"templateID\"].(string)\n\n\t// 清理一次性token\n\ttokenMutex.Lock()\n\tdelete(exportTokenCache, token)\n\tdelete(exportTokenExpiration, token)\n\ttokenMutex.Unlock()\n\n\t// 导出模板\n\tif file, name, err := sysExportTemplateService.ExportTemplate(templateID); err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t} else {\n\t\tc.Header(\"Content-Disposition\", fmt.Sprintf(\"attachment; filename=%s\", name+\"模板.xlsx\"))\n\t\tc.Header(\"success\", \"true\")\n\t\tc.Data(http.StatusOK, \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\", file.Bytes())\n\t}\n}\n\n// ImportExcel 导入表格\n// @Tags SysImportTemplate\n// @Summary 导入表格\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/importExcel [post]\nfunc (sysExportTemplateApi *SysExportTemplateApi) ImportExcel(c *gin.Context) {\n\ttemplateID := c.Query(\"templateID\")\n\tif templateID == \"\" {\n\t\tresponse.FailWithMessage(\"模板ID不能为空\", c)\n\t\treturn\n\t}\n\tfile, err := c.FormFile(\"file\")\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"文件获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"文件获取失败\", c)\n\t\treturn\n\t}\n\tif err := sysExportTemplateService.ImportExcel(templateID, file); err != nil {\n\t\tglobal.GVA_LOG.Error(err.Error(), zap.Error(err))\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t} else {\n\t\tresponse.OkWithMessage(\"导入成功\", c)\n\t}\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_initdb.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype DBApi struct{}\n\n// InitDB\n// @Tags     InitDB\n// @Summary  初始化用户数据库\n// @Produce  application/json\n// @Param    data  body      request.InitDB                  true  \"初始化数据库参数\"\n// @Success  200   {object}  response.Response{data=string}  \"初始化用户数据库\"\n// @Router   /init/initdb [post]\nfunc (i *DBApi) InitDB(c *gin.Context) {\n\tif global.GVA_DB != nil {\n\t\tglobal.GVA_LOG.Error(\"已存在数据库配置!\")\n\t\tresponse.FailWithMessage(\"已存在数据库配置\", c)\n\t\treturn\n\t}\n\tvar dbInfo request.InitDB\n\tif err := c.ShouldBindJSON(&dbInfo); err != nil {\n\t\tglobal.GVA_LOG.Error(\"参数校验不通过!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"参数校验不通过\", c)\n\t\treturn\n\t}\n\tif err := initDBService.InitDB(dbInfo); err != nil {\n\t\tglobal.GVA_LOG.Error(\"自动创建数据库失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"自动创建数据库失败，请查看后台日志，检查后在进行初始化\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"自动创建数据库成功\", c)\n}\n\n// CheckDB\n// @Tags     CheckDB\n// @Summary  初始化用户数据库\n// @Produce  application/json\n// @Success  200  {object}  response.Response{data=map[string]interface{},msg=string}  \"初始化用户数据库\"\n// @Router   /init/checkdb [post]\nfunc (i *DBApi) CheckDB(c *gin.Context) {\n\tvar (\n\t\tmessage  = \"前往初始化数据库\"\n\t\tneedInit = true\n\t)\n\n\tif global.GVA_DB != nil {\n\t\tmessage = \"数据库无需初始化\"\n\t\tneedInit = false\n\t}\n\tglobal.GVA_LOG.Info(message)\n\tresponse.OkWithDetailed(gin.H{\"needInit\": needInit}, message, c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_jwt_blacklist.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype JwtApi struct{}\n\n// JsonInBlacklist\n// @Tags      Jwt\n// @Summary   jwt加入黑名单\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{msg=string}  \"jwt加入黑名单\"\n// @Router    /jwt/jsonInBlacklist [post]\nfunc (j *JwtApi) JsonInBlacklist(c *gin.Context) {\n\ttoken := utils.GetToken(c)\n\tjwt := system.JwtBlacklist{Jwt: token}\n\terr := jwtService.JsonInBlacklist(jwt)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"jwt作废失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"jwt作废失败\", c)\n\t\treturn\n\t}\n\tutils.ClearToken(c)\n\tresponse.OkWithMessage(\"jwt作废成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_login_log.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype LoginLogApi struct{}\n\nfunc (s *LoginLogApi) DeleteLoginLog(c *gin.Context) {\n\tvar loginLog system.SysLoginLog\n\terr := c.ShouldBindJSON(&loginLog)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = loginLogService.DeleteLoginLog(loginLog)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\nfunc (s *LoginLogApi) DeleteLoginLogByIds(c *gin.Context) {\n\tvar SDS request.IdsReq\n\terr := c.ShouldBindJSON(&SDS)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = loginLogService.DeleteLoginLogByIds(SDS)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\nfunc (s *LoginLogApi) FindLoginLog(c *gin.Context) {\n\tvar loginLog system.SysLoginLog\n\terr := c.ShouldBindQuery(&loginLog)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\treLoginLog, err := loginLogService.GetLoginLog(loginLog.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(reLoginLog, \"查询成功\", c)\n}\n\nfunc (s *LoginLogApi) GetLoginLogList(c *gin.Context) {\n\tvar pageInfo systemReq.SysLoginLogSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := loginLogService.GetLoginLogInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_menu.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype AuthorityMenuApi struct{}\n\n// GetMenu\n// @Tags      AuthorityMenu\n// @Summary   获取用户动态路由\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     data  body      request.Empty                                                  true  \"空\"\n// @Success   200   {object}  response.Response{data=systemRes.SysMenusResponse,msg=string}  \"获取用户动态路由,返回包括系统菜单详情列表\"\n// @Router    /menu/getMenu [post]\nfunc (a *AuthorityMenuApi) GetMenu(c *gin.Context) {\n\tmenus, err := menuService.GetMenuTree(utils.GetUserAuthorityId(c))\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tif menus == nil {\n\t\tmenus = []system.SysMenu{}\n\t}\n\tresponse.OkWithDetailed(systemRes.SysMenusResponse{Menus: menus}, \"获取成功\", c)\n}\n\n// GetBaseMenuTree\n// @Tags      AuthorityMenu\n// @Summary   获取用户动态路由\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     data  body      request.Empty                                                      true  \"空\"\n// @Success   200   {object}  response.Response{data=systemRes.SysBaseMenusResponse,msg=string}  \"获取用户动态路由,返回包括系统菜单列表\"\n// @Router    /menu/getBaseMenuTree [post]\nfunc (a *AuthorityMenuApi) GetBaseMenuTree(c *gin.Context) {\n\tauthority := utils.GetUserAuthorityId(c)\n\tmenus, err := menuService.GetBaseMenuTree(authority)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysBaseMenusResponse{Menus: menus}, \"获取成功\", c)\n}\n\n// AddMenuAuthority\n// @Tags      AuthorityMenu\n// @Summary   增加menu和角色关联关系\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.AddMenuAuthorityInfo  true  \"角色ID\"\n// @Success   200   {object}  response.Response{msg=string}   \"增加menu和角色关联关系\"\n// @Router    /menu/addMenuAuthority [post]\nfunc (a *AuthorityMenuApi) AddMenuAuthority(c *gin.Context) {\n\tvar authorityMenu systemReq.AddMenuAuthorityInfo\n\terr := c.ShouldBindJSON(&authorityMenu)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif err := utils.Verify(authorityMenu, utils.AuthorityIdVerify); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tadminAuthorityID := utils.GetUserAuthorityId(c)\n\tif err := menuService.AddMenuAuthority(authorityMenu.Menus, adminAuthorityID, authorityMenu.AuthorityId); err != nil {\n\t\tglobal.GVA_LOG.Error(\"添加失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"添加失败\", c)\n\t} else {\n\t\tresponse.OkWithMessage(\"添加成功\", c)\n\t}\n}\n\n// GetMenuAuthority\n// @Tags      AuthorityMenu\n// @Summary   获取指定角色menu\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetAuthorityId                                     true  \"角色ID\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"获取指定角色menu\"\n// @Router    /menu/getMenuAuthority [post]\nfunc (a *AuthorityMenuApi) GetMenuAuthority(c *gin.Context) {\n\tvar param request.GetAuthorityId\n\terr := c.ShouldBindJSON(&param)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(param, utils.AuthorityIdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tmenus, err := menuService.GetMenuAuthority(&param)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithDetailed(systemRes.SysMenusResponse{Menus: menus}, \"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"menus\": menus}, \"获取成功\", c)\n}\n\n// AddBaseMenu\n// @Tags      Menu\n// @Summary   新增菜单\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysBaseMenu             true  \"路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\"\n// @Success   200   {object}  response.Response{msg=string}  \"新增菜单\"\n// @Router    /menu/addBaseMenu [post]\nfunc (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) {\n\tvar menu system.SysBaseMenu\n\terr := c.ShouldBindJSON(&menu)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(menu, utils.MenuVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(menu.Meta, utils.MenuMetaVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = menuService.AddBaseMenu(menu)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"添加失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"添加失败：\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"添加成功\", c)\n}\n\n// DeleteBaseMenu\n// @Tags      Menu\n// @Summary   删除菜单\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetById                true  \"菜单id\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除菜单\"\n// @Router    /menu/deleteBaseMenu [post]\nfunc (a *AuthorityMenuApi) DeleteBaseMenu(c *gin.Context) {\n\tvar menu request.GetById\n\terr := c.ShouldBindJSON(&menu)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(menu, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = baseMenuService.DeleteBaseMenu(menu.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// UpdateBaseMenu\n// @Tags      Menu\n// @Summary   更新菜单\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysBaseMenu             true  \"路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\"\n// @Success   200   {object}  response.Response{msg=string}  \"更新菜单\"\n// @Router    /menu/updateBaseMenu [post]\nfunc (a *AuthorityMenuApi) UpdateBaseMenu(c *gin.Context) {\n\tvar menu system.SysBaseMenu\n\terr := c.ShouldBindJSON(&menu)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(menu, utils.MenuVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(menu.Meta, utils.MenuMetaVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = baseMenuService.UpdateBaseMenu(menu)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// GetBaseMenuById\n// @Tags      Menu\n// @Summary   根据id获取菜单\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetById                                                   true  \"菜单id\"\n// @Success   200   {object}  response.Response{data=systemRes.SysBaseMenuResponse,msg=string}  \"根据id获取菜单,返回包括系统菜单列表\"\n// @Router    /menu/getBaseMenuById [post]\nfunc (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) {\n\tvar idInfo request.GetById\n\terr := c.ShouldBindJSON(&idInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(idInfo, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tmenu, err := baseMenuService.GetBaseMenuById(idInfo.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, \"获取成功\", c)\n}\n\n// GetMenuRoles\n// @Tags      AuthorityMenu\n// @Summary   获取拥有指定菜单的角色ID列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     menuId  query     uint                                                         true  \"菜单ID\"\n// @Success   200     {object}  response.Response{data=map[string]interface{},msg=string}    \"获取成功\"\n// @Router    /menu/getMenuRoles [get]\nfunc (a *AuthorityMenuApi) GetMenuRoles(c *gin.Context) {\n\tvar req systemReq.SetMenuAuthorities\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif req.MenuId == 0 {\n\t\tresponse.FailWithMessage(\"菜单ID不能为空\", c)\n\t\treturn\n\t}\n\tauthorityIds, err := menuService.GetAuthoritiesByMenuId(req.MenuId)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tif authorityIds == nil {\n\t\tauthorityIds = []uint{}\n\t}\n\tdefaultRouterAuthorityIds, err := menuService.GetDefaultRouterAuthorityIds(req.MenuId)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取首页角色失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tif defaultRouterAuthorityIds == nil {\n\t\tdefaultRouterAuthorityIds = []uint{}\n\t}\n\tresponse.OkWithDetailed(gin.H{\n\t\t\"authorityIds\":              authorityIds,\n\t\t\"defaultRouterAuthorityIds\": defaultRouterAuthorityIds,\n\t}, \"获取成功\", c)\n}\n\n// SetMenuRoles\n// @Tags      AuthorityMenu\n// @Summary   全量覆盖某菜单关联的角色列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.SetMenuAuthorities   true  \"菜单ID和角色ID列表\"\n// @Success   200   {object}  response.Response{msg=string}  \"设置成功\"\n// @Router    /menu/setMenuRoles [post]\nfunc (a *AuthorityMenuApi) SetMenuRoles(c *gin.Context) {\n\tvar req systemReq.SetMenuAuthorities\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif req.MenuId == 0 {\n\t\tresponse.FailWithMessage(\"菜单ID不能为空\", c)\n\t\treturn\n\t}\n\tif err := menuService.SetMenuAuthorities(req.MenuId, req.AuthorityIds); err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n\n// GetMenuList\n// @Tags      Menu\n// @Summary   分页获取基础menu列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.PageInfo                                        true  \"页码, 每页大小\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取基础menu列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /menu/getMenuList [post]\nfunc (a *AuthorityMenuApi) GetMenuList(c *gin.Context) {\n\tauthorityID := utils.GetUserAuthorityId(c)\n\tmenuList, err := menuService.GetInfoList(authorityID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(menuList, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_operation_record.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype OperationRecordApi struct{}\n\n// DeleteSysOperationRecord\n// @Tags      SysOperationRecord\n// @Summary   删除SysOperationRecord\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysOperationRecord      true  \"SysOperationRecord模型\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除SysOperationRecord\"\n// @Router    /sysOperationRecord/deleteSysOperationRecord [delete]\nfunc (s *OperationRecordApi) DeleteSysOperationRecord(c *gin.Context) {\n\tvar sysOperationRecord system.SysOperationRecord\n\terr := c.ShouldBindJSON(&sysOperationRecord)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = operationRecordService.DeleteSysOperationRecord(sysOperationRecord)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// DeleteSysOperationRecordByIds\n// @Tags      SysOperationRecord\n// @Summary   批量删除SysOperationRecord\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.IdsReq                 true  \"批量删除SysOperationRecord\"\n// @Success   200   {object}  response.Response{msg=string}  \"批量删除SysOperationRecord\"\n// @Router    /sysOperationRecord/deleteSysOperationRecordByIds [delete]\nfunc (s *OperationRecordApi) DeleteSysOperationRecordByIds(c *gin.Context) {\n\tvar IDS request.IdsReq\n\terr := c.ShouldBindJSON(&IDS)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = operationRecordService.DeleteSysOperationRecordByIds(IDS)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\n// FindSysOperationRecord\n// @Tags      SysOperationRecord\n// @Summary   用id查询SysOperationRecord\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     system.SysOperationRecord                                  true  \"Id\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"用id查询SysOperationRecord\"\n// @Router    /sysOperationRecord/findSysOperationRecord [get]\nfunc (s *OperationRecordApi) FindSysOperationRecord(c *gin.Context) {\n\tvar sysOperationRecord system.SysOperationRecord\n\terr := c.ShouldBindQuery(&sysOperationRecord)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(sysOperationRecord, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\treSysOperationRecord, err := operationRecordService.GetSysOperationRecord(sysOperationRecord.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"reSysOperationRecord\": reSysOperationRecord}, \"查询成功\", c)\n}\n\n// GetSysOperationRecordList\n// @Tags      SysOperationRecord\n// @Summary   分页获取SysOperationRecord列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  query     request.SysOperationRecordSearch                        true  \"页码, 每页大小, 搜索条件\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /sysOperationRecord/getSysOperationRecordList [get]\nfunc (s *OperationRecordApi) GetSysOperationRecordList(c *gin.Context) {\n\tvar pageInfo systemReq.SysOperationRecordSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := operationRecordService.GetSysOperationRecordInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_params.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype SysParamsApi struct{}\n\n// CreateSysParams 创建参数\n// @Tags SysParams\n// @Summary 创建参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body system.SysParams true \"创建参数\"\n// @Success 200 {object} response.Response{msg=string} \"创建成功\"\n// @Router /sysParams/createSysParams [post]\nfunc (sysParamsApi *SysParamsApi) CreateSysParams(c *gin.Context) {\n\tvar sysParams system.SysParams\n\terr := c.ShouldBindJSON(&sysParams)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = sysParamsService.CreateSysParams(&sysParams)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// DeleteSysParams 删除参数\n// @Tags SysParams\n// @Summary 删除参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body system.SysParams true \"删除参数\"\n// @Success 200 {object} response.Response{msg=string} \"删除成功\"\n// @Router /sysParams/deleteSysParams [delete]\nfunc (sysParamsApi *SysParamsApi) DeleteSysParams(c *gin.Context) {\n\tID := c.Query(\"ID\")\n\terr := sysParamsService.DeleteSysParams(ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// DeleteSysParamsByIds 批量删除参数\n// @Tags SysParams\n// @Summary 批量删除参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{msg=string} \"批量删除成功\"\n// @Router /sysParams/deleteSysParamsByIds [delete]\nfunc (sysParamsApi *SysParamsApi) DeleteSysParamsByIds(c *gin.Context) {\n\tIDs := c.QueryArray(\"IDs[]\")\n\terr := sysParamsService.DeleteSysParamsByIds(IDs)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\n// UpdateSysParams 更新参数\n// @Tags SysParams\n// @Summary 更新参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body system.SysParams true \"更新参数\"\n// @Success 200 {object} response.Response{msg=string} \"更新成功\"\n// @Router /sysParams/updateSysParams [put]\nfunc (sysParamsApi *SysParamsApi) UpdateSysParams(c *gin.Context) {\n\tvar sysParams system.SysParams\n\terr := c.ShouldBindJSON(&sysParams)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = sysParamsService.UpdateSysParams(sysParams)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// FindSysParams 用id查询参数\n// @Tags SysParams\n// @Summary 用id查询参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query system.SysParams true \"用id查询参数\"\n// @Success 200 {object} response.Response{data=system.SysParams,msg=string} \"查询成功\"\n// @Router /sysParams/findSysParams [get]\nfunc (sysParamsApi *SysParamsApi) FindSysParams(c *gin.Context) {\n\tID := c.Query(\"ID\")\n\tresysParams, err := sysParamsService.GetSysParams(ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithData(resysParams, c)\n}\n\n// GetSysParamsList 分页获取参数列表\n// @Tags SysParams\n// @Summary 分页获取参数列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query systemReq.SysParamsSearch true \"分页获取参数列表\"\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /sysParams/getSysParamsList [get]\nfunc (sysParamsApi *SysParamsApi) GetSysParamsList(c *gin.Context) {\n\tvar pageInfo systemReq.SysParamsSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := sysParamsService.GetSysParamsInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// GetSysParam 根据key获取参数value\n// @Tags SysParams\n// @Summary 根据key获取参数value\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param key query string true \"key\"\n// @Success 200 {object} response.Response{data=system.SysParams,msg=string} \"获取成功\"\n// @Router /sysParams/getSysParam [get]\nfunc (sysParamsApi *SysParamsApi) GetSysParam(c *gin.Context) {\n\tk := c.Query(\"key\")\n\tparams, err := sysParamsService.GetSysParam(k)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(params, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_skills.go",
    "content": "package system\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype SkillsApi struct{}\n\nfunc (s *SkillsApi) GetTools(c *gin.Context) {\n\tdata, err := skillsService.Tools(c.Request.Context())\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取工具列表失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取工具列表失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"tools\": data}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) GetSkillList(c *gin.Context) {\n\tvar req request.SkillToolRequest\n\t_ = c.ShouldBindJSON(&req)\n\tdata, err := skillsService.List(c.Request.Context(), req.Tool)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取技能列表失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取技能列表失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"skills\": data}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) GetSkillDetail(c *gin.Context) {\n\tvar req request.SkillDetailRequest\n\t_ = c.ShouldBindJSON(&req)\n\tdata, err := skillsService.Detail(c.Request.Context(), req.Tool, req.Skill)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取技能详情失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取技能详情失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"detail\": data}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) SaveSkill(c *gin.Context) {\n\tvar req request.SkillSaveRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.Save(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存技能失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存技能失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"保存成功\", c)\n}\n\nfunc (s *SkillsApi) DeleteSkill(c *gin.Context) {\n\tvar req request.SkillDeleteRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.Delete(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除技能失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除技能失败: \"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\nfunc (s *SkillsApi) CreateScript(c *gin.Context) {\n\tvar req request.SkillScriptCreateRequest\n\t_ = c.ShouldBindJSON(&req)\n\tfileName, content, err := skillsService.CreateScript(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建脚本失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建脚本失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"fileName\": fileName, \"content\": content}, \"创建成功\", c)\n}\n\nfunc (s *SkillsApi) GetScript(c *gin.Context) {\n\tvar req request.SkillFileRequest\n\t_ = c.ShouldBindJSON(&req)\n\tcontent, err := skillsService.GetScript(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"读取脚本失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"读取脚本失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"content\": content}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) SaveScript(c *gin.Context) {\n\tvar req request.SkillFileSaveRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.SaveScript(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存脚本失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存脚本失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"保存成功\", c)\n}\n\nfunc (s *SkillsApi) CreateResource(c *gin.Context) {\n\tvar req request.SkillResourceCreateRequest\n\t_ = c.ShouldBindJSON(&req)\n\tfileName, content, err := skillsService.CreateResource(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建资源失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建资源失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"fileName\": fileName, \"content\": content}, \"创建成功\", c)\n}\n\nfunc (s *SkillsApi) GetResource(c *gin.Context) {\n\tvar req request.SkillFileRequest\n\t_ = c.ShouldBindJSON(&req)\n\tcontent, err := skillsService.GetResource(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"读取资源失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"读取资源失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"content\": content}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) SaveResource(c *gin.Context) {\n\tvar req request.SkillFileSaveRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.SaveResource(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存资源失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存资源失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"保存成功\", c)\n}\n\nfunc (s *SkillsApi) CreateReference(c *gin.Context) {\n\tvar req request.SkillReferenceCreateRequest\n\t_ = c.ShouldBindJSON(&req)\n\tfileName, content, err := skillsService.CreateReference(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建参考失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建参考失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"fileName\": fileName, \"content\": content}, \"创建成功\", c)\n}\n\nfunc (s *SkillsApi) GetReference(c *gin.Context) {\n\tvar req request.SkillFileRequest\n\t_ = c.ShouldBindJSON(&req)\n\tcontent, err := skillsService.GetReference(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"读取参考失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"读取参考失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"content\": content}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) SaveReference(c *gin.Context) {\n\tvar req request.SkillFileSaveRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.SaveReference(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存参考失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存参考失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"保存成功\", c)\n}\n\nfunc (s *SkillsApi) CreateTemplate(c *gin.Context) {\n\tvar req request.SkillTemplateCreateRequest\n\t_ = c.ShouldBindJSON(&req)\n\tfileName, content, err := skillsService.CreateTemplate(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建模板失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建模板失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"fileName\": fileName, \"content\": content}, \"创建成功\", c)\n}\n\nfunc (s *SkillsApi) GetTemplate(c *gin.Context) {\n\tvar req request.SkillFileRequest\n\t_ = c.ShouldBindJSON(&req)\n\tcontent, err := skillsService.GetTemplate(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"读取模板失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"读取模板失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"content\": content}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) SaveTemplate(c *gin.Context) {\n\tvar req request.SkillFileSaveRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.SaveTemplate(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存模板失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存模板失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"保存成功\", c)\n}\n\nfunc (s *SkillsApi) GetGlobalConstraint(c *gin.Context) {\n\tvar req request.SkillToolRequest\n\t_ = c.ShouldBindJSON(&req)\n\tcontent, exists, err := skillsService.GetGlobalConstraint(c.Request.Context(), req.Tool)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"读取全局约束失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"读取全局约束失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"content\": content, \"exists\": exists}, \"获取成功\", c)\n}\n\nfunc (s *SkillsApi) SaveGlobalConstraint(c *gin.Context) {\n\tvar req request.SkillGlobalConstraintSaveRequest\n\t_ = c.ShouldBindJSON(&req)\n\tif err := skillsService.SaveGlobalConstraint(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存全局约束失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存全局约束失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"保存成功\", c)\n}\n\nfunc (s *SkillsApi) PackageSkill(c *gin.Context) {\n\tvar req request.SkillPackageRequest\n\t_ = c.ShouldBindJSON(&req)\n\n\tfileName, data, err := skillsService.Package(c.Request.Context(), req)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"打包技能失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"打包技能失败: \"+err.Error(), c)\n\t\treturn\n\t}\n\n\tc.Header(\"Content-Type\", \"application/zip\")\n\tc.Header(\"Content-Disposition\", \"attachment; filename=\\\"\"+fileName+\"\\\"\")\n\tc.Data(http.StatusOK, \"application/zip\", data)\n}\n\nfunc (s *SkillsApi) DownloadOnlineSkill(c *gin.Context) {\n\tvar req request.DownloadOnlineSkillReq\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\tresponse.FailWithMessage(\"参数错误\", c)\n\t\treturn\n\t}\n\n\tif err := skillsService.DownloadOnlineSkill(c.Request.Context(), req); err != nil {\n\t\tglobal.GVA_LOG.Error(\"下载在线技能失败\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"下载在线技能失败: \"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"下载成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_system.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype SystemApi struct{}\n\n// GetSystemConfig\n// @Tags      System\n// @Summary   获取配置文件内容\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=systemRes.SysConfigResponse,msg=string}  \"获取配置文件内容,返回包括系统配置\"\n// @Router    /system/getSystemConfig [post]\nfunc (s *SystemApi) GetSystemConfig(c *gin.Context) {\n\tconfig, err := systemConfigService.GetSystemConfig()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysConfigResponse{Config: config}, \"获取成功\", c)\n}\n\n// SetSystemConfig\n// @Tags      System\n// @Summary   设置配置文件内容\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     data  body      system.System                   true  \"设置配置文件内容\"\n// @Success   200   {object}  response.Response{data=string}  \"设置配置文件内容\"\n// @Router    /system/setSystemConfig [post]\nfunc (s *SystemApi) SetSystemConfig(c *gin.Context) {\n\tvar sys system.System\n\terr := c.ShouldBindJSON(&sys)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = systemConfigService.SetSystemConfig(sys)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n\n// ReloadSystem\n// @Tags      System\n// @Summary   重载系统\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Success   200  {object}  response.Response{msg=string}  \"重载系统\"\n// @Router    /system/reloadSystem [post]\nfunc (s *SystemApi) ReloadSystem(c *gin.Context) {\n\t// 触发系统重载事件\n\terr := utils.GlobalSystemEvents.TriggerReload()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"重载系统失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"重载系统失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"重载系统成功\", c)\n}\n\n// GetServerInfo\n// @Tags      System\n// @Summary   获取服务器信息\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"获取服务器信息\"\n// @Router    /system/getServerInfo [post]\nfunc (s *SystemApi) GetServerInfo(c *gin.Context) {\n\tserver, err := systemConfigService.GetServerInfo()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"server\": server}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_user.go",
    "content": "package system\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/redis/go-redis/v9\"\n\t\"go.uber.org/zap\"\n)\n\n// Login\n// @Tags     Base\n// @Summary  用户登录\n// @Produce   application/json\n// @Param    data  body      systemReq.Login                                             true  \"用户名, 密码, 验证码\"\n// @Success  200   {object}  response.Response{data=systemRes.LoginResponse,msg=string}  \"返回包括用户信息,token,过期时间\"\n// @Router   /base/login [post]\nfunc (b *BaseApi) Login(c *gin.Context) {\n\tvar l systemReq.Login\n\terr := c.ShouldBindJSON(&l)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(l, utils.LoginVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\tkey := c.ClientIP()\n\t// 判断验证码是否开启\n\topenCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha               // 是否开启防爆次数\n\topenCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间\n\tv, ok := global.BlackCache.Get(key)\n\tif !ok {\n\t\tglobal.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))\n\t}\n\n\tvar oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v)\n\tif oc && (l.Captcha == \"\" || l.CaptchaId == \"\" || !store.Verify(l.CaptchaId, l.Captcha, true)) {\n\t\t// 验证码次数+1\n\t\tglobal.BlackCache.Increment(key, 1)\n\t\tresponse.FailWithMessage(\"验证码错误\", c)\n\t\t// 记录登录失败日志\n\t\tloginLogService.CreateLoginLog(system.SysLoginLog{\n\t\t\tUsername:     l.Username,\n\t\t\tIp:           c.ClientIP(),\n\t\t\tAgent:        c.Request.UserAgent(),\n\t\t\tStatus:       false,\n\t\t\tErrorMessage: \"验证码错误\",\n\t\t})\n\t\treturn\n\t}\n\n\tu := &system.SysUser{Username: l.Username, Password: l.Password}\n\tuser, err := userService.Login(u)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"登陆失败! 用户名不存在或者密码错误!\", zap.Error(err))\n\t\t// 验证码次数+1\n\t\tglobal.BlackCache.Increment(key, 1)\n\t\tresponse.FailWithMessage(\"用户名不存在或者密码错误\", c)\n\t\t// 记录登录失败日志\n\t\tloginLogService.CreateLoginLog(system.SysLoginLog{\n\t\t\tUsername:     l.Username,\n\t\t\tIp:           c.ClientIP(),\n\t\t\tAgent:        c.Request.UserAgent(),\n\t\t\tStatus:       false,\n\t\t\tErrorMessage: \"用户名不存在或者密码错误\",\n\t\t})\n\t\treturn\n\t}\n\tif user.Enable != 1 {\n\t\tglobal.GVA_LOG.Error(\"登陆失败! 用户被禁止登录!\")\n\t\t// 验证码次数+1\n\t\tglobal.BlackCache.Increment(key, 1)\n\t\tresponse.FailWithMessage(\"用户被禁止登录\", c)\n\t\t// 记录登录失败日志\n\t\tloginLogService.CreateLoginLog(system.SysLoginLog{\n\t\t\tUsername:     l.Username,\n\t\t\tIp:           c.ClientIP(),\n\t\t\tAgent:        c.Request.UserAgent(),\n\t\t\tStatus:       false,\n\t\t\tErrorMessage: \"用户被禁止登录\",\n\t\t\tUserID:       user.ID,\n\t\t})\n\t\treturn\n\t}\n\tb.TokenNext(c, *user)\n}\n\n// TokenNext 登录以后签发jwt\nfunc (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {\n\ttoken, claims, err := utils.LoginToken(&user)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取token失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取token失败\", c)\n\t\treturn\n\t}\n\t// 记录登录成功日志\n\tloginLogService.CreateLoginLog(system.SysLoginLog{\n\t\tUsername: user.Username,\n\t\tIp:       c.ClientIP(),\n\t\tAgent:    c.Request.UserAgent(),\n\t\tStatus:   true,\n\t\tUserID:   user.ID,\n\t\tErrorMessage: \"登录成功\",\n\t})\n\tif !global.GVA_CONFIG.System.UseMultipoint {\n\t\tutils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))\n\t\tresponse.OkWithDetailed(systemRes.LoginResponse{\n\t\t\tUser:      user,\n\t\t\tToken:     token,\n\t\t\tExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,\n\t\t}, \"登录成功\", c)\n\t\treturn\n\t}\n\n\tif jwtStr, err := jwtService.GetRedisJWT(user.Username); err == redis.Nil {\n\t\tif err := utils.SetRedisJWT(token, user.Username); err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"设置登录状态失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"设置登录状态失败\", c)\n\t\t\treturn\n\t\t}\n\t\tutils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))\n\t\tresponse.OkWithDetailed(systemRes.LoginResponse{\n\t\t\tUser:      user,\n\t\t\tToken:     token,\n\t\t\tExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,\n\t\t}, \"登录成功\", c)\n\t} else if err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置登录状态失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置登录状态失败\", c)\n\t} else {\n\t\tvar blackJWT system.JwtBlacklist\n\t\tblackJWT.Jwt = jwtStr\n\t\tif err := jwtService.JsonInBlacklist(blackJWT); err != nil {\n\t\t\tresponse.FailWithMessage(\"jwt作废失败\", c)\n\t\t\treturn\n\t\t}\n\t\tif err := utils.SetRedisJWT(token, user.GetUsername()); err != nil {\n\t\t\tresponse.FailWithMessage(\"设置登录状态失败\", c)\n\t\t\treturn\n\t\t}\n\t\tutils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))\n\t\tresponse.OkWithDetailed(systemRes.LoginResponse{\n\t\t\tUser:      user,\n\t\t\tToken:     token,\n\t\t\tExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,\n\t\t}, \"登录成功\", c)\n\t}\n}\n\n// Register\n// @Tags     SysUser\n// @Summary  用户注册账号\n// @Produce   application/json\n// @Param    data  body      systemReq.Register                                            true  \"用户名, 昵称, 密码, 角色ID\"\n// @Success  200   {object}  response.Response{data=systemRes.SysUserResponse,msg=string}  \"用户注册账号,返回包括用户信息\"\n// @Router   /user/admin_register [post]\nfunc (b *BaseApi) Register(c *gin.Context) {\n\tvar r systemReq.Register\n\terr := c.ShouldBindJSON(&r)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(r, utils.RegisterVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tvar authorities []system.SysAuthority\n\tfor _, v := range r.AuthorityIds {\n\t\tauthorities = append(authorities, system.SysAuthority{\n\t\t\tAuthorityId: v,\n\t\t})\n\t}\n\tuser := &system.SysUser{Username: r.Username, NickName: r.NickName, Password: r.Password, HeaderImg: r.HeaderImg, AuthorityId: r.AuthorityId, Authorities: authorities, Enable: r.Enable, Phone: r.Phone, Email: r.Email}\n\tuserReturn, err := userService.Register(*user)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"注册失败!\", zap.Error(err))\n\t\tresponse.FailWithDetailed(systemRes.SysUserResponse{User: userReturn}, \"注册失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(systemRes.SysUserResponse{User: userReturn}, \"注册成功\", c)\n}\n\n// ChangePassword\n// @Tags      SysUser\n// @Summary   用户修改密码\n// @Security  ApiKeyAuth\n// @Produce  application/json\n// @Param     data  body      systemReq.ChangePasswordReq    true  \"用户名, 原密码, 新密码\"\n// @Success   200   {object}  response.Response{msg=string}  \"用户修改密码\"\n// @Router    /user/changePassword [post]\nfunc (b *BaseApi) ChangePassword(c *gin.Context) {\n\tvar req systemReq.ChangePasswordReq\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(req, utils.ChangePasswordVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tuid := utils.GetUserID(c)\n\tu := &system.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password}\n\terr = userService.ChangePassword(u, req.NewPassword)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"修改失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"修改失败，原密码与当前账户不符\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"修改成功\", c)\n}\n\n// GetUserList\n// @Tags      SysUser\n// @Summary   分页获取用户列表\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.GetUserList                                        true  \"页码, 每页大小\"\n// @Success   200   {object}  response.Response{data=response.PageResult,msg=string}  \"分页获取用户列表,返回包括列表,总数,页码,每页数量\"\n// @Router    /user/getUserList [post]\nfunc (b *BaseApi) GetUserList(c *gin.Context) {\n\tvar pageInfo systemReq.GetUserList\n\terr := c.ShouldBindJSON(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(pageInfo, utils.PageInfoVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := userService.GetUserInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// SetUserAuthority\n// @Tags      SysUser\n// @Summary   更改用户权限\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.SetUserAuth          true  \"用户UUID, 角色ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"设置用户权限\"\n// @Router    /user/setUserAuthority [post]\nfunc (b *BaseApi) SetUserAuthority(c *gin.Context) {\n\tvar sua systemReq.SetUserAuth\n\terr := c.ShouldBindJSON(&sua)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif UserVerifyErr := utils.Verify(sua, utils.SetUserAuthorityVerify); UserVerifyErr != nil {\n\t\tresponse.FailWithMessage(UserVerifyErr.Error(), c)\n\t\treturn\n\t}\n\tuserID := utils.GetUserID(c)\n\terr = userService.SetUserAuthority(userID, sua.AuthorityId)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"修改失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tclaims := utils.GetUserInfo(c)\n\tclaims.AuthorityId = sua.AuthorityId\n\ttoken, err := utils.NewJWT().CreateToken(*claims)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"修改失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tc.Header(\"new-token\", token)\n\tc.Header(\"new-expires-at\", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))\n\tutils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))\n\tresponse.OkWithMessage(\"修改成功\", c)\n}\n\n// SetUserAuthorities\n// @Tags      SysUser\n// @Summary   设置用户权限\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      systemReq.SetUserAuthorities   true  \"用户UUID, 角色ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"设置用户权限\"\n// @Router    /user/setUserAuthorities [post]\nfunc (b *BaseApi) SetUserAuthorities(c *gin.Context) {\n\tvar sua systemReq.SetUserAuthorities\n\terr := c.ShouldBindJSON(&sua)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tauthorityID := utils.GetUserAuthorityId(c)\n\terr = userService.SetUserAuthorities(authorityID, sua.ID, sua.AuthorityIds)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"修改失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"修改失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"修改成功\", c)\n}\n\n// DeleteUser\n// @Tags      SysUser\n// @Summary   删除用户\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      request.GetById                true  \"用户ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"删除用户\"\n// @Router    /user/deleteUser [delete]\nfunc (b *BaseApi) DeleteUser(c *gin.Context) {\n\tvar reqId request.GetById\n\terr := c.ShouldBindJSON(&reqId)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(reqId, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tjwtId := utils.GetUserID(c)\n\tif jwtId == uint(reqId.ID) {\n\t\tresponse.FailWithMessage(\"删除失败, 无法删除自己。\", c)\n\t\treturn\n\t}\n\terr = userService.DeleteUser(reqId.ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// SetUserInfo\n// @Tags      SysUser\n// @Summary   设置用户信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysUser                                             true  \"ID, 用户名, 昵称, 头像链接\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"设置用户信息\"\n// @Router    /user/setUserInfo [put]\nfunc (b *BaseApi) SetUserInfo(c *gin.Context) {\n\tvar user systemReq.ChangeUserInfo\n\terr := c.ShouldBindJSON(&user)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = utils.Verify(user, utils.IdVerify)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tif len(user.AuthorityIds) != 0 {\n\t\tauthorityID := utils.GetUserAuthorityId(c)\n\t\terr = userService.SetUserAuthorities(authorityID, user.ID, user.AuthorityIds)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"设置失败\", c)\n\t\t\treturn\n\t\t}\n\t}\n\terr = userService.SetUserInfo(system.SysUser{\n\t\tGVA_MODEL: global.GVA_MODEL{\n\t\t\tID: user.ID,\n\t\t},\n\t\tNickName:  user.NickName,\n\t\tHeaderImg: user.HeaderImg,\n\t\tPhone:     user.Phone,\n\t\tEmail:     user.Email,\n\t\tEnable:    user.Enable,\n\t})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n\n// SetSelfInfo\n// @Tags      SysUser\n// @Summary   设置用户信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      system.SysUser                                             true  \"ID, 用户名, 昵称, 头像链接\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"设置用户信息\"\n// @Router    /user/SetSelfInfo [put]\nfunc (b *BaseApi) SetSelfInfo(c *gin.Context) {\n\tvar user systemReq.ChangeUserInfo\n\terr := c.ShouldBindJSON(&user)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tuser.ID = utils.GetUserID(c)\n\terr = userService.SetSelfInfo(system.SysUser{\n\t\tGVA_MODEL: global.GVA_MODEL{\n\t\t\tID: user.ID,\n\t\t},\n\t\tNickName:  user.NickName,\n\t\tHeaderImg: user.HeaderImg,\n\t\tPhone:     user.Phone,\n\t\tEmail:     user.Email,\n\t\tEnable:    user.Enable,\n\t})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n\n// SetSelfSetting\n// @Tags      SysUser\n// @Summary   设置用户配置\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Param     data  body      map[string]interface{}  true  \"用户配置数据\"\n// @Success   200   {object}  response.Response{data=map[string]interface{},msg=string}  \"设置用户配置\"\n// @Router    /user/SetSelfSetting [put]\nfunc (b *BaseApi) SetSelfSetting(c *gin.Context) {\n\tvar req common.JSONMap\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\terr = userService.SetSelfSetting(req, utils.GetUserID(c))\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"设置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"设置失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"设置成功\", c)\n}\n\n// GetUserInfo\n// @Tags      SysUser\n// @Summary   获取用户信息\n// @Security  ApiKeyAuth\n// @accept    application/json\n// @Produce   application/json\n// @Success   200  {object}  response.Response{data=map[string]interface{},msg=string}  \"获取用户信息\"\n// @Router    /user/getUserInfo [get]\nfunc (b *BaseApi) GetUserInfo(c *gin.Context) {\n\tuuid := utils.GetUserUuid(c)\n\tReqUser, err := userService.GetUserInfo(uuid)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(gin.H{\"userInfo\": ReqUser}, \"获取成功\", c)\n}\n\n// ResetPassword\n// @Tags      SysUser\n// @Summary   重置用户密码\n// @Security  ApiKeyAuth\n// @Produce  application/json\n// @Param     data  body      system.SysUser                 true  \"ID\"\n// @Success   200   {object}  response.Response{msg=string}  \"重置用户密码\"\n// @Router    /user/resetPassword [post]\nfunc (b *BaseApi) ResetPassword(c *gin.Context) {\n\tvar rps systemReq.ResetPassword\n\terr := c.ShouldBindJSON(&rps)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = userService.ResetPassword(rps.ID, rps.Password)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"重置失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"重置失败\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"重置成功\", c)\n}\n"
  },
  {
    "path": "server/api/v1/system/sys_version.go",
    "content": "package system\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype SysVersionApi struct{}\n\n// buildMenuTree 构建菜单树结构\nfunc buildMenuTree(menus []system.SysBaseMenu) []system.SysBaseMenu {\n\t// 创建菜单映射\n\tmenuMap := make(map[uint]*system.SysBaseMenu)\n\tfor i := range menus {\n\t\tmenuMap[menus[i].ID] = &menus[i]\n\t}\n\n\t// 构建树结构\n\tvar rootMenus []system.SysBaseMenu\n\tfor _, menu := range menus {\n\t\tif menu.ParentId == 0 {\n\t\t\t// 根菜单\n\t\t\tmenuData := convertMenuToStruct(menu, menuMap)\n\t\t\trootMenus = append(rootMenus, menuData)\n\t\t}\n\t}\n\n\t// 按sort排序根菜单\n\tsort.Slice(rootMenus, func(i, j int) bool {\n\t\treturn rootMenus[i].Sort < rootMenus[j].Sort\n\t})\n\n\treturn rootMenus\n}\n\n// convertMenuToStruct 将菜单转换为结构体并递归处理子菜单\nfunc convertMenuToStruct(menu system.SysBaseMenu, menuMap map[uint]*system.SysBaseMenu) system.SysBaseMenu {\n\tresult := system.SysBaseMenu{\n\t\tPath:      menu.Path,\n\t\tName:      menu.Name,\n\t\tHidden:    menu.Hidden,\n\t\tComponent: menu.Component,\n\t\tSort:      menu.Sort,\n\t\tMeta:      menu.Meta,\n\t}\n\n\t// 清理并复制参数数据\n\tif len(menu.Parameters) > 0 {\n\t\tcleanParameters := make([]system.SysBaseMenuParameter, 0, len(menu.Parameters))\n\t\tfor _, param := range menu.Parameters {\n\t\t\tcleanParam := system.SysBaseMenuParameter{\n\t\t\t\tType:  param.Type,\n\t\t\t\tKey:   param.Key,\n\t\t\t\tValue: param.Value,\n\t\t\t\t// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID\n\t\t\t}\n\t\t\tcleanParameters = append(cleanParameters, cleanParam)\n\t\t}\n\t\tresult.Parameters = cleanParameters\n\t}\n\n\t// 清理并复制菜单按钮数据\n\tif len(menu.MenuBtn) > 0 {\n\t\tcleanMenuBtns := make([]system.SysBaseMenuBtn, 0, len(menu.MenuBtn))\n\t\tfor _, btn := range menu.MenuBtn {\n\t\t\tcleanBtn := system.SysBaseMenuBtn{\n\t\t\t\tName: btn.Name,\n\t\t\t\tDesc: btn.Desc,\n\t\t\t\t// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID\n\t\t\t}\n\t\t\tcleanMenuBtns = append(cleanMenuBtns, cleanBtn)\n\t\t}\n\t\tresult.MenuBtn = cleanMenuBtns\n\t}\n\n\t// 查找并处理子菜单\n\tvar children []system.SysBaseMenu\n\tfor _, childMenu := range menuMap {\n\t\tif childMenu.ParentId == menu.ID {\n\t\t\tchildData := convertMenuToStruct(*childMenu, menuMap)\n\t\t\tchildren = append(children, childData)\n\t\t}\n\t}\n\n\t// 按sort排序子菜单\n\tif len(children) > 0 {\n\t\tsort.Slice(children, func(i, j int) bool {\n\t\t\treturn children[i].Sort < children[j].Sort\n\t\t})\n\t\tresult.Children = children\n\t}\n\n\treturn result\n}\n\n// DeleteSysVersion 删除版本管理\n// @Tags SysVersion\n// @Summary 删除版本管理\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body system.SysVersion true \"删除版本管理\"\n// @Success 200 {object} response.Response{msg=string} \"删除成功\"\n// @Router /sysVersion/deleteSysVersion [delete]\nfunc (sysVersionApi *SysVersionApi) DeleteSysVersion(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tID := c.Query(\"ID\")\n\terr := sysVersionService.DeleteSysVersion(ctx, ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// DeleteSysVersionByIds 批量删除版本管理\n// @Tags SysVersion\n// @Summary 批量删除版本管理\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{msg=string} \"批量删除成功\"\n// @Router /sysVersion/deleteSysVersionByIds [delete]\nfunc (sysVersionApi *SysVersionApi) DeleteSysVersionByIds(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tIDs := c.QueryArray(\"IDs[]\")\n\terr := sysVersionService.DeleteSysVersionByIds(ctx, IDs)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\n// FindSysVersion 用id查询版本管理\n// @Tags SysVersion\n// @Summary 用id查询版本管理\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param ID query uint true \"用id查询版本管理\"\n// @Success 200 {object} response.Response{data=system.SysVersion,msg=string} \"查询成功\"\n// @Router /sysVersion/findSysVersion [get]\nfunc (sysVersionApi *SysVersionApi) FindSysVersion(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tID := c.Query(\"ID\")\n\tresysVersion, err := sysVersionService.GetSysVersion(ctx, ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithData(resysVersion, c)\n}\n\n// GetSysVersionList 分页获取版本管理列表\n// @Tags SysVersion\n// @Summary 分页获取版本管理列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query systemReq.SysVersionSearch true \"分页获取版本管理列表\"\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /sysVersion/getSysVersionList [get]\nfunc (sysVersionApi *SysVersionApi) GetSysVersionList(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\tvar pageInfo systemReq.SysVersionSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := sysVersionService.GetSysVersionInfoList(ctx, pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// GetSysVersionPublic 不需要鉴权的版本管理接口\n// @Tags SysVersion\n// @Summary 不需要鉴权的版本管理接口\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /sysVersion/getSysVersionPublic [get]\nfunc (sysVersionApi *SysVersionApi) GetSysVersionPublic(c *gin.Context) {\n\t// 创建业务用Context\n\tctx := c.Request.Context()\n\n\t// 此接口不需要鉴权\n\t// 示例为返回了一个固定的消息接口，一般本接口用于C端服务，需要自己实现业务逻辑\n\tsysVersionService.GetSysVersionPublic(ctx)\n\tresponse.OkWithDetailed(gin.H{\n\t\t\"info\": \"不需要鉴权的版本管理接口信息\",\n\t}, \"获取成功\", c)\n}\n\n// ExportVersion 创建发版数据\n// @Tags SysVersion\n// @Summary 创建发版数据\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body systemReq.ExportVersionRequest true \"创建发版数据\"\n// @Success 200 {object} response.Response{msg=string} \"创建成功\"\n// @Router /sysVersion/exportVersion [post]\nfunc (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {\n\tctx := c.Request.Context()\n\n\tvar req systemReq.ExportVersionRequest\n\terr := c.ShouldBindJSON(&req)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\n\t// 获取选中的菜单数据\n\tvar menuData []system.SysBaseMenu\n\tif len(req.MenuIds) > 0 {\n\t\tmenuData, err = sysVersionService.GetMenusByIds(ctx, req.MenuIds)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"获取菜单数据失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"获取菜单数据失败:\"+err.Error(), c)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// 获取选中的API数据\n\tvar apiData []system.SysApi\n\tif len(req.ApiIds) > 0 {\n\t\tapiData, err = sysVersionService.GetApisByIds(ctx, req.ApiIds)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"获取API数据失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"获取API数据失败:\"+err.Error(), c)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// 获取选中的字典数据\n\tvar dictData []system.SysDictionary\n\tif len(req.DictIds) > 0 {\n\t\tdictData, err = sysVersionService.GetDictionariesByIds(ctx, req.DictIds)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"获取字典数据失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"获取字典数据失败:\"+err.Error(), c)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// 处理菜单数据，构建递归的children结构\n\tprocessedMenus := buildMenuTree(menuData)\n\n\t// 处理API数据，清除ID和时间戳字段\n\tprocessedApis := make([]system.SysApi, 0, len(apiData))\n\tfor _, api := range apiData {\n\t\tcleanApi := system.SysApi{\n\t\t\tPath:        api.Path,\n\t\t\tDescription: api.Description,\n\t\t\tApiGroup:    api.ApiGroup,\n\t\t\tMethod:      api.Method,\n\t\t}\n\t\tprocessedApis = append(processedApis, cleanApi)\n\t}\n\n\t// 处理字典数据，清除ID和时间戳字段，包含字典详情\n\tprocessedDicts := make([]system.SysDictionary, 0, len(dictData))\n\tfor _, dict := range dictData {\n\t\tcleanDict := system.SysDictionary{\n\t\t\tName:   dict.Name,\n\t\t\tType:   dict.Type,\n\t\t\tStatus: dict.Status,\n\t\t\tDesc:   dict.Desc,\n\t\t}\n\t\t\n\t\t// 处理字典详情数据，清除ID和时间戳字段\n\t\tcleanDetails := make([]system.SysDictionaryDetail, 0, len(dict.SysDictionaryDetails))\n\t\tfor _, detail := range dict.SysDictionaryDetails {\n\t\t\tcleanDetail := system.SysDictionaryDetail{\n\t\t\t\tLabel:  detail.Label,\n\t\t\t\tValue:  detail.Value,\n\t\t\t\tExtend: detail.Extend,\n\t\t\t\tStatus: detail.Status,\n\t\t\t\tSort:   detail.Sort,\n\t\t\t\t// 不复制 ID, CreatedAt, UpdatedAt, SysDictionaryID\n\t\t\t}\n\t\t\tcleanDetails = append(cleanDetails, cleanDetail)\n\t\t}\n\t\tcleanDict.SysDictionaryDetails = cleanDetails\n\t\t\n\t\tprocessedDicts = append(processedDicts, cleanDict)\n\t}\n\n\t// 构建导出数据\n\texportData := systemRes.ExportVersionResponse{\n\t\tVersion: systemReq.VersionInfo{\n\t\t\tName:        req.VersionName,\n\t\t\tCode:        req.VersionCode,\n\t\t\tDescription: req.Description,\n\t\t\tExportTime:  time.Now().Format(\"2006-01-02 15:04:05\"),\n\t\t},\n\t\tMenus:        processedMenus,\n\t\tApis:         processedApis,\n\t\tDictionaries: processedDicts,\n\t}\n\n\t// 转换为JSON\n\tjsonData, err := json.MarshalIndent(exportData, \"\", \"  \")\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"JSON序列化失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"JSON序列化失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\t// 保存版本记录\n\tversion := system.SysVersion{\n\t\tVersionName: utils.Pointer(req.VersionName),\n\t\tVersionCode: utils.Pointer(req.VersionCode),\n\t\tDescription: utils.Pointer(req.Description),\n\t\tVersionData: utils.Pointer(string(jsonData)),\n\t}\n\n\terr = sysVersionService.CreateSysVersion(ctx, &version)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存版本记录失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"保存版本记录失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\tresponse.OkWithMessage(\"创建发版成功\", c)\n}\n\n// DownloadVersionJson 下载版本JSON数据\n// @Tags SysVersion\n// @Summary 下载版本JSON数据\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param ID query string true \"版本ID\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"下载成功\"\n// @Router /sysVersion/downloadVersionJson [get]\nfunc (sysVersionApi *SysVersionApi) DownloadVersionJson(c *gin.Context) {\n\tctx := c.Request.Context()\n\n\tID := c.Query(\"ID\")\n\tif ID == \"\" {\n\t\tresponse.FailWithMessage(\"版本ID不能为空\", c)\n\t\treturn\n\t}\n\n\t// 获取版本记录\n\tversion, err := sysVersionService.GetSysVersion(ctx, ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取版本记录失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取版本记录失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\t// 构建JSON数据\n\tvar jsonData []byte\n\tif version.VersionData != nil && *version.VersionData != \"\" {\n\t\tjsonData = []byte(*version.VersionData)\n\t} else {\n\t\t// 如果没有存储的JSON数据，构建一个基本的结构\n\t\tbasicData := systemRes.ExportVersionResponse{\n\t\t\tVersion: systemReq.VersionInfo{\n\t\t\t\tName:        *version.VersionName,\n\t\t\t\tCode:        *version.VersionCode,\n\t\t\t\tDescription: *version.Description,\n\t\t\t\tExportTime:  version.CreatedAt.Format(\"2006-01-02 15:04:05\"),\n\t\t\t},\n\t\t\tMenus: []system.SysBaseMenu{},\n\t\t\tApis:  []system.SysApi{},\n\t\t}\n\t\tjsonData, _ = json.MarshalIndent(basicData, \"\", \"  \")\n\t}\n\n\t// 设置下载响应头\n\tfilename := fmt.Sprintf(\"version_%s_%s.json\", *version.VersionCode, time.Now().Format(\"20060102150405\"))\n\tc.Header(\"Content-Type\", \"application/json\")\n\tc.Header(\"Content-Disposition\", fmt.Sprintf(\"attachment; filename=%s\", filename))\n\tc.Header(\"Content-Length\", strconv.Itoa(len(jsonData)))\n\n\tc.Data(http.StatusOK, \"application/json\", jsonData)\n}\n\n// ImportVersion 导入版本数据\n// @Tags SysVersion\n// @Summary 导入版本数据\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body systemReq.ImportVersionRequest true \"版本JSON数据\"\n// @Success 200 {object} response.Response{msg=string} \"导入成功\"\n// @Router /sysVersion/importVersion [post]\nfunc (sysVersionApi *SysVersionApi) ImportVersion(c *gin.Context) {\n\tctx := c.Request.Context()\n\n\t// 获取JSON数据\n\tvar importData systemReq.ImportVersionRequest\n\terr := c.ShouldBindJSON(&importData)\n\tif err != nil {\n\t\tresponse.FailWithMessage(\"解析JSON数据失败:\"+err.Error(), c)\n\t\treturn\n\t}\n\n\t// 验证数据格式\n\tif importData.VersionInfo.Name == \"\" || importData.VersionInfo.Code == \"\" {\n\t\tresponse.FailWithMessage(\"版本信息格式错误\", c)\n\t\treturn\n\t}\n\n\t// 导入菜单数据\n\tif len(importData.ExportMenu) > 0 {\n\t\tif err := sysVersionService.ImportMenus(ctx, importData.ExportMenu); err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"导入菜单失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"导入菜单失败: \"+err.Error(), c)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// 导入API数据\n\tif len(importData.ExportApi) > 0 {\n\t\tif err := sysVersionService.ImportApis(importData.ExportApi); err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"导入API失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"导入API失败: \"+err.Error(), c)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// 导入字典数据\n\tif len(importData.ExportDictionary) > 0 {\n\t\tif err := sysVersionService.ImportDictionaries(importData.ExportDictionary); err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"导入字典失败!\", zap.Error(err))\n\t\t\tresponse.FailWithMessage(\"导入字典失败: \"+err.Error(), c)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// 创建导入记录\n\tjsonData, _ := json.Marshal(importData)\n\tversion := system.SysVersion{\n\t\tVersionName: utils.Pointer(importData.VersionInfo.Name),\n\t\tVersionCode: utils.Pointer(fmt.Sprintf(\"%s_imported_%s\", importData.VersionInfo.Code, time.Now().Format(\"20060102150405\"))),\n\t\tDescription: utils.Pointer(fmt.Sprintf(\"导入版本: %s\", importData.VersionInfo.Description)),\n\t\tVersionData: utils.Pointer(string(jsonData)),\n\t}\n\n\terr = sysVersionService.CreateSysVersion(ctx, &version)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"保存导入记录失败!\", zap.Error(err))\n\t\t// 这里不返回错误，因为数据已经导入成功\n\t}\n\n\tresponse.OkWithMessage(\"导入成功\", c)\n}\n"
  },
  {
    "path": "server/config/auto_code.go",
    "content": "package config\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype Autocode struct {\n\tWeb    string `mapstructure:\"web\" json:\"web\" yaml:\"web\"`\n\tRoot   string `mapstructure:\"root\" json:\"root\" yaml:\"root\"`\n\tServer string `mapstructure:\"server\" json:\"server\" yaml:\"server\"`\n\tModule string `mapstructure:\"module\" json:\"module\" yaml:\"module\"`\n\tAiPath string `mapstructure:\"ai-path\" json:\"ai-path\" yaml:\"ai-path\"`\n}\n\nfunc (a *Autocode) WebRoot() string {\n\twebs := strings.Split(a.Web, \"/\")\n\tif len(webs) == 0 {\n\t\twebs = strings.Split(a.Web, \"\\\\\")\n\t}\n\treturn filepath.Join(webs...)\n}\n"
  },
  {
    "path": "server/config/captcha.go",
    "content": "package config\n\ntype Captcha struct {\n\tKeyLong            int `mapstructure:\"key-long\" json:\"key-long\" yaml:\"key-long\"`                                     // 验证码长度\n\tImgWidth           int `mapstructure:\"img-width\" json:\"img-width\" yaml:\"img-width\"`                                  // 验证码宽度\n\tImgHeight          int `mapstructure:\"img-height\" json:\"img-height\" yaml:\"img-height\"`                               // 验证码高度\n\tOpenCaptcha        int `mapstructure:\"open-captcha\" json:\"open-captcha\" yaml:\"open-captcha\"`                         // 防爆破验证码开启此数，0代表每次登录都需要验证码，其他数字代表错误密码次数，如3代表错误三次后出现验证码\n\tOpenCaptchaTimeOut int `mapstructure:\"open-captcha-timeout\" json:\"open-captcha-timeout\" yaml:\"open-captcha-timeout\"` // 防爆破验证码超时时间，单位：s(秒)\n}\n"
  },
  {
    "path": "server/config/config.go",
    "content": "package config\n\ntype Server struct {\n\tJWT       JWT     `mapstructure:\"jwt\" json:\"jwt\" yaml:\"jwt\"`\n\tZap       Zap     `mapstructure:\"zap\" json:\"zap\" yaml:\"zap\"`\n\tRedis     Redis   `mapstructure:\"redis\" json:\"redis\" yaml:\"redis\"`\n\tRedisList []Redis `mapstructure:\"redis-list\" json:\"redis-list\" yaml:\"redis-list\"`\n\tMongo     Mongo   `mapstructure:\"mongo\" json:\"mongo\" yaml:\"mongo\"`\n\tEmail     Email   `mapstructure:\"email\" json:\"email\" yaml:\"email\"`\n\tSystem    System  `mapstructure:\"system\" json:\"system\" yaml:\"system\"`\n\tCaptcha   Captcha `mapstructure:\"captcha\" json:\"captcha\" yaml:\"captcha\"`\n\t// auto\n\tAutoCode Autocode `mapstructure:\"autocode\" json:\"autocode\" yaml:\"autocode\"`\n\t// gorm\n\tMysql  Mysql           `mapstructure:\"mysql\" json:\"mysql\" yaml:\"mysql\"`\n\tMssql  Mssql           `mapstructure:\"mssql\" json:\"mssql\" yaml:\"mssql\"`\n\tPgsql  Pgsql           `mapstructure:\"pgsql\" json:\"pgsql\" yaml:\"pgsql\"`\n\tOracle Oracle          `mapstructure:\"oracle\" json:\"oracle\" yaml:\"oracle\"`\n\tSqlite Sqlite          `mapstructure:\"sqlite\" json:\"sqlite\" yaml:\"sqlite\"`\n\tDBList []SpecializedDB `mapstructure:\"db-list\" json:\"db-list\" yaml:\"db-list\"`\n\t// oss\n\tLocal        Local        `mapstructure:\"local\" json:\"local\" yaml:\"local\"`\n\tQiniu        Qiniu        `mapstructure:\"qiniu\" json:\"qiniu\" yaml:\"qiniu\"`\n\tAliyunOSS    AliyunOSS    `mapstructure:\"aliyun-oss\" json:\"aliyun-oss\" yaml:\"aliyun-oss\"`\n\tHuaWeiObs    HuaWeiObs    `mapstructure:\"hua-wei-obs\" json:\"hua-wei-obs\" yaml:\"hua-wei-obs\"`\n\tTencentCOS   TencentCOS   `mapstructure:\"tencent-cos\" json:\"tencent-cos\" yaml:\"tencent-cos\"`\n\tAwsS3        AwsS3        `mapstructure:\"aws-s3\" json:\"aws-s3\" yaml:\"aws-s3\"`\n\tCloudflareR2 CloudflareR2 `mapstructure:\"cloudflare-r2\" json:\"cloudflare-r2\" yaml:\"cloudflare-r2\"`\n\tMinio        Minio        `mapstructure:\"minio\" json:\"minio\" yaml:\"minio\"`\n\n\tExcel Excel `mapstructure:\"excel\" json:\"excel\" yaml:\"excel\"`\n\n\tDiskList []DiskList `mapstructure:\"disk-list\" json:\"disk-list\" yaml:\"disk-list\"`\n\n\t// 跨域配置\n\tCors CORS `mapstructure:\"cors\" json:\"cors\" yaml:\"cors\"`\n\n\t// MCP配置\n\tMCP MCP `mapstructure:\"mcp\" json:\"mcp\" yaml:\"mcp\"`\n}\n"
  },
  {
    "path": "server/config/cors.go",
    "content": "package config\n\ntype CORS struct {\n\tMode      string          `mapstructure:\"mode\" json:\"mode\" yaml:\"mode\"`\n\tWhitelist []CORSWhitelist `mapstructure:\"whitelist\" json:\"whitelist\" yaml:\"whitelist\"`\n}\n\ntype CORSWhitelist struct {\n\tAllowOrigin      string `mapstructure:\"allow-origin\" json:\"allow-origin\" yaml:\"allow-origin\"`\n\tAllowMethods     string `mapstructure:\"allow-methods\" json:\"allow-methods\" yaml:\"allow-methods\"`\n\tAllowHeaders     string `mapstructure:\"allow-headers\" json:\"allow-headers\" yaml:\"allow-headers\"`\n\tExposeHeaders    string `mapstructure:\"expose-headers\" json:\"expose-headers\" yaml:\"expose-headers\"`\n\tAllowCredentials bool   `mapstructure:\"allow-credentials\" json:\"allow-credentials\" yaml:\"allow-credentials\"`\n}\n"
  },
  {
    "path": "server/config/db_list.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"gorm.io/gorm/logger\"\n)\n\ntype DsnProvider interface {\n\tDsn() string\n}\n\n// Embeded 结构体可以压平到上一层，从而保持 config 文件的结构和原来一样\n// 见 playground: https://go.dev/play/p/KIcuhqEoxmY\n\n// GeneralDB 也被 Pgsql 和 Mysql 原样使用\ntype GeneralDB struct {\n\tPrefix       string `mapstructure:\"prefix\" json:\"prefix\" yaml:\"prefix\"`                         // 数据库前缀\n\tPort         string `mapstructure:\"port\" json:\"port\" yaml:\"port\"`                               // 数据库端口\n\tConfig       string `mapstructure:\"config\" json:\"config\" yaml:\"config\"`                         // 高级配置\n\tDbname       string `mapstructure:\"db-name\" json:\"db-name\" yaml:\"db-name\"`                      // 数据库名\n\tUsername     string `mapstructure:\"username\" json:\"username\" yaml:\"username\"`                   // 数据库账号\n\tPassword     string `mapstructure:\"password\" json:\"password\" yaml:\"password\"`                   // 数据库密码\n\tPath         string `mapstructure:\"path\" json:\"path\" yaml:\"path\"`                               // 数据库地址\n\tEngine       string `mapstructure:\"engine\" json:\"engine\" yaml:\"engine\" default:\"InnoDB\"`        // 数据库引擎，默认InnoDB\n\tLogMode      string `mapstructure:\"log-mode\" json:\"log-mode\" yaml:\"log-mode\"`                   // 是否开启Gorm全局日志\n\tMaxIdleConns int    `mapstructure:\"max-idle-conns\" json:\"max-idle-conns\" yaml:\"max-idle-conns\"` // 空闲中的最大连接数\n\tMaxOpenConns int    `mapstructure:\"max-open-conns\" json:\"max-open-conns\" yaml:\"max-open-conns\"` // 打开到数据库的最大连接数\n\tSingular     bool   `mapstructure:\"singular\" json:\"singular\" yaml:\"singular\"`                   // 是否开启全局禁用复数，true表示开启\n\tLogZap       bool   `mapstructure:\"log-zap\" json:\"log-zap\" yaml:\"log-zap\"`                      // 是否通过zap写入日志文件\n}\n\nfunc (c GeneralDB) LogLevel() logger.LogLevel {\n\tswitch strings.ToLower(c.LogMode) {\n\tcase \"silent\":\n\t\treturn logger.Silent\n\tcase \"error\":\n\t\treturn logger.Error\n\tcase \"warn\":\n\t\treturn logger.Warn\n\tcase \"info\":\n\t\treturn logger.Info\n\tdefault:\n\t\treturn logger.Info\n\t}\n}\n\ntype SpecializedDB struct {\n\tType      string `mapstructure:\"type\" json:\"type\" yaml:\"type\"`\n\tAliasName string `mapstructure:\"alias-name\" json:\"alias-name\" yaml:\"alias-name\"`\n\tGeneralDB `yaml:\",inline\" mapstructure:\",squash\"`\n\tDisable   bool `mapstructure:\"disable\" json:\"disable\" yaml:\"disable\"`\n}\n"
  },
  {
    "path": "server/config/disk.go",
    "content": "package config\n\ntype Disk struct {\n\tMountPoint string `mapstructure:\"mount-point\" json:\"mount-point\" yaml:\"mount-point\"`\n}\n\ntype DiskList struct {\n\tDisk `yaml:\",inline\" mapstructure:\",squash\"`\n}\n"
  },
  {
    "path": "server/config/email.go",
    "content": "package config\n\ntype Email struct {\n\tTo          string `mapstructure:\"to\" json:\"to\" yaml:\"to\"`                               // 收件人:多个以英文逗号分隔 例：a@qq.com b@qq.com 正式开发中请把此项目作为参数使用\n\tFrom        string `mapstructure:\"from\" json:\"from\" yaml:\"from\"`                         // 发件人  你自己要发邮件的邮箱\n\tHost        string `mapstructure:\"host\" json:\"host\" yaml:\"host\"`                         // 服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议\n\tSecret      string `mapstructure:\"secret\" json:\"secret\" yaml:\"secret\"`                   // 密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥\n\tNickname    string `mapstructure:\"nickname\" json:\"nickname\" yaml:\"nickname\"`             // 昵称    发件人昵称 通常为自己的邮箱\n\tPort        int    `mapstructure:\"port\" json:\"port\" yaml:\"port\"`                         // 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465\n\tIsSSL       bool   `mapstructure:\"is-ssl\" json:\"is-ssl\" yaml:\"is-ssl\"`                   // 是否SSL   是否开启SSL\n\tIsLoginAuth bool   `mapstructure:\"is-loginauth\" json:\"is-loginauth\" yaml:\"is-loginauth\"` // 是否LoginAuth   是否使用LoginAuth认证方式（适用于IBM、微软邮箱服务器等）\n}\n"
  },
  {
    "path": "server/config/excel.go",
    "content": "package config\n\ntype Excel struct {\n\tDir string `mapstructure:\"dir\" json:\"dir\" yaml:\"dir\"`\n}\n"
  },
  {
    "path": "server/config/gorm_mssql.go",
    "content": "package config\n\ntype Mssql struct {\n\tGeneralDB `yaml:\",inline\" mapstructure:\",squash\"`\n}\n\n// Dsn \"sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm\"\nfunc (m *Mssql) Dsn() string {\n\treturn \"sqlserver://\" + m.Username + \":\" + m.Password + \"@\" + m.Path + \":\" + m.Port + \"?database=\" + m.Dbname + \"&encrypt=disable\"\n}\n"
  },
  {
    "path": "server/config/gorm_mysql.go",
    "content": "package config\n\ntype Mysql struct {\n\tGeneralDB `yaml:\",inline\" mapstructure:\",squash\"`\n}\n\nfunc (m *Mysql) Dsn() string {\n\treturn m.Username + \":\" + m.Password + \"@tcp(\" + m.Path + \":\" + m.Port + \")/\" + m.Dbname + \"?\" + m.Config\n}\n"
  },
  {
    "path": "server/config/gorm_oracle.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n)\n\ntype Oracle struct {\n\tGeneralDB `yaml:\",inline\" mapstructure:\",squash\"`\n}\n\nfunc (m *Oracle) Dsn() string {\n\tdsn := fmt.Sprintf(\"oracle://%s:%s@%s/%s?%s\", url.PathEscape(m.Username), url.PathEscape(m.Password),\n\t\tnet.JoinHostPort(m.Path, m.Port), url.PathEscape(m.Dbname), m.Config)\n\treturn dsn\n\n}\n"
  },
  {
    "path": "server/config/gorm_pgsql.go",
    "content": "package config\n\ntype Pgsql struct {\n\tGeneralDB `yaml:\",inline\" mapstructure:\",squash\"`\n}\n\n// Dsn 基于配置文件获取 dsn\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (p *Pgsql) Dsn() string {\n\treturn \"host=\" + p.Path + \" user=\" + p.Username + \" password=\" + p.Password + \" dbname=\" + p.Dbname + \" port=\" + p.Port + \" \" + p.Config\n}\n\n// LinkDsn 根据 dbname 生成 dsn\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (p *Pgsql) LinkDsn(dbname string) string {\n\treturn \"host=\" + p.Path + \" user=\" + p.Username + \" password=\" + p.Password + \" dbname=\" + dbname + \" port=\" + p.Port + \" \" + p.Config\n}\n"
  },
  {
    "path": "server/config/gorm_sqlite.go",
    "content": "package config\n\nimport (\n\t\"path/filepath\"\n)\n\ntype Sqlite struct {\n\tGeneralDB `yaml:\",inline\" mapstructure:\",squash\"`\n}\n\nfunc (s *Sqlite) Dsn() string {\n\treturn filepath.Join(s.Path, s.Dbname+\".db\")\n}\n"
  },
  {
    "path": "server/config/jwt.go",
    "content": "package config\n\ntype JWT struct {\n\tSigningKey  string `mapstructure:\"signing-key\" json:\"signing-key\" yaml:\"signing-key\"`    // jwt签名\n\tExpiresTime string `mapstructure:\"expires-time\" json:\"expires-time\" yaml:\"expires-time\"` // 过期时间\n\tBufferTime  string `mapstructure:\"buffer-time\" json:\"buffer-time\" yaml:\"buffer-time\"`    // 缓冲时间\n\tIssuer      string `mapstructure:\"issuer\" json:\"issuer\" yaml:\"issuer\"`                   // 签发者\n}\n"
  },
  {
    "path": "server/config/mcp.go",
    "content": "package config\n\ntype MCP struct {\n\tName        string `mapstructure:\"name\" json:\"name\" yaml:\"name\"`                         // MCP名称\n\tVersion     string `mapstructure:\"version\" json:\"version\" yaml:\"version\"`                // MCP版本\n\tSSEPath     string `mapstructure:\"sse_path\" json:\"sse_path\" yaml:\"sse_path\"`             // SSE路径\n\tMessagePath string `mapstructure:\"message_path\" json:\"message_path\" yaml:\"message_path\"` // 消息路径\n\tUrlPrefix   string `mapstructure:\"url_prefix\" json:\"url_prefix\" yaml:\"url_prefix\"`       // URL前缀\n\tAddr        int    `mapstructure:\"addr\" json:\"addr\" yaml:\"addr\"`                         // 独立MCP服务端口\n\tSeparate    bool   `mapstructure:\"separate\" json:\"separate\" yaml:\"separate\"`             // 是否独立运行MCP服务\n}\n"
  },
  {
    "path": "server/config/mongo.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype Mongo struct {\n\tColl             string       `json:\"coll\" yaml:\"coll\" mapstructure:\"coll\"`                                           // collection name\n\tOptions          string       `json:\"options\" yaml:\"options\" mapstructure:\"options\"`                                  // mongodb options\n\tDatabase         string       `json:\"database\" yaml:\"database\" mapstructure:\"database\"`                               // database name\n\tUsername         string       `json:\"username\" yaml:\"username\" mapstructure:\"username\"`                               // 用户名\n\tPassword         string       `json:\"password\" yaml:\"password\" mapstructure:\"password\"`                               // 密码\n\tAuthSource       string       `json:\"auth-source\" yaml:\"auth-source\" mapstructure:\"auth-source\"`                      // 验证数据库\n\tMinPoolSize      uint64       `json:\"min-pool-size\" yaml:\"min-pool-size\" mapstructure:\"min-pool-size\"`                // 最小连接池\n\tMaxPoolSize      uint64       `json:\"max-pool-size\" yaml:\"max-pool-size\" mapstructure:\"max-pool-size\"`                // 最大连接池\n\tSocketTimeoutMs  int64        `json:\"socket-timeout-ms\" yaml:\"socket-timeout-ms\" mapstructure:\"socket-timeout-ms\"`    // socket超时时间\n\tConnectTimeoutMs int64        `json:\"connect-timeout-ms\" yaml:\"connect-timeout-ms\" mapstructure:\"connect-timeout-ms\"` // 连接超时时间\n\tIsZap            bool         `json:\"is-zap\" yaml:\"is-zap\" mapstructure:\"is-zap\"`                                     // 是否开启zap日志\n\tHosts            []*MongoHost `json:\"hosts\" yaml:\"hosts\" mapstructure:\"hosts\"`                                        // 主机列表\n}\n\ntype MongoHost struct {\n\tHost string `json:\"host\" yaml:\"host\" mapstructure:\"host\"` // ip地址\n\tPort string `json:\"port\" yaml:\"port\" mapstructure:\"port\"` // 端口\n}\n\n// Uri .\nfunc (x *Mongo) Uri() string {\n\tlength := len(x.Hosts)\n\thosts := make([]string, 0, length)\n\tfor i := 0; i < length; i++ {\n\t\tif x.Hosts[i].Host != \"\" && x.Hosts[i].Port != \"\" {\n\t\t\thosts = append(hosts, x.Hosts[i].Host+\":\"+x.Hosts[i].Port)\n\t\t}\n\t}\n\tif x.Options != \"\" {\n\t\treturn fmt.Sprintf(\"mongodb://%s/%s?%s\", strings.Join(hosts, \",\"), x.Database, x.Options)\n\t}\n\treturn fmt.Sprintf(\"mongodb://%s/%s\", strings.Join(hosts, \",\"), x.Database)\n}\n"
  },
  {
    "path": "server/config/oss_aliyun.go",
    "content": "package config\n\ntype AliyunOSS struct {\n\tEndpoint        string `mapstructure:\"endpoint\" json:\"endpoint\" yaml:\"endpoint\"`\n\tAccessKeyId     string `mapstructure:\"access-key-id\" json:\"access-key-id\" yaml:\"access-key-id\"`\n\tAccessKeySecret string `mapstructure:\"access-key-secret\" json:\"access-key-secret\" yaml:\"access-key-secret\"`\n\tBucketName      string `mapstructure:\"bucket-name\" json:\"bucket-name\" yaml:\"bucket-name\"`\n\tBucketUrl       string `mapstructure:\"bucket-url\" json:\"bucket-url\" yaml:\"bucket-url\"`\n\tBasePath        string `mapstructure:\"base-path\" json:\"base-path\" yaml:\"base-path\"`\n}\n"
  },
  {
    "path": "server/config/oss_aws.go",
    "content": "package config\n\ntype AwsS3 struct {\n\tBucket           string `mapstructure:\"bucket\" json:\"bucket\" yaml:\"bucket\"`\n\tRegion           string `mapstructure:\"region\" json:\"region\" yaml:\"region\"`\n\tEndpoint         string `mapstructure:\"endpoint\" json:\"endpoint\" yaml:\"endpoint\"`\n\tSecretID         string `mapstructure:\"secret-id\" json:\"secret-id\" yaml:\"secret-id\"`\n\tSecretKey        string `mapstructure:\"secret-key\" json:\"secret-key\" yaml:\"secret-key\"`\n\tBaseURL          string `mapstructure:\"base-url\" json:\"base-url\" yaml:\"base-url\"`\n\tPathPrefix       string `mapstructure:\"path-prefix\" json:\"path-prefix\" yaml:\"path-prefix\"`\n\tS3ForcePathStyle bool   `mapstructure:\"s3-force-path-style\" json:\"s3-force-path-style\" yaml:\"s3-force-path-style\"`\n\tDisableSSL       bool   `mapstructure:\"disable-ssl\" json:\"disable-ssl\" yaml:\"disable-ssl\"`\n}\n"
  },
  {
    "path": "server/config/oss_cloudflare.go",
    "content": "package config\n\ntype CloudflareR2 struct {\n\tBucket          string `mapstructure:\"bucket\" json:\"bucket\" yaml:\"bucket\"`\n\tBaseURL         string `mapstructure:\"base-url\" json:\"base-url\" yaml:\"base-url\"`\n\tPath            string `mapstructure:\"path\" json:\"path\" yaml:\"path\"`\n\tAccountID       string `mapstructure:\"account-id\" json:\"account-id\" yaml:\"account-id\"`\n\tAccessKeyID     string `mapstructure:\"access-key-id\" json:\"access-key-id\" yaml:\"access-key-id\"`\n\tSecretAccessKey string `mapstructure:\"secret-access-key\" json:\"secret-access-key\" yaml:\"secret-access-key\"`\n}\n"
  },
  {
    "path": "server/config/oss_huawei.go",
    "content": "package config\n\ntype HuaWeiObs struct {\n\tPath      string `mapstructure:\"path\" json:\"path\" yaml:\"path\"`\n\tBucket    string `mapstructure:\"bucket\" json:\"bucket\" yaml:\"bucket\"`\n\tEndpoint  string `mapstructure:\"endpoint\" json:\"endpoint\" yaml:\"endpoint\"`\n\tAccessKey string `mapstructure:\"access-key\" json:\"access-key\" yaml:\"access-key\"`\n\tSecretKey string `mapstructure:\"secret-key\" json:\"secret-key\" yaml:\"secret-key\"`\n}\n"
  },
  {
    "path": "server/config/oss_local.go",
    "content": "package config\n\ntype Local struct {\n\tPath      string `mapstructure:\"path\" json:\"path\" yaml:\"path\"`                   // 本地文件访问路径\n\tStorePath string `mapstructure:\"store-path\" json:\"store-path\" yaml:\"store-path\"` // 本地文件存储路径\n}\n"
  },
  {
    "path": "server/config/oss_minio.go",
    "content": "package config\n\ntype Minio struct {\n\tEndpoint        string `mapstructure:\"endpoint\" json:\"endpoint\" yaml:\"endpoint\"`\n\tAccessKeyId     string `mapstructure:\"access-key-id\" json:\"access-key-id\" yaml:\"access-key-id\"`\n\tAccessKeySecret string `mapstructure:\"access-key-secret\" json:\"access-key-secret\" yaml:\"access-key-secret\"`\n\tBucketName      string `mapstructure:\"bucket-name\" json:\"bucket-name\" yaml:\"bucket-name\"`\n\tUseSSL          bool   `mapstructure:\"use-ssl\" json:\"use-ssl\" yaml:\"use-ssl\"`\n\tBasePath        string `mapstructure:\"base-path\" json:\"base-path\" yaml:\"base-path\"`\n\tBucketUrl       string `mapstructure:\"bucket-url\" json:\"bucket-url\" yaml:\"bucket-url\"`\n}\n"
  },
  {
    "path": "server/config/oss_qiniu.go",
    "content": "package config\n\ntype Qiniu struct {\n\tZone          string `mapstructure:\"zone\" json:\"zone\" yaml:\"zone\"`                                  // 存储区域\n\tBucket        string `mapstructure:\"bucket\" json:\"bucket\" yaml:\"bucket\"`                            // 空间名称\n\tImgPath       string `mapstructure:\"img-path\" json:\"img-path\" yaml:\"img-path\"`                      // CDN加速域名\n\tAccessKey     string `mapstructure:\"access-key\" json:\"access-key\" yaml:\"access-key\"`                // 秘钥AK\n\tSecretKey     string `mapstructure:\"secret-key\" json:\"secret-key\" yaml:\"secret-key\"`                // 秘钥SK\n\tUseHTTPS      bool   `mapstructure:\"use-https\" json:\"use-https\" yaml:\"use-https\"`                   // 是否使用https\n\tUseCdnDomains bool   `mapstructure:\"use-cdn-domains\" json:\"use-cdn-domains\" yaml:\"use-cdn-domains\"` // 上传是否使用CDN上传加速\n}\n"
  },
  {
    "path": "server/config/oss_tencent.go",
    "content": "package config\n\ntype TencentCOS struct {\n\tBucket     string `mapstructure:\"bucket\" json:\"bucket\" yaml:\"bucket\"`\n\tRegion     string `mapstructure:\"region\" json:\"region\" yaml:\"region\"`\n\tSecretID   string `mapstructure:\"secret-id\" json:\"secret-id\" yaml:\"secret-id\"`\n\tSecretKey  string `mapstructure:\"secret-key\" json:\"secret-key\" yaml:\"secret-key\"`\n\tBaseURL    string `mapstructure:\"base-url\" json:\"base-url\" yaml:\"base-url\"`\n\tPathPrefix string `mapstructure:\"path-prefix\" json:\"path-prefix\" yaml:\"path-prefix\"`\n}\n"
  },
  {
    "path": "server/config/redis.go",
    "content": "package config\n\ntype Redis struct {\n\tName         string   `mapstructure:\"name\" json:\"name\" yaml:\"name\"`                         // 代表当前实例的名字\n\tAddr         string   `mapstructure:\"addr\" json:\"addr\" yaml:\"addr\"`                         // 服务器地址:端口\n\tPassword     string   `mapstructure:\"password\" json:\"password\" yaml:\"password\"`             // 密码\n\tDB           int      `mapstructure:\"db\" json:\"db\" yaml:\"db\"`                               // 单实例模式下redis的哪个数据库\n\tUseCluster   bool     `mapstructure:\"useCluster\" json:\"useCluster\" yaml:\"useCluster\"`       // 是否使用集群模式\n\tClusterAddrs []string `mapstructure:\"clusterAddrs\" json:\"clusterAddrs\" yaml:\"clusterAddrs\"` // 集群模式下的节点地址列表\n}\n"
  },
  {
    "path": "server/config/system.go",
    "content": "package config\n\ntype System struct {\n\tDbType        string `mapstructure:\"db-type\" json:\"db-type\" yaml:\"db-type\"`    // 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql\n\tOssType       string `mapstructure:\"oss-type\" json:\"oss-type\" yaml:\"oss-type\"` // Oss类型\n\tRouterPrefix  string `mapstructure:\"router-prefix\" json:\"router-prefix\" yaml:\"router-prefix\"`\n\tAddr          int    `mapstructure:\"addr\" json:\"addr\" yaml:\"addr\"` // 端口值\n\tLimitCountIP  int    `mapstructure:\"iplimit-count\" json:\"iplimit-count\" yaml:\"iplimit-count\"`\n\tLimitTimeIP   int    `mapstructure:\"iplimit-time\" json:\"iplimit-time\" yaml:\"iplimit-time\"`\n\tUseMultipoint bool   `mapstructure:\"use-multipoint\" json:\"use-multipoint\" yaml:\"use-multipoint\"`    // 多点登录拦截\n\tUseRedis      bool   `mapstructure:\"use-redis\" json:\"use-redis\" yaml:\"use-redis\"`                   // 使用redis\n\tUseMongo      bool   `mapstructure:\"use-mongo\" json:\"use-mongo\" yaml:\"use-mongo\"`                   // 使用mongo\n\tUseStrictAuth bool   `mapstructure:\"use-strict-auth\" json:\"use-strict-auth\" yaml:\"use-strict-auth\"` // 使用树形角色分配模式\n\tDisableAutoMigrate   bool   `mapstructure:\"disable-auto-migrate\" json:\"disable-auto-migrate\" yaml:\"disable-auto-migrate\"`          // 自动迁移数据库表结构，生产环境建议设为false，手动迁移\n}\n"
  },
  {
    "path": "server/config/zap.go",
    "content": "package config\n\nimport (\n\t\"go.uber.org/zap/zapcore\"\n\t\"time\"\n)\n\ntype Zap struct {\n\tLevel         string `mapstructure:\"level\" json:\"level\" yaml:\"level\"`                            // 级别\n\tPrefix        string `mapstructure:\"prefix\" json:\"prefix\" yaml:\"prefix\"`                         // 日志前缀\n\tFormat        string `mapstructure:\"format\" json:\"format\" yaml:\"format\"`                         // 输出\n\tDirector      string `mapstructure:\"director\" json:\"director\"  yaml:\"director\"`                  // 日志文件夹\n\tEncodeLevel   string `mapstructure:\"encode-level\" json:\"encode-level\" yaml:\"encode-level\"`       // 编码级\n\tStacktraceKey string `mapstructure:\"stacktrace-key\" json:\"stacktrace-key\" yaml:\"stacktrace-key\"` // 栈名\n\tShowLine      bool   `mapstructure:\"show-line\" json:\"show-line\" yaml:\"show-line\"`                // 显示行\n\tLogInConsole  bool   `mapstructure:\"log-in-console\" json:\"log-in-console\" yaml:\"log-in-console\"` // 输出控制台\n\tRetentionDay  int    `mapstructure:\"retention-day\" json:\"retention-day\" yaml:\"retention-day\"`    // 日志保留天数\n}\n\n// Levels 根据字符串转化为 zapcore.Levels\nfunc (c *Zap) Levels() []zapcore.Level {\n\tlevels := make([]zapcore.Level, 0, 7)\n\tlevel, err := zapcore.ParseLevel(c.Level)\n\tif err != nil {\n\t\tlevel = zapcore.DebugLevel\n\t}\n\tfor ; level <= zapcore.FatalLevel; level++ {\n\t\tlevels = append(levels, level)\n\t}\n\treturn levels\n}\n\nfunc (c *Zap) Encoder() zapcore.Encoder {\n\tconfig := zapcore.EncoderConfig{\n\t\tTimeKey:       \"time\",\n\t\tNameKey:       \"name\",\n\t\tLevelKey:      \"level\",\n\t\tCallerKey:     \"caller\",\n\t\tMessageKey:    \"message\",\n\t\tStacktraceKey: c.StacktraceKey,\n\t\tLineEnding:    zapcore.DefaultLineEnding,\n\t\tEncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {\n\t\t\tencoder.AppendString(c.Prefix + t.Format(\"2006-01-02 15:04:05.000\"))\n\t\t},\n\t\tEncodeLevel:    c.LevelEncoder(),\n\t\tEncodeCaller:   zapcore.FullCallerEncoder,\n\t\tEncodeDuration: zapcore.SecondsDurationEncoder,\n\t}\n\tif c.Format == \"json\" {\n\t\treturn zapcore.NewJSONEncoder(config)\n\t}\n\treturn zapcore.NewConsoleEncoder(config)\n\n}\n\n// LevelEncoder 根据 EncodeLevel 返回 zapcore.LevelEncoder\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (c *Zap) LevelEncoder() zapcore.LevelEncoder {\n\tswitch {\n\tcase c.EncodeLevel == \"LowercaseLevelEncoder\": // 小写编码器(默认)\n\t\treturn zapcore.LowercaseLevelEncoder\n\tcase c.EncodeLevel == \"LowercaseColorLevelEncoder\": // 小写编码器带颜色\n\t\treturn zapcore.LowercaseColorLevelEncoder\n\tcase c.EncodeLevel == \"CapitalLevelEncoder\": // 大写编码器\n\t\treturn zapcore.CapitalLevelEncoder\n\tcase c.EncodeLevel == \"CapitalColorLevelEncoder\": // 大写编码器带颜色\n\t\treturn zapcore.CapitalColorLevelEncoder\n\tdefault:\n\t\treturn zapcore.LowercaseLevelEncoder\n\t}\n}\n"
  },
  {
    "path": "server/config.docker.yaml",
    "content": "# github.com/flipped-aurora/gin-vue-admin/server Global Configuration\n\n# jwt configuration\njwt:\n    signing-key: qmPlus\n    expires-time: 7d\n    buffer-time: 1d\n    issuer: qmPlus\n# zap logger configuration\nzap:\n    level: info\n    format: console\n    prefix: \"[github.com/flipped-aurora/gin-vue-admin/server]\"\n    director: log\n    show-line: true\n    encode-level: LowercaseColorLevelEncoder\n    stacktrace-key: stacktrace\n    log-in-console: true\n    retention-day: -1\n\n# redis configuration\nredis:\n    #是否使用redis集群模式\n    useCluster: false\n    #使用集群模式addr和db默认无效\n    addr: 177.7.0.14:6379\n    password: \"\"\n    db: 0\n    clusterAddrs:\n        - \"177.7.0.14:7000\"\n        - \"177.7.0.15:7001\"\n        - \"177.7.0.13:7002\"\n\n# redis-list configuration\nredis-list:\n    - name: cache           # 数据库的名称,注意: name 需要在 redis-list 中唯一\n      useCluster: false     # 是否使用redis集群模式\n      addr: 177.7.0.14:6379  # 使用集群模式addr和db默认无效\n      password: \"\"\n      db: 0\n      clusterAddrs:\n          - \"177.7.0.14:7000\"\n          - \"177.7.0.15:7001\"\n          - \"177.7.0.13:7002\"\n\n# mongo configuration\nmongo:\n    coll: ''\n    options: ''\n    database: ''\n    username: ''\n    password: ''\n    auth-source: ''\n    min-pool-size: 0\n    max-pool-size: 100\n    socket-timeout-ms: 0\n    connect-timeout-ms: 0\n    is-zap: false\n    hosts:\n        - host: ''\n          port: ''\n\n# email configuration\nemail:\n    to: xxx@qq.com\n    port: 465\n    from: xxx@163.com\n    host: smtp.163.com\n    is-ssl: true\n    secret: xxx\n    nickname: test\n\n# system configuration\nsystem:\n    env: local # 修改为public可以关闭路由日志输出\n    addr: 8888\n    db-type: mysql\n    oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置\n    use-redis: false # 使用redis\n    use-mongo: false     # 使用mongo\n    use-multipoint: false\n    # IP限制次数 一个小时15000次\n    iplimit-count: 15000\n    #  IP限制一个小时\n    iplimit-time: 3600\n    #  路由全局前缀\n    router-prefix: \"\"\n    #  严格角色模式 打开后权限将会存在上下级关系\n    use-strict-auth: false\n\n# captcha configuration\ncaptcha:\n    key-long: 6\n    img-width: 240\n    img-height: 80\n    open-captcha: 0 # 0代表一直开启，大于0代表限制次数\n    open-captcha-timeout: 3600 # open-captcha大于0时才生效\n\n# mysql connect configuration\n# 未初始化之前请勿手动修改数据库信息！！！如果一定要手动初始化请看（https://gin-vue-admin.com/docs/first_master）\nmysql:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\n\n# pgsql connect configuration\n# 未初始化之前请勿手动修改数据库信息！！！如果一定要手动初始化请看（https://gin-vue-admin.com/docs/first_master）\npgsql:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\noracle:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\nmssql:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\nsqlite:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\ndb-list:\n    - disable: true # 是否禁用\n      type: \"\" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle\n      alias-name: \"\" # 数据库的名称,注意: alias-name 需要在db-list中唯一\n      path: \"\"\n      port: \"\"\n      config: \"\"\n      db-name: \"\"\n      username: \"\"\n      password: \"\"\n      max-idle-conns: 10\n      max-open-conns: 100\n      log-mode: \"\"\n      log-zap: false\n\n# local configuration\nlocal:\n    path: uploads/file\n    store-path: uploads/file\n\n# autocode configuration\nautocode:\n    web: web/src\n    root: \"\" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径\n    server: server\n    module: 'github.com/flipped-aurora/gin-vue-admin/server'\n    ai-path: \"\"  # AI服务路径\n\n# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)\nqiniu:\n    zone: ZoneHuaDong\n    bucket: \"\"\n    img-path: \"\"\n    use-https: false\n    access-key: \"\"\n    secret-key: \"\"\n    use-cdn-domains: false\n\n# minio oss configuration\nminio:\n    endpoint: yourEndpoint\n    access-key-id: yourAccessKeyId\n    access-key-secret: yourAccessKeySecret\n    bucket-name: yourBucketName\n    use-ssl: false\n    base-path: \"\"\n    bucket-url: \"http://host:9000/yourBucketName\"\n\n# aliyun oss configuration\naliyun-oss:\n    endpoint: yourEndpoint\n    access-key-id: yourAccessKeyId\n    access-key-secret: yourAccessKeySecret\n    bucket-name: yourBucketName\n    bucket-url: yourBucketUrl\n    base-path: yourBasePath\n\n# tencent cos configuration\ntencent-cos:\n    bucket: xxxxx-10005608\n    region: ap-shanghai\n    secret-id: your-secret-id\n    secret-key: your-secret-key\n    base-url: https://gin.vue.admin\n    path-prefix: github.com/flipped-aurora/gin-vue-admin/server\n\n# aws s3 configuration (minio compatible)\naws-s3:\n    bucket: xxxxx-10005608\n    region: ap-shanghai\n    endpoint: \"\"\n    s3-force-path-style: false\n    disable-ssl: false\n    secret-id: your-secret-id\n    secret-key: your-secret-key\n    base-url: https://gin.vue.admin\n    path-prefix: github.com/flipped-aurora/gin-vue-admin/server\n\n# cloudflare r2 configuration\ncloudflare-r2:\n    bucket: xxxx0bucket\n    base-url: https://gin.vue.admin.com\n    path: uploads\n    account-id: xxx_account_id\n    access-key-id: xxx_key_id\n    secret-access-key: xxx_secret_key\n\n# huawei obs configuration\nhua-wei-obs:\n    path: you-path\n    bucket: you-bucket\n    endpoint: you-endpoint\n    access-key: you-access-key\n    secret-key: you-secret-key\n\n# excel configuration\nexcel:\n    dir: ./resource/excel/\n\n# disk usage configuration\ndisk-list:\n    - mount-point: \"/\"\n\n# 跨域配置\n# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用\ncors:\n    mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝\n    whitelist:\n        - allow-origin: example1.com\n          allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id\n          allow-methods: POST, GET\n          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type\n\n          allow-credentials: true # 布尔值\n        - allow-origin: example2.com\n          allow-headers: content-type\n          allow-methods: GET, POST\n          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type\n          allow-credentials: true # 布尔值\nmcp:\n    name: GVA_MCP\n    version: v1.0.0\n    sse_path: /sse\n    message_path: /message\n    url_prefix: ''\n    addr: 8889\n    separate: false\n"
  },
  {
    "path": "server/config.yaml",
    "content": "# github.com/flipped-aurora/gin-vue-admin/server Global Configuration\n\n# jwt configuration\njwt:\n    signing-key: qmPlus\n    expires-time: 7d\n    buffer-time: 1d\n    issuer: qmPlus\n# zap logger configuration\nzap:\n    level: info\n    format: console\n    prefix: \"[github.com/flipped-aurora/gin-vue-admin/server]\"\n    director: log\n    show-line: true\n    encode-level: LowercaseColorLevelEncoder\n    stacktrace-key: stacktrace\n    log-in-console: true\n    retention-day: -1\n\n# redis configuration\nredis:\n    #是否使用redis集群模式\n    useCluster: false\n    #使用集群模式addr和db默认无效\n    addr: 127.0.0.1:6379\n    password: \"\"\n    db: 0\n    clusterAddrs:\n        - \"172.21.0.3:7000\"\n        - \"172.21.0.4:7001\"\n        - \"172.21.0.2:7002\"\n\n# redis-list configuration\nredis-list:\n    - name: cache           # 数据库的名称,注意: name 需要在 redis-list 中唯一\n      useCluster: false     # 是否使用redis集群模式\n      addr: 127.0.0.1:6379  # 使用集群模式addr和db默认无效\n      password: \"\"\n      db: 0\n      clusterAddrs:\n          - \"172.21.0.3:7000\"\n          - \"172.21.0.4:7001\"\n          - \"172.21.0.2:7002\"\n\n# mongo configuration\nmongo:\n    coll: ''\n    options: ''\n    database: ''\n    username: ''\n    password: ''\n    auth-source: ''\n    min-pool-size: 0\n    max-pool-size: 100\n    socket-timeout-ms: 0\n    connect-timeout-ms: 0\n    is-zap: false\n    hosts:\n        - host: ''\n          port: ''\n\n# email configuration\nemail:\n    to: xxx@qq.com\n    port: 465\n    from: xxx@163.com\n    host: smtp.163.com\n    is-ssl: true\n    secret: xxx\n    nickname: test\n\n# system configuration\nsystem:\n    env: local # 修改为public可以关闭路由日志输出\n    addr: 8888\n    db-type: mysql\n    oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置\n    use-redis: false # 使用redis\n    use-mongo: false     # 使用mongo\n    use-multipoint: false\n    # IP限制次数 一个小时15000次\n    iplimit-count: 15000\n    #  IP限制一个小时\n    iplimit-time: 3600\n    #  路由全局前缀\n    router-prefix: \"\"\n    #  严格角色模式 打开后权限将会存在上下级关系\n    use-strict-auth: false\n    #  禁用自动迁移数据库表结构，生产环境建议设为true，手动迁移\n    disable-auto-migrate: false\n\n# captcha configuration\ncaptcha:\n    key-long: 6\n    img-width: 240\n    img-height: 80\n    open-captcha: 0 # 0代表一直开启，大于0代表限制次数\n    open-captcha-timeout: 3600 # open-captcha大于0时才生效\n\n# mysql connect configuration\n# 未初始化之前请勿手动修改数据库信息！！！如果一定要手动初始化请看（https://gin-vue-admin.com/docs/first_master）\nmysql:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\n\n# pgsql connect configuration\n# 未初始化之前请勿手动修改数据库信息！！！如果一定要手动初始化请看（https://gin-vue-admin.com/docs/first_master）\npgsql:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\noracle:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\nmssql:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\nsqlite:\n    path: \"\"\n    port: \"\"\n    config: \"\"\n    db-name: \"\"\n    username: \"\"\n    password: \"\"\n    max-idle-conns: 10\n    max-open-conns: 100\n    log-mode: \"\"\n    log-zap: false\ndb-list:\n    - disable: true # 是否禁用\n      type: \"\" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle\n      alias-name: \"\" # 数据库的名称,注意: alias-name 需要在db-list中唯一\n      path: \"\"\n      port: \"\"\n      config: \"\"\n      db-name: \"\"\n      username: \"\"\n      password: \"\"\n      max-idle-conns: 10\n      max-open-conns: 100\n      log-mode: \"\"\n      log-zap: false\n\n# local configuration\nlocal:\n    path: uploads/file\n    store-path: uploads/file\n\n# autocode configuration\nautocode:\n    web: web/src\n    root: \"\" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径\n    server: server\n    module: 'github.com/flipped-aurora/gin-vue-admin/server'\n    ai-path: \"\"  # AI服务路径\n\n# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)\nqiniu:\n    zone: ZoneHuaDong\n    bucket: \"\"\n    img-path: \"\"\n    use-https: false\n    access-key: \"\"\n    secret-key: \"\"\n    use-cdn-domains: false\n\n# minio oss configuration\nminio:\n    endpoint: yourEndpoint\n    access-key-id: yourAccessKeyId\n    access-key-secret: yourAccessKeySecret\n    bucket-name: yourBucketName\n    use-ssl: false\n    base-path: \"\"\n    bucket-url: \"http://host:9000/yourBucketName\"\n\n# aliyun oss configuration\naliyun-oss:\n    endpoint: yourEndpoint\n    access-key-id: yourAccessKeyId\n    access-key-secret: yourAccessKeySecret\n    bucket-name: yourBucketName\n    bucket-url: yourBucketUrl\n    base-path: yourBasePath\n\n# tencent cos configuration\ntencent-cos:\n    bucket: xxxxx-10005608\n    region: ap-shanghai\n    secret-id: your-secret-id\n    secret-key: your-secret-key\n    base-url: https://gin.vue.admin\n    path-prefix: github.com/flipped-aurora/gin-vue-admin/server\n\n# aws s3 configuration (minio compatible)\naws-s3:\n    bucket: xxxxx-10005608\n    region: ap-shanghai\n    endpoint: \"\"\n    s3-force-path-style: false\n    disable-ssl: false\n    secret-id: your-secret-id\n    secret-key: your-secret-key\n    base-url: https://gin.vue.admin\n    path-prefix: github.com/flipped-aurora/gin-vue-admin/server\n\n# cloudflare r2 configuration\ncloudflare-r2:\n    bucket: xxxx0bucket\n    base-url: https://gin.vue.admin.com\n    path: uploads\n    account-id: xxx_account_id\n    access-key-id: xxx_key_id\n    secret-access-key: xxx_secret_key\n\n# huawei obs configuration\nhua-wei-obs:\n    path: you-path\n    bucket: you-bucket\n    endpoint: you-endpoint\n    access-key: you-access-key\n    secret-key: you-secret-key\n\n# excel configuration\nexcel:\n    dir: ./resource/excel/\n\n# disk usage configuration\ndisk-list:\n    - mount-point: \"/\"\n\n# 跨域配置\n# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用\ncors:\n    mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝\n    whitelist:\n        - allow-origin: example1.com\n          allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id\n          allow-methods: POST, GET\n          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type\n          allow-credentials: true # 布尔值\n        - allow-origin: example2.com\n          allow-headers: content-type\n          allow-methods: GET, POST\n          expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type\n          allow-credentials: true # 布尔值\nmcp:\n    name: GVA_MCP\n    version: v1.0.0\n    sse_path: /sse\n    message_path: /message\n    url_prefix: ''\n    addr: 8889\n    separate: false\n"
  },
  {
    "path": "server/core/internal/constant.go",
    "content": "package internal\n\nconst (\n\tConfigEnv         = \"GVA_CONFIG\"\n\tConfigDefaultFile = \"config.yaml\"\n\tConfigTestFile    = \"config.test.yaml\"\n\tConfigDebugFile   = \"config.debug.yaml\"\n\tConfigReleaseFile = \"config.release.yaml\"\n)\n"
  },
  {
    "path": "server/core/internal/cutter.go",
    "content": "package internal\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Cutter 实现 io.Writer 接口\n// 用于日志切割, strings.Join([]string{director,layout, formats..., level+\".log\"}, os.PathSeparator)\ntype Cutter struct {\n\tlevel        string        // 日志级别(debug, info, warn, error, dpanic, panic, fatal)\n\tlayout       string        // 时间格式 2006-01-02 15:04:05\n\tformats      []string      // 自定义参数([]string{Director,\"2006-01-02\", \"business\"(此参数可不写), level+\".log\"}\n\tdirector     string        // 日志文件夹\n\tretentionDay int           //日志保留天数\n\tfile         *os.File      // 文件句柄\n\tmutex        *sync.RWMutex // 读写锁\n}\n\ntype CutterOption func(*Cutter)\n\n// CutterWithLayout 时间格式\nfunc CutterWithLayout(layout string) CutterOption {\n\treturn func(c *Cutter) {\n\t\tc.layout = layout\n\t}\n}\n\n// CutterWithFormats 格式化参数\nfunc CutterWithFormats(format ...string) CutterOption {\n\treturn func(c *Cutter) {\n\t\tif len(format) > 0 {\n\t\t\tc.formats = format\n\t\t}\n\t}\n}\n\nfunc NewCutter(director string, level string, retentionDay int, options ...CutterOption) *Cutter {\n\trotate := &Cutter{\n\t\tlevel:        level,\n\t\tdirector:     director,\n\t\tretentionDay: retentionDay,\n\t\tmutex:        new(sync.RWMutex),\n\t}\n\tfor i := 0; i < len(options); i++ {\n\t\toptions[i](rotate)\n\t}\n\treturn rotate\n}\n\n// Write satisfies the io.Writer interface. It writes to the\n// appropriate file handle that is currently being used.\n// If we have reached rotation time, the target file gets\n// automatically rotated, and also purged if necessary.\nfunc (c *Cutter) Write(bytes []byte) (n int, err error) {\n\tc.mutex.Lock()\n\tdefer func() {\n\t\tif c.file != nil {\n\t\t\t_ = c.file.Close()\n\t\t\tc.file = nil\n\t\t}\n\t\tc.mutex.Unlock()\n\t}()\n\tlength := len(c.formats)\n\tvalues := make([]string, 0, 3+length)\n\tvalues = append(values, c.director)\n\tif c.layout != \"\" {\n\t\tvalues = append(values, time.Now().Format(c.layout))\n\t}\n\tfor i := 0; i < length; i++ {\n\t\tvalues = append(values, c.formats[i])\n\t}\n\tvalues = append(values, c.level+\".log\")\n\tfilename := filepath.Join(values...)\n\tdirector := filepath.Dir(filename)\n\terr = os.MkdirAll(director, os.ModePerm)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer func() {\n\t\terr := removeNDaysFolders(c.director, c.retentionDay)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"清理过期日志失败\", err)\n\t\t}\n\t}()\n\n\tc.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn c.file.Write(bytes)\n}\n\nfunc (c *Cutter) Sync() error {\n\tc.mutex.Lock()\n\tdefer c.mutex.Unlock()\n\n\tif c.file != nil {\n\t\treturn c.file.Sync()\n\t}\n\treturn nil\n}\n\n// 增加日志目录文件清理 小于等于零的值默认忽略不再处理\nfunc removeNDaysFolders(dir string, days int) error {\n\tif days <= 0 {\n\t\treturn nil\n\t}\n\tcutoff := time.Now().AddDate(0, 0, -days)\n\treturn filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.IsDir() && info.ModTime().Before(cutoff) && path != dir {\n\t\t\terr = os.RemoveAll(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "server/core/internal/zap_core.go",
    "content": "package internal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\tastutil \"github.com/flipped-aurora/gin-vue-admin/server/utils/ast\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/stacktrace\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype ZapCore struct {\n\tlevel zapcore.Level\n\tzapcore.Core\n}\n\nfunc NewZapCore(level zapcore.Level) *ZapCore {\n\tentity := &ZapCore{level: level}\n\tsyncer := entity.WriteSyncer()\n\tlevelEnabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool {\n\t\treturn l == level\n\t})\n\tentity.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, levelEnabler)\n\treturn entity\n}\n\nfunc (z *ZapCore) WriteSyncer(formats ...string) zapcore.WriteSyncer {\n\tcutter := NewCutter(\n\t\tglobal.GVA_CONFIG.Zap.Director,\n\t\tz.level.String(),\n\t\tglobal.GVA_CONFIG.Zap.RetentionDay,\n\t\tCutterWithLayout(time.DateOnly),\n\t\tCutterWithFormats(formats...),\n\t)\n\tif global.GVA_CONFIG.Zap.LogInConsole {\n\t\tmultiSyncer := zapcore.NewMultiWriteSyncer(os.Stdout, cutter)\n\t\treturn zapcore.AddSync(multiSyncer)\n\t}\n\treturn zapcore.AddSync(cutter)\n}\n\nfunc (z *ZapCore) Enabled(level zapcore.Level) bool {\n\treturn z.level == level\n}\n\nfunc (z *ZapCore) With(fields []zapcore.Field) zapcore.Core {\n\treturn z.Core.With(fields)\n}\n\nfunc (z *ZapCore) Check(entry zapcore.Entry, check *zapcore.CheckedEntry) *zapcore.CheckedEntry {\n\tif z.Enabled(entry.Level) {\n\t\treturn check.AddCore(entry, z)\n\t}\n\treturn check\n}\n\nfunc (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {\n\tfor i := 0; i < len(fields); i++ {\n\t\tif fields[i].Key == \"business\" || fields[i].Key == \"folder\" || fields[i].Key == \"directory\" {\n\t\t\tsyncer := z.WriteSyncer(fields[i].String)\n\t\t\tz.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level)\n\t\t}\n\t}\n\t// 先写入原日志目标\n\terr := z.Core.Write(entry, fields)\n\n\t// 捕捉 Error 及以上级别日志并入库，且可提取 zap.Error(err) 的错误内容\n\tif entry.Level >= zapcore.ErrorLevel {\n\t\t// 避免与 GORM zap 写入互相递归：跳过由 gorm logger writer 触发的日志\n\t\tif strings.Contains(entry.Caller.File, \"gorm_logger_writer.go\") {\n\t\t\treturn err\n\t\t}\n\n\t\tform := \"后端\"\n\t\tlevel := entry.Level.String()\n\t\t// 生成基础信息\n\t\tinfo := entry.Message\n\n\t\t// 提取 zap.Error(err) 内容\n\t\tvar errStr string\n\t\tfor i := 0; i < len(fields); i++ {\n\t\t\tf := fields[i]\n\t\t\tif f.Type == zapcore.ErrorType || f.Key == \"error\" || f.Key == \"err\" {\n\t\t\t\tif f.Interface != nil {\n\t\t\t\t\terrStr = fmt.Sprintf(\"%v\", f.Interface)\n\t\t\t\t} else if f.String != \"\" {\n\t\t\t\t\terrStr = f.String\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif errStr != \"\" {\n\t\t\tinfo = fmt.Sprintf(\"%s | 错误: %s\", info, errStr)\n\t\t}\n\n\t\t// 附加来源与堆栈信息\n\t\tif entry.Caller.File != \"\" {\n\t\t\tinfo = fmt.Sprintf(\"%s \\n 源文件:%s:%d\", info, entry.Caller.File, entry.Caller.Line)\n\t\t}\n\t\tstack := entry.Stack\n\t\tif stack != \"\" {\n\t\t\tinfo = fmt.Sprintf(\"%s \\n 调用栈：%s\", info, stack)\n\t\t\t// 解析最终业务调用方，并提取其方法源码\n\t\t\tif frame, ok := stacktrace.FindFinalCaller(stack); ok {\n\t\t\t\tfnName, fnSrc, sLine, eLine, exErr := astutil.ExtractFuncSourceByPosition(frame.File, frame.Line)\n\t\t\t\tif exErr == nil {\n\t\t\t\t\tinfo = fmt.Sprintf(\"%s \\n 最终调用方法:%s:%d (%s lines %d-%d)\\n----- 产生日志的方法代码如下 -----\\n%s\", info, frame.File, frame.Line, fnName, sLine, eLine, fnSrc)\n\t\t\t\t} else {\n\t\t\t\t\tinfo = fmt.Sprintf(\"%s \\n 最终调用方法:%s:%d (%s) | extract_err=%v\", info, frame.File, frame.Line, fnName, exErr)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 使用后台上下文，避免依赖 gin.Context\n\t\tctx := context.Background()\n\t\t_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(ctx, &system.SysError{\n\t\t\tForm:  &form,\n\t\t\tInfo:  &info,\n\t\t\tLevel: level,\n\t\t})\n\t}\n\treturn err\n}\n\nfunc (z *ZapCore) Sync() error {\n\treturn z.Core.Sync()\n}\n"
  },
  {
    "path": "server/core/server.go",
    "content": "package core\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"go.uber.org/zap\"\n\t\"time\"\n)\n\nfunc RunServer() {\n\tif global.GVA_CONFIG.System.UseRedis {\n\t\t// 初始化redis服务\n\t\tinitialize.Redis()\n\t\tif global.GVA_CONFIG.System.UseMultipoint {\n\t\t\tinitialize.RedisList()\n\t\t}\n\t}\n\n\tif global.GVA_CONFIG.System.UseMongo {\n\t\terr := initialize.Mongo.Initialization()\n\t\tif err != nil {\n\t\t\tzap.L().Error(fmt.Sprintf(\"%+v\", err))\n\t\t}\n\t}\n\t// 从db加载jwt数据\n\tif global.GVA_DB != nil {\n\t\tsystem.LoadAll()\n\t}\n\n\tRouter := initialize.Routers()\n\n\taddress := fmt.Sprintf(\":%d\", global.GVA_CONFIG.System.Addr)\n\n\tfmt.Printf(`\n\t欢迎使用 gin-vue-admin\n\t当前版本:%s\n\t加群方式:微信号：shouzi_1994 QQ群：470239250\n\t项目地址：https://github.com/flipped-aurora/gin-vue-admin\n\t插件市场:https://plugin.gin-vue-admin.com\n\tGVA讨论社区:https://support.qq.com/products/371961\n\t默认自动化文档地址:http://127.0.0.1%s/swagger/index.html\n\t默认MCP SSE地址:http://127.0.0.1%s%s\n\t默认MCP Message地址:http://127.0.0.1%s%s\n\t默认前端文件运行地址:http://127.0.0.1:8080\n\t--------------------------------------版权声明--------------------------------------\n\t** 版权所有方：flipped-aurora开源团队 **\n\t** 版权持有公司：北京翻转极光科技有限责任公司 **\n\t** 剔除授权标识需购买商用授权：https://plugin.gin-vue-admin.com/license **\n\t** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**\n`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)\n\tinitServer(address, Router, 10*time.Minute, 10*time.Minute)\n}\n"
  },
  {
    "path": "server/core/server_run.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype server interface {\n\tListenAndServe() error\n\tShutdown(context.Context) error\n}\n\n// initServer 启动服务并实现优雅关闭\nfunc initServer(address string, router *gin.Engine, readTimeout, writeTimeout time.Duration) {\n\t// 创建服务\n\tsrv := &http.Server{\n\t\tAddr:           address,\n\t\tHandler:        router,\n\t\tReadTimeout:    readTimeout,\n\t\tWriteTimeout:   writeTimeout,\n\t\tMaxHeaderBytes: 1 << 20,\n\t}\n\n\t// 在goroutine中启动服务\n\tgo func() {\n\t\tif err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\t\tfmt.Printf(\"listen: %s\\n\", err)\n\t\t\tzap.L().Error(\"server启动失败\", zap.Error(err))\n\t\t\tos.Exit(1)\n\t\t}\n\t}()\n\n\t// 等待中断信号以优雅地关闭服务器\n\tquit := make(chan os.Signal, 1)\n\t// kill (无参数) 默认发送 syscall.SIGTERM\n\t// kill -2 发送 syscall.SIGINT\n\t// kill -9 发送 syscall.SIGKILL，但是无法被捕获，所以不需要添加\n\tsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)\n\t<-quit\n\tzap.L().Info(\"关闭WEB服务...\")\n\n\t// 设置5秒的超时时间\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\n\tdefer cancel()\n\n\tif err := srv.Shutdown(ctx); err != nil {\n\t\tzap.L().Fatal(\"WEB服务关闭异常\", zap.Error(err))\n\t}\n\n\tzap.L().Info(\"WEB服务已关闭\")\n}\n"
  },
  {
    "path": "server/core/viper.go",
    "content": "package core\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/core/internal\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/spf13/viper\"\n)\n\n// Viper 配置\nfunc Viper() *viper.Viper {\n\tconfig := getConfigPath()\n\n\tv := viper.New()\n\tv.SetConfigFile(config)\n\tv.SetConfigType(\"yaml\")\n\terr := v.ReadInConfig()\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"fatal error config file: %w\", err))\n\t}\n\tv.WatchConfig()\n\n\tv.OnConfigChange(func(e fsnotify.Event) {\n\t\tfmt.Println(\"config file changed:\", e.Name)\n\t\tif err = v.Unmarshal(&global.GVA_CONFIG); err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t})\n\tif err = v.Unmarshal(&global.GVA_CONFIG); err != nil {\n\t\tpanic(fmt.Errorf(\"fatal error unmarshal config: %w\", err))\n\t}\n\n\t// root 适配性 根据root位置去找到对应迁移位置,保证root路径有效\n\tglobal.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs(\"..\")\n\treturn v\n}\n\n// getConfigPath 获取配置文件路径, 优先级: 命令行 > 环境变量 > 默认值\nfunc getConfigPath() (config string) {\n\t// `-c` flag parse\n\tflag.StringVar(&config, \"c\", \"\", \"choose config file.\")\n\tflag.Parse()\n\tif config != \"\" { // 命令行参数不为空 将值赋值于config\n\t\tfmt.Printf(\"您正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\\n\", config)\n\t\treturn\n\t}\n\tif env := os.Getenv(internal.ConfigEnv); env != \"\" { // 判断环境变量 GVA_CONFIG\n\t\tconfig = env\n\t\tfmt.Printf(\"您正在使用 %s 环境变量, config 的路径为 %s\\n\", internal.ConfigEnv, config)\n\t\treturn\n\t}\n\n\tswitch gin.Mode() { // 根据 gin 模式文件名\n\tcase gin.DebugMode:\n\t\tconfig = internal.ConfigDebugFile\n\tcase gin.ReleaseMode:\n\t\tconfig = internal.ConfigReleaseFile\n\tcase gin.TestMode:\n\t\tconfig = internal.ConfigTestFile\n\t}\n\tfmt.Printf(\"您正在使用 gin 的 %s 模式运行, config 的路径为 %s\\n\", gin.Mode(), config)\n\n\t_, err := os.Stat(config)\n\tif err != nil || os.IsNotExist(err) {\n\t\tconfig = internal.ConfigDefaultFile\n\t\tfmt.Printf(\"配置文件路径不存在, 使用默认配置文件路径: %s\\n\", config)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "server/core/zap.go",
    "content": "package core\n\nimport (\n    \"fmt\"\n    \"github.com/flipped-aurora/gin-vue-admin/server/core/internal\"\n    \"github.com/flipped-aurora/gin-vue-admin/server/global\"\n    \"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n    \"go.uber.org/zap\"\n    \"go.uber.org/zap/zapcore\"\n    \"os\"\n)\n\n// Zap 获取 zap.Logger\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc Zap() (logger *zap.Logger) {\n\tif ok, _ := utils.PathExists(global.GVA_CONFIG.Zap.Director); !ok { // 判断是否有Director文件夹\n\t\tfmt.Printf(\"create %v directory\\n\", global.GVA_CONFIG.Zap.Director)\n\t\t_ = os.Mkdir(global.GVA_CONFIG.Zap.Director, os.ModePerm)\n\t}\n\tlevels := global.GVA_CONFIG.Zap.Levels()\n\tlength := len(levels)\n\tcores := make([]zapcore.Core, 0, length)\n\tfor i := 0; i < length; i++ {\n\t\tcore := internal.NewZapCore(levels[i])\n\t\tcores = append(cores, core)\n\t}\n    // 构建基础 logger（错误级别的入库逻辑已在自定义 ZapCore 中处理）\n    logger = zap.New(zapcore.NewTee(cores...))\n\t// 启用 Error 及以上级别的堆栈捕捉，确保 entry.Stack 可用\n\topts := []zap.Option{zap.AddStacktrace(zapcore.ErrorLevel)}\n\tif global.GVA_CONFIG.Zap.ShowLine {\n\t\topts = append(opts, zap.AddCaller())\n\t}\n\tlogger = logger.WithOptions(opts...)\n\treturn logger\n}\n"
  },
  {
    "path": "server/docs/docs.go",
    "content": "// Code generated by swaggo/swag. DO NOT EDIT.\n\npackage docs\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/swaggo/swag\"\n)\n\nconst docTemplate = `{\n    \"schemes\": {{ marshal .Schemes }},\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"{{escape .Description}}\",\n        \"title\": \"{{.Title}}\",\n        \"contact\": {},\n        \"version\": \"{{.Version}}\"\n    },\n    \"host\": \"{{.Host}}\",\n    \"basePath\": \"{{.BasePath}}\",\n    \"paths\": {\n        \"/api/createApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"创建基础api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"api路径, api中文描述, api组, 方法\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysApi\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建基础api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/deleteApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"删除api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysApi\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/deleteApisByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"删除选中Api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.IdsReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除选中Api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/enterSyncApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"确认同步API\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"确认同步API\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/freshCasbin\": {\n            \"get\": {\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"刷新casbin缓存\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"刷新成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getAllApis\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"获取所有的Api 不分页\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取所有的Api 不分页,返回包括api列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAPIListResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getApiById\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"根据id获取api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"根据id获取api\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"根据id获取api,返回包括api详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAPIResponse\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getApiGroups\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"获取API分组\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取API分组\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getApiList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"分页获取API列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"分页获取API列表\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SearchApiParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取API列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/ignoreApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"IgnoreApi\"\n                ],\n                \"summary\": \"忽略API\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"同步API\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/syncApi\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"同步API\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"同步API\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/updateApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"修改基础api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"api路径, api中文描述, api组, 方法\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysApi\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"修改基础api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/attachmentCategory/addCategory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"AttachmentCategory\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AddCategory\"\n                ],\n                \"summary\": \"添加媒体库分类\",\n                \"parameters\": [\n                    {\n                        \"description\": \"媒体库分类数据\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaAttachmentCategory\"\n                        }\n                    }\n                ],\n                \"responses\": {}\n            }\n        },\n        \"/attachmentCategory/deleteCategory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"AttachmentCategory\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"DeleteCategory\"\n                ],\n                \"summary\": \"删除分类\",\n                \"parameters\": [\n                    {\n                        \"description\": \"分类id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除分类\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/attachmentCategory/getCategoryList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"AttachmentCategory\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"GetCategoryList\"\n                ],\n                \"summary\": \"媒体库分类列表\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"媒体库分类列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/example.ExaAttachmentCategory\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/copyAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"拷贝角色\",\n                \"parameters\": [\n                    {\n                        \"description\": \"旧角色id, 新权限id, 新权限名, 新父角色id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/response.SysAuthorityCopyResponse\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"拷贝角色,返回包括系统角色详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/createAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"创建角色\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限名, 父角色id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建角色,返回包括系统角色详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/deleteAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"删除角色\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除角色\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除角色\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/getAuthorityList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"分页获取角色列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.PageInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取角色列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/setDataAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"设置角色资源权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"设置角色资源权限\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置角色资源权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/updateAuthority\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"更新角色信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限名, 父角色id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新角色信息,返回包括系统角色详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authorityBtn/canRemoveAuthorityBtn\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityBtn\"\n                ],\n                \"summary\": \"设置权限按钮\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authorityBtn/getAuthorityBtn\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityBtn\"\n                ],\n                \"summary\": \"获取权限按钮\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id, 角色id, 选中的按钮id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAuthorityBtnReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"返回列表成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityBtnRes\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authorityBtn/setAuthorityBtn\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityBtn\"\n                ],\n                \"summary\": \"设置权限按钮\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id, 角色id, 选中的按钮id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAuthorityBtnReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"返回列表成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/addFunc\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AddFunc\"\n                ],\n                \"summary\": \"增加方法\",\n                \"parameters\": [\n                    {\n                        \"description\": \"增加方法\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AutoCode\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"创建成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/createPackage\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePackage\"\n                ],\n                \"summary\": \"创建package\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建package\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAutoCodePackageCreate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/createTemp\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodeTemplate\"\n                ],\n                \"summary\": \"自动代码模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建自动代码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AutoCode\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"创建成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/delPackage\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"删除package\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建package\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/delSysHistory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"删除回滚记录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除回滚记录\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getColumn\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取当前表所有字段\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取当前表所有字段\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getDB\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取当前所有数据库\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取当前所有数据库\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getMeta\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取meta信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取meta信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getPackage\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePackage\"\n                ],\n                \"summary\": \"获取package\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getSysHistory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"查询回滚记录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.PageInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询回滚记录,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getTables\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取当前数据库所有表\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取当前数据库所有表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getTemplates\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePackage\"\n                ],\n                \"summary\": \"获取package\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/initAPI\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"打包插件\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"打包插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/initMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"打包插件\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"打包插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/installPlugin\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"安装插件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"this is a test file\",\n                        \"name\": \"plug\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"安装插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"array\",\n                                            \"items\": {\n                                                \"type\": \"object\"\n                                            }\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/preview\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodeTemplate\"\n                ],\n                \"summary\": \"预览创建后的代码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"预览创建代码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AutoCode\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"预览创建后的代码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/pubPlug\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"打包插件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"插件名称\",\n                        \"name\": \"plugName\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"打包插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/rollback\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"回滚自动生成代码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAutoHistoryRollBack\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"回滚自动生成代码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/base/captcha\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Base\"\n                ],\n                \"summary\": \"生成验证码\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysCaptchaResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/base/login\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Base\"\n                ],\n                \"summary\": \"用户登录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名, 密码, 验证码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Login\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"返回包括用户信息,token,过期时间\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.LoginResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/casbin/UpdateCasbin\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Casbin\"\n                ],\n                \"summary\": \"更新角色api权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限模型列表\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.CasbinInReceive\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新角色api权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/casbin/getPolicyPathByAuthorityId\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Casbin\"\n                ],\n                \"summary\": \"获取权限列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限模型列表\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.CasbinInReceive\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取权限列表,返回包括casbin详情列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PolicyPathResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/customer/customer\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"获取单一客户信息\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"客户名\",\n                        \"name\": \"customerName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"客户手机号\",\n                        \"name\": \"customerPhoneData\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"管理角色ID\",\n                        \"name\": \"sysUserAuthorityID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"管理ID\",\n                        \"name\": \"sysUserId\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取单一客户信息,返回包括客户详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.ExaCustomerResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"更新客户信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"客户ID, 客户信息\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaCustomer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新客户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"创建客户\",\n                \"parameters\": [\n                    {\n                        \"description\": \"客户用户名, 客户手机号码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaCustomer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建客户\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"删除客户\",\n                \"parameters\": [\n                    {\n                        \"description\": \"客户ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaCustomer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除客户\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/customer/customerList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"分页获取权限客户列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取权限客户列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/email/emailTest\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"发送测试邮件\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"发送成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/email/sendEmail\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"发送邮件\",\n                \"parameters\": [\n                    {\n                        \"description\": \"发送邮件必须的参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/response.Email\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"发送成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/breakpointContinue\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"断点续传到服务器\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"an example for breakpoint resume, 断点续传示例\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"断点续传到服务器\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/deleteFile\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"删除文件\",\n                \"parameters\": [\n                    {\n                        \"description\": \"传入文件里面id即可\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaFileUploadAndDownload\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除文件\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/findFile\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"查找文件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"Find the file, 查找文件\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查找文件,返回包括文件详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.FileResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"创建文件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"上传文件完成\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建文件,返回包括文件路径\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.FilePathResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/getFileList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"分页文件列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小, 分类id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.ExaAttachmentCategorySearch\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页文件列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/importURL\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"导入URL\",\n                \"parameters\": [\n                    {\n                        \"description\": \"对象\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaFileUploadAndDownload\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"导入URL\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/removeChunk\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"删除切片\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"删除缓存切片\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除切片\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/upload\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"上传文件示例\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"上传文件示例\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"上传文件示例,返回包括文件详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.ExaFileResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/createInfo\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"创建公告\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建公告\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/model.Info\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/deleteInfo\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"删除公告\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除公告\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/model.Info\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/deleteInfoByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"批量删除公告\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"批量删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/findInfo\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"用id查询公告\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"内容\",\n                        \"name\": \"content\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"标题\",\n                        \"name\": \"title\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"作者\",\n                        \"name\": \"userID\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/model.Info\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/getInfoDataSource\": {\n            \"get\": {\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"获取Info的数据源\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/getInfoList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"分页获取公告列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/getInfoPublic\": {\n            \"get\": {\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"不需要鉴权的公告接口\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/updateInfo\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"更新公告\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新公告\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/model.Info\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/init/checkdb\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"CheckDB\"\n                ],\n                \"summary\": \"初始化用户数据库\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"初始化用户数据库\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/init/initdb\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"InitDB\"\n                ],\n                \"summary\": \"初始化用户数据库\",\n                \"parameters\": [\n                    {\n                        \"description\": \"初始化数据库参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.InitDB\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"初始化用户数据库\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/jwt/jsonInBlacklist\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Jwt\"\n                ],\n                \"summary\": \"jwt加入黑名单\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"jwt加入黑名单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/addBaseMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"新增菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"新增菜单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/addMenuAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"增加menu和角色关联关系\",\n                \"parameters\": [\n                    {\n                        \"description\": \"角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AddMenuAuthorityInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"增加menu和角色关联关系\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/deleteBaseMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"删除菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除菜单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getBaseMenuById\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"根据id获取菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"根据id获取菜单,返回包括系统菜单列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysBaseMenuResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getBaseMenuTree\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"获取用户动态路由\",\n                \"parameters\": [\n                    {\n                        \"description\": \"空\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Empty\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取用户动态路由,返回包括系统菜单列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysBaseMenusResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"获取用户动态路由\",\n                \"parameters\": [\n                    {\n                        \"description\": \"空\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Empty\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取用户动态路由,返回包括系统菜单详情列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysMenusResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getMenuAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"获取指定角色menu\",\n                \"parameters\": [\n                    {\n                        \"description\": \"角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetAuthorityId\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取指定角色menu\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getMenuList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"分页获取基础menu列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.PageInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取基础menu列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/updateBaseMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"更新菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新菜单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/createSysDictionary\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"创建SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionary模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionary\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/deleteSysDictionary\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"删除SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionary模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionary\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/findSysDictionary\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"用id查询SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"描述\",\n                        \"name\": \"desc\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典名（中）\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"description\": \"状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典名（英）\",\n                        \"name\": \"type\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用id查询SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/getSysDictionaryList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"分页获取SysDictionary列表\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/updateSysDictionary\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"更新SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionary模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionary\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/createSysDictionaryDetail\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"创建SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionaryDetail模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/deleteSysDictionaryDetail\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"删除SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionaryDetail模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/findSysDictionaryDetail\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"用id查询SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"扩展值\",\n                        \"name\": \"extend\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"展示值\",\n                        \"name\": \"label\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"排序标记\",\n                        \"name\": \"sort\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"description\": \"启用状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"关联标记\",\n                        \"name\": \"sysDictionaryID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典值\",\n                        \"name\": \"value\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用id查询SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/getSysDictionaryDetailList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"分页获取SysDictionaryDetail列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"扩展值\",\n                        \"name\": \"extend\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"展示值\",\n                        \"name\": \"label\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"排序标记\",\n                        \"name\": \"sort\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"description\": \"启用状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"关联标记\",\n                        \"name\": \"sysDictionaryID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典值\",\n                        \"name\": \"value\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/updateSysDictionaryDetail\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"更新SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新SysDictionaryDetail\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/ExportTemplate\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"导出表格模板\",\n                \"responses\": {}\n            }\n        },\n        \"/sysExportTemplate/createSysExportTemplate\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"创建导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysExportTemplate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"创建成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/deleteSysExportTemplate\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"删除导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysExportTemplate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"删除成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/deleteSysExportTemplateByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"批量删除导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"批量删除导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.IdsReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"批量删除成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/exportExcel\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"导出表格\",\n                \"responses\": {}\n            }\n        },\n        \"/sysExportTemplate/findSysExportTemplate\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"用id查询导出模板\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"数据库名称\",\n                        \"name\": \"dbName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板名称\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"order\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"表名称\",\n                        \"name\": \"tableName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板标识\",\n                        \"name\": \"templateID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板信息\",\n                        \"name\": \"templateInfo\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"查询成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/getSysExportTemplateList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"分页获取导出模板列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"数据库名称\",\n                        \"name\": \"dbName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板名称\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"order\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"表名称\",\n                        \"name\": \"tableName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板标识\",\n                        \"name\": \"templateID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板信息\",\n                        \"name\": \"templateInfo\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"获取成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/importExcel\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysImportTemplate\"\n                ],\n                \"summary\": \"导入表格\",\n                \"responses\": {}\n            }\n        },\n        \"/sysExportTemplate/updateSysExportTemplate\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"更新导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysExportTemplate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"更新成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/createSysOperationRecord\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"创建SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建SysOperationRecord\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysOperationRecord\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/deleteSysOperationRecord\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"删除SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysOperationRecord模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysOperationRecord\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/deleteSysOperationRecordByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"批量删除SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"description\": \"批量删除SysOperationRecord\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.IdsReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"批量删除SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/findSysOperationRecord\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"用id查询SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"代理\",\n                        \"name\": \"agent\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求Body\",\n                        \"name\": \"body\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"错误信息\",\n                        \"name\": \"error_message\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求ip\",\n                        \"name\": \"ip\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"延迟\",\n                        \"name\": \"latency\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求方法\",\n                        \"name\": \"method\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求路径\",\n                        \"name\": \"path\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"响应Body\",\n                        \"name\": \"resp\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"请求状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"用户id\",\n                        \"name\": \"user_id\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用id查询SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/getSysOperationRecordList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"分页获取SysOperationRecord列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"代理\",\n                        \"name\": \"agent\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求Body\",\n                        \"name\": \"body\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"错误信息\",\n                        \"name\": \"error_message\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求ip\",\n                        \"name\": \"ip\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"延迟\",\n                        \"name\": \"latency\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求方法\",\n                        \"name\": \"method\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求路径\",\n                        \"name\": \"path\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"响应Body\",\n                        \"name\": \"resp\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"请求状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"用户id\",\n                        \"name\": \"user_id\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/createSysParams\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"创建参数\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/deleteSysParams\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"删除参数\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/deleteSysParamsByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"批量删除参数\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"批量删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/findSysParams\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"用id查询参数\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数说明\",\n                        \"name\": \"desc\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数键\",\n                        \"name\": \"key\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数名称\",\n                        \"name\": \"name\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数值\",\n                        \"name\": \"value\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/system.SysParams\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/getSysParam\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"根据key获取参数value\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"key\",\n                        \"name\": \"key\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/system.SysParams\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/getSysParamsList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"分页获取参数列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"key\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/updateSysParams\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"更新参数\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/getServerInfo\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"获取服务器信息\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取服务器信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/getSystemConfig\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"获取配置文件内容\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取配置文件内容,返回包括系统配置\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysConfigResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/reloadSystem\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"重启系统\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"重启系统\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/setSystemConfig\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"设置配置文件内容\",\n                \"parameters\": [\n                    {\n                        \"description\": \"设置配置文件内容\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.System\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置配置文件内容\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/SetSelfInfo\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID, 用户名, 昵称, 头像链接\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/SetSelfSetting\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户配置\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户配置数据\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": true\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户配置\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/admin_register\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"用户注册账号\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名, 昵称, 密码, 角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Register\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用户注册账号,返回包括用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysUserResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/changePassword\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"用户修改密码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名, 原密码, 新密码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.ChangePasswordReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用户修改密码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/deleteUser\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"删除用户\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除用户\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/getUserInfo\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"获取用户信息\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/getUserList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"分页获取用户列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetUserList\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取用户列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/resetPassword\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"重置用户密码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"重置用户密码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/setUserAuthorities\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户UUID, 角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SetUserAuthorities\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/setUserAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"更改用户权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户UUID, 角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SetUserAuth\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/setUserInfo\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID, 用户名, 昵称, 头像链接\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"common.JSONMap\": {\n            \"type\": \"object\",\n            \"additionalProperties\": true\n        },\n        \"config.AliyunOSS\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key-id\": {\n                    \"type\": \"string\"\n                },\n                \"access-key-secret\": {\n                    \"type\": \"string\"\n                },\n                \"base-path\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-name\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-url\": {\n                    \"type\": \"string\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Autocode\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ai-path\": {\n                    \"type\": \"string\"\n                },\n                \"module\": {\n                    \"type\": \"string\"\n                },\n                \"root\": {\n                    \"type\": \"string\"\n                },\n                \"server\": {\n                    \"type\": \"string\"\n                },\n                \"web\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.AwsS3\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"base-url\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"disable-ssl\": {\n                    \"type\": \"boolean\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                },\n                \"path-prefix\": {\n                    \"type\": \"string\"\n                },\n                \"region\": {\n                    \"type\": \"string\"\n                },\n                \"s3-force-path-style\": {\n                    \"type\": \"boolean\"\n                },\n                \"secret-id\": {\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.CORS\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"mode\": {\n                    \"type\": \"string\"\n                },\n                \"whitelist\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.CORSWhitelist\"\n                    }\n                }\n            }\n        },\n        \"config.CORSWhitelist\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"allow-credentials\": {\n                    \"type\": \"boolean\"\n                },\n                \"allow-headers\": {\n                    \"type\": \"string\"\n                },\n                \"allow-methods\": {\n                    \"type\": \"string\"\n                },\n                \"allow-origin\": {\n                    \"type\": \"string\"\n                },\n                \"expose-headers\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Captcha\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"img-height\": {\n                    \"description\": \"验证码高度\",\n                    \"type\": \"integer\"\n                },\n                \"img-width\": {\n                    \"description\": \"验证码宽度\",\n                    \"type\": \"integer\"\n                },\n                \"key-long\": {\n                    \"description\": \"验证码长度\",\n                    \"type\": \"integer\"\n                },\n                \"open-captcha\": {\n                    \"description\": \"防爆破验证码开启此数，0代表每次登录都需要验证码，其他数字代表错误密码此数，如3代表错误三次后出现验证码\",\n                    \"type\": \"integer\"\n                },\n                \"open-captcha-timeout\": {\n                    \"description\": \"防爆破验证码超时时间，单位：s(秒)\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"config.CloudflareR2\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key-id\": {\n                    \"type\": \"string\"\n                },\n                \"account-id\": {\n                    \"type\": \"string\"\n                },\n                \"base-url\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"type\": \"string\"\n                },\n                \"secret-access-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.DiskList\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"mount-point\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Excel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"dir\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.HuaWeiObs\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.JWT\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"buffer-time\": {\n                    \"description\": \"缓冲时间\",\n                    \"type\": \"string\"\n                },\n                \"expires-time\": {\n                    \"description\": \"过期时间\",\n                    \"type\": \"string\"\n                },\n                \"issuer\": {\n                    \"description\": \"签发者\",\n                    \"type\": \"string\"\n                },\n                \"signing-key\": {\n                    \"description\": \"jwt签名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Local\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"path\": {\n                    \"description\": \"本地文件访问路径\",\n                    \"type\": \"string\"\n                },\n                \"store-path\": {\n                    \"description\": \"本地文件存储路径\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Minio\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key-id\": {\n                    \"type\": \"string\"\n                },\n                \"access-key-secret\": {\n                    \"type\": \"string\"\n                },\n                \"base-path\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-name\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-url\": {\n                    \"type\": \"string\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                },\n                \"use-ssl\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"config.Mongo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"auth-source\": {\n                    \"description\": \"验证数据库\",\n                    \"type\": \"string\"\n                },\n                \"coll\": {\n                    \"description\": \"collection name\",\n                    \"type\": \"string\"\n                },\n                \"connect-timeout-ms\": {\n                    \"description\": \"连接超时时间\",\n                    \"type\": \"integer\"\n                },\n                \"database\": {\n                    \"description\": \"database name\",\n                    \"type\": \"string\"\n                },\n                \"hosts\": {\n                    \"description\": \"主机列表\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.MongoHost\"\n                    }\n                },\n                \"is-zap\": {\n                    \"description\": \"是否开启zap日志\",\n                    \"type\": \"boolean\"\n                },\n                \"max-pool-size\": {\n                    \"description\": \"最大连接池\",\n                    \"type\": \"integer\"\n                },\n                \"min-pool-size\": {\n                    \"description\": \"最小连接池\",\n                    \"type\": \"integer\"\n                },\n                \"options\": {\n                    \"description\": \"mongodb options\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                },\n                \"socket-timeout-ms\": {\n                    \"description\": \"socket超时时间\",\n                    \"type\": \"integer\"\n                },\n                \"username\": {\n                    \"description\": \"用户名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.MongoHost\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\n                    \"description\": \"ip地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"端口\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Mssql\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Mysql\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Oracle\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Pgsql\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Qiniu\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key\": {\n                    \"description\": \"秘钥AK\",\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"description\": \"空间名称\",\n                    \"type\": \"string\"\n                },\n                \"img-path\": {\n                    \"description\": \"CDN加速域名\",\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"description\": \"秘钥SK\",\n                    \"type\": \"string\"\n                },\n                \"use-cdn-domains\": {\n                    \"description\": \"上传是否使用CDN上传加速\",\n                    \"type\": \"boolean\"\n                },\n                \"use-https\": {\n                    \"description\": \"是否使用https\",\n                    \"type\": \"boolean\"\n                },\n                \"zone\": {\n                    \"description\": \"存储区域\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Redis\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"addr\": {\n                    \"description\": \"服务器地址:端口\",\n                    \"type\": \"string\"\n                },\n                \"clusterAddrs\": {\n                    \"description\": \"集群模式下的节点地址列表\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"db\": {\n                    \"description\": \"单实例模式下redis的哪个数据库\",\n                    \"type\": \"integer\"\n                },\n                \"name\": {\n                    \"description\": \"代表当前实例的名字\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                },\n                \"useCluster\": {\n                    \"description\": \"是否使用集群模式\",\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"config.Server\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"aliyun-oss\": {\n                    \"$ref\": \"#/definitions/config.AliyunOSS\"\n                },\n                \"autocode\": {\n                    \"description\": \"auto\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.Autocode\"\n                        }\n                    ]\n                },\n                \"aws-s3\": {\n                    \"$ref\": \"#/definitions/config.AwsS3\"\n                },\n                \"captcha\": {\n                    \"$ref\": \"#/definitions/config.Captcha\"\n                },\n                \"cloudflare-r2\": {\n                    \"$ref\": \"#/definitions/config.CloudflareR2\"\n                },\n                \"cors\": {\n                    \"description\": \"跨域配置\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.CORS\"\n                        }\n                    ]\n                },\n                \"db-list\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.SpecializedDB\"\n                    }\n                },\n                \"disk-list\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.DiskList\"\n                    }\n                },\n                \"email\": {\n                    \"$ref\": \"#/definitions/github_com_flipped-aurora_gin-vue-admin_server_config.Email\"\n                },\n                \"excel\": {\n                    \"$ref\": \"#/definitions/config.Excel\"\n                },\n                \"hua-wei-obs\": {\n                    \"$ref\": \"#/definitions/config.HuaWeiObs\"\n                },\n                \"jwt\": {\n                    \"$ref\": \"#/definitions/config.JWT\"\n                },\n                \"local\": {\n                    \"description\": \"oss\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.Local\"\n                        }\n                    ]\n                },\n                \"minio\": {\n                    \"$ref\": \"#/definitions/config.Minio\"\n                },\n                \"mongo\": {\n                    \"$ref\": \"#/definitions/config.Mongo\"\n                },\n                \"mssql\": {\n                    \"$ref\": \"#/definitions/config.Mssql\"\n                },\n                \"mysql\": {\n                    \"description\": \"gorm\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.Mysql\"\n                        }\n                    ]\n                },\n                \"oracle\": {\n                    \"$ref\": \"#/definitions/config.Oracle\"\n                },\n                \"pgsql\": {\n                    \"$ref\": \"#/definitions/config.Pgsql\"\n                },\n                \"qiniu\": {\n                    \"$ref\": \"#/definitions/config.Qiniu\"\n                },\n                \"redis\": {\n                    \"$ref\": \"#/definitions/config.Redis\"\n                },\n                \"redis-list\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.Redis\"\n                    }\n                },\n                \"sqlite\": {\n                    \"$ref\": \"#/definitions/config.Sqlite\"\n                },\n                \"system\": {\n                    \"$ref\": \"#/definitions/config.System\"\n                },\n                \"tencent-cos\": {\n                    \"$ref\": \"#/definitions/config.TencentCOS\"\n                },\n                \"zap\": {\n                    \"$ref\": \"#/definitions/config.Zap\"\n                }\n            }\n        },\n        \"config.SpecializedDB\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"alias-name\": {\n                    \"type\": \"string\"\n                },\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"disable\": {\n                    \"type\": \"boolean\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"type\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Sqlite\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.System\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"addr\": {\n                    \"description\": \"端口值\",\n                    \"type\": \"integer\"\n                },\n                \"db-type\": {\n                    \"description\": \"数据库类型:mysql(默认)|sqlite|sqlserver|postgresql\",\n                    \"type\": \"string\"\n                },\n                \"iplimit-count\": {\n                    \"type\": \"integer\"\n                },\n                \"iplimit-time\": {\n                    \"type\": \"integer\"\n                },\n                \"oss-type\": {\n                    \"description\": \"Oss类型\",\n                    \"type\": \"string\"\n                },\n                \"router-prefix\": {\n                    \"type\": \"string\"\n                },\n                \"use-mongo\": {\n                    \"description\": \"使用mongo\",\n                    \"type\": \"boolean\"\n                },\n                \"use-multipoint\": {\n                    \"description\": \"多点登录拦截\",\n                    \"type\": \"boolean\"\n                },\n                \"use-redis\": {\n                    \"description\": \"使用redis\",\n                    \"type\": \"boolean\"\n                },\n                \"use-strict-auth\": {\n                    \"description\": \"使用树形角色分配模式\",\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"config.TencentCOS\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"base-url\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"path-prefix\": {\n                    \"type\": \"string\"\n                },\n                \"region\": {\n                    \"type\": \"string\"\n                },\n                \"secret-id\": {\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Zap\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"director\": {\n                    \"description\": \"日志文件夹\",\n                    \"type\": \"string\"\n                },\n                \"encode-level\": {\n                    \"description\": \"编码级\",\n                    \"type\": \"string\"\n                },\n                \"format\": {\n                    \"description\": \"输出\",\n                    \"type\": \"string\"\n                },\n                \"level\": {\n                    \"description\": \"级别\",\n                    \"type\": \"string\"\n                },\n                \"log-in-console\": {\n                    \"description\": \"输出控制台\",\n                    \"type\": \"boolean\"\n                },\n                \"prefix\": {\n                    \"description\": \"日志前缀\",\n                    \"type\": \"string\"\n                },\n                \"retention-day\": {\n                    \"description\": \"日志保留天数\",\n                    \"type\": \"integer\"\n                },\n                \"show-line\": {\n                    \"description\": \"显示行\",\n                    \"type\": \"boolean\"\n                },\n                \"stacktrace-key\": {\n                    \"description\": \"栈名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaAttachmentCategory\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/example.ExaAttachmentCategory\"\n                    }\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"pid\": {\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaCustomer\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"customerName\": {\n                    \"description\": \"客户名\",\n                    \"type\": \"string\"\n                },\n                \"customerPhoneData\": {\n                    \"description\": \"客户手机号\",\n                    \"type\": \"string\"\n                },\n                \"sysUser\": {\n                    \"description\": \"管理详情\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    ]\n                },\n                \"sysUserAuthorityID\": {\n                    \"description\": \"管理角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"sysUserId\": {\n                    \"description\": \"管理ID\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaFile\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"chunkTotal\": {\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"exaFileChunk\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/example.ExaFileChunk\"\n                    }\n                },\n                \"fileMd5\": {\n                    \"type\": \"string\"\n                },\n                \"fileName\": {\n                    \"type\": \"string\"\n                },\n                \"filePath\": {\n                    \"type\": \"string\"\n                },\n                \"isFinish\": {\n                    \"type\": \"boolean\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaFileChunk\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"exaFileID\": {\n                    \"type\": \"integer\"\n                },\n                \"fileChunkNumber\": {\n                    \"type\": \"integer\"\n                },\n                \"fileChunkPath\": {\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaFileUploadAndDownload\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"classId\": {\n                    \"description\": \"分类id\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"description\": \"编号\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"description\": \"文件名\",\n                    \"type\": \"string\"\n                },\n                \"tag\": {\n                    \"description\": \"文件标签\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"url\": {\n                    \"description\": \"文件地址\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"github_com_flipped-aurora_gin-vue-admin_server_config.Email\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"from\": {\n                    \"description\": \"发件人  你自己要发邮件的邮箱\",\n                    \"type\": \"string\"\n                },\n                \"host\": {\n                    \"description\": \"服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议\",\n                    \"type\": \"string\"\n                },\n                \"is-ssl\": {\n                    \"description\": \"是否SSL   是否开启SSL\",\n                    \"type\": \"boolean\"\n                },\n                \"nickname\": {\n                    \"description\": \"昵称    发件人昵称 通常为自己的邮箱\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465\",\n                    \"type\": \"integer\"\n                },\n                \"secret\": {\n                    \"description\": \"密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥\",\n                    \"type\": \"string\"\n                },\n                \"to\": {\n                    \"description\": \"收件人:多个以英文逗号分隔 例：a@qq.com b@qq.com 正式开发中请把此项目作为参数使用\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"model.Info\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"attachments\": {\n                    \"description\": \"附件\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"object\"\n                    }\n                },\n                \"content\": {\n                    \"description\": \"内容\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"title\": {\n                    \"description\": \"标题\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"userID\": {\n                    \"description\": \"作者\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.AddMenuAuthorityInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                }\n            }\n        },\n        \"request.AutoCode\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"abbreviation\": {\n                    \"description\": \"Struct简称\",\n                    \"type\": \"string\",\n                    \"example\": \"Struct简称\"\n                },\n                \"autoCreateApiToSql\": {\n                    \"description\": \"是否自动创建api\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoCreateBtnAuth\": {\n                    \"description\": \"是否自动创建按钮权限\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoCreateMenuToSql\": {\n                    \"description\": \"是否自动创建menu\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoCreateResource\": {\n                    \"description\": \"是否自动创建资源标识\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoMigrate\": {\n                    \"description\": \"是否自动迁移表结构\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"businessDB\": {\n                    \"description\": \"业务数据库\",\n                    \"type\": \"string\",\n                    \"example\": \"业务数据库\"\n                },\n                \"description\": {\n                    \"description\": \"Struct中文名称\",\n                    \"type\": \"string\",\n                    \"example\": \"Struct中文名称\"\n                },\n                \"fields\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/request.AutoCodeField\"\n                    }\n                },\n                \"generateServer\": {\n                    \"description\": \"是否生成server\",\n                    \"type\": \"boolean\",\n                    \"example\": true\n                },\n                \"generateWeb\": {\n                    \"description\": \"是否生成web\",\n                    \"type\": \"boolean\",\n                    \"example\": true\n                },\n                \"gvaModel\": {\n                    \"description\": \"是否使用gva默认Model\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"humpPackageName\": {\n                    \"description\": \"go文件名称\",\n                    \"type\": \"string\",\n                    \"example\": \"go文件名称\"\n                },\n                \"isAdd\": {\n                    \"description\": \"是否新增\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"isTree\": {\n                    \"description\": \"是否树形结构\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"onlyTemplate\": {\n                    \"description\": \"是否只生成模板\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"package\": {\n                    \"type\": \"string\"\n                },\n                \"packageName\": {\n                    \"description\": \"文件名称\",\n                    \"type\": \"string\",\n                    \"example\": \"文件名称\"\n                },\n                \"primaryField\": {\n                    \"$ref\": \"#/definitions/request.AutoCodeField\"\n                },\n                \"structName\": {\n                    \"description\": \"Struct名称\",\n                    \"type\": \"string\",\n                    \"example\": \"Struct名称\"\n                },\n                \"tableName\": {\n                    \"description\": \"表名\",\n                    \"type\": \"string\",\n                    \"example\": \"表名\"\n                },\n                \"treeJson\": {\n                    \"description\": \"展示的树json字段\",\n                    \"type\": \"string\",\n                    \"example\": \"展示的树json字段\"\n                }\n            }\n        },\n        \"request.AutoCodeField\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"checkDataSource\": {\n                    \"description\": \"是否检查数据源\",\n                    \"type\": \"boolean\"\n                },\n                \"clearable\": {\n                    \"description\": \"是否可清空\",\n                    \"type\": \"boolean\"\n                },\n                \"columnName\": {\n                    \"description\": \"数据库字段\",\n                    \"type\": \"string\"\n                },\n                \"comment\": {\n                    \"description\": \"数据库字段描述\",\n                    \"type\": \"string\"\n                },\n                \"dataSource\": {\n                    \"description\": \"数据源\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/request.DataSource\"\n                        }\n                    ]\n                },\n                \"dataTypeLong\": {\n                    \"description\": \"数据库字段长度\",\n                    \"type\": \"string\"\n                },\n                \"defaultValue\": {\n                    \"description\": \"是否必填\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"是否前端详情\",\n                    \"type\": \"boolean\"\n                },\n                \"dictType\": {\n                    \"description\": \"字典\",\n                    \"type\": \"string\"\n                },\n                \"errorText\": {\n                    \"description\": \"校验失败文字\",\n                    \"type\": \"string\"\n                },\n                \"excel\": {\n                    \"description\": \"是否导入/导出\",\n                    \"type\": \"boolean\"\n                },\n                \"fieldDesc\": {\n                    \"description\": \"中文名\",\n                    \"type\": \"string\"\n                },\n                \"fieldIndexType\": {\n                    \"description\": \"索引类型\",\n                    \"type\": \"string\"\n                },\n                \"fieldJson\": {\n                    \"description\": \"FieldJson\",\n                    \"type\": \"string\"\n                },\n                \"fieldName\": {\n                    \"description\": \"Field名\",\n                    \"type\": \"string\"\n                },\n                \"fieldSearchHide\": {\n                    \"description\": \"是否隐藏查询条件\",\n                    \"type\": \"boolean\"\n                },\n                \"fieldSearchType\": {\n                    \"description\": \"搜索条件\",\n                    \"type\": \"string\"\n                },\n                \"fieldType\": {\n                    \"description\": \"Field数据类型\",\n                    \"type\": \"string\"\n                },\n                \"form\": {\n                    \"description\": \"Front           bool        ` + \"`\" + `json:\\\"front\\\"` + \"`\" + `           // 是否前端可见\",\n                    \"type\": \"boolean\"\n                },\n                \"primaryKey\": {\n                    \"description\": \"是否主键\",\n                    \"type\": \"boolean\"\n                },\n                \"require\": {\n                    \"description\": \"是否必填\",\n                    \"type\": \"boolean\"\n                },\n                \"sort\": {\n                    \"description\": \"是否增加排序\",\n                    \"type\": \"boolean\"\n                },\n                \"table\": {\n                    \"description\": \"是否前端表格列\",\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"request.CasbinInReceive\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"权限id\",\n                    \"type\": \"integer\"\n                },\n                \"casbinInfos\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/request.CasbinInfo\"\n                    }\n                }\n            }\n        },\n        \"request.CasbinInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"method\": {\n                    \"description\": \"方法\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"路径\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.ChangePasswordReq\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"newPassword\": {\n                    \"description\": \"新密码\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.DataSource\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"association\": {\n                    \"description\": \"关联关系 1 一对一 2 一对多\",\n                    \"type\": \"integer\"\n                },\n                \"dbName\": {\n                    \"type\": \"string\"\n                },\n                \"hasDeletedAt\": {\n                    \"type\": \"boolean\"\n                },\n                \"label\": {\n                    \"type\": \"string\"\n                },\n                \"table\": {\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.Empty\": {\n            \"type\": \"object\"\n        },\n        \"request.ExaAttachmentCategorySearch\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"classId\": {\n                    \"type\": \"integer\"\n                },\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.GetAuthorityId\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.GetById\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.GetUserList\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"email\": {\n                    \"type\": \"string\"\n                },\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"nickName\": {\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                },\n                \"phone\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.IdsReq\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ids\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"request.InitDB\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"adminPassword\",\n                \"dbName\"\n            ],\n            \"properties\": {\n                \"adminPassword\": {\n                    \"type\": \"string\"\n                },\n                \"dbName\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"dbPath\": {\n                    \"description\": \"sqlite数据库文件路径\",\n                    \"type\": \"string\"\n                },\n                \"dbType\": {\n                    \"description\": \"数据库类型\",\n                    \"type\": \"string\"\n                },\n                \"host\": {\n                    \"description\": \"服务器地址\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库连接端口\",\n                    \"type\": \"string\"\n                },\n                \"template\": {\n                    \"description\": \"postgresql指定template\",\n                    \"type\": \"string\"\n                },\n                \"userName\": {\n                    \"description\": \"数据库用户名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.Login\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"captcha\": {\n                    \"description\": \"验证码\",\n                    \"type\": \"string\"\n                },\n                \"captchaId\": {\n                    \"description\": \"验证码ID\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"description\": \"用户名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.PageInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.Register\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"type\": \"string\",\n                    \"example\": \"int 角色id\"\n                },\n                \"authorityIds\": {\n                    \"type\": \"string\",\n                    \"example\": \"[]uint 角色id\"\n                },\n                \"email\": {\n                    \"type\": \"string\",\n                    \"example\": \"电子邮箱\"\n                },\n                \"enable\": {\n                    \"type\": \"string\",\n                    \"example\": \"int 是否启用\"\n                },\n                \"headerImg\": {\n                    \"type\": \"string\",\n                    \"example\": \"头像链接\"\n                },\n                \"nickName\": {\n                    \"type\": \"string\",\n                    \"example\": \"昵称\"\n                },\n                \"passWord\": {\n                    \"type\": \"string\",\n                    \"example\": \"密码\"\n                },\n                \"phone\": {\n                    \"type\": \"string\",\n                    \"example\": \"电话号码\"\n                },\n                \"userName\": {\n                    \"type\": \"string\",\n                    \"example\": \"用户名\"\n                }\n            }\n        },\n        \"request.SearchApiParams\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"apiGroup\": {\n                    \"description\": \"api组\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"排序方式:升序false(默认)|降序true\",\n                    \"type\": \"boolean\"\n                },\n                \"description\": {\n                    \"description\": \"api中文描述\",\n                    \"type\": \"string\"\n                },\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"description\": \"方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\",\n                    \"type\": \"string\"\n                },\n                \"orderKey\": {\n                    \"description\": \"排序\",\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                },\n                \"path\": {\n                    \"description\": \"api路径\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.SetUserAuth\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.SetUserAuthorities\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityIds\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"id\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.SysAuthorityBtnReq\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"type\": \"integer\"\n                },\n                \"menuID\": {\n                    \"type\": \"integer\"\n                },\n                \"selected\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"request.SysAutoCodePackageCreate\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"desc\": {\n                    \"type\": \"string\",\n                    \"example\": \"描述\"\n                },\n                \"label\": {\n                    \"type\": \"string\",\n                    \"example\": \"展示名\"\n                },\n                \"packageName\": {\n                    \"type\": \"string\",\n                    \"example\": \"包名\"\n                },\n                \"template\": {\n                    \"type\": \"string\",\n                    \"example\": \"模版\"\n                }\n            }\n        },\n        \"request.SysAutoHistoryRollBack\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deleteApi\": {\n                    \"description\": \"是否删除接口\",\n                    \"type\": \"boolean\"\n                },\n                \"deleteMenu\": {\n                    \"description\": \"是否删除菜单\",\n                    \"type\": \"boolean\"\n                },\n                \"deleteTable\": {\n                    \"description\": \"是否删除表\",\n                    \"type\": \"boolean\"\n                },\n                \"id\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"response.Email\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"body\": {\n                    \"description\": \"邮件内容\",\n                    \"type\": \"string\"\n                },\n                \"subject\": {\n                    \"description\": \"邮件标题\",\n                    \"type\": \"string\"\n                },\n                \"to\": {\n                    \"description\": \"邮件发送给谁\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.ExaCustomerResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"customer\": {\n                    \"$ref\": \"#/definitions/example.ExaCustomer\"\n                }\n            }\n        },\n        \"response.ExaFileResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"file\": {\n                    \"$ref\": \"#/definitions/example.ExaFileUploadAndDownload\"\n                }\n            }\n        },\n        \"response.FilePathResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"filePath\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.FileResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"file\": {\n                    \"$ref\": \"#/definitions/example.ExaFile\"\n                }\n            }\n        },\n        \"response.LoginResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"expiresAt\": {\n                    \"type\": \"integer\"\n                },\n                \"token\": {\n                    \"type\": \"string\"\n                },\n                \"user\": {\n                    \"$ref\": \"#/definitions/system.SysUser\"\n                }\n            }\n        },\n        \"response.PageResult\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"list\": {},\n                \"page\": {\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"type\": \"integer\"\n                },\n                \"total\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"response.PolicyPathResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"paths\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/request.CasbinInfo\"\n                    }\n                }\n            }\n        },\n        \"response.Response\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"integer\"\n                },\n                \"data\": {},\n                \"msg\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.SysAPIListResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"apis\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysApi\"\n                    }\n                }\n            }\n        },\n        \"response.SysAPIResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"api\": {\n                    \"$ref\": \"#/definitions/system.SysApi\"\n                }\n            }\n        },\n        \"response.SysAuthorityBtnRes\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"selected\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"response.SysAuthorityCopyResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authority\": {\n                    \"$ref\": \"#/definitions/system.SysAuthority\"\n                },\n                \"oldAuthorityId\": {\n                    \"description\": \"旧角色ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"response.SysAuthorityResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authority\": {\n                    \"$ref\": \"#/definitions/system.SysAuthority\"\n                }\n            }\n        },\n        \"response.SysBaseMenuResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"menu\": {\n                    \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                }\n            }\n        },\n        \"response.SysBaseMenusResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                }\n            }\n        },\n        \"response.SysCaptchaResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"captchaId\": {\n                    \"type\": \"string\"\n                },\n                \"captchaLength\": {\n                    \"type\": \"integer\"\n                },\n                \"openCaptcha\": {\n                    \"type\": \"boolean\"\n                },\n                \"picPath\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.SysConfigResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"$ref\": \"#/definitions/config.Server\"\n                }\n            }\n        },\n        \"response.SysMenusResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysMenu\"\n                    }\n                }\n            }\n        },\n        \"response.SysUserResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\n                    \"$ref\": \"#/definitions/system.SysUser\"\n                }\n            }\n        },\n        \"system.Condition\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"column\": {\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"from\": {\n                    \"type\": \"string\"\n                },\n                \"operator\": {\n                    \"type\": \"string\"\n                },\n                \"templateID\": {\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.JoinTemplate\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"joins\": {\n                    \"type\": \"string\"\n                },\n                \"on\": {\n                    \"type\": \"string\"\n                },\n                \"table\": {\n                    \"type\": \"string\"\n                },\n                \"templateID\": {\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.Meta\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"activeName\": {\n                    \"type\": \"string\"\n                },\n                \"closeTab\": {\n                    \"description\": \"自动关闭tab\",\n                    \"type\": \"boolean\"\n                },\n                \"defaultMenu\": {\n                    \"description\": \"是否是基础路由（开发中）\",\n                    \"type\": \"boolean\"\n                },\n                \"icon\": {\n                    \"description\": \"菜单图标\",\n                    \"type\": \"string\"\n                },\n                \"keepAlive\": {\n                    \"description\": \"是否缓存\",\n                    \"type\": \"boolean\"\n                },\n                \"title\": {\n                    \"description\": \"菜单名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysApi\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"apiGroup\": {\n                    \"description\": \"api组\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"description\": {\n                    \"description\": \"api中文描述\",\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"description\": \"方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"api路径\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysAuthority\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"authorityName\": {\n                    \"description\": \"角色名\",\n                    \"type\": \"string\"\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"dataAuthorityId\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"defaultRouter\": {\n                    \"description\": \"默认菜单(默认dashboard)\",\n                    \"type\": \"string\"\n                },\n                \"deletedAt\": {\n                    \"type\": \"string\"\n                },\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                },\n                \"parentId\": {\n                    \"description\": \"父角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysBaseMenu\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"authoritys\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                },\n                \"component\": {\n                    \"description\": \"对应前端文件路径\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"hidden\": {\n                    \"description\": \"是否在列表隐藏\",\n                    \"type\": \"boolean\"\n                },\n                \"menuBtn\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuBtn\"\n                    }\n                },\n                \"meta\": {\n                    \"description\": \"附加属性\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.Meta\"\n                        }\n                    ]\n                },\n                \"name\": {\n                    \"description\": \"路由name\",\n                    \"type\": \"string\"\n                },\n                \"parameters\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuParameter\"\n                    }\n                },\n                \"parentId\": {\n                    \"description\": \"父菜单ID\",\n                    \"type\": \"integer\"\n                },\n                \"path\": {\n                    \"description\": \"路由path\",\n                    \"type\": \"string\"\n                },\n                \"sort\": {\n                    \"description\": \"排序标记\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysBaseMenuBtn\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"sysBaseMenuID\": {\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysBaseMenuParameter\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"description\": \"地址栏携带参数的key\",\n                    \"type\": \"string\"\n                },\n                \"sysBaseMenuID\": {\n                    \"type\": \"integer\"\n                },\n                \"type\": {\n                    \"description\": \"地址栏携带参数为params还是query\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"description\": \"地址栏携带参数的值\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysDictionary\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"描述\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"description\": \"字典名（中）\",\n                    \"type\": \"string\"\n                },\n                \"status\": {\n                    \"description\": \"状态\",\n                    \"type\": \"boolean\"\n                },\n                \"sysDictionaryDetails\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                    }\n                },\n                \"type\": {\n                    \"description\": \"字典名（英）\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysDictionaryDetail\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"extend\": {\n                    \"description\": \"扩展值\",\n                    \"type\": \"string\"\n                },\n                \"label\": {\n                    \"description\": \"展示值\",\n                    \"type\": \"string\"\n                },\n                \"sort\": {\n                    \"description\": \"排序标记\",\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"description\": \"启用状态\",\n                    \"type\": \"boolean\"\n                },\n                \"sysDictionaryID\": {\n                    \"description\": \"关联标记\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"description\": \"字典值\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysExportTemplate\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"conditions\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.Condition\"\n                    }\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"dbName\": {\n                    \"description\": \"数据库名称\",\n                    \"type\": \"string\"\n                },\n                \"joinTemplate\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.JoinTemplate\"\n                    }\n                },\n                \"limit\": {\n                    \"type\": \"integer\"\n                },\n                \"name\": {\n                    \"description\": \"模板名称\",\n                    \"type\": \"string\"\n                },\n                \"order\": {\n                    \"type\": \"string\"\n                },\n                \"tableName\": {\n                    \"description\": \"表名称\",\n                    \"type\": \"string\"\n                },\n                \"templateID\": {\n                    \"description\": \"模板标识\",\n                    \"type\": \"string\"\n                },\n                \"templateInfo\": {\n                    \"description\": \"模板信息\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysMenu\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"authoritys\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"btns\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysMenu\"\n                    }\n                },\n                \"component\": {\n                    \"description\": \"对应前端文件路径\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"hidden\": {\n                    \"description\": \"是否在列表隐藏\",\n                    \"type\": \"boolean\"\n                },\n                \"menuBtn\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuBtn\"\n                    }\n                },\n                \"menuId\": {\n                    \"type\": \"integer\"\n                },\n                \"meta\": {\n                    \"description\": \"附加属性\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.Meta\"\n                        }\n                    ]\n                },\n                \"name\": {\n                    \"description\": \"路由name\",\n                    \"type\": \"string\"\n                },\n                \"parameters\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuParameter\"\n                    }\n                },\n                \"parentId\": {\n                    \"description\": \"父菜单ID\",\n                    \"type\": \"integer\"\n                },\n                \"path\": {\n                    \"description\": \"路由path\",\n                    \"type\": \"string\"\n                },\n                \"sort\": {\n                    \"description\": \"排序标记\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysOperationRecord\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"agent\": {\n                    \"description\": \"代理\",\n                    \"type\": \"string\"\n                },\n                \"body\": {\n                    \"description\": \"请求Body\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"error_message\": {\n                    \"description\": \"错误信息\",\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"description\": \"请求ip\",\n                    \"type\": \"string\"\n                },\n                \"latency\": {\n                    \"description\": \"延迟\",\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"description\": \"请求方法\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"请求路径\",\n                    \"type\": \"string\"\n                },\n                \"resp\": {\n                    \"description\": \"响应Body\",\n                    \"type\": \"string\"\n                },\n                \"status\": {\n                    \"description\": \"请求状态\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"user\": {\n                    \"$ref\": \"#/definitions/system.SysUser\"\n                },\n                \"user_id\": {\n                    \"description\": \"用户id\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"system.SysParams\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"key\",\n                \"name\",\n                \"value\"\n            ],\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"参数说明\",\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"description\": \"参数键\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"description\": \"参数名称\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"description\": \"参数值\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysUser\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"authorities\": {\n                    \"description\": \"多用户角色\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"authority\": {\n                    \"description\": \"用户角色\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    ]\n                },\n                \"authorityId\": {\n                    \"description\": \"用户角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"email\": {\n                    \"description\": \"用户邮箱\",\n                    \"type\": \"string\"\n                },\n                \"enable\": {\n                    \"description\": \"用户是否被冻结 1正常 2冻结\",\n                    \"type\": \"integer\"\n                },\n                \"headerImg\": {\n                    \"description\": \"用户头像\",\n                    \"type\": \"string\"\n                },\n                \"nickName\": {\n                    \"description\": \"用户昵称\",\n                    \"type\": \"string\"\n                },\n                \"originSetting\": {\n                    \"description\": \"配置\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/common.JSONMap\"\n                        }\n                    ]\n                },\n                \"phone\": {\n                    \"description\": \"用户手机号\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"userName\": {\n                    \"description\": \"用户登录名\",\n                    \"type\": \"string\"\n                },\n                \"uuid\": {\n                    \"description\": \"用户UUID\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.System\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"$ref\": \"#/definitions/config.Server\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"ApiKeyAuth\": {\n            \"type\": \"apiKey\",\n            \"name\": \"x-token\",\n            \"in\": \"header\"\n        }\n    },\n    \"tags\": [\n        {\n            \"name\": \"Base\"\n        },\n        {\n            \"description\": \"用户\",\n            \"name\": \"SysUser\"\n        }\n    ]\n}`\n\n// SwaggerInfo holds exported Swagger Info so clients can modify it\nvar SwaggerInfo = &swag.Spec{\n\tVersion:          global.Version,\n\tHost:             \"\",\n\tBasePath:         \"\",\n\tSchemes:          []string{},\n\tTitle:            \"Gin-Vue-Admin Swagger API接口文档\",\n\tDescription:      \"使用gin+vue进行极速开发的全栈开发基础平台\",\n\tInfoInstanceName: \"swagger\",\n\tSwaggerTemplate:  docTemplate,\n}\n\nfunc init() {\n\tswag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)\n}\n"
  },
  {
    "path": "server/docs/swagger.json",
    "content": "{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"description\": \"使用gin+vue进行极速开发的全栈开发基础平台\",\n        \"title\": \"Gin-Vue-Admin Swagger API接口文档\",\n        \"contact\": {},\n        \"version\": \"v2.7.9-beta\"\n    },\n    \"paths\": {\n        \"/api/createApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"创建基础api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"api路径, api中文描述, api组, 方法\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysApi\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建基础api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/deleteApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"删除api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysApi\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/deleteApisByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"删除选中Api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.IdsReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除选中Api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/enterSyncApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"确认同步API\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"确认同步API\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/freshCasbin\": {\n            \"get\": {\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"刷新casbin缓存\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"刷新成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getAllApis\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"获取所有的Api 不分页\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取所有的Api 不分页,返回包括api列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAPIListResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getApiById\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"根据id获取api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"根据id获取api\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"根据id获取api,返回包括api详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAPIResponse\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getApiGroups\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"获取API分组\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取API分组\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/getApiList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"分页获取API列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"分页获取API列表\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SearchApiParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取API列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/ignoreApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"IgnoreApi\"\n                ],\n                \"summary\": \"忽略API\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"同步API\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/syncApi\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"同步API\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"同步API\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/api/updateApi\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysApi\"\n                ],\n                \"summary\": \"修改基础api\",\n                \"parameters\": [\n                    {\n                        \"description\": \"api路径, api中文描述, api组, 方法\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysApi\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"修改基础api\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/attachmentCategory/addCategory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"AttachmentCategory\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AddCategory\"\n                ],\n                \"summary\": \"添加媒体库分类\",\n                \"parameters\": [\n                    {\n                        \"description\": \"媒体库分类数据\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaAttachmentCategory\"\n                        }\n                    }\n                ],\n                \"responses\": {}\n            }\n        },\n        \"/attachmentCategory/deleteCategory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"AttachmentCategory\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"DeleteCategory\"\n                ],\n                \"summary\": \"删除分类\",\n                \"parameters\": [\n                    {\n                        \"description\": \"分类id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除分类\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/attachmentCategory/getCategoryList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"AttachmentCategory\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"GetCategoryList\"\n                ],\n                \"summary\": \"媒体库分类列表\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"媒体库分类列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/example.ExaAttachmentCategory\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/copyAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"拷贝角色\",\n                \"parameters\": [\n                    {\n                        \"description\": \"旧角色id, 新权限id, 新权限名, 新父角色id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/response.SysAuthorityCopyResponse\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"拷贝角色,返回包括系统角色详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/createAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"创建角色\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限名, 父角色id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建角色,返回包括系统角色详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/deleteAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"删除角色\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除角色\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除角色\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/getAuthorityList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"分页获取角色列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.PageInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取角色列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/setDataAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"设置角色资源权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"设置角色资源权限\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置角色资源权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authority/updateAuthority\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Authority\"\n                ],\n                \"summary\": \"更新角色信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限名, 父角色id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新角色信息,返回包括系统角色详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authorityBtn/canRemoveAuthorityBtn\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityBtn\"\n                ],\n                \"summary\": \"设置权限按钮\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authorityBtn/getAuthorityBtn\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityBtn\"\n                ],\n                \"summary\": \"获取权限按钮\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id, 角色id, 选中的按钮id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAuthorityBtnReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"返回列表成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysAuthorityBtnRes\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/authorityBtn/setAuthorityBtn\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityBtn\"\n                ],\n                \"summary\": \"设置权限按钮\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id, 角色id, 选中的按钮id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAuthorityBtnReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"返回列表成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/addFunc\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AddFunc\"\n                ],\n                \"summary\": \"增加方法\",\n                \"parameters\": [\n                    {\n                        \"description\": \"增加方法\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AutoCode\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"创建成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/createPackage\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePackage\"\n                ],\n                \"summary\": \"创建package\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建package\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAutoCodePackageCreate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/createTemp\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodeTemplate\"\n                ],\n                \"summary\": \"自动代码模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建自动代码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AutoCode\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"创建成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/delPackage\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"删除package\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建package\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/delSysHistory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"删除回滚记录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除回滚记录\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getColumn\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取当前表所有字段\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取当前表所有字段\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getDB\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取当前所有数据库\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取当前所有数据库\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getMeta\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取meta信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取meta信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getPackage\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePackage\"\n                ],\n                \"summary\": \"获取package\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getSysHistory\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"查询回滚记录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.PageInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询回滚记录,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getTables\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"获取当前数据库所有表\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取当前数据库所有表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/getTemplates\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePackage\"\n                ],\n                \"summary\": \"获取package\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建package成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/initAPI\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"打包插件\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"打包插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/initMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"打包插件\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"打包插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/installPlugin\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"安装插件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"this is a test file\",\n                        \"name\": \"plug\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"安装插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"array\",\n                                            \"items\": {\n                                                \"type\": \"object\"\n                                            }\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/preview\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodeTemplate\"\n                ],\n                \"summary\": \"预览创建后的代码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"预览创建代码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AutoCode\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"预览创建后的代码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/pubPlug\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCodePlugin\"\n                ],\n                \"summary\": \"打包插件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"插件名称\",\n                        \"name\": \"plugName\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"打包插件成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/autoCode/rollback\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AutoCode\"\n                ],\n                \"summary\": \"回滚自动生成代码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"请求参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SysAutoHistoryRollBack\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"回滚自动生成代码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/base/captcha\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Base\"\n                ],\n                \"summary\": \"生成验证码\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysCaptchaResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/base/login\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Base\"\n                ],\n                \"summary\": \"用户登录\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名, 密码, 验证码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Login\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"返回包括用户信息,token,过期时间\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.LoginResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/casbin/UpdateCasbin\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Casbin\"\n                ],\n                \"summary\": \"更新角色api权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限模型列表\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.CasbinInReceive\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新角色api权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/casbin/getPolicyPathByAuthorityId\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Casbin\"\n                ],\n                \"summary\": \"获取权限列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"权限id, 权限模型列表\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.CasbinInReceive\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取权限列表,返回包括casbin详情列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PolicyPathResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/customer/customer\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"获取单一客户信息\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"客户名\",\n                        \"name\": \"customerName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"客户手机号\",\n                        \"name\": \"customerPhoneData\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"管理角色ID\",\n                        \"name\": \"sysUserAuthorityID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"管理ID\",\n                        \"name\": \"sysUserId\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取单一客户信息,返回包括客户详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.ExaCustomerResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"更新客户信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"客户ID, 客户信息\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaCustomer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新客户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"创建客户\",\n                \"parameters\": [\n                    {\n                        \"description\": \"客户用户名, 客户手机号码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaCustomer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建客户\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"删除客户\",\n                \"parameters\": [\n                    {\n                        \"description\": \"客户ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaCustomer\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除客户\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/customer/customerList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaCustomer\"\n                ],\n                \"summary\": \"分页获取权限客户列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取权限客户列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/email/emailTest\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"发送测试邮件\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"发送成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/email/sendEmail\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"发送邮件\",\n                \"parameters\": [\n                    {\n                        \"description\": \"发送邮件必须的参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/response.Email\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"发送成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/breakpointContinue\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"断点续传到服务器\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"an example for breakpoint resume, 断点续传示例\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"断点续传到服务器\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/deleteFile\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"删除文件\",\n                \"parameters\": [\n                    {\n                        \"description\": \"传入文件里面id即可\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaFileUploadAndDownload\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除文件\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/findFile\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"查找文件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"Find the file, 查找文件\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查找文件,返回包括文件详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.FileResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"创建文件\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"上传文件完成\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建文件,返回包括文件路径\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.FilePathResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/getFileList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"分页文件列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小, 分类id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.ExaAttachmentCategorySearch\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页文件列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/importURL\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"导入URL\",\n                \"parameters\": [\n                    {\n                        \"description\": \"对象\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/example.ExaFileUploadAndDownload\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"导入URL\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/removeChunk\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"删除切片\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"删除缓存切片\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除切片\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/fileUploadAndDownload/upload\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"multipart/form-data\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"ExaFileUploadAndDownload\"\n                ],\n                \"summary\": \"上传文件示例\",\n                \"parameters\": [\n                    {\n                        \"type\": \"file\",\n                        \"description\": \"上传文件示例\",\n                        \"name\": \"file\",\n                        \"in\": \"formData\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"上传文件示例,返回包括文件详情\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.ExaFileResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/createInfo\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"创建公告\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建公告\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/model.Info\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/deleteInfo\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"删除公告\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除公告\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/model.Info\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/deleteInfoByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"批量删除公告\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"批量删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/findInfo\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"用id查询公告\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"内容\",\n                        \"name\": \"content\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"标题\",\n                        \"name\": \"title\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"作者\",\n                        \"name\": \"userID\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/model.Info\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/getInfoDataSource\": {\n            \"get\": {\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"获取Info的数据源\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/getInfoList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"分页获取公告列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/getInfoPublic\": {\n            \"get\": {\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"不需要鉴权的公告接口\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/updateInfo\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Info\"\n                ],\n                \"summary\": \"更新公告\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新公告\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/model.Info\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/init/checkdb\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"CheckDB\"\n                ],\n                \"summary\": \"初始化用户数据库\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"初始化用户数据库\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/init/initdb\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"InitDB\"\n                ],\n                \"summary\": \"初始化用户数据库\",\n                \"parameters\": [\n                    {\n                        \"description\": \"初始化数据库参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.InitDB\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"初始化用户数据库\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/jwt/jsonInBlacklist\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Jwt\"\n                ],\n                \"summary\": \"jwt加入黑名单\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"jwt加入黑名单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/addBaseMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"新增菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"新增菜单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/addMenuAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"增加menu和角色关联关系\",\n                \"parameters\": [\n                    {\n                        \"description\": \"角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.AddMenuAuthorityInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"增加menu和角色关联关系\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/deleteBaseMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"删除菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除菜单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getBaseMenuById\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"根据id获取菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"菜单id\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"根据id获取菜单,返回包括系统菜单列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysBaseMenuResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getBaseMenuTree\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"获取用户动态路由\",\n                \"parameters\": [\n                    {\n                        \"description\": \"空\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Empty\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取用户动态路由,返回包括系统菜单列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysBaseMenusResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"获取用户动态路由\",\n                \"parameters\": [\n                    {\n                        \"description\": \"空\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Empty\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取用户动态路由,返回包括系统菜单详情列表\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysMenusResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getMenuAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"AuthorityMenu\"\n                ],\n                \"summary\": \"获取指定角色menu\",\n                \"parameters\": [\n                    {\n                        \"description\": \"角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetAuthorityId\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取指定角色menu\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/getMenuList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"分页获取基础menu列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.PageInfo\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取基础menu列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/menu/updateBaseMenu\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"Menu\"\n                ],\n                \"summary\": \"更新菜单\",\n                \"parameters\": [\n                    {\n                        \"description\": \"路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新菜单\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/createSysDictionary\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"创建SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionary模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionary\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/deleteSysDictionary\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"删除SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionary模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionary\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/findSysDictionary\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"用id查询SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"描述\",\n                        \"name\": \"desc\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典名（中）\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"description\": \"状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典名（英）\",\n                        \"name\": \"type\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用id查询SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/getSysDictionaryList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"分页获取SysDictionary列表\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionary/updateSysDictionary\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionary\"\n                ],\n                \"summary\": \"更新SysDictionary\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionary模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionary\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新SysDictionary\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/createSysDictionaryDetail\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"创建SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionaryDetail模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/deleteSysDictionaryDetail\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"删除SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysDictionaryDetail模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/findSysDictionaryDetail\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"用id查询SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"扩展值\",\n                        \"name\": \"extend\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"展示值\",\n                        \"name\": \"label\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"排序标记\",\n                        \"name\": \"sort\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"description\": \"启用状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"关联标记\",\n                        \"name\": \"sysDictionaryID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典值\",\n                        \"name\": \"value\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用id查询SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/getSysDictionaryDetailList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"分页获取SysDictionaryDetail列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"扩展值\",\n                        \"name\": \"extend\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"展示值\",\n                        \"name\": \"label\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"排序标记\",\n                        \"name\": \"sort\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"description\": \"启用状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"关联标记\",\n                        \"name\": \"sysDictionaryID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"字典值\",\n                        \"name\": \"value\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysDictionaryDetail/updateSysDictionaryDetail\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysDictionaryDetail\"\n                ],\n                \"summary\": \"更新SysDictionaryDetail\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新SysDictionaryDetail\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新SysDictionaryDetail\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/ExportTemplate\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"导出表格模板\",\n                \"responses\": {}\n            }\n        },\n        \"/sysExportTemplate/createSysExportTemplate\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"创建导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysExportTemplate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"创建成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/deleteSysExportTemplate\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"删除导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysExportTemplate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"删除成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/deleteSysExportTemplateByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"批量删除导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"批量删除导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.IdsReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"批量删除成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/exportExcel\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"导出表格\",\n                \"responses\": {}\n            }\n        },\n        \"/sysExportTemplate/findSysExportTemplate\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"用id查询导出模板\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"数据库名称\",\n                        \"name\": \"dbName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板名称\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"order\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"表名称\",\n                        \"name\": \"tableName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板标识\",\n                        \"name\": \"templateID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板信息\",\n                        \"name\": \"templateInfo\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"查询成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/getSysExportTemplateList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"分页获取导出模板列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"数据库名称\",\n                        \"name\": \"dbName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板名称\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"order\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"表名称\",\n                        \"name\": \"tableName\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板标识\",\n                        \"name\": \"templateID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"模板信息\",\n                        \"name\": \"templateInfo\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"获取成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysExportTemplate/importExcel\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysImportTemplate\"\n                ],\n                \"summary\": \"导入表格\",\n                \"responses\": {}\n            }\n        },\n        \"/sysExportTemplate/updateSysExportTemplate\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysExportTemplate\"\n                ],\n                \"summary\": \"更新导出模板\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新导出模板\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysExportTemplate\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"更新成功\\\"}\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/createSysOperationRecord\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"创建SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建SysOperationRecord\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysOperationRecord\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/deleteSysOperationRecord\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"删除SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"description\": \"SysOperationRecord模型\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysOperationRecord\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/deleteSysOperationRecordByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"批量删除SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"description\": \"批量删除SysOperationRecord\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.IdsReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"批量删除SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/findSysOperationRecord\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"用id查询SysOperationRecord\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"代理\",\n                        \"name\": \"agent\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求Body\",\n                        \"name\": \"body\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"错误信息\",\n                        \"name\": \"error_message\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求ip\",\n                        \"name\": \"ip\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"延迟\",\n                        \"name\": \"latency\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求方法\",\n                        \"name\": \"method\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求路径\",\n                        \"name\": \"path\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"响应Body\",\n                        \"name\": \"resp\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"请求状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"用户id\",\n                        \"name\": \"user_id\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用id查询SysOperationRecord\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysOperationRecord/getSysOperationRecordList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysOperationRecord\"\n                ],\n                \"summary\": \"分页获取SysOperationRecord列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"代理\",\n                        \"name\": \"agent\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求Body\",\n                        \"name\": \"body\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"错误信息\",\n                        \"name\": \"error_message\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求ip\",\n                        \"name\": \"ip\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"延迟\",\n                        \"name\": \"latency\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求方法\",\n                        \"name\": \"method\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"请求路径\",\n                        \"name\": \"path\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"响应Body\",\n                        \"name\": \"resp\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"请求状态\",\n                        \"name\": \"status\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"用户id\",\n                        \"name\": \"user_id\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/createSysParams\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"创建参数\",\n                \"parameters\": [\n                    {\n                        \"description\": \"创建参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"创建成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/deleteSysParams\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"删除参数\",\n                \"parameters\": [\n                    {\n                        \"description\": \"删除参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/deleteSysParamsByIds\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"批量删除参数\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"批量删除成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/findSysParams\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"用id查询参数\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"主键ID\",\n                        \"name\": \"ID\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"创建时间\",\n                        \"name\": \"createdAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数说明\",\n                        \"name\": \"desc\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数键\",\n                        \"name\": \"key\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数名称\",\n                        \"name\": \"name\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"更新时间\",\n                        \"name\": \"updatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"参数值\",\n                        \"name\": \"value\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"查询成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/system.SysParams\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/getSysParam\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"根据key获取参数value\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"key\",\n                        \"name\": \"key\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/system.SysParams\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/getSysParamsList\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"分页获取参数列表\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"endCreatedAt\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"key\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"关键字\",\n                        \"name\": \"keyword\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"name\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"页码\",\n                        \"name\": \"page\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"每页大小\",\n                        \"name\": \"pageSize\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"startCreatedAt\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/sysParams/updateSysParams\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysParams\"\n                ],\n                \"summary\": \"更新参数\",\n                \"parameters\": [\n                    {\n                        \"description\": \"更新参数\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysParams\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"更新成功\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/getServerInfo\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"获取服务器信息\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取服务器信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/getSystemConfig\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"获取配置文件内容\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取配置文件内容,返回包括系统配置\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysConfigResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/reloadSystem\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"重启系统\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"重启系统\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/system/setSystemConfig\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"System\"\n                ],\n                \"summary\": \"设置配置文件内容\",\n                \"parameters\": [\n                    {\n                        \"description\": \"设置配置文件内容\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.System\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置配置文件内容\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/SetSelfInfo\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID, 用户名, 昵称, 头像链接\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/SetSelfSetting\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户配置\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户配置数据\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"type\": \"object\",\n                            \"additionalProperties\": true\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户配置\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/admin_register\": {\n            \"post\": {\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"用户注册账号\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名, 昵称, 密码, 角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.Register\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用户注册账号,返回包括用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.SysUserResponse\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/changePassword\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"用户修改密码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户名, 原密码, 新密码\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.ChangePasswordReq\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"用户修改密码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/deleteUser\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"删除用户\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetById\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"删除用户\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/getUserInfo\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"获取用户信息\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"获取用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/getUserList\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"分页获取用户列表\",\n                \"parameters\": [\n                    {\n                        \"description\": \"页码, 每页大小\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.GetUserList\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"分页获取用户列表,返回包括列表,总数,页码,每页数量\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"$ref\": \"#/definitions/response.PageResult\"\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/resetPassword\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"重置用户密码\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"重置用户密码\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/setUserAuthorities\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户UUID, 角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SetUserAuthorities\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/setUserAuthority\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"更改用户权限\",\n                \"parameters\": [\n                    {\n                        \"description\": \"用户UUID, 角色ID\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/request.SetUserAuth\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户权限\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/setUserInfo\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"ApiKeyAuth\": []\n                    }\n                ],\n                \"consumes\": [\n                    \"application/json\"\n                ],\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"tags\": [\n                    \"SysUser\"\n                ],\n                \"summary\": \"设置用户信息\",\n                \"parameters\": [\n                    {\n                        \"description\": \"ID, 用户名, 昵称, 头像链接\",\n                        \"name\": \"data\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"设置用户信息\",\n                        \"schema\": {\n                            \"allOf\": [\n                                {\n                                    \"$ref\": \"#/definitions/response.Response\"\n                                },\n                                {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"data\": {\n                                            \"type\": \"object\",\n                                            \"additionalProperties\": true\n                                        },\n                                        \"msg\": {\n                                            \"type\": \"string\"\n                                        }\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"common.JSONMap\": {\n            \"type\": \"object\",\n            \"additionalProperties\": true\n        },\n        \"config.AliyunOSS\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key-id\": {\n                    \"type\": \"string\"\n                },\n                \"access-key-secret\": {\n                    \"type\": \"string\"\n                },\n                \"base-path\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-name\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-url\": {\n                    \"type\": \"string\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Autocode\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ai-path\": {\n                    \"type\": \"string\"\n                },\n                \"module\": {\n                    \"type\": \"string\"\n                },\n                \"root\": {\n                    \"type\": \"string\"\n                },\n                \"server\": {\n                    \"type\": \"string\"\n                },\n                \"web\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.AwsS3\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"base-url\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"disable-ssl\": {\n                    \"type\": \"boolean\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                },\n                \"path-prefix\": {\n                    \"type\": \"string\"\n                },\n                \"region\": {\n                    \"type\": \"string\"\n                },\n                \"s3-force-path-style\": {\n                    \"type\": \"boolean\"\n                },\n                \"secret-id\": {\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.CORS\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"mode\": {\n                    \"type\": \"string\"\n                },\n                \"whitelist\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.CORSWhitelist\"\n                    }\n                }\n            }\n        },\n        \"config.CORSWhitelist\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"allow-credentials\": {\n                    \"type\": \"boolean\"\n                },\n                \"allow-headers\": {\n                    \"type\": \"string\"\n                },\n                \"allow-methods\": {\n                    \"type\": \"string\"\n                },\n                \"allow-origin\": {\n                    \"type\": \"string\"\n                },\n                \"expose-headers\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Captcha\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"img-height\": {\n                    \"description\": \"验证码高度\",\n                    \"type\": \"integer\"\n                },\n                \"img-width\": {\n                    \"description\": \"验证码宽度\",\n                    \"type\": \"integer\"\n                },\n                \"key-long\": {\n                    \"description\": \"验证码长度\",\n                    \"type\": \"integer\"\n                },\n                \"open-captcha\": {\n                    \"description\": \"防爆破验证码开启此数，0代表每次登录都需要验证码，其他数字代表错误密码此数，如3代表错误三次后出现验证码\",\n                    \"type\": \"integer\"\n                },\n                \"open-captcha-timeout\": {\n                    \"description\": \"防爆破验证码超时时间，单位：s(秒)\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"config.CloudflareR2\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key-id\": {\n                    \"type\": \"string\"\n                },\n                \"account-id\": {\n                    \"type\": \"string\"\n                },\n                \"base-url\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"type\": \"string\"\n                },\n                \"secret-access-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.DiskList\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"mount-point\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Excel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"dir\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.HuaWeiObs\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.JWT\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"buffer-time\": {\n                    \"description\": \"缓冲时间\",\n                    \"type\": \"string\"\n                },\n                \"expires-time\": {\n                    \"description\": \"过期时间\",\n                    \"type\": \"string\"\n                },\n                \"issuer\": {\n                    \"description\": \"签发者\",\n                    \"type\": \"string\"\n                },\n                \"signing-key\": {\n                    \"description\": \"jwt签名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Local\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"path\": {\n                    \"description\": \"本地文件访问路径\",\n                    \"type\": \"string\"\n                },\n                \"store-path\": {\n                    \"description\": \"本地文件存储路径\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Minio\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key-id\": {\n                    \"type\": \"string\"\n                },\n                \"access-key-secret\": {\n                    \"type\": \"string\"\n                },\n                \"base-path\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-name\": {\n                    \"type\": \"string\"\n                },\n                \"bucket-url\": {\n                    \"type\": \"string\"\n                },\n                \"endpoint\": {\n                    \"type\": \"string\"\n                },\n                \"use-ssl\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"config.Mongo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"auth-source\": {\n                    \"description\": \"验证数据库\",\n                    \"type\": \"string\"\n                },\n                \"coll\": {\n                    \"description\": \"collection name\",\n                    \"type\": \"string\"\n                },\n                \"connect-timeout-ms\": {\n                    \"description\": \"连接超时时间\",\n                    \"type\": \"integer\"\n                },\n                \"database\": {\n                    \"description\": \"database name\",\n                    \"type\": \"string\"\n                },\n                \"hosts\": {\n                    \"description\": \"主机列表\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.MongoHost\"\n                    }\n                },\n                \"is-zap\": {\n                    \"description\": \"是否开启zap日志\",\n                    \"type\": \"boolean\"\n                },\n                \"max-pool-size\": {\n                    \"description\": \"最大连接池\",\n                    \"type\": \"integer\"\n                },\n                \"min-pool-size\": {\n                    \"description\": \"最小连接池\",\n                    \"type\": \"integer\"\n                },\n                \"options\": {\n                    \"description\": \"mongodb options\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                },\n                \"socket-timeout-ms\": {\n                    \"description\": \"socket超时时间\",\n                    \"type\": \"integer\"\n                },\n                \"username\": {\n                    \"description\": \"用户名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.MongoHost\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\n                    \"description\": \"ip地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"端口\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Mssql\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Mysql\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Oracle\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Pgsql\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Qiniu\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"access-key\": {\n                    \"description\": \"秘钥AK\",\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"description\": \"空间名称\",\n                    \"type\": \"string\"\n                },\n                \"img-path\": {\n                    \"description\": \"CDN加速域名\",\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"description\": \"秘钥SK\",\n                    \"type\": \"string\"\n                },\n                \"use-cdn-domains\": {\n                    \"description\": \"上传是否使用CDN上传加速\",\n                    \"type\": \"boolean\"\n                },\n                \"use-https\": {\n                    \"description\": \"是否使用https\",\n                    \"type\": \"boolean\"\n                },\n                \"zone\": {\n                    \"description\": \"存储区域\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Redis\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"addr\": {\n                    \"description\": \"服务器地址:端口\",\n                    \"type\": \"string\"\n                },\n                \"clusterAddrs\": {\n                    \"description\": \"集群模式下的节点地址列表\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"db\": {\n                    \"description\": \"单实例模式下redis的哪个数据库\",\n                    \"type\": \"integer\"\n                },\n                \"name\": {\n                    \"description\": \"代表当前实例的名字\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                },\n                \"useCluster\": {\n                    \"description\": \"是否使用集群模式\",\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"config.Server\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"aliyun-oss\": {\n                    \"$ref\": \"#/definitions/config.AliyunOSS\"\n                },\n                \"autocode\": {\n                    \"description\": \"auto\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.Autocode\"\n                        }\n                    ]\n                },\n                \"aws-s3\": {\n                    \"$ref\": \"#/definitions/config.AwsS3\"\n                },\n                \"captcha\": {\n                    \"$ref\": \"#/definitions/config.Captcha\"\n                },\n                \"cloudflare-r2\": {\n                    \"$ref\": \"#/definitions/config.CloudflareR2\"\n                },\n                \"cors\": {\n                    \"description\": \"跨域配置\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.CORS\"\n                        }\n                    ]\n                },\n                \"db-list\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.SpecializedDB\"\n                    }\n                },\n                \"disk-list\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.DiskList\"\n                    }\n                },\n                \"email\": {\n                    \"$ref\": \"#/definitions/github_com_flipped-aurora_gin-vue-admin_server_config.Email\"\n                },\n                \"excel\": {\n                    \"$ref\": \"#/definitions/config.Excel\"\n                },\n                \"hua-wei-obs\": {\n                    \"$ref\": \"#/definitions/config.HuaWeiObs\"\n                },\n                \"jwt\": {\n                    \"$ref\": \"#/definitions/config.JWT\"\n                },\n                \"local\": {\n                    \"description\": \"oss\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.Local\"\n                        }\n                    ]\n                },\n                \"minio\": {\n                    \"$ref\": \"#/definitions/config.Minio\"\n                },\n                \"mongo\": {\n                    \"$ref\": \"#/definitions/config.Mongo\"\n                },\n                \"mssql\": {\n                    \"$ref\": \"#/definitions/config.Mssql\"\n                },\n                \"mysql\": {\n                    \"description\": \"gorm\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/config.Mysql\"\n                        }\n                    ]\n                },\n                \"oracle\": {\n                    \"$ref\": \"#/definitions/config.Oracle\"\n                },\n                \"pgsql\": {\n                    \"$ref\": \"#/definitions/config.Pgsql\"\n                },\n                \"qiniu\": {\n                    \"$ref\": \"#/definitions/config.Qiniu\"\n                },\n                \"redis\": {\n                    \"$ref\": \"#/definitions/config.Redis\"\n                },\n                \"redis-list\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/config.Redis\"\n                    }\n                },\n                \"sqlite\": {\n                    \"$ref\": \"#/definitions/config.Sqlite\"\n                },\n                \"system\": {\n                    \"$ref\": \"#/definitions/config.System\"\n                },\n                \"tencent-cos\": {\n                    \"$ref\": \"#/definitions/config.TencentCOS\"\n                },\n                \"zap\": {\n                    \"$ref\": \"#/definitions/config.Zap\"\n                }\n            }\n        },\n        \"config.SpecializedDB\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"alias-name\": {\n                    \"type\": \"string\"\n                },\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"disable\": {\n                    \"type\": \"boolean\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"type\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Sqlite\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"description\": \"高级配置\",\n                    \"type\": \"string\"\n                },\n                \"db-name\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"engine\": {\n                    \"description\": \"数据库引擎，默认InnoDB\",\n                    \"type\": \"string\",\n                    \"default\": \"InnoDB\"\n                },\n                \"log-mode\": {\n                    \"description\": \"是否开启Gorm全局日志\",\n                    \"type\": \"string\"\n                },\n                \"log-zap\": {\n                    \"description\": \"是否通过zap写入日志文件\",\n                    \"type\": \"boolean\"\n                },\n                \"max-idle-conns\": {\n                    \"description\": \"空闲中的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"max-open-conns\": {\n                    \"description\": \"打开到数据库的最大连接数\",\n                    \"type\": \"integer\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"数据库地址\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库端口\",\n                    \"type\": \"string\"\n                },\n                \"prefix\": {\n                    \"description\": \"数据库前缀\",\n                    \"type\": \"string\"\n                },\n                \"singular\": {\n                    \"description\": \"是否开启全局禁用复数，true表示开启\",\n                    \"type\": \"boolean\"\n                },\n                \"username\": {\n                    \"description\": \"数据库账号\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.System\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"addr\": {\n                    \"description\": \"端口值\",\n                    \"type\": \"integer\"\n                },\n                \"db-type\": {\n                    \"description\": \"数据库类型:mysql(默认)|sqlite|sqlserver|postgresql\",\n                    \"type\": \"string\"\n                },\n                \"iplimit-count\": {\n                    \"type\": \"integer\"\n                },\n                \"iplimit-time\": {\n                    \"type\": \"integer\"\n                },\n                \"oss-type\": {\n                    \"description\": \"Oss类型\",\n                    \"type\": \"string\"\n                },\n                \"router-prefix\": {\n                    \"type\": \"string\"\n                },\n                \"use-mongo\": {\n                    \"description\": \"使用mongo\",\n                    \"type\": \"boolean\"\n                },\n                \"use-multipoint\": {\n                    \"description\": \"多点登录拦截\",\n                    \"type\": \"boolean\"\n                },\n                \"use-redis\": {\n                    \"description\": \"使用redis\",\n                    \"type\": \"boolean\"\n                },\n                \"use-strict-auth\": {\n                    \"description\": \"使用树形角色分配模式\",\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"config.TencentCOS\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"base-url\": {\n                    \"type\": \"string\"\n                },\n                \"bucket\": {\n                    \"type\": \"string\"\n                },\n                \"path-prefix\": {\n                    \"type\": \"string\"\n                },\n                \"region\": {\n                    \"type\": \"string\"\n                },\n                \"secret-id\": {\n                    \"type\": \"string\"\n                },\n                \"secret-key\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.Zap\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"director\": {\n                    \"description\": \"日志文件夹\",\n                    \"type\": \"string\"\n                },\n                \"encode-level\": {\n                    \"description\": \"编码级\",\n                    \"type\": \"string\"\n                },\n                \"format\": {\n                    \"description\": \"输出\",\n                    \"type\": \"string\"\n                },\n                \"level\": {\n                    \"description\": \"级别\",\n                    \"type\": \"string\"\n                },\n                \"log-in-console\": {\n                    \"description\": \"输出控制台\",\n                    \"type\": \"boolean\"\n                },\n                \"prefix\": {\n                    \"description\": \"日志前缀\",\n                    \"type\": \"string\"\n                },\n                \"retention-day\": {\n                    \"description\": \"日志保留天数\",\n                    \"type\": \"integer\"\n                },\n                \"show-line\": {\n                    \"description\": \"显示行\",\n                    \"type\": \"boolean\"\n                },\n                \"stacktrace-key\": {\n                    \"description\": \"栈名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaAttachmentCategory\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/example.ExaAttachmentCategory\"\n                    }\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"pid\": {\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaCustomer\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"customerName\": {\n                    \"description\": \"客户名\",\n                    \"type\": \"string\"\n                },\n                \"customerPhoneData\": {\n                    \"description\": \"客户手机号\",\n                    \"type\": \"string\"\n                },\n                \"sysUser\": {\n                    \"description\": \"管理详情\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.SysUser\"\n                        }\n                    ]\n                },\n                \"sysUserAuthorityID\": {\n                    \"description\": \"管理角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"sysUserId\": {\n                    \"description\": \"管理ID\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaFile\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"chunkTotal\": {\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"exaFileChunk\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/example.ExaFileChunk\"\n                    }\n                },\n                \"fileMd5\": {\n                    \"type\": \"string\"\n                },\n                \"fileName\": {\n                    \"type\": \"string\"\n                },\n                \"filePath\": {\n                    \"type\": \"string\"\n                },\n                \"isFinish\": {\n                    \"type\": \"boolean\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaFileChunk\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"exaFileID\": {\n                    \"type\": \"integer\"\n                },\n                \"fileChunkNumber\": {\n                    \"type\": \"integer\"\n                },\n                \"fileChunkPath\": {\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"example.ExaFileUploadAndDownload\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"classId\": {\n                    \"description\": \"分类id\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"description\": \"编号\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"description\": \"文件名\",\n                    \"type\": \"string\"\n                },\n                \"tag\": {\n                    \"description\": \"文件标签\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"url\": {\n                    \"description\": \"文件地址\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"github_com_flipped-aurora_gin-vue-admin_server_config.Email\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"from\": {\n                    \"description\": \"发件人  你自己要发邮件的邮箱\",\n                    \"type\": \"string\"\n                },\n                \"host\": {\n                    \"description\": \"服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议\",\n                    \"type\": \"string\"\n                },\n                \"is-ssl\": {\n                    \"description\": \"是否SSL   是否开启SSL\",\n                    \"type\": \"boolean\"\n                },\n                \"nickname\": {\n                    \"description\": \"昵称    发件人昵称 通常为自己的邮箱\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465\",\n                    \"type\": \"integer\"\n                },\n                \"secret\": {\n                    \"description\": \"密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥\",\n                    \"type\": \"string\"\n                },\n                \"to\": {\n                    \"description\": \"收件人:多个以英文逗号分隔 例：a@qq.com b@qq.com 正式开发中请把此项目作为参数使用\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"model.Info\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"attachments\": {\n                    \"description\": \"附件\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"object\"\n                    }\n                },\n                \"content\": {\n                    \"description\": \"内容\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"title\": {\n                    \"description\": \"标题\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"userID\": {\n                    \"description\": \"作者\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.AddMenuAuthorityInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                }\n            }\n        },\n        \"request.AutoCode\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"abbreviation\": {\n                    \"description\": \"Struct简称\",\n                    \"type\": \"string\",\n                    \"example\": \"Struct简称\"\n                },\n                \"autoCreateApiToSql\": {\n                    \"description\": \"是否自动创建api\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoCreateBtnAuth\": {\n                    \"description\": \"是否自动创建按钮权限\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoCreateMenuToSql\": {\n                    \"description\": \"是否自动创建menu\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoCreateResource\": {\n                    \"description\": \"是否自动创建资源标识\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"autoMigrate\": {\n                    \"description\": \"是否自动迁移表结构\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"businessDB\": {\n                    \"description\": \"业务数据库\",\n                    \"type\": \"string\",\n                    \"example\": \"业务数据库\"\n                },\n                \"description\": {\n                    \"description\": \"Struct中文名称\",\n                    \"type\": \"string\",\n                    \"example\": \"Struct中文名称\"\n                },\n                \"fields\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/request.AutoCodeField\"\n                    }\n                },\n                \"generateServer\": {\n                    \"description\": \"是否生成server\",\n                    \"type\": \"boolean\",\n                    \"example\": true\n                },\n                \"generateWeb\": {\n                    \"description\": \"是否生成web\",\n                    \"type\": \"boolean\",\n                    \"example\": true\n                },\n                \"gvaModel\": {\n                    \"description\": \"是否使用gva默认Model\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"humpPackageName\": {\n                    \"description\": \"go文件名称\",\n                    \"type\": \"string\",\n                    \"example\": \"go文件名称\"\n                },\n                \"isAdd\": {\n                    \"description\": \"是否新增\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"isTree\": {\n                    \"description\": \"是否树形结构\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"onlyTemplate\": {\n                    \"description\": \"是否只生成模板\",\n                    \"type\": \"boolean\",\n                    \"example\": false\n                },\n                \"package\": {\n                    \"type\": \"string\"\n                },\n                \"packageName\": {\n                    \"description\": \"文件名称\",\n                    \"type\": \"string\",\n                    \"example\": \"文件名称\"\n                },\n                \"primaryField\": {\n                    \"$ref\": \"#/definitions/request.AutoCodeField\"\n                },\n                \"structName\": {\n                    \"description\": \"Struct名称\",\n                    \"type\": \"string\",\n                    \"example\": \"Struct名称\"\n                },\n                \"tableName\": {\n                    \"description\": \"表名\",\n                    \"type\": \"string\",\n                    \"example\": \"表名\"\n                },\n                \"treeJson\": {\n                    \"description\": \"展示的树json字段\",\n                    \"type\": \"string\",\n                    \"example\": \"展示的树json字段\"\n                }\n            }\n        },\n        \"request.AutoCodeField\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"checkDataSource\": {\n                    \"description\": \"是否检查数据源\",\n                    \"type\": \"boolean\"\n                },\n                \"clearable\": {\n                    \"description\": \"是否可清空\",\n                    \"type\": \"boolean\"\n                },\n                \"columnName\": {\n                    \"description\": \"数据库字段\",\n                    \"type\": \"string\"\n                },\n                \"comment\": {\n                    \"description\": \"数据库字段描述\",\n                    \"type\": \"string\"\n                },\n                \"dataSource\": {\n                    \"description\": \"数据源\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/request.DataSource\"\n                        }\n                    ]\n                },\n                \"dataTypeLong\": {\n                    \"description\": \"数据库字段长度\",\n                    \"type\": \"string\"\n                },\n                \"defaultValue\": {\n                    \"description\": \"是否必填\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"是否前端详情\",\n                    \"type\": \"boolean\"\n                },\n                \"dictType\": {\n                    \"description\": \"字典\",\n                    \"type\": \"string\"\n                },\n                \"errorText\": {\n                    \"description\": \"校验失败文字\",\n                    \"type\": \"string\"\n                },\n                \"excel\": {\n                    \"description\": \"是否导入/导出\",\n                    \"type\": \"boolean\"\n                },\n                \"fieldDesc\": {\n                    \"description\": \"中文名\",\n                    \"type\": \"string\"\n                },\n                \"fieldIndexType\": {\n                    \"description\": \"索引类型\",\n                    \"type\": \"string\"\n                },\n                \"fieldJson\": {\n                    \"description\": \"FieldJson\",\n                    \"type\": \"string\"\n                },\n                \"fieldName\": {\n                    \"description\": \"Field名\",\n                    \"type\": \"string\"\n                },\n                \"fieldSearchHide\": {\n                    \"description\": \"是否隐藏查询条件\",\n                    \"type\": \"boolean\"\n                },\n                \"fieldSearchType\": {\n                    \"description\": \"搜索条件\",\n                    \"type\": \"string\"\n                },\n                \"fieldType\": {\n                    \"description\": \"Field数据类型\",\n                    \"type\": \"string\"\n                },\n                \"form\": {\n                    \"description\": \"Front           bool        `json:\\\"front\\\"`           // 是否前端可见\",\n                    \"type\": \"boolean\"\n                },\n                \"primaryKey\": {\n                    \"description\": \"是否主键\",\n                    \"type\": \"boolean\"\n                },\n                \"require\": {\n                    \"description\": \"是否必填\",\n                    \"type\": \"boolean\"\n                },\n                \"sort\": {\n                    \"description\": \"是否增加排序\",\n                    \"type\": \"boolean\"\n                },\n                \"table\": {\n                    \"description\": \"是否前端表格列\",\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"request.CasbinInReceive\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"权限id\",\n                    \"type\": \"integer\"\n                },\n                \"casbinInfos\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/request.CasbinInfo\"\n                    }\n                }\n            }\n        },\n        \"request.CasbinInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"method\": {\n                    \"description\": \"方法\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"路径\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.ChangePasswordReq\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"newPassword\": {\n                    \"description\": \"新密码\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.DataSource\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"association\": {\n                    \"description\": \"关联关系 1 一对一 2 一对多\",\n                    \"type\": \"integer\"\n                },\n                \"dbName\": {\n                    \"type\": \"string\"\n                },\n                \"hasDeletedAt\": {\n                    \"type\": \"boolean\"\n                },\n                \"label\": {\n                    \"type\": \"string\"\n                },\n                \"table\": {\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.Empty\": {\n            \"type\": \"object\"\n        },\n        \"request.ExaAttachmentCategorySearch\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"classId\": {\n                    \"type\": \"integer\"\n                },\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.GetAuthorityId\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.GetById\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.GetUserList\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"email\": {\n                    \"type\": \"string\"\n                },\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"nickName\": {\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                },\n                \"phone\": {\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.IdsReq\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ids\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"request.InitDB\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"adminPassword\",\n                \"dbName\"\n            ],\n            \"properties\": {\n                \"adminPassword\": {\n                    \"type\": \"string\"\n                },\n                \"dbName\": {\n                    \"description\": \"数据库名\",\n                    \"type\": \"string\"\n                },\n                \"dbPath\": {\n                    \"description\": \"sqlite数据库文件路径\",\n                    \"type\": \"string\"\n                },\n                \"dbType\": {\n                    \"description\": \"数据库类型\",\n                    \"type\": \"string\"\n                },\n                \"host\": {\n                    \"description\": \"服务器地址\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"数据库密码\",\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"description\": \"数据库连接端口\",\n                    \"type\": \"string\"\n                },\n                \"template\": {\n                    \"description\": \"postgresql指定template\",\n                    \"type\": \"string\"\n                },\n                \"userName\": {\n                    \"description\": \"数据库用户名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.Login\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"captcha\": {\n                    \"description\": \"验证码\",\n                    \"type\": \"string\"\n                },\n                \"captchaId\": {\n                    \"description\": \"验证码ID\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"description\": \"密码\",\n                    \"type\": \"string\"\n                },\n                \"username\": {\n                    \"description\": \"用户名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.PageInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.Register\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"type\": \"string\",\n                    \"example\": \"int 角色id\"\n                },\n                \"authorityIds\": {\n                    \"type\": \"string\",\n                    \"example\": \"[]uint 角色id\"\n                },\n                \"email\": {\n                    \"type\": \"string\",\n                    \"example\": \"电子邮箱\"\n                },\n                \"enable\": {\n                    \"type\": \"string\",\n                    \"example\": \"int 是否启用\"\n                },\n                \"headerImg\": {\n                    \"type\": \"string\",\n                    \"example\": \"头像链接\"\n                },\n                \"nickName\": {\n                    \"type\": \"string\",\n                    \"example\": \"昵称\"\n                },\n                \"passWord\": {\n                    \"type\": \"string\",\n                    \"example\": \"密码\"\n                },\n                \"phone\": {\n                    \"type\": \"string\",\n                    \"example\": \"电话号码\"\n                },\n                \"userName\": {\n                    \"type\": \"string\",\n                    \"example\": \"用户名\"\n                }\n            }\n        },\n        \"request.SearchApiParams\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"apiGroup\": {\n                    \"description\": \"api组\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"排序方式:升序false(默认)|降序true\",\n                    \"type\": \"boolean\"\n                },\n                \"description\": {\n                    \"description\": \"api中文描述\",\n                    \"type\": \"string\"\n                },\n                \"keyword\": {\n                    \"description\": \"关键字\",\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"description\": \"方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\",\n                    \"type\": \"string\"\n                },\n                \"orderKey\": {\n                    \"description\": \"排序\",\n                    \"type\": \"string\"\n                },\n                \"page\": {\n                    \"description\": \"页码\",\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"description\": \"每页大小\",\n                    \"type\": \"integer\"\n                },\n                \"path\": {\n                    \"description\": \"api路径\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"request.SetUserAuth\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.SetUserAuthorities\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityIds\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"id\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"request.SysAuthorityBtnReq\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"type\": \"integer\"\n                },\n                \"menuID\": {\n                    \"type\": \"integer\"\n                },\n                \"selected\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"request.SysAutoCodePackageCreate\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"desc\": {\n                    \"type\": \"string\",\n                    \"example\": \"描述\"\n                },\n                \"label\": {\n                    \"type\": \"string\",\n                    \"example\": \"展示名\"\n                },\n                \"packageName\": {\n                    \"type\": \"string\",\n                    \"example\": \"包名\"\n                },\n                \"template\": {\n                    \"type\": \"string\",\n                    \"example\": \"模版\"\n                }\n            }\n        },\n        \"request.SysAutoHistoryRollBack\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deleteApi\": {\n                    \"description\": \"是否删除接口\",\n                    \"type\": \"boolean\"\n                },\n                \"deleteMenu\": {\n                    \"description\": \"是否删除菜单\",\n                    \"type\": \"boolean\"\n                },\n                \"deleteTable\": {\n                    \"description\": \"是否删除表\",\n                    \"type\": \"boolean\"\n                },\n                \"id\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"response.Email\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"body\": {\n                    \"description\": \"邮件内容\",\n                    \"type\": \"string\"\n                },\n                \"subject\": {\n                    \"description\": \"邮件标题\",\n                    \"type\": \"string\"\n                },\n                \"to\": {\n                    \"description\": \"邮件发送给谁\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.ExaCustomerResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"customer\": {\n                    \"$ref\": \"#/definitions/example.ExaCustomer\"\n                }\n            }\n        },\n        \"response.ExaFileResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"file\": {\n                    \"$ref\": \"#/definitions/example.ExaFileUploadAndDownload\"\n                }\n            }\n        },\n        \"response.FilePathResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"filePath\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.FileResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"file\": {\n                    \"$ref\": \"#/definitions/example.ExaFile\"\n                }\n            }\n        },\n        \"response.LoginResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"expiresAt\": {\n                    \"type\": \"integer\"\n                },\n                \"token\": {\n                    \"type\": \"string\"\n                },\n                \"user\": {\n                    \"$ref\": \"#/definitions/system.SysUser\"\n                }\n            }\n        },\n        \"response.PageResult\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"list\": {},\n                \"page\": {\n                    \"type\": \"integer\"\n                },\n                \"pageSize\": {\n                    \"type\": \"integer\"\n                },\n                \"total\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"response.PolicyPathResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"paths\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/request.CasbinInfo\"\n                    }\n                }\n            }\n        },\n        \"response.Response\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"integer\"\n                },\n                \"data\": {},\n                \"msg\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.SysAPIListResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"apis\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysApi\"\n                    }\n                }\n            }\n        },\n        \"response.SysAPIResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"api\": {\n                    \"$ref\": \"#/definitions/system.SysApi\"\n                }\n            }\n        },\n        \"response.SysAuthorityBtnRes\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"selected\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"response.SysAuthorityCopyResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authority\": {\n                    \"$ref\": \"#/definitions/system.SysAuthority\"\n                },\n                \"oldAuthorityId\": {\n                    \"description\": \"旧角色ID\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"response.SysAuthorityResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authority\": {\n                    \"$ref\": \"#/definitions/system.SysAuthority\"\n                }\n            }\n        },\n        \"response.SysBaseMenuResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"menu\": {\n                    \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                }\n            }\n        },\n        \"response.SysBaseMenusResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                }\n            }\n        },\n        \"response.SysCaptchaResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"captchaId\": {\n                    \"type\": \"string\"\n                },\n                \"captchaLength\": {\n                    \"type\": \"integer\"\n                },\n                \"openCaptcha\": {\n                    \"type\": \"boolean\"\n                },\n                \"picPath\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"response.SysConfigResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"$ref\": \"#/definitions/config.Server\"\n                }\n            }\n        },\n        \"response.SysMenusResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysMenu\"\n                    }\n                }\n            }\n        },\n        \"response.SysUserResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\n                    \"$ref\": \"#/definitions/system.SysUser\"\n                }\n            }\n        },\n        \"system.Condition\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"column\": {\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"from\": {\n                    \"type\": \"string\"\n                },\n                \"operator\": {\n                    \"type\": \"string\"\n                },\n                \"templateID\": {\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.JoinTemplate\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"joins\": {\n                    \"type\": \"string\"\n                },\n                \"on\": {\n                    \"type\": \"string\"\n                },\n                \"table\": {\n                    \"type\": \"string\"\n                },\n                \"templateID\": {\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.Meta\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"activeName\": {\n                    \"type\": \"string\"\n                },\n                \"closeTab\": {\n                    \"description\": \"自动关闭tab\",\n                    \"type\": \"boolean\"\n                },\n                \"defaultMenu\": {\n                    \"description\": \"是否是基础路由（开发中）\",\n                    \"type\": \"boolean\"\n                },\n                \"icon\": {\n                    \"description\": \"菜单图标\",\n                    \"type\": \"string\"\n                },\n                \"keepAlive\": {\n                    \"description\": \"是否缓存\",\n                    \"type\": \"boolean\"\n                },\n                \"title\": {\n                    \"description\": \"菜单名\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysApi\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"apiGroup\": {\n                    \"description\": \"api组\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"description\": {\n                    \"description\": \"api中文描述\",\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"description\": \"方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"api路径\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysAuthority\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"authorityId\": {\n                    \"description\": \"角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"authorityName\": {\n                    \"description\": \"角色名\",\n                    \"type\": \"string\"\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"dataAuthorityId\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"defaultRouter\": {\n                    \"description\": \"默认菜单(默认dashboard)\",\n                    \"type\": \"string\"\n                },\n                \"deletedAt\": {\n                    \"type\": \"string\"\n                },\n                \"menus\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                },\n                \"parentId\": {\n                    \"description\": \"父角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysBaseMenu\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"authoritys\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenu\"\n                    }\n                },\n                \"component\": {\n                    \"description\": \"对应前端文件路径\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"hidden\": {\n                    \"description\": \"是否在列表隐藏\",\n                    \"type\": \"boolean\"\n                },\n                \"menuBtn\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuBtn\"\n                    }\n                },\n                \"meta\": {\n                    \"description\": \"附加属性\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.Meta\"\n                        }\n                    ]\n                },\n                \"name\": {\n                    \"description\": \"路由name\",\n                    \"type\": \"string\"\n                },\n                \"parameters\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuParameter\"\n                    }\n                },\n                \"parentId\": {\n                    \"description\": \"父菜单ID\",\n                    \"type\": \"integer\"\n                },\n                \"path\": {\n                    \"description\": \"路由path\",\n                    \"type\": \"string\"\n                },\n                \"sort\": {\n                    \"description\": \"排序标记\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysBaseMenuBtn\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"sysBaseMenuID\": {\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysBaseMenuParameter\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"description\": \"地址栏携带参数的key\",\n                    \"type\": \"string\"\n                },\n                \"sysBaseMenuID\": {\n                    \"type\": \"integer\"\n                },\n                \"type\": {\n                    \"description\": \"地址栏携带参数为params还是query\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"description\": \"地址栏携带参数的值\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysDictionary\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"描述\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"description\": \"字典名（中）\",\n                    \"type\": \"string\"\n                },\n                \"status\": {\n                    \"description\": \"状态\",\n                    \"type\": \"boolean\"\n                },\n                \"sysDictionaryDetails\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysDictionaryDetail\"\n                    }\n                },\n                \"type\": {\n                    \"description\": \"字典名（英）\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysDictionaryDetail\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"extend\": {\n                    \"description\": \"扩展值\",\n                    \"type\": \"string\"\n                },\n                \"label\": {\n                    \"description\": \"展示值\",\n                    \"type\": \"string\"\n                },\n                \"sort\": {\n                    \"description\": \"排序标记\",\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"description\": \"启用状态\",\n                    \"type\": \"boolean\"\n                },\n                \"sysDictionaryID\": {\n                    \"description\": \"关联标记\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"description\": \"字典值\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysExportTemplate\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"conditions\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.Condition\"\n                    }\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"dbName\": {\n                    \"description\": \"数据库名称\",\n                    \"type\": \"string\"\n                },\n                \"joinTemplate\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.JoinTemplate\"\n                    }\n                },\n                \"limit\": {\n                    \"type\": \"integer\"\n                },\n                \"name\": {\n                    \"description\": \"模板名称\",\n                    \"type\": \"string\"\n                },\n                \"order\": {\n                    \"type\": \"string\"\n                },\n                \"tableName\": {\n                    \"description\": \"表名称\",\n                    \"type\": \"string\"\n                },\n                \"templateID\": {\n                    \"description\": \"模板标识\",\n                    \"type\": \"string\"\n                },\n                \"templateInfo\": {\n                    \"description\": \"模板信息\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysMenu\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"authoritys\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"btns\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"children\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysMenu\"\n                    }\n                },\n                \"component\": {\n                    \"description\": \"对应前端文件路径\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"hidden\": {\n                    \"description\": \"是否在列表隐藏\",\n                    \"type\": \"boolean\"\n                },\n                \"menuBtn\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuBtn\"\n                    }\n                },\n                \"menuId\": {\n                    \"type\": \"integer\"\n                },\n                \"meta\": {\n                    \"description\": \"附加属性\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.Meta\"\n                        }\n                    ]\n                },\n                \"name\": {\n                    \"description\": \"路由name\",\n                    \"type\": \"string\"\n                },\n                \"parameters\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysBaseMenuParameter\"\n                    }\n                },\n                \"parentId\": {\n                    \"description\": \"父菜单ID\",\n                    \"type\": \"integer\"\n                },\n                \"path\": {\n                    \"description\": \"路由path\",\n                    \"type\": \"string\"\n                },\n                \"sort\": {\n                    \"description\": \"排序标记\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysOperationRecord\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"agent\": {\n                    \"description\": \"代理\",\n                    \"type\": \"string\"\n                },\n                \"body\": {\n                    \"description\": \"请求Body\",\n                    \"type\": \"string\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"error_message\": {\n                    \"description\": \"错误信息\",\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"description\": \"请求ip\",\n                    \"type\": \"string\"\n                },\n                \"latency\": {\n                    \"description\": \"延迟\",\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"description\": \"请求方法\",\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"description\": \"请求路径\",\n                    \"type\": \"string\"\n                },\n                \"resp\": {\n                    \"description\": \"响应Body\",\n                    \"type\": \"string\"\n                },\n                \"status\": {\n                    \"description\": \"请求状态\",\n                    \"type\": \"integer\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"user\": {\n                    \"$ref\": \"#/definitions/system.SysUser\"\n                },\n                \"user_id\": {\n                    \"description\": \"用户id\",\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"system.SysParams\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"key\",\n                \"name\",\n                \"value\"\n            ],\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"desc\": {\n                    \"description\": \"参数说明\",\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"description\": \"参数键\",\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"description\": \"参数名称\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"value\": {\n                    \"description\": \"参数值\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.SysUser\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ID\": {\n                    \"description\": \"主键ID\",\n                    \"type\": \"integer\"\n                },\n                \"authorities\": {\n                    \"description\": \"多用户角色\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/system.SysAuthority\"\n                    }\n                },\n                \"authority\": {\n                    \"description\": \"用户角色\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/system.SysAuthority\"\n                        }\n                    ]\n                },\n                \"authorityId\": {\n                    \"description\": \"用户角色ID\",\n                    \"type\": \"integer\"\n                },\n                \"createdAt\": {\n                    \"description\": \"创建时间\",\n                    \"type\": \"string\"\n                },\n                \"email\": {\n                    \"description\": \"用户邮箱\",\n                    \"type\": \"string\"\n                },\n                \"enable\": {\n                    \"description\": \"用户是否被冻结 1正常 2冻结\",\n                    \"type\": \"integer\"\n                },\n                \"headerImg\": {\n                    \"description\": \"用户头像\",\n                    \"type\": \"string\"\n                },\n                \"nickName\": {\n                    \"description\": \"用户昵称\",\n                    \"type\": \"string\"\n                },\n                \"originSetting\": {\n                    \"description\": \"配置\",\n                    \"allOf\": [\n                        {\n                            \"$ref\": \"#/definitions/common.JSONMap\"\n                        }\n                    ]\n                },\n                \"phone\": {\n                    \"description\": \"用户手机号\",\n                    \"type\": \"string\"\n                },\n                \"updatedAt\": {\n                    \"description\": \"更新时间\",\n                    \"type\": \"string\"\n                },\n                \"userName\": {\n                    \"description\": \"用户登录名\",\n                    \"type\": \"string\"\n                },\n                \"uuid\": {\n                    \"description\": \"用户UUID\",\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"system.System\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"$ref\": \"#/definitions/config.Server\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"ApiKeyAuth\": {\n            \"type\": \"apiKey\",\n            \"name\": \"x-token\",\n            \"in\": \"header\"\n        }\n    },\n    \"tags\": [\n        {\n            \"name\": \"Base\"\n        },\n        {\n            \"description\": \"用户\",\n            \"name\": \"SysUser\"\n        }\n    ]\n}"
  },
  {
    "path": "server/docs/swagger.yaml",
    "content": "definitions:\n  common.JSONMap:\n    additionalProperties: true\n    type: object\n  config.AliyunOSS:\n    properties:\n      access-key-id:\n        type: string\n      access-key-secret:\n        type: string\n      base-path:\n        type: string\n      bucket-name:\n        type: string\n      bucket-url:\n        type: string\n      endpoint:\n        type: string\n    type: object\n  config.Autocode:\n    properties:\n      ai-path:\n        type: string\n      module:\n        type: string\n      root:\n        type: string\n      server:\n        type: string\n      web:\n        type: string\n    type: object\n  config.AwsS3:\n    properties:\n      base-url:\n        type: string\n      bucket:\n        type: string\n      disable-ssl:\n        type: boolean\n      endpoint:\n        type: string\n      path-prefix:\n        type: string\n      region:\n        type: string\n      s3-force-path-style:\n        type: boolean\n      secret-id:\n        type: string\n      secret-key:\n        type: string\n    type: object\n  config.CORS:\n    properties:\n      mode:\n        type: string\n      whitelist:\n        items:\n          $ref: '#/definitions/config.CORSWhitelist'\n        type: array\n    type: object\n  config.CORSWhitelist:\n    properties:\n      allow-credentials:\n        type: boolean\n      allow-headers:\n        type: string\n      allow-methods:\n        type: string\n      allow-origin:\n        type: string\n      expose-headers:\n        type: string\n    type: object\n  config.Captcha:\n    properties:\n      img-height:\n        description: 验证码高度\n        type: integer\n      img-width:\n        description: 验证码宽度\n        type: integer\n      key-long:\n        description: 验证码长度\n        type: integer\n      open-captcha:\n        description: 防爆破验证码开启此数，0代表每次登录都需要验证码，其他数字代表错误密码此数，如3代表错误三次后出现验证码\n        type: integer\n      open-captcha-timeout:\n        description: 防爆破验证码超时时间，单位：s(秒)\n        type: integer\n    type: object\n  config.CloudflareR2:\n    properties:\n      access-key-id:\n        type: string\n      account-id:\n        type: string\n      base-url:\n        type: string\n      bucket:\n        type: string\n      path:\n        type: string\n      secret-access-key:\n        type: string\n    type: object\n  config.DiskList:\n    properties:\n      mount-point:\n        type: string\n    type: object\n  config.Excel:\n    properties:\n      dir:\n        type: string\n    type: object\n  config.HuaWeiObs:\n    properties:\n      access-key:\n        type: string\n      bucket:\n        type: string\n      endpoint:\n        type: string\n      path:\n        type: string\n      secret-key:\n        type: string\n    type: object\n  config.JWT:\n    properties:\n      buffer-time:\n        description: 缓冲时间\n        type: string\n      expires-time:\n        description: 过期时间\n        type: string\n      issuer:\n        description: 签发者\n        type: string\n      signing-key:\n        description: jwt签名\n        type: string\n    type: object\n  config.Local:\n    properties:\n      path:\n        description: 本地文件访问路径\n        type: string\n      store-path:\n        description: 本地文件存储路径\n        type: string\n    type: object\n  config.Minio:\n    properties:\n      access-key-id:\n        type: string\n      access-key-secret:\n        type: string\n      base-path:\n        type: string\n      bucket-name:\n        type: string\n      bucket-url:\n        type: string\n      endpoint:\n        type: string\n      use-ssl:\n        type: boolean\n    type: object\n  config.Mongo:\n    properties:\n      auth-source:\n        description: 验证数据库\n        type: string\n      coll:\n        description: collection name\n        type: string\n      connect-timeout-ms:\n        description: 连接超时时间\n        type: integer\n      database:\n        description: database name\n        type: string\n      hosts:\n        description: 主机列表\n        items:\n          $ref: '#/definitions/config.MongoHost'\n        type: array\n      is-zap:\n        description: 是否开启zap日志\n        type: boolean\n      max-pool-size:\n        description: 最大连接池\n        type: integer\n      min-pool-size:\n        description: 最小连接池\n        type: integer\n      options:\n        description: mongodb options\n        type: string\n      password:\n        description: 密码\n        type: string\n      socket-timeout-ms:\n        description: socket超时时间\n        type: integer\n      username:\n        description: 用户名\n        type: string\n    type: object\n  config.MongoHost:\n    properties:\n      host:\n        description: ip地址\n        type: string\n      port:\n        description: 端口\n        type: string\n    type: object\n  config.Mssql:\n    properties:\n      config:\n        description: 高级配置\n        type: string\n      db-name:\n        description: 数据库名\n        type: string\n      engine:\n        default: InnoDB\n        description: 数据库引擎，默认InnoDB\n        type: string\n      log-mode:\n        description: 是否开启Gorm全局日志\n        type: string\n      log-zap:\n        description: 是否通过zap写入日志文件\n        type: boolean\n      max-idle-conns:\n        description: 空闲中的最大连接数\n        type: integer\n      max-open-conns:\n        description: 打开到数据库的最大连接数\n        type: integer\n      password:\n        description: 数据库密码\n        type: string\n      path:\n        description: 数据库地址\n        type: string\n      port:\n        description: 数据库端口\n        type: string\n      prefix:\n        description: 数据库前缀\n        type: string\n      singular:\n        description: 是否开启全局禁用复数，true表示开启\n        type: boolean\n      username:\n        description: 数据库账号\n        type: string\n    type: object\n  config.Mysql:\n    properties:\n      config:\n        description: 高级配置\n        type: string\n      db-name:\n        description: 数据库名\n        type: string\n      engine:\n        default: InnoDB\n        description: 数据库引擎，默认InnoDB\n        type: string\n      log-mode:\n        description: 是否开启Gorm全局日志\n        type: string\n      log-zap:\n        description: 是否通过zap写入日志文件\n        type: boolean\n      max-idle-conns:\n        description: 空闲中的最大连接数\n        type: integer\n      max-open-conns:\n        description: 打开到数据库的最大连接数\n        type: integer\n      password:\n        description: 数据库密码\n        type: string\n      path:\n        description: 数据库地址\n        type: string\n      port:\n        description: 数据库端口\n        type: string\n      prefix:\n        description: 数据库前缀\n        type: string\n      singular:\n        description: 是否开启全局禁用复数，true表示开启\n        type: boolean\n      username:\n        description: 数据库账号\n        type: string\n    type: object\n  config.Oracle:\n    properties:\n      config:\n        description: 高级配置\n        type: string\n      db-name:\n        description: 数据库名\n        type: string\n      engine:\n        default: InnoDB\n        description: 数据库引擎，默认InnoDB\n        type: string\n      log-mode:\n        description: 是否开启Gorm全局日志\n        type: string\n      log-zap:\n        description: 是否通过zap写入日志文件\n        type: boolean\n      max-idle-conns:\n        description: 空闲中的最大连接数\n        type: integer\n      max-open-conns:\n        description: 打开到数据库的最大连接数\n        type: integer\n      password:\n        description: 数据库密码\n        type: string\n      path:\n        description: 数据库地址\n        type: string\n      port:\n        description: 数据库端口\n        type: string\n      prefix:\n        description: 数据库前缀\n        type: string\n      singular:\n        description: 是否开启全局禁用复数，true表示开启\n        type: boolean\n      username:\n        description: 数据库账号\n        type: string\n    type: object\n  config.Pgsql:\n    properties:\n      config:\n        description: 高级配置\n        type: string\n      db-name:\n        description: 数据库名\n        type: string\n      engine:\n        default: InnoDB\n        description: 数据库引擎，默认InnoDB\n        type: string\n      log-mode:\n        description: 是否开启Gorm全局日志\n        type: string\n      log-zap:\n        description: 是否通过zap写入日志文件\n        type: boolean\n      max-idle-conns:\n        description: 空闲中的最大连接数\n        type: integer\n      max-open-conns:\n        description: 打开到数据库的最大连接数\n        type: integer\n      password:\n        description: 数据库密码\n        type: string\n      path:\n        description: 数据库地址\n        type: string\n      port:\n        description: 数据库端口\n        type: string\n      prefix:\n        description: 数据库前缀\n        type: string\n      singular:\n        description: 是否开启全局禁用复数，true表示开启\n        type: boolean\n      username:\n        description: 数据库账号\n        type: string\n    type: object\n  config.Qiniu:\n    properties:\n      access-key:\n        description: 秘钥AK\n        type: string\n      bucket:\n        description: 空间名称\n        type: string\n      img-path:\n        description: CDN加速域名\n        type: string\n      secret-key:\n        description: 秘钥SK\n        type: string\n      use-cdn-domains:\n        description: 上传是否使用CDN上传加速\n        type: boolean\n      use-https:\n        description: 是否使用https\n        type: boolean\n      zone:\n        description: 存储区域\n        type: string\n    type: object\n  config.Redis:\n    properties:\n      addr:\n        description: 服务器地址:端口\n        type: string\n      clusterAddrs:\n        description: 集群模式下的节点地址列表\n        items:\n          type: string\n        type: array\n      db:\n        description: 单实例模式下redis的哪个数据库\n        type: integer\n      name:\n        description: 代表当前实例的名字\n        type: string\n      password:\n        description: 密码\n        type: string\n      useCluster:\n        description: 是否使用集群模式\n        type: boolean\n    type: object\n  config.Server:\n    properties:\n      aliyun-oss:\n        $ref: '#/definitions/config.AliyunOSS'\n      autocode:\n        allOf:\n        - $ref: '#/definitions/config.Autocode'\n        description: auto\n      aws-s3:\n        $ref: '#/definitions/config.AwsS3'\n      captcha:\n        $ref: '#/definitions/config.Captcha'\n      cloudflare-r2:\n        $ref: '#/definitions/config.CloudflareR2'\n      cors:\n        allOf:\n        - $ref: '#/definitions/config.CORS'\n        description: 跨域配置\n      db-list:\n        items:\n          $ref: '#/definitions/config.SpecializedDB'\n        type: array\n      disk-list:\n        items:\n          $ref: '#/definitions/config.DiskList'\n        type: array\n      email:\n        $ref: '#/definitions/github_com_flipped-aurora_gin-vue-admin_server_config.Email'\n      excel:\n        $ref: '#/definitions/config.Excel'\n      hua-wei-obs:\n        $ref: '#/definitions/config.HuaWeiObs'\n      jwt:\n        $ref: '#/definitions/config.JWT'\n      local:\n        allOf:\n        - $ref: '#/definitions/config.Local'\n        description: oss\n      minio:\n        $ref: '#/definitions/config.Minio'\n      mongo:\n        $ref: '#/definitions/config.Mongo'\n      mssql:\n        $ref: '#/definitions/config.Mssql'\n      mysql:\n        allOf:\n        - $ref: '#/definitions/config.Mysql'\n        description: gorm\n      oracle:\n        $ref: '#/definitions/config.Oracle'\n      pgsql:\n        $ref: '#/definitions/config.Pgsql'\n      qiniu:\n        $ref: '#/definitions/config.Qiniu'\n      redis:\n        $ref: '#/definitions/config.Redis'\n      redis-list:\n        items:\n          $ref: '#/definitions/config.Redis'\n        type: array\n      sqlite:\n        $ref: '#/definitions/config.Sqlite'\n      system:\n        $ref: '#/definitions/config.System'\n      tencent-cos:\n        $ref: '#/definitions/config.TencentCOS'\n      zap:\n        $ref: '#/definitions/config.Zap'\n    type: object\n  config.SpecializedDB:\n    properties:\n      alias-name:\n        type: string\n      config:\n        description: 高级配置\n        type: string\n      db-name:\n        description: 数据库名\n        type: string\n      disable:\n        type: boolean\n      engine:\n        default: InnoDB\n        description: 数据库引擎，默认InnoDB\n        type: string\n      log-mode:\n        description: 是否开启Gorm全局日志\n        type: string\n      log-zap:\n        description: 是否通过zap写入日志文件\n        type: boolean\n      max-idle-conns:\n        description: 空闲中的最大连接数\n        type: integer\n      max-open-conns:\n        description: 打开到数据库的最大连接数\n        type: integer\n      password:\n        description: 数据库密码\n        type: string\n      path:\n        description: 数据库地址\n        type: string\n      port:\n        description: 数据库端口\n        type: string\n      prefix:\n        description: 数据库前缀\n        type: string\n      singular:\n        description: 是否开启全局禁用复数，true表示开启\n        type: boolean\n      type:\n        type: string\n      username:\n        description: 数据库账号\n        type: string\n    type: object\n  config.Sqlite:\n    properties:\n      config:\n        description: 高级配置\n        type: string\n      db-name:\n        description: 数据库名\n        type: string\n      engine:\n        default: InnoDB\n        description: 数据库引擎，默认InnoDB\n        type: string\n      log-mode:\n        description: 是否开启Gorm全局日志\n        type: string\n      log-zap:\n        description: 是否通过zap写入日志文件\n        type: boolean\n      max-idle-conns:\n        description: 空闲中的最大连接数\n        type: integer\n      max-open-conns:\n        description: 打开到数据库的最大连接数\n        type: integer\n      password:\n        description: 数据库密码\n        type: string\n      path:\n        description: 数据库地址\n        type: string\n      port:\n        description: 数据库端口\n        type: string\n      prefix:\n        description: 数据库前缀\n        type: string\n      singular:\n        description: 是否开启全局禁用复数，true表示开启\n        type: boolean\n      username:\n        description: 数据库账号\n        type: string\n    type: object\n  config.System:\n    properties:\n      addr:\n        description: 端口值\n        type: integer\n      db-type:\n        description: 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql\n        type: string\n      iplimit-count:\n        type: integer\n      iplimit-time:\n        type: integer\n      oss-type:\n        description: Oss类型\n        type: string\n      router-prefix:\n        type: string\n      use-mongo:\n        description: 使用mongo\n        type: boolean\n      use-multipoint:\n        description: 多点登录拦截\n        type: boolean\n      use-redis:\n        description: 使用redis\n        type: boolean\n      use-strict-auth:\n        description: 使用树形角色分配模式\n        type: boolean\n    type: object\n  config.TencentCOS:\n    properties:\n      base-url:\n        type: string\n      bucket:\n        type: string\n      path-prefix:\n        type: string\n      region:\n        type: string\n      secret-id:\n        type: string\n      secret-key:\n        type: string\n    type: object\n  config.Zap:\n    properties:\n      director:\n        description: 日志文件夹\n        type: string\n      encode-level:\n        description: 编码级\n        type: string\n      format:\n        description: 输出\n        type: string\n      level:\n        description: 级别\n        type: string\n      log-in-console:\n        description: 输出控制台\n        type: boolean\n      prefix:\n        description: 日志前缀\n        type: string\n      retention-day:\n        description: 日志保留天数\n        type: integer\n      show-line:\n        description: 显示行\n        type: boolean\n      stacktrace-key:\n        description: 栈名\n        type: string\n    type: object\n  example.ExaAttachmentCategory:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      children:\n        items:\n          $ref: '#/definitions/example.ExaAttachmentCategory'\n        type: array\n      createdAt:\n        description: 创建时间\n        type: string\n      name:\n        type: string\n      pid:\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  example.ExaCustomer:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      customerName:\n        description: 客户名\n        type: string\n      customerPhoneData:\n        description: 客户手机号\n        type: string\n      sysUser:\n        allOf:\n        - $ref: '#/definitions/system.SysUser'\n        description: 管理详情\n      sysUserAuthorityID:\n        description: 管理角色ID\n        type: integer\n      sysUserId:\n        description: 管理ID\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  example.ExaFile:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      chunkTotal:\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      exaFileChunk:\n        items:\n          $ref: '#/definitions/example.ExaFileChunk'\n        type: array\n      fileMd5:\n        type: string\n      fileName:\n        type: string\n      filePath:\n        type: string\n      isFinish:\n        type: boolean\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  example.ExaFileChunk:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      exaFileID:\n        type: integer\n      fileChunkNumber:\n        type: integer\n      fileChunkPath:\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  example.ExaFileUploadAndDownload:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      classId:\n        description: 分类id\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      key:\n        description: 编号\n        type: string\n      name:\n        description: 文件名\n        type: string\n      tag:\n        description: 文件标签\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n      url:\n        description: 文件地址\n        type: string\n    type: object\n  github_com_flipped-aurora_gin-vue-admin_server_config.Email:\n    properties:\n      from:\n        description: 发件人  你自己要发邮件的邮箱\n        type: string\n      host:\n        description: 服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议\n        type: string\n      is-ssl:\n        description: 是否SSL   是否开启SSL\n        type: boolean\n      nickname:\n        description: 昵称    发件人昵称 通常为自己的邮箱\n        type: string\n      port:\n        description: 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465\n        type: integer\n      secret:\n        description: 密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥\n        type: string\n      to:\n        description: 收件人:多个以英文逗号分隔 例：a@qq.com b@qq.com 正式开发中请把此项目作为参数使用\n        type: string\n    type: object\n  model.Info:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      attachments:\n        description: 附件\n        items:\n          type: object\n        type: array\n      content:\n        description: 内容\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      title:\n        description: 标题\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n      userID:\n        description: 作者\n        type: integer\n    type: object\n  request.AddMenuAuthorityInfo:\n    properties:\n      authorityId:\n        description: 角色ID\n        type: integer\n      menus:\n        items:\n          $ref: '#/definitions/system.SysBaseMenu'\n        type: array\n    type: object\n  request.AutoCode:\n    properties:\n      abbreviation:\n        description: Struct简称\n        example: Struct简称\n        type: string\n      autoCreateApiToSql:\n        description: 是否自动创建api\n        example: false\n        type: boolean\n      autoCreateBtnAuth:\n        description: 是否自动创建按钮权限\n        example: false\n        type: boolean\n      autoCreateMenuToSql:\n        description: 是否自动创建menu\n        example: false\n        type: boolean\n      autoCreateResource:\n        description: 是否自动创建资源标识\n        example: false\n        type: boolean\n      autoMigrate:\n        description: 是否自动迁移表结构\n        example: false\n        type: boolean\n      businessDB:\n        description: 业务数据库\n        example: 业务数据库\n        type: string\n      description:\n        description: Struct中文名称\n        example: Struct中文名称\n        type: string\n      fields:\n        items:\n          $ref: '#/definitions/request.AutoCodeField'\n        type: array\n      generateServer:\n        description: 是否生成server\n        example: true\n        type: boolean\n      generateWeb:\n        description: 是否生成web\n        example: true\n        type: boolean\n      gvaModel:\n        description: 是否使用gva默认Model\n        example: false\n        type: boolean\n      humpPackageName:\n        description: go文件名称\n        example: go文件名称\n        type: string\n      isAdd:\n        description: 是否新增\n        example: false\n        type: boolean\n      isTree:\n        description: 是否树形结构\n        example: false\n        type: boolean\n      onlyTemplate:\n        description: 是否只生成模板\n        example: false\n        type: boolean\n      package:\n        type: string\n      packageName:\n        description: 文件名称\n        example: 文件名称\n        type: string\n      primaryField:\n        $ref: '#/definitions/request.AutoCodeField'\n      structName:\n        description: Struct名称\n        example: Struct名称\n        type: string\n      tableName:\n        description: 表名\n        example: 表名\n        type: string\n      treeJson:\n        description: 展示的树json字段\n        example: 展示的树json字段\n        type: string\n    type: object\n  request.AutoCodeField:\n    properties:\n      checkDataSource:\n        description: 是否检查数据源\n        type: boolean\n      clearable:\n        description: 是否可清空\n        type: boolean\n      columnName:\n        description: 数据库字段\n        type: string\n      comment:\n        description: 数据库字段描述\n        type: string\n      dataSource:\n        allOf:\n        - $ref: '#/definitions/request.DataSource'\n        description: 数据源\n      dataTypeLong:\n        description: 数据库字段长度\n        type: string\n      defaultValue:\n        description: 是否必填\n        type: string\n      desc:\n        description: 是否前端详情\n        type: boolean\n      dictType:\n        description: 字典\n        type: string\n      errorText:\n        description: 校验失败文字\n        type: string\n      excel:\n        description: 是否导入/导出\n        type: boolean\n      fieldDesc:\n        description: 中文名\n        type: string\n      fieldIndexType:\n        description: 索引类型\n        type: string\n      fieldJson:\n        description: FieldJson\n        type: string\n      fieldName:\n        description: Field名\n        type: string\n      fieldSearchHide:\n        description: 是否隐藏查询条件\n        type: boolean\n      fieldSearchType:\n        description: 搜索条件\n        type: string\n      fieldType:\n        description: Field数据类型\n        type: string\n      form:\n        description: Front           bool        `json:\"front\"`           // 是否前端可见\n        type: boolean\n      primaryKey:\n        description: 是否主键\n        type: boolean\n      require:\n        description: 是否必填\n        type: boolean\n      sort:\n        description: 是否增加排序\n        type: boolean\n      table:\n        description: 是否前端表格列\n        type: boolean\n    type: object\n  request.CasbinInReceive:\n    properties:\n      authorityId:\n        description: 权限id\n        type: integer\n      casbinInfos:\n        items:\n          $ref: '#/definitions/request.CasbinInfo'\n        type: array\n    type: object\n  request.CasbinInfo:\n    properties:\n      method:\n        description: 方法\n        type: string\n      path:\n        description: 路径\n        type: string\n    type: object\n  request.ChangePasswordReq:\n    properties:\n      newPassword:\n        description: 新密码\n        type: string\n      password:\n        description: 密码\n        type: string\n    type: object\n  request.DataSource:\n    properties:\n      association:\n        description: 关联关系 1 一对一 2 一对多\n        type: integer\n      dbName:\n        type: string\n      hasDeletedAt:\n        type: boolean\n      label:\n        type: string\n      table:\n        type: string\n      value:\n        type: string\n    type: object\n  request.Empty:\n    type: object\n  request.ExaAttachmentCategorySearch:\n    properties:\n      classId:\n        type: integer\n      keyword:\n        description: 关键字\n        type: string\n      page:\n        description: 页码\n        type: integer\n      pageSize:\n        description: 每页大小\n        type: integer\n    type: object\n  request.GetAuthorityId:\n    properties:\n      authorityId:\n        description: 角色ID\n        type: integer\n    type: object\n  request.GetById:\n    properties:\n      id:\n        description: 主键ID\n        type: integer\n    type: object\n  request.GetUserList:\n    properties:\n      email:\n        type: string\n      keyword:\n        description: 关键字\n        type: string\n      nickName:\n        type: string\n      page:\n        description: 页码\n        type: integer\n      pageSize:\n        description: 每页大小\n        type: integer\n      phone:\n        type: string\n      username:\n        type: string\n    type: object\n  request.IdsReq:\n    properties:\n      ids:\n        items:\n          type: integer\n        type: array\n    type: object\n  request.InitDB:\n    properties:\n      adminPassword:\n        type: string\n      dbName:\n        description: 数据库名\n        type: string\n      dbPath:\n        description: sqlite数据库文件路径\n        type: string\n      dbType:\n        description: 数据库类型\n        type: string\n      host:\n        description: 服务器地址\n        type: string\n      password:\n        description: 数据库密码\n        type: string\n      port:\n        description: 数据库连接端口\n        type: string\n      template:\n        description: postgresql指定template\n        type: string\n      userName:\n        description: 数据库用户名\n        type: string\n    required:\n    - adminPassword\n    - dbName\n    type: object\n  request.Login:\n    properties:\n      captcha:\n        description: 验证码\n        type: string\n      captchaId:\n        description: 验证码ID\n        type: string\n      password:\n        description: 密码\n        type: string\n      username:\n        description: 用户名\n        type: string\n    type: object\n  request.PageInfo:\n    properties:\n      keyword:\n        description: 关键字\n        type: string\n      page:\n        description: 页码\n        type: integer\n      pageSize:\n        description: 每页大小\n        type: integer\n    type: object\n  request.Register:\n    properties:\n      authorityId:\n        example: int 角色id\n        type: string\n      authorityIds:\n        example: '[]uint 角色id'\n        type: string\n      email:\n        example: 电子邮箱\n        type: string\n      enable:\n        example: int 是否启用\n        type: string\n      headerImg:\n        example: 头像链接\n        type: string\n      nickName:\n        example: 昵称\n        type: string\n      passWord:\n        example: 密码\n        type: string\n      phone:\n        example: 电话号码\n        type: string\n      userName:\n        example: 用户名\n        type: string\n    type: object\n  request.SearchApiParams:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      apiGroup:\n        description: api组\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      desc:\n        description: 排序方式:升序false(默认)|降序true\n        type: boolean\n      description:\n        description: api中文描述\n        type: string\n      keyword:\n        description: 关键字\n        type: string\n      method:\n        description: 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\n        type: string\n      orderKey:\n        description: 排序\n        type: string\n      page:\n        description: 页码\n        type: integer\n      pageSize:\n        description: 每页大小\n        type: integer\n      path:\n        description: api路径\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  request.SetUserAuth:\n    properties:\n      authorityId:\n        description: 角色ID\n        type: integer\n    type: object\n  request.SetUserAuthorities:\n    properties:\n      authorityIds:\n        description: 角色ID\n        items:\n          type: integer\n        type: array\n      id:\n        type: integer\n    type: object\n  request.SysAuthorityBtnReq:\n    properties:\n      authorityId:\n        type: integer\n      menuID:\n        type: integer\n      selected:\n        items:\n          type: integer\n        type: array\n    type: object\n  request.SysAutoCodePackageCreate:\n    properties:\n      desc:\n        example: 描述\n        type: string\n      label:\n        example: 展示名\n        type: string\n      packageName:\n        example: 包名\n        type: string\n      template:\n        example: 模版\n        type: string\n    type: object\n  request.SysAutoHistoryRollBack:\n    properties:\n      deleteApi:\n        description: 是否删除接口\n        type: boolean\n      deleteMenu:\n        description: 是否删除菜单\n        type: boolean\n      deleteTable:\n        description: 是否删除表\n        type: boolean\n      id:\n        description: 主键ID\n        type: integer\n    type: object\n  response.Email:\n    properties:\n      body:\n        description: 邮件内容\n        type: string\n      subject:\n        description: 邮件标题\n        type: string\n      to:\n        description: 邮件发送给谁\n        type: string\n    type: object\n  response.ExaCustomerResponse:\n    properties:\n      customer:\n        $ref: '#/definitions/example.ExaCustomer'\n    type: object\n  response.ExaFileResponse:\n    properties:\n      file:\n        $ref: '#/definitions/example.ExaFileUploadAndDownload'\n    type: object\n  response.FilePathResponse:\n    properties:\n      filePath:\n        type: string\n    type: object\n  response.FileResponse:\n    properties:\n      file:\n        $ref: '#/definitions/example.ExaFile'\n    type: object\n  response.LoginResponse:\n    properties:\n      expiresAt:\n        type: integer\n      token:\n        type: string\n      user:\n        $ref: '#/definitions/system.SysUser'\n    type: object\n  response.PageResult:\n    properties:\n      list: {}\n      page:\n        type: integer\n      pageSize:\n        type: integer\n      total:\n        type: integer\n    type: object\n  response.PolicyPathResponse:\n    properties:\n      paths:\n        items:\n          $ref: '#/definitions/request.CasbinInfo'\n        type: array\n    type: object\n  response.Response:\n    properties:\n      code:\n        type: integer\n      data: {}\n      msg:\n        type: string\n    type: object\n  response.SysAPIListResponse:\n    properties:\n      apis:\n        items:\n          $ref: '#/definitions/system.SysApi'\n        type: array\n    type: object\n  response.SysAPIResponse:\n    properties:\n      api:\n        $ref: '#/definitions/system.SysApi'\n    type: object\n  response.SysAuthorityBtnRes:\n    properties:\n      selected:\n        items:\n          type: integer\n        type: array\n    type: object\n  response.SysAuthorityCopyResponse:\n    properties:\n      authority:\n        $ref: '#/definitions/system.SysAuthority'\n      oldAuthorityId:\n        description: 旧角色ID\n        type: integer\n    type: object\n  response.SysAuthorityResponse:\n    properties:\n      authority:\n        $ref: '#/definitions/system.SysAuthority'\n    type: object\n  response.SysBaseMenuResponse:\n    properties:\n      menu:\n        $ref: '#/definitions/system.SysBaseMenu'\n    type: object\n  response.SysBaseMenusResponse:\n    properties:\n      menus:\n        items:\n          $ref: '#/definitions/system.SysBaseMenu'\n        type: array\n    type: object\n  response.SysCaptchaResponse:\n    properties:\n      captchaId:\n        type: string\n      captchaLength:\n        type: integer\n      openCaptcha:\n        type: boolean\n      picPath:\n        type: string\n    type: object\n  response.SysConfigResponse:\n    properties:\n      config:\n        $ref: '#/definitions/config.Server'\n    type: object\n  response.SysMenusResponse:\n    properties:\n      menus:\n        items:\n          $ref: '#/definitions/system.SysMenu'\n        type: array\n    type: object\n  response.SysUserResponse:\n    properties:\n      user:\n        $ref: '#/definitions/system.SysUser'\n    type: object\n  system.Condition:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      column:\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      from:\n        type: string\n      operator:\n        type: string\n      templateID:\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.JoinTemplate:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      joins:\n        type: string\n      \"on\":\n        type: string\n      table:\n        type: string\n      templateID:\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.Meta:\n    properties:\n      activeName:\n        type: string\n      closeTab:\n        description: 自动关闭tab\n        type: boolean\n      defaultMenu:\n        description: 是否是基础路由（开发中）\n        type: boolean\n      icon:\n        description: 菜单图标\n        type: string\n      keepAlive:\n        description: 是否缓存\n        type: boolean\n      title:\n        description: 菜单名\n        type: string\n    type: object\n  system.SysApi:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      apiGroup:\n        description: api组\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      description:\n        description: api中文描述\n        type: string\n      method:\n        description: 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\n        type: string\n      path:\n        description: api路径\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysAuthority:\n    properties:\n      authorityId:\n        description: 角色ID\n        type: integer\n      authorityName:\n        description: 角色名\n        type: string\n      children:\n        items:\n          $ref: '#/definitions/system.SysAuthority'\n        type: array\n      createdAt:\n        description: 创建时间\n        type: string\n      dataAuthorityId:\n        items:\n          $ref: '#/definitions/system.SysAuthority'\n        type: array\n      defaultRouter:\n        description: 默认菜单(默认dashboard)\n        type: string\n      deletedAt:\n        type: string\n      menus:\n        items:\n          $ref: '#/definitions/system.SysBaseMenu'\n        type: array\n      parentId:\n        description: 父角色ID\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysBaseMenu:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      authoritys:\n        items:\n          $ref: '#/definitions/system.SysAuthority'\n        type: array\n      children:\n        items:\n          $ref: '#/definitions/system.SysBaseMenu'\n        type: array\n      component:\n        description: 对应前端文件路径\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      hidden:\n        description: 是否在列表隐藏\n        type: boolean\n      menuBtn:\n        items:\n          $ref: '#/definitions/system.SysBaseMenuBtn'\n        type: array\n      meta:\n        allOf:\n        - $ref: '#/definitions/system.Meta'\n        description: 附加属性\n      name:\n        description: 路由name\n        type: string\n      parameters:\n        items:\n          $ref: '#/definitions/system.SysBaseMenuParameter'\n        type: array\n      parentId:\n        description: 父菜单ID\n        type: integer\n      path:\n        description: 路由path\n        type: string\n      sort:\n        description: 排序标记\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysBaseMenuBtn:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      desc:\n        type: string\n      name:\n        type: string\n      sysBaseMenuID:\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysBaseMenuParameter:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      key:\n        description: 地址栏携带参数的key\n        type: string\n      sysBaseMenuID:\n        type: integer\n      type:\n        description: 地址栏携带参数为params还是query\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n      value:\n        description: 地址栏携带参数的值\n        type: string\n    type: object\n  system.SysDictionary:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      desc:\n        description: 描述\n        type: string\n      name:\n        description: 字典名（中）\n        type: string\n      status:\n        description: 状态\n        type: boolean\n      sysDictionaryDetails:\n        items:\n          $ref: '#/definitions/system.SysDictionaryDetail'\n        type: array\n      type:\n        description: 字典名（英）\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysDictionaryDetail:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      extend:\n        description: 扩展值\n        type: string\n      label:\n        description: 展示值\n        type: string\n      sort:\n        description: 排序标记\n        type: integer\n      status:\n        description: 启用状态\n        type: boolean\n      sysDictionaryID:\n        description: 关联标记\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n      value:\n        description: 字典值\n        type: string\n    type: object\n  system.SysExportTemplate:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      conditions:\n        items:\n          $ref: '#/definitions/system.Condition'\n        type: array\n      createdAt:\n        description: 创建时间\n        type: string\n      dbName:\n        description: 数据库名称\n        type: string\n      joinTemplate:\n        items:\n          $ref: '#/definitions/system.JoinTemplate'\n        type: array\n      limit:\n        type: integer\n      name:\n        description: 模板名称\n        type: string\n      order:\n        type: string\n      tableName:\n        description: 表名称\n        type: string\n      templateID:\n        description: 模板标识\n        type: string\n      templateInfo:\n        description: 模板信息\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysMenu:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      authoritys:\n        items:\n          $ref: '#/definitions/system.SysAuthority'\n        type: array\n      btns:\n        additionalProperties:\n          type: integer\n        type: object\n      children:\n        items:\n          $ref: '#/definitions/system.SysMenu'\n        type: array\n      component:\n        description: 对应前端文件路径\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      hidden:\n        description: 是否在列表隐藏\n        type: boolean\n      menuBtn:\n        items:\n          $ref: '#/definitions/system.SysBaseMenuBtn'\n        type: array\n      menuId:\n        type: integer\n      meta:\n        allOf:\n        - $ref: '#/definitions/system.Meta'\n        description: 附加属性\n      name:\n        description: 路由name\n        type: string\n      parameters:\n        items:\n          $ref: '#/definitions/system.SysBaseMenuParameter'\n        type: array\n      parentId:\n        description: 父菜单ID\n        type: integer\n      path:\n        description: 路由path\n        type: string\n      sort:\n        description: 排序标记\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n    type: object\n  system.SysOperationRecord:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      agent:\n        description: 代理\n        type: string\n      body:\n        description: 请求Body\n        type: string\n      createdAt:\n        description: 创建时间\n        type: string\n      error_message:\n        description: 错误信息\n        type: string\n      ip:\n        description: 请求ip\n        type: string\n      latency:\n        description: 延迟\n        type: string\n      method:\n        description: 请求方法\n        type: string\n      path:\n        description: 请求路径\n        type: string\n      resp:\n        description: 响应Body\n        type: string\n      status:\n        description: 请求状态\n        type: integer\n      updatedAt:\n        description: 更新时间\n        type: string\n      user:\n        $ref: '#/definitions/system.SysUser'\n      user_id:\n        description: 用户id\n        type: integer\n    type: object\n  system.SysParams:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      desc:\n        description: 参数说明\n        type: string\n      key:\n        description: 参数键\n        type: string\n      name:\n        description: 参数名称\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n      value:\n        description: 参数值\n        type: string\n    required:\n    - key\n    - name\n    - value\n    type: object\n  system.SysUser:\n    properties:\n      ID:\n        description: 主键ID\n        type: integer\n      authorities:\n        description: 多用户角色\n        items:\n          $ref: '#/definitions/system.SysAuthority'\n        type: array\n      authority:\n        allOf:\n        - $ref: '#/definitions/system.SysAuthority'\n        description: 用户角色\n      authorityId:\n        description: 用户角色ID\n        type: integer\n      createdAt:\n        description: 创建时间\n        type: string\n      email:\n        description: 用户邮箱\n        type: string\n      enable:\n        description: 用户是否被冻结 1正常 2冻结\n        type: integer\n      headerImg:\n        description: 用户头像\n        type: string\n      nickName:\n        description: 用户昵称\n        type: string\n      originSetting:\n        allOf:\n        - $ref: '#/definitions/common.JSONMap'\n        description: 配置\n      phone:\n        description: 用户手机号\n        type: string\n      updatedAt:\n        description: 更新时间\n        type: string\n      userName:\n        description: 用户登录名\n        type: string\n      uuid:\n        description: 用户UUID\n        type: string\n    type: object\n  system.System:\n    properties:\n      config:\n        $ref: '#/definitions/config.Server'\n    type: object\ninfo:\n  contact: {}\n  description: 使用gin+vue进行极速开发的全栈开发基础平台\n  title: Gin-Vue-Admin Swagger API接口文档\n  version: v2.7.9-beta\npaths:\n  /api/createApi:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: api路径, api中文描述, api组, 方法\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysApi'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建基础api\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建基础api\n      tags:\n      - SysApi\n  /api/deleteApi:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysApi'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除api\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除api\n      tags:\n      - SysApi\n  /api/deleteApisByIds:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.IdsReq'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除选中Api\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除选中Api\n      tags:\n      - SysApi\n  /api/enterSyncApi:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 确认同步API\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 确认同步API\n      tags:\n      - SysApi\n  /api/freshCasbin:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 刷新成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      summary: 刷新casbin缓存\n      tags:\n      - SysApi\n  /api/getAllApis:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取所有的Api 不分页,返回包括api列表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysAPIListResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取所有的Api 不分页\n      tags:\n      - SysApi\n  /api/getApiById:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 根据id获取api\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 根据id获取api,返回包括api详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysAPIResponse'\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 根据id获取api\n      tags:\n      - SysApi\n  /api/getApiGroups:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取API分组\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取API分组\n      tags:\n      - SysApi\n  /api/getApiList:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 分页获取API列表\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SearchApiParams'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取API列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取API列表\n      tags:\n      - SysApi\n  /api/ignoreApi:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 同步API\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 忽略API\n      tags:\n      - IgnoreApi\n  /api/syncApi:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 同步API\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 同步API\n      tags:\n      - SysApi\n  /api/updateApi:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: api路径, api中文描述, api组, 方法\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysApi'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 修改基础api\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 修改基础api\n      tags:\n      - SysApi\n  /attachmentCategory/addCategory:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 媒体库分类数据\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/example.ExaAttachmentCategory'\n      produces:\n      - application/json\n      responses: {}\n      security:\n      - AttachmentCategory: []\n      summary: 添加媒体库分类\n      tags:\n      - AddCategory\n  /attachmentCategory/deleteCategory:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 分类id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除分类\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - AttachmentCategory: []\n      summary: 删除分类\n      tags:\n      - DeleteCategory\n  /attachmentCategory/getCategoryList:\n    get:\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 媒体库分类列表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/example.ExaAttachmentCategory'\n                msg:\n                  type: string\n              type: object\n      security:\n      - AttachmentCategory: []\n      summary: 媒体库分类列表\n      tags:\n      - GetCategoryList\n  /authority/copyAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 旧角色id, 新权限id, 新权限名, 新父角色id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/response.SysAuthorityCopyResponse'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 拷贝角色,返回包括系统角色详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysAuthorityResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 拷贝角色\n      tags:\n      - Authority\n  /authority/createAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 权限id, 权限名, 父角色id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysAuthority'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建角色,返回包括系统角色详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysAuthorityResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建角色\n      tags:\n      - Authority\n  /authority/deleteAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 删除角色\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysAuthority'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除角色\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除角色\n      tags:\n      - Authority\n  /authority/getAuthorityList:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 页码, 每页大小\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.PageInfo'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取角色列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取角色列表\n      tags:\n      - Authority\n  /authority/setDataAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 设置角色资源权限\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysAuthority'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置角色资源权限\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置角色资源权限\n      tags:\n      - Authority\n  /authority/updateAuthority:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 权限id, 权限名, 父角色id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysAuthority'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新角色信息,返回包括系统角色详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysAuthorityResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新角色信息\n      tags:\n      - Authority\n  /authorityBtn/canRemoveAuthorityBtn:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置权限按钮\n      tags:\n      - AuthorityBtn\n  /authorityBtn/getAuthorityBtn:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 菜单id, 角色id, 选中的按钮id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SysAuthorityBtnReq'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 返回列表成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysAuthorityBtnRes'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取权限按钮\n      tags:\n      - AuthorityBtn\n  /authorityBtn/setAuthorityBtn:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 菜单id, 角色id, 选中的按钮id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SysAuthorityBtnReq'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 返回列表成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置权限按钮\n      tags:\n      - AuthorityBtn\n  /autoCode/addFunc:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 增加方法\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.AutoCode'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 增加方法\n      tags:\n      - AddFunc\n  /autoCode/createPackage:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建package\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SysAutoCodePackageCreate'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建package成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建package\n      tags:\n      - AutoCodePackage\n  /autoCode/createTemp:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建自动代码\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.AutoCode'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 自动代码模板\n      tags:\n      - AutoCodeTemplate\n  /autoCode/delPackage:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建package\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除package成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除package\n      tags:\n      - AutoCode\n  /autoCode/delSysHistory:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 请求参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除回滚记录\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除回滚记录\n      tags:\n      - AutoCode\n  /autoCode/getColumn:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取当前表所有字段\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取当前表所有字段\n      tags:\n      - AutoCode\n  /autoCode/getDB:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取当前所有数据库\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取当前所有数据库\n      tags:\n      - AutoCode\n  /autoCode/getMeta:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 请求参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取meta信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取meta信息\n      tags:\n      - AutoCode\n  /autoCode/getPackage:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建package成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取package\n      tags:\n      - AutoCodePackage\n  /autoCode/getSysHistory:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 请求参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.PageInfo'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 查询回滚记录,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 查询回滚记录\n      tags:\n      - AutoCode\n  /autoCode/getTables:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取当前数据库所有表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取当前数据库所有表\n      tags:\n      - AutoCode\n  /autoCode/getTemplates:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建package成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取package\n      tags:\n      - AutoCodePackage\n  /autoCode/initAPI:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 打包插件成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 打包插件\n      tags:\n      - AutoCodePlugin\n  /autoCode/initMenu:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 打包插件成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 打包插件\n      tags:\n      - AutoCodePlugin\n  /autoCode/installPlugin:\n    post:\n      consumes:\n      - multipart/form-data\n      parameters:\n      - description: this is a test file\n        in: formData\n        name: plug\n        required: true\n        type: file\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 安装插件成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  items:\n                    type: object\n                  type: array\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 安装插件\n      tags:\n      - AutoCodePlugin\n  /autoCode/preview:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 预览创建代码\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.AutoCode'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 预览创建后的代码\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 预览创建后的代码\n      tags:\n      - AutoCodeTemplate\n  /autoCode/pubPlug:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 插件名称\n        in: query\n        name: plugName\n        required: true\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 打包插件成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 打包插件\n      tags:\n      - AutoCodePlugin\n  /autoCode/rollback:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 请求参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SysAutoHistoryRollBack'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 回滚自动生成代码\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 回滚自动生成代码\n      tags:\n      - AutoCode\n  /base/captcha:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysCaptchaResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 生成验证码\n      tags:\n      - Base\n  /base/login:\n    post:\n      parameters:\n      - description: 用户名, 密码, 验证码\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.Login'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 返回包括用户信息,token,过期时间\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.LoginResponse'\n                msg:\n                  type: string\n              type: object\n      summary: 用户登录\n      tags:\n      - Base\n  /casbin/UpdateCasbin:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 权限id, 权限模型列表\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.CasbinInReceive'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新角色api权限\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新角色api权限\n      tags:\n      - Casbin\n  /casbin/getPolicyPathByAuthorityId:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 权限id, 权限模型列表\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.CasbinInReceive'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取权限列表,返回包括casbin详情列表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PolicyPathResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取权限列表\n      tags:\n      - Casbin\n  /customer/customer:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 客户ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/example.ExaCustomer'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除客户\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除客户\n      tags:\n      - ExaCustomer\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 客户名\n        in: query\n        name: customerName\n        type: string\n      - description: 客户手机号\n        in: query\n        name: customerPhoneData\n        type: string\n      - description: 管理角色ID\n        in: query\n        name: sysUserAuthorityID\n        type: integer\n      - description: 管理ID\n        in: query\n        name: sysUserId\n        type: integer\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取单一客户信息,返回包括客户详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.ExaCustomerResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取单一客户信息\n      tags:\n      - ExaCustomer\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 客户用户名, 客户手机号码\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/example.ExaCustomer'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建客户\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建客户\n      tags:\n      - ExaCustomer\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 客户ID, 客户信息\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/example.ExaCustomer'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新客户信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新客户信息\n      tags:\n      - ExaCustomer\n  /customer/customerList:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取权限客户列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取权限客户列表\n      tags:\n      - ExaCustomer\n  /email/emailTest:\n    post:\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 发送测试邮件\n      tags:\n      - System\n  /email/sendEmail:\n    post:\n      parameters:\n      - description: 发送邮件必须的参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/response.Email'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 发送邮件\n      tags:\n      - System\n  /fileUploadAndDownload/breakpointContinue:\n    post:\n      consumes:\n      - multipart/form-data\n      parameters:\n      - description: an example for breakpoint resume, 断点续传示例\n        in: formData\n        name: file\n        required: true\n        type: file\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 断点续传到服务器\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 断点续传到服务器\n      tags:\n      - ExaFileUploadAndDownload\n  /fileUploadAndDownload/deleteFile:\n    post:\n      parameters:\n      - description: 传入文件里面id即可\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/example.ExaFileUploadAndDownload'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除文件\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除文件\n      tags:\n      - ExaFileUploadAndDownload\n  /fileUploadAndDownload/findFile:\n    get:\n      consumes:\n      - multipart/form-data\n      parameters:\n      - description: Find the file, 查找文件\n        in: formData\n        name: file\n        required: true\n        type: file\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 查找文件,返回包括文件详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.FileResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 查找文件\n      tags:\n      - ExaFileUploadAndDownload\n    post:\n      consumes:\n      - multipart/form-data\n      parameters:\n      - description: 上传文件完成\n        in: formData\n        name: file\n        required: true\n        type: file\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建文件,返回包括文件路径\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.FilePathResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建文件\n      tags:\n      - ExaFileUploadAndDownload\n  /fileUploadAndDownload/getFileList:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 页码, 每页大小, 分类id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.ExaAttachmentCategorySearch'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页文件列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页文件列表\n      tags:\n      - ExaFileUploadAndDownload\n  /fileUploadAndDownload/importURL:\n    post:\n      parameters:\n      - description: 对象\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/example.ExaFileUploadAndDownload'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 导入URL\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 导入URL\n      tags:\n      - ExaFileUploadAndDownload\n  /fileUploadAndDownload/removeChunk:\n    post:\n      consumes:\n      - multipart/form-data\n      parameters:\n      - description: 删除缓存切片\n        in: formData\n        name: file\n        required: true\n        type: file\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除切片\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除切片\n      tags:\n      - ExaFileUploadAndDownload\n  /fileUploadAndDownload/upload:\n    post:\n      consumes:\n      - multipart/form-data\n      parameters:\n      - description: 上传文件示例\n        in: formData\n        name: file\n        required: true\n        type: file\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 上传文件示例,返回包括文件详情\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.ExaFileResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 上传文件示例\n      tags:\n      - ExaFileUploadAndDownload\n  /info/createInfo:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建公告\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/model.Info'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建公告\n      tags:\n      - Info\n  /info/deleteInfo:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 删除公告\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/model.Info'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除公告\n      tags:\n      - Info\n  /info/deleteInfoByIds:\n    delete:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 批量删除成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 批量删除公告\n      tags:\n      - Info\n  /info/findInfo:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 内容\n        in: query\n        name: content\n        type: string\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 标题\n        in: query\n        name: title\n        type: string\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      - description: 作者\n        in: query\n        name: userID\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 查询成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/model.Info'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 用id查询公告\n      tags:\n      - Info\n  /info/getInfoDataSource:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 查询成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  type: object\n                msg:\n                  type: string\n              type: object\n      summary: 获取Info的数据源\n      tags:\n      - Info\n  /info/getInfoList:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - in: query\n        name: endCreatedAt\n        type: string\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      - in: query\n        name: startCreatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取公告列表\n      tags:\n      - Info\n  /info/getInfoPublic:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - in: query\n        name: endCreatedAt\n        type: string\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      - in: query\n        name: startCreatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  type: object\n                msg:\n                  type: string\n              type: object\n      summary: 不需要鉴权的公告接口\n      tags:\n      - Info\n  /info/updateInfo:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 更新公告\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/model.Info'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新公告\n      tags:\n      - Info\n  /init/checkdb:\n    post:\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 初始化用户数据库\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      summary: 初始化用户数据库\n      tags:\n      - CheckDB\n  /init/initdb:\n    post:\n      parameters:\n      - description: 初始化数据库参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.InitDB'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 初始化用户数据库\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  type: string\n              type: object\n      summary: 初始化用户数据库\n      tags:\n      - InitDB\n  /jwt/jsonInBlacklist:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: jwt加入黑名单\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: jwt加入黑名单\n      tags:\n      - Jwt\n  /menu/addBaseMenu:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysBaseMenu'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 新增菜单\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 新增菜单\n      tags:\n      - Menu\n  /menu/addMenuAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 角色ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.AddMenuAuthorityInfo'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 增加menu和角色关联关系\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 增加menu和角色关联关系\n      tags:\n      - AuthorityMenu\n  /menu/deleteBaseMenu:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 菜单id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除菜单\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除菜单\n      tags:\n      - Menu\n  /menu/getBaseMenuById:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 菜单id\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 根据id获取菜单,返回包括系统菜单列表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysBaseMenuResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 根据id获取菜单\n      tags:\n      - Menu\n  /menu/getBaseMenuTree:\n    post:\n      parameters:\n      - description: 空\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.Empty'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取用户动态路由,返回包括系统菜单列表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysBaseMenusResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取用户动态路由\n      tags:\n      - AuthorityMenu\n  /menu/getMenu:\n    post:\n      parameters:\n      - description: 空\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.Empty'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取用户动态路由,返回包括系统菜单详情列表\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysMenusResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取用户动态路由\n      tags:\n      - AuthorityMenu\n  /menu/getMenuAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 角色ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetAuthorityId'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取指定角色menu\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取指定角色menu\n      tags:\n      - AuthorityMenu\n  /menu/getMenuList:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 页码, 每页大小\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.PageInfo'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取基础menu列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取基础menu列表\n      tags:\n      - Menu\n  /menu/updateBaseMenu:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysBaseMenu'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新菜单\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新菜单\n      tags:\n      - Menu\n  /sysDictionary/createSysDictionary:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: SysDictionary模型\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysDictionary'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建SysDictionary\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建SysDictionary\n      tags:\n      - SysDictionary\n  /sysDictionary/deleteSysDictionary:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: SysDictionary模型\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysDictionary'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除SysDictionary\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除SysDictionary\n      tags:\n      - SysDictionary\n  /sysDictionary/findSysDictionary:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 描述\n        in: query\n        name: desc\n        type: string\n      - description: 字典名（中）\n        in: query\n        name: name\n        type: string\n      - description: 状态\n        in: query\n        name: status\n        type: boolean\n      - description: 字典名（英）\n        in: query\n        name: type\n        type: string\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 用id查询SysDictionary\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 用id查询SysDictionary\n      tags:\n      - SysDictionary\n  /sysDictionary/getSysDictionaryList:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取SysDictionary列表\n      tags:\n      - SysDictionary\n  /sysDictionary/updateSysDictionary:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: SysDictionary模型\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysDictionary'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新SysDictionary\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新SysDictionary\n      tags:\n      - SysDictionary\n  /sysDictionaryDetail/createSysDictionaryDetail:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: SysDictionaryDetail模型\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysDictionaryDetail'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建SysDictionaryDetail\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建SysDictionaryDetail\n      tags:\n      - SysDictionaryDetail\n  /sysDictionaryDetail/deleteSysDictionaryDetail:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: SysDictionaryDetail模型\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysDictionaryDetail'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除SysDictionaryDetail\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除SysDictionaryDetail\n      tags:\n      - SysDictionaryDetail\n  /sysDictionaryDetail/findSysDictionaryDetail:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 扩展值\n        in: query\n        name: extend\n        type: string\n      - description: 展示值\n        in: query\n        name: label\n        type: string\n      - description: 排序标记\n        in: query\n        name: sort\n        type: integer\n      - description: 启用状态\n        in: query\n        name: status\n        type: boolean\n      - description: 关联标记\n        in: query\n        name: sysDictionaryID\n        type: integer\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      - description: 字典值\n        in: query\n        name: value\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 用id查询SysDictionaryDetail\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 用id查询SysDictionaryDetail\n      tags:\n      - SysDictionaryDetail\n  /sysDictionaryDetail/getSysDictionaryDetailList:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 扩展值\n        in: query\n        name: extend\n        type: string\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - description: 展示值\n        in: query\n        name: label\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      - description: 排序标记\n        in: query\n        name: sort\n        type: integer\n      - description: 启用状态\n        in: query\n        name: status\n        type: boolean\n      - description: 关联标记\n        in: query\n        name: sysDictionaryID\n        type: integer\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      - description: 字典值\n        in: query\n        name: value\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取SysDictionaryDetail列表\n      tags:\n      - SysDictionaryDetail\n  /sysDictionaryDetail/updateSysDictionaryDetail:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 更新SysDictionaryDetail\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysDictionaryDetail'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新SysDictionaryDetail\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新SysDictionaryDetail\n      tags:\n      - SysDictionaryDetail\n  /sysExportTemplate/ExportTemplate:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses: {}\n      security:\n      - ApiKeyAuth: []\n      summary: 导出表格模板\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/createSysExportTemplate:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建导出模板\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysExportTemplate'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 创建导出模板\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/deleteSysExportTemplate:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 删除导出模板\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysExportTemplate'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 删除导出模板\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/deleteSysExportTemplateByIds:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 批量删除导出模板\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.IdsReq'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"批量删除成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 批量删除导出模板\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/exportExcel:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses: {}\n      security:\n      - ApiKeyAuth: []\n      summary: 导出表格\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/findSysExportTemplate:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 数据库名称\n        in: query\n        name: dbName\n        type: string\n      - in: query\n        name: limit\n        type: integer\n      - description: 模板名称\n        in: query\n        name: name\n        type: string\n      - in: query\n        name: order\n        type: string\n      - description: 表名称\n        in: query\n        name: tableName\n        type: string\n      - description: 模板标识\n        in: query\n        name: templateID\n        type: string\n      - description: 模板信息\n        in: query\n        name: templateInfo\n        type: string\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 用id查询导出模板\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/getSysExportTemplateList:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 数据库名称\n        in: query\n        name: dbName\n        type: string\n      - in: query\n        name: endCreatedAt\n        type: string\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - in: query\n        name: limit\n        type: integer\n      - description: 模板名称\n        in: query\n        name: name\n        type: string\n      - in: query\n        name: order\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      - in: query\n        name: startCreatedAt\n        type: string\n      - description: 表名称\n        in: query\n        name: tableName\n        type: string\n      - description: 模板标识\n        in: query\n        name: templateID\n        type: string\n      - description: 模板信息\n        in: query\n        name: templateInfo\n        type: string\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取导出模板列表\n      tags:\n      - SysExportTemplate\n  /sysExportTemplate/importExcel:\n    post:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses: {}\n      security:\n      - ApiKeyAuth: []\n      summary: 导入表格\n      tags:\n      - SysImportTemplate\n  /sysExportTemplate/updateSysExportTemplate:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 更新导出模板\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysExportTemplate'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: '{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}'\n          schema:\n            type: string\n      security:\n      - ApiKeyAuth: []\n      summary: 更新导出模板\n      tags:\n      - SysExportTemplate\n  /sysOperationRecord/createSysOperationRecord:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建SysOperationRecord\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysOperationRecord'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建SysOperationRecord\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建SysOperationRecord\n      tags:\n      - SysOperationRecord\n  /sysOperationRecord/deleteSysOperationRecord:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: SysOperationRecord模型\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysOperationRecord'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除SysOperationRecord\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除SysOperationRecord\n      tags:\n      - SysOperationRecord\n  /sysOperationRecord/deleteSysOperationRecordByIds:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 批量删除SysOperationRecord\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.IdsReq'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 批量删除SysOperationRecord\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 批量删除SysOperationRecord\n      tags:\n      - SysOperationRecord\n  /sysOperationRecord/findSysOperationRecord:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 代理\n        in: query\n        name: agent\n        type: string\n      - description: 请求Body\n        in: query\n        name: body\n        type: string\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 错误信息\n        in: query\n        name: error_message\n        type: string\n      - description: 请求ip\n        in: query\n        name: ip\n        type: string\n      - description: 延迟\n        in: query\n        name: latency\n        type: string\n      - description: 请求方法\n        in: query\n        name: method\n        type: string\n      - description: 请求路径\n        in: query\n        name: path\n        type: string\n      - description: 响应Body\n        in: query\n        name: resp\n        type: string\n      - description: 请求状态\n        in: query\n        name: status\n        type: integer\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      - description: 用户id\n        in: query\n        name: user_id\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 用id查询SysOperationRecord\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 用id查询SysOperationRecord\n      tags:\n      - SysOperationRecord\n  /sysOperationRecord/getSysOperationRecordList:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 代理\n        in: query\n        name: agent\n        type: string\n      - description: 请求Body\n        in: query\n        name: body\n        type: string\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 错误信息\n        in: query\n        name: error_message\n        type: string\n      - description: 请求ip\n        in: query\n        name: ip\n        type: string\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - description: 延迟\n        in: query\n        name: latency\n        type: string\n      - description: 请求方法\n        in: query\n        name: method\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      - description: 请求路径\n        in: query\n        name: path\n        type: string\n      - description: 响应Body\n        in: query\n        name: resp\n        type: string\n      - description: 请求状态\n        in: query\n        name: status\n        type: integer\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      - description: 用户id\n        in: query\n        name: user_id\n        type: integer\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取SysOperationRecord列表\n      tags:\n      - SysOperationRecord\n  /sysParams/createSysParams:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 创建参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysParams'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 创建成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 创建参数\n      tags:\n      - SysParams\n  /sysParams/deleteSysParams:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 删除参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysParams'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除参数\n      tags:\n      - SysParams\n  /sysParams/deleteSysParamsByIds:\n    delete:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 批量删除成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 批量删除参数\n      tags:\n      - SysParams\n  /sysParams/findSysParams:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: 主键ID\n        in: query\n        name: ID\n        type: integer\n      - description: 创建时间\n        in: query\n        name: createdAt\n        type: string\n      - description: 参数说明\n        in: query\n        name: desc\n        type: string\n      - description: 参数键\n        in: query\n        name: key\n        required: true\n        type: string\n      - description: 参数名称\n        in: query\n        name: name\n        required: true\n        type: string\n      - description: 更新时间\n        in: query\n        name: updatedAt\n        type: string\n      - description: 参数值\n        in: query\n        name: value\n        required: true\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 查询成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/system.SysParams'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 用id查询参数\n      tags:\n      - SysParams\n  /sysParams/getSysParam:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - description: key\n        in: query\n        name: key\n        required: true\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/system.SysParams'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 根据key获取参数value\n      tags:\n      - SysParams\n  /sysParams/getSysParamsList:\n    get:\n      consumes:\n      - application/json\n      parameters:\n      - in: query\n        name: endCreatedAt\n        type: string\n      - in: query\n        name: key\n        type: string\n      - description: 关键字\n        in: query\n        name: keyword\n        type: string\n      - in: query\n        name: name\n        type: string\n      - description: 页码\n        in: query\n        name: page\n        type: integer\n      - description: 每页大小\n        in: query\n        name: pageSize\n        type: integer\n      - in: query\n        name: startCreatedAt\n        type: string\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取参数列表\n      tags:\n      - SysParams\n  /sysParams/updateSysParams:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 更新参数\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysParams'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 更新成功\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更新参数\n      tags:\n      - SysParams\n  /system/getServerInfo:\n    post:\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取服务器信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取服务器信息\n      tags:\n      - System\n  /system/getSystemConfig:\n    post:\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取配置文件内容,返回包括系统配置\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysConfigResponse'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取配置文件内容\n      tags:\n      - System\n  /system/reloadSystem:\n    post:\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 重启系统\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 重启系统\n      tags:\n      - System\n  /system/setSystemConfig:\n    post:\n      parameters:\n      - description: 设置配置文件内容\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.System'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置配置文件内容\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置配置文件内容\n      tags:\n      - System\n  /user/SetSelfInfo:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: ID, 用户名, 昵称, 头像链接\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysUser'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置用户信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置用户信息\n      tags:\n      - SysUser\n  /user/SetSelfSetting:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: 用户配置数据\n        in: body\n        name: data\n        required: true\n        schema:\n          additionalProperties: true\n          type: object\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置用户配置\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置用户配置\n      tags:\n      - SysUser\n  /user/admin_register:\n    post:\n      parameters:\n      - description: 用户名, 昵称, 密码, 角色ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.Register'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 用户注册账号,返回包括用户信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.SysUserResponse'\n                msg:\n                  type: string\n              type: object\n      summary: 用户注册账号\n      tags:\n      - SysUser\n  /user/changePassword:\n    post:\n      parameters:\n      - description: 用户名, 原密码, 新密码\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.ChangePasswordReq'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 用户修改密码\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 用户修改密码\n      tags:\n      - SysUser\n  /user/deleteUser:\n    delete:\n      consumes:\n      - application/json\n      parameters:\n      - description: 用户ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetById'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 删除用户\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 删除用户\n      tags:\n      - SysUser\n  /user/getUserInfo:\n    get:\n      consumes:\n      - application/json\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 获取用户信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 获取用户信息\n      tags:\n      - SysUser\n  /user/getUserList:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 页码, 每页大小\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.GetUserList'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 分页获取用户列表,返回包括列表,总数,页码,每页数量\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  $ref: '#/definitions/response.PageResult'\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 分页获取用户列表\n      tags:\n      - SysUser\n  /user/resetPassword:\n    post:\n      parameters:\n      - description: ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysUser'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 重置用户密码\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 重置用户密码\n      tags:\n      - SysUser\n  /user/setUserAuthorities:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 用户UUID, 角色ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SetUserAuthorities'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置用户权限\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置用户权限\n      tags:\n      - SysUser\n  /user/setUserAuthority:\n    post:\n      consumes:\n      - application/json\n      parameters:\n      - description: 用户UUID, 角色ID\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/request.SetUserAuth'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置用户权限\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 更改用户权限\n      tags:\n      - SysUser\n  /user/setUserInfo:\n    put:\n      consumes:\n      - application/json\n      parameters:\n      - description: ID, 用户名, 昵称, 头像链接\n        in: body\n        name: data\n        required: true\n        schema:\n          $ref: '#/definitions/system.SysUser'\n      produces:\n      - application/json\n      responses:\n        \"200\":\n          description: 设置用户信息\n          schema:\n            allOf:\n            - $ref: '#/definitions/response.Response'\n            - properties:\n                data:\n                  additionalProperties: true\n                  type: object\n                msg:\n                  type: string\n              type: object\n      security:\n      - ApiKeyAuth: []\n      summary: 设置用户信息\n      tags:\n      - SysUser\nsecurityDefinitions:\n  ApiKeyAuth:\n    in: header\n    name: x-token\n    type: apiKey\nswagger: \"2.0\"\ntags:\n- name: Base\n- description: 用户\n  name: SysUser\n"
  },
  {
    "path": "server/global/global.go",
    "content": "package global\n\nimport (\n\t\"fmt\"\n\t\"github.com/mark3labs/mcp-go/server\"\n\t\"sync\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/qiniu/qmgo\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/timer\"\n\t\"github.com/songzhibin97/gkit/cache/local_cache\"\n\n\t\"golang.org/x/sync/singleflight\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\n\t\"github.com/redis/go-redis/v9\"\n\t\"github.com/spf13/viper\"\n\t\"gorm.io/gorm\"\n)\n\nvar (\n\tGVA_DB        *gorm.DB\n\tGVA_DBList    map[string]*gorm.DB\n\tGVA_REDIS     redis.UniversalClient\n\tGVA_REDISList map[string]redis.UniversalClient\n\tGVA_MONGO     *qmgo.QmgoClient\n\tGVA_CONFIG    config.Server\n\tGVA_VP        *viper.Viper\n\t// GVA_LOG    *oplogging.Logger\n\tGVA_LOG                 *zap.Logger\n\tGVA_Timer               timer.Timer = timer.NewTimerTask()\n\tGVA_Concurrency_Control             = &singleflight.Group{}\n\tGVA_ROUTERS             gin.RoutesInfo\n\tGVA_ACTIVE_DBNAME       *string\n\tGVA_MCP_SERVER          *server.MCPServer\n\tBlackCache              local_cache.Cache\n\tlock                    sync.RWMutex\n)\n\n// GetGlobalDBByDBName 通过名称获取db list中的db\nfunc GetGlobalDBByDBName(dbname string) *gorm.DB {\n\tlock.RLock()\n\tdefer lock.RUnlock()\n\treturn GVA_DBList[dbname]\n}\n\n// MustGetGlobalDBByDBName 通过名称获取db 如果不存在则panic\nfunc MustGetGlobalDBByDBName(dbname string) *gorm.DB {\n\tlock.RLock()\n\tdefer lock.RUnlock()\n\tdb, ok := GVA_DBList[dbname]\n\tif !ok || db == nil {\n\t\tpanic(\"db no init\")\n\t}\n\treturn db\n}\n\nfunc GetRedis(name string) redis.UniversalClient {\n\tredis, ok := GVA_REDISList[name]\n\tif !ok || redis == nil {\n\t\tpanic(fmt.Sprintf(\"redis `%s` no init\", name))\n\t}\n\treturn redis\n}\n"
  },
  {
    "path": "server/global/model.go",
    "content": "package global\n\nimport (\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n)\n\ntype GVA_MODEL struct {\n\tID        uint           `gorm:\"primarykey\" json:\"ID\"` // 主键ID\n\tCreatedAt time.Time      // 创建时间\n\tUpdatedAt time.Time      // 更新时间\n\tDeletedAt gorm.DeletedAt `gorm:\"index\" json:\"-\"` // 删除时间\n}\n"
  },
  {
    "path": "server/global/version.go",
    "content": "package global\n\n// Version 版本信息\n// 目前只有Version正式使用 其余为预留\nconst (\n\t// Version 当前版本号\n\tVersion = \"v2.9.0\"\n\t// AppName 应用名称\n\tAppName = \"Gin-Vue-Admin\"\n\t// Description 应用描述\n\tDescription = \"使用gin+vue进行极速开发的全栈开发基础平台\"\n)\n"
  },
  {
    "path": "server/go.mod",
    "content": "module github.com/flipped-aurora/gin-vue-admin/server\n\ngo 1.24.0\n\ntoolchain go1.24.2\n\nrequire (\n\tgithub.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible\n\tgithub.com/aws/aws-sdk-go-v2 v1.41.2\n\tgithub.com/aws/aws-sdk-go-v2/config v1.32.10\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.19.10\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.96.1\n\tgithub.com/casbin/casbin/v2 v2.103.0\n\tgithub.com/casbin/gorm-adapter/v3 v3.32.0\n\tgithub.com/dzwvip/gorm-oracle v0.1.2\n\tgithub.com/fsnotify/fsnotify v1.8.0\n\tgithub.com/gin-gonic/gin v1.10.0\n\tgithub.com/glebarez/sqlite v1.11.0\n\tgithub.com/go-sql-driver/mysql v1.8.1\n\tgithub.com/goccy/go-json v0.10.4\n\tgithub.com/golang-jwt/jwt/v5 v5.2.2\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/gookit/color v1.5.4\n\tgithub.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible\n\tgithub.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible\n\tgithub.com/mark3labs/mcp-go v0.41.1\n\tgithub.com/mholt/archives v0.1.1\n\tgithub.com/minio/minio-go/v7 v7.0.84\n\tgithub.com/mojocn/base64Captcha v1.3.8\n\tgithub.com/otiai10/copy v1.14.1\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/qiniu/go-sdk/v7 v7.25.2\n\tgithub.com/qiniu/qmgo v1.1.9\n\tgithub.com/redis/go-redis/v9 v9.7.0\n\tgithub.com/robfig/cron/v3 v3.0.1\n\tgithub.com/shirou/gopsutil/v3 v3.24.5\n\tgithub.com/songzhibin97/gkit v1.2.13\n\tgithub.com/spf13/viper v1.19.0\n\tgithub.com/stretchr/testify v1.10.0\n\tgithub.com/swaggo/files v1.0.1\n\tgithub.com/swaggo/gin-swagger v1.6.0\n\tgithub.com/swaggo/swag v1.16.4\n\tgithub.com/tencentyun/cos-go-sdk-v5 v0.7.60\n\tgithub.com/unrolled/secure v1.17.0\n\tgithub.com/xuri/excelize/v2 v2.9.0\n\tgo.mongodb.org/mongo-driver v1.17.2\n\tgo.uber.org/automaxprocs v1.6.0\n\tgo.uber.org/zap v1.27.0\n\tgolang.org/x/crypto v0.37.0\n\tgolang.org/x/sync v0.13.0\n\tgolang.org/x/text v0.24.0\n\tgopkg.in/yaml.v3 v3.0.1\n\tgorm.io/datatypes v1.2.5\n\tgorm.io/driver/mysql v1.5.7\n\tgorm.io/driver/postgres v1.5.11\n\tgorm.io/driver/sqlserver v1.5.4\n\tgorm.io/gen v0.3.26\n\tgorm.io/gorm v1.25.12\n)\n\nrequire (\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/BurntSushi/toml v1.4.0 // indirect\n\tgithub.com/KyleBanks/depth v1.2.1 // indirect\n\tgithub.com/STARRY-S/zip v0.2.1 // indirect\n\tgithub.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect\n\tgithub.com/andybalholm/brotli v1.1.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect\n\tgithub.com/aws/smithy-go v1.24.1 // indirect\n\tgithub.com/bahlo/generic-list-go v0.2.0 // indirect\n\tgithub.com/bmatcuk/doublestar/v4 v4.8.0 // indirect\n\tgithub.com/bodgit/plumbing v1.3.0 // indirect\n\tgithub.com/bodgit/sevenzip v1.6.0 // indirect\n\tgithub.com/bodgit/windows v1.0.1 // indirect\n\tgithub.com/buger/jsonparser v1.1.1 // indirect\n\tgithub.com/bytedance/sonic v1.12.7 // indirect\n\tgithub.com/bytedance/sonic/loader v0.2.3 // indirect\n\tgithub.com/casbin/govaluate v1.3.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/clbanning/mxj v1.8.4 // indirect\n\tgithub.com/cloudwego/base64x v0.1.5 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/emirpasic/gods v1.12.0 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.8 // indirect\n\tgithub.com/gammazero/toposort v0.1.1 // indirect\n\tgithub.com/gin-contrib/sse v1.0.0 // indirect\n\tgithub.com/glebarez/go-sqlite v1.22.0 // indirect\n\tgithub.com/go-ini/ini v1.67.0 // indirect\n\tgithub.com/go-ole/go-ole v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/spec v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/go-playground/validator/v10 v10.24.0 // indirect\n\tgithub.com/gofrs/flock v0.12.1 // indirect\n\tgithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect\n\tgithub.com/golang-sql/sqlexp v0.1.0 // indirect\n\tgithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect\n\tgithub.com/golang/snappy v0.0.4 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/invopop/jsonschema v0.13.0 // indirect\n\tgithub.com/jackc/pgpassfile v1.0.0 // indirect\n\tgithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect\n\tgithub.com/jackc/pgx/v5 v5.7.2 // indirect\n\tgithub.com/jackc/puddle/v2 v2.2.2 // indirect\n\tgithub.com/jinzhu/inflection v1.0.0 // indirect\n\tgithub.com/jinzhu/now v1.1.5 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.9 // indirect\n\tgithub.com/klauspost/pgzip v1.2.6 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect\n\tgithub.com/magiconair/properties v1.8.9 // indirect\n\tgithub.com/mailru/easyjson v0.9.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/microsoft/go-mssqldb v1.8.0 // indirect\n\tgithub.com/minio/md5-simd v1.1.2 // indirect\n\tgithub.com/minio/minlz v1.0.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect\n\tgithub.com/montanaflynn/stats v0.7.1 // indirect\n\tgithub.com/mozillazg/go-httpheader v0.4.0 // indirect\n\tgithub.com/ncruces/go-strftime v0.1.9 // indirect\n\tgithub.com/nwaples/rardecode/v2 v2.1.0 // indirect\n\tgithub.com/otiai10/mint v1.6.3 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.3 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.22 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect\n\tgithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect\n\tgithub.com/richardlehane/mscfb v1.0.4 // indirect\n\tgithub.com/richardlehane/msoleps v1.0.4 // indirect\n\tgithub.com/rs/xid v1.6.0 // indirect\n\tgithub.com/sagikazarmark/locafero v0.7.0 // indirect\n\tgithub.com/sagikazarmark/slog-shim v0.1.0 // indirect\n\tgithub.com/shoenig/go-m1cpu v0.1.6 // indirect\n\tgithub.com/sijms/go-ora/v2 v2.7.17 // indirect\n\tgithub.com/sorairolake/lzip-go v0.3.5 // indirect\n\tgithub.com/sourcegraph/conc v0.3.0 // indirect\n\tgithub.com/spf13/afero v1.12.0 // indirect\n\tgithub.com/spf13/cast v1.7.1 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/therootcompany/xz v1.0.1 // indirect\n\tgithub.com/thoas/go-funk v0.7.0 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.14 // indirect\n\tgithub.com/tklauser/numcpus v0.9.0 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/ugorji/go/codec v1.2.12 // indirect\n\tgithub.com/ulikunitz/xz v0.5.12 // indirect\n\tgithub.com/wk8/go-ordered-map/v2 v2.1.8 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 // indirect\n\tgithub.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71 // indirect\n\tgithub.com/yosida95/uritemplate/v3 v3.0.2 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.4 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo4.org v0.0.0-20230225012048-214862532bf5 // indirect\n\tgolang.org/x/arch v0.13.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect\n\tgolang.org/x/image v0.23.0 // indirect\n\tgolang.org/x/mod v0.22.0 // indirect\n\tgolang.org/x/net v0.35.0 // indirect\n\tgolang.org/x/sys v0.32.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.29.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgorm.io/hints v1.1.2 // indirect\n\tgorm.io/plugin/dbresolver v1.5.3 // indirect\n\tmodernc.org/fileutil v1.3.0 // indirect\n\tmodernc.org/libc v1.61.9 // indirect\n\tmodernc.org/mathutil v1.7.1 // indirect\n\tmodernc.org/memory v1.8.2 // indirect\n\tmodernc.org/sqlite v1.34.5 // indirect\n)\n"
  },
  {
    "path": "server/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=\ngithub.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=\ngithub.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=\ngithub.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=\ngithub.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=\ngithub.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=\ngithub.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=\ngithub.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=\ngithub.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=\ngithub.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=\ngithub.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=\ngithub.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=\ngithub.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3 h1:+mQ8NQBh7B7c2FBtppRnwkrmuwFON1XQQ+5yblomZKk=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3/go.mod h1:u67RKh3BRmS4FYLH+rN3N4T5fqpd9m2ttAwBJYEdosU=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 h1:IJRzQTvdpjHRPItx9gzNcz7Y1F+xqAR+xiy9rr5ZYl8=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.96.1 h1:giB30dEeoar5bgDnkE0q+z7cFjcHaCjulpmPVmuKR84=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.96.1/go.mod h1:071TH4M3botFLWDbzQLfBR7tXYi7Fs2RsXSiH7nlUlY=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=\ngithub.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=\ngithub.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=\ngithub.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=\ngithub.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=\ngithub.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=\ngithub.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=\ngithub.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=\ngithub.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=\ngithub.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=\ngithub.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=\ngithub.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=\ngithub.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=\ngithub.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=\ngithub.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=\ngithub.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=\ngithub.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=\ngithub.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=\ngithub.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=\ngithub.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=\ngithub.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=\ngithub.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=\ngithub.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=\ngithub.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=\ngithub.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=\ngithub.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus=\ngithub.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI=\ngithub.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=\ngithub.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=\ngithub.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=\ngithub.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=\ngithub.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=\ngithub.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=\ngithub.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/dzwvip/gorm-oracle v0.1.2 h1:811aFDY7oDfKWHc0Z0lHdXzzr89EmKBSwc/jLJ8GU5g=\ngithub.com/dzwvip/gorm-oracle v0.1.2/go.mod h1:TbF7idnO9UgGpJ0qJpDZby1/wGquzP5GYof88ScBITE=\ngithub.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=\ngithub.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=\ngithub.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=\ngithub.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=\ngithub.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=\ngithub.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=\ngithub.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=\ngithub.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=\ngithub.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=\ngithub.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=\ngithub.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=\ngithub.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=\ngithub.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=\ngithub.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=\ngithub.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=\ngithub.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=\ngithub.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=\ngithub.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=\ngithub.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=\ngithub.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=\ngithub.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=\ngithub.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=\ngithub.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=\ngithub.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=\ngithub.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=\ngithub.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=\ngithub.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=\ngithub.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=\ngithub.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=\ngithub.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=\ngithub.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=\ngithub.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=\ngithub.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=\ngithub.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ4fSNB6mMRuYNvFWou7BZs6SZB925hPrnk=\ngithub.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=\ngithub.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=\ngithub.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=\ngithub.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=\ngithub.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=\ngithub.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=\ngithub.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=\ngithub.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=\ngithub.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=\ngithub.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=\ngithub.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=\ngithub.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=\ngithub.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=\ngithub.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=\ngithub.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=\ngithub.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=\ngithub.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=\ngithub.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA=\ngithub.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=\ngithub.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=\ngithub.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY=\ngithub.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I=\ngithub.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=\ngithub.com/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw=\ngithub.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo=\ngithub.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=\ngithub.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=\ngithub.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=\ngithub.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=\ngithub.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=\ngithub.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=\ngithub.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=\ngithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=\ngithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=\ngithub.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg=\ngithub.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4=\ngithub.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=\ngithub.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=\ngithub.com/mozillazg/go-httpheader v0.4.0 h1:aBn6aRXtFzyDLZ4VIRLsZbbJloagQfMnCiYgOq6hK4w=\ngithub.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA=\ngithub.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=\ngithub.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=\ngithub.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=\ngithub.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=\ngithub.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=\ngithub.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=\ngithub.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=\ngithub.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=\ngithub.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=\ngithub.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=\ngithub.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=\ngithub.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=\ngithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=\ngithub.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=\ngithub.com/qiniu/go-sdk/v7 v7.25.2 h1:URwgZpxySdiwu2yQpHk93X4LXWHyFRp1x3Vmlk/YWvo=\ngithub.com/qiniu/go-sdk/v7 v7.25.2/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o=\ngithub.com/qiniu/qmgo v1.1.9 h1:3G3h9RLyjIUW9YSAQEPP2WqqNnboZ2Z/zO3mugjVb3E=\ngithub.com/qiniu/qmgo v1.1.9/go.mod h1:aba4tNSlMWrwUhe7RdILfwBRIgvBujt1y10X+T1YZSI=\ngithub.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=\ngithub.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=\ngithub.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=\ngithub.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=\ngithub.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=\ngithub.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=\ngithub.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=\ngithub.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=\ngithub.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=\ngithub.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=\ngithub.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=\ngithub.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=\ngithub.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=\ngithub.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=\ngithub.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=\ngithub.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=\ngithub.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=\ngithub.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=\ngithub.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=\ngithub.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=\ngithub.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=\ngithub.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=\ngithub.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=\ngithub.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=\ngithub.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=\ngithub.com/sijms/go-ora/v2 v2.7.17 h1:M/pYIqjaMUeBxyzOWp2oj4ntF6fHSBloJWGNH9vbmsU=\ngithub.com/sijms/go-ora/v2 v2.7.17/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=\ngithub.com/songzhibin97/gkit v1.2.13 h1:paY0XJkdRuy9/8k9nTnbdrzo8pC22jIIFldUkOQv5nU=\ngithub.com/songzhibin97/gkit v1.2.13/go.mod h1:38CreNR27eTGaG1UMGihrXqI4xc3nGfYxLVKKVx6Ngg=\ngithub.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=\ngithub.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=\ngithub.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=\ngithub.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=\ngithub.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=\ngithub.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=\ngithub.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=\ngithub.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=\ngithub.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=\ngithub.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=\ngithub.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=\ngithub.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=\ngithub.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=\ngithub.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=\ngithub.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=\ngithub.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=\ngithub.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o=\ngithub.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=\ngithub.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=\ngithub.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=\ngithub.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=\ngithub.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=\ngithub.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=\ngithub.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=\ngithub.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=\ngithub.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=\ngithub.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=\ngithub.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=\ngithub.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=\ngithub.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=\ngithub.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=\ngithub.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6 h1:8m6DWBG+dlFNbx5ynvrE7NgI+Y7OlZVMVTpayoW+rCc=\ngithub.com/xuri/efp v0.0.0-20241211021726-c4e992084aa6/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=\ngithub.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=\ngithub.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=\ngithub.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71 h1:hOh7aVDrvGJRxzXrQbDY8E+02oaI//5cHL+97oYpEPw=\ngithub.com/xuri/nfp v0.0.0-20250111060730-82a408b9aa71/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngithub.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=\ngithub.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=\ngithub.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngo.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=\ngo.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=\ngo.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=\ngo4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=\ngolang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=\ngolang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=\ngolang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=\ngolang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=\ngolang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=\ngolang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=\ngolang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=\ngolang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=\ngolang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=\ngolang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=\ngolang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=\ngolang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=\ngolang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=\ngolang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngolang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=\ngolang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=\ngolang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I=\ngorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=\ngorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=\ngorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=\ngorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=\ngorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=\ngorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=\ngorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=\ngorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=\ngorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=\ngorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY=\ngorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE=\ngorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=\ngorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=\ngorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=\ngorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=\ngorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=\ngorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=\ngorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=\ngorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=\ngorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=\ngorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=\ngorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nmodernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=\nmodernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=\nmodernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00=\nmodernc.org/ccgo/v4 v4.23.13/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0=\nmodernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=\nmodernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=\nmodernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=\nmodernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8=\nmodernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=\nmodernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM=\nmodernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk=\nmodernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=\nmodernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=\nmodernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=\nmodernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=\nmodernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=\nmodernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=\nmodernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=\nmodernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=\nmodernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=\nmodernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=\nmodernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=\nmodernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=\nmodernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=\nmodernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nnullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "server/initialize/db_list.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"gorm.io/gorm\"\n)\n\nconst sys = \"system\"\n\nfunc DBList() {\n\tdbMap := make(map[string]*gorm.DB)\n\tfor _, info := range global.GVA_CONFIG.DBList {\n\t\tif info.Disable {\n\t\t\tcontinue\n\t\t}\n\t\tswitch info.Type {\n\t\tcase \"mysql\":\n\t\t\tdbMap[info.AliasName] = GormMysqlByConfig(config.Mysql{GeneralDB: info.GeneralDB})\n\t\tcase \"mssql\":\n\t\t\tdbMap[info.AliasName] = GormMssqlByConfig(config.Mssql{GeneralDB: info.GeneralDB})\n\t\tcase \"pgsql\":\n\t\t\tdbMap[info.AliasName] = GormPgSqlByConfig(config.Pgsql{GeneralDB: info.GeneralDB})\n\t\tcase \"oracle\":\n\t\t\tdbMap[info.AliasName] = GormOracleByConfig(config.Oracle{GeneralDB: info.GeneralDB})\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t}\n\t// 做特殊判断,是否有迁移\n\t// 适配低版本迁移多数据库版本\n\tif sysDB, ok := dbMap[sys]; ok {\n\t\tglobal.GVA_DB = sysDB\n\t}\n\tglobal.GVA_DBList = dbMap\n}\n"
  },
  {
    "path": "server/initialize/ensure_tables.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tadapter \"github.com/casbin/gorm-adapter/v3\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderEnsureTables = system.InitOrderExternal - 1\n\ntype ensureTables struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderEnsureTables, &ensureTables{})\n}\n\nfunc (e *ensureTables) InitializerName() string {\n\treturn \"ensure_tables_created\"\n}\nfunc (e *ensureTables) InitializeData(ctx context.Context) (next context.Context, err error) {\n\treturn ctx, nil\n}\n\nfunc (e *ensureTables) DataInserted(ctx context.Context) bool {\n\treturn true\n}\n\nfunc (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\ttables := []interface{}{\n\t\tsysModel.SysApi{},\n\t\tsysModel.SysUser{},\n\t\tsysModel.SysBaseMenu{},\n\t\tsysModel.SysAuthority{},\n\t\tsysModel.JwtBlacklist{},\n\t\tsysModel.SysDictionary{},\n\t\tsysModel.SysAutoCodeHistory{},\n\t\tsysModel.SysOperationRecord{},\n\t\tsysModel.SysDictionaryDetail{},\n\t\tsysModel.SysBaseMenuParameter{},\n\t\tsysModel.SysBaseMenuBtn{},\n\t\tsysModel.SysAuthorityBtn{},\n\t\tsysModel.SysAutoCodePackage{},\n\t\tsysModel.SysExportTemplate{},\n\t\tsysModel.Condition{},\n\t\tsysModel.JoinTemplate{},\n\t\tsysModel.SysParams{},\n\t\tsysModel.SysVersion{},\n\t\tsysModel.SysError{},\n\t\tsysModel.SysLoginLog{},\n\t\tsysModel.SysApiToken{},\n\t\tadapter.CasbinRule{},\n\n\t\texample.ExaFile{},\n\t\texample.ExaCustomer{},\n\t\texample.ExaFileChunk{},\n\t\texample.ExaFileUploadAndDownload{},\n\t\texample.ExaAttachmentCategory{},\n\n\t\tmodel.Info{},\n\t}\n\tfor _, t := range tables {\n\t\t_ = db.AutoMigrate(&t)\n\t\t// 视图 authority_menu 会被当成表来创建，引发冲突错误（更新版本的gorm似乎不会）\n\t\t// 由于 AutoMigrate() 基本无需考虑错误，因此显式忽略\n\t}\n\treturn ctx, nil\n}\n\nfunc (e *ensureTables) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\ttables := []interface{}{\n\t\tsysModel.SysApi{},\n\t\tsysModel.SysUser{},\n\t\tsysModel.SysBaseMenu{},\n\t\tsysModel.SysAuthority{},\n\t\tsysModel.JwtBlacklist{},\n\t\tsysModel.SysDictionary{},\n\t\tsysModel.SysAutoCodeHistory{},\n\t\tsysModel.SysOperationRecord{},\n\t\tsysModel.SysDictionaryDetail{},\n\t\tsysModel.SysBaseMenuParameter{},\n\t\tsysModel.SysBaseMenuBtn{},\n\t\tsysModel.SysAuthorityBtn{},\n\t\tsysModel.SysAutoCodePackage{},\n\t\tsysModel.SysExportTemplate{},\n\t\tsysModel.Condition{},\n\t\tsysModel.JoinTemplate{},\n\n\t\tadapter.CasbinRule{},\n\n\t\texample.ExaFile{},\n\t\texample.ExaCustomer{},\n\t\texample.ExaFileChunk{},\n\t\texample.ExaFileUploadAndDownload{},\n\t\texample.ExaAttachmentCategory{},\n\n\t\tmodel.Info{},\n\t}\n\tyes := true\n\tfor _, t := range tables {\n\t\tyes = yes && db.Migrator().HasTable(t)\n\t}\n\treturn yes\n}\n"
  },
  {
    "path": "server/initialize/gorm.go",
    "content": "package initialize\n\nimport (\n\t\"os\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\n\t\"go.uber.org/zap\"\n\t\"gorm.io/gorm\"\n)\n\nfunc Gorm() *gorm.DB {\n\tswitch global.GVA_CONFIG.System.DbType {\n\tcase \"mysql\":\n\t\tglobal.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mysql.Dbname\n\t\treturn GormMysql()\n\tcase \"pgsql\":\n\t\tglobal.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Pgsql.Dbname\n\t\treturn GormPgSql()\n\tcase \"oracle\":\n\t\tglobal.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Oracle.Dbname\n\t\treturn GormOracle()\n\tcase \"mssql\":\n\t\tglobal.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mssql.Dbname\n\t\treturn GormMssql()\n\tcase \"sqlite\":\n\t\tglobal.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Sqlite.Dbname\n\t\treturn GormSqlite()\n\tdefault:\n\t\tglobal.GVA_ACTIVE_DBNAME = &global.GVA_CONFIG.Mysql.Dbname\n\t\treturn GormMysql()\n\t}\n}\n\nfunc RegisterTables() {\n\tif global.GVA_CONFIG.System.DisableAutoMigrate {\n\t\tglobal.GVA_LOG.Info(\"auto-migrate is disabled, skipping table registration\")\n\t\treturn\n\t}\n\n\tdb := global.GVA_DB\n\terr := db.AutoMigrate(\n\n\t\tsystem.SysApi{},\n\t\tsystem.SysIgnoreApi{},\n\t\tsystem.SysUser{},\n\t\tsystem.SysBaseMenu{},\n\t\tsystem.JwtBlacklist{},\n\t\tsystem.SysAuthority{},\n\t\tsystem.SysDictionary{},\n\t\tsystem.SysOperationRecord{},\n\t\tsystem.SysAutoCodeHistory{},\n\t\tsystem.SysDictionaryDetail{},\n\t\tsystem.SysBaseMenuParameter{},\n\t\tsystem.SysBaseMenuBtn{},\n\t\tsystem.SysAuthorityBtn{},\n\t\tsystem.SysAutoCodePackage{},\n\t\tsystem.SysExportTemplate{},\n\t\tsystem.Condition{},\n\t\tsystem.JoinTemplate{},\n\t\tsystem.SysParams{},\n\t\tsystem.SysVersion{},\n\t\tsystem.SysError{},\n\t\tsystem.SysApiToken{},\n\t\tsystem.SysLoginLog{},\n\n\t\texample.ExaFile{},\n\t\texample.ExaCustomer{},\n\t\texample.ExaFileChunk{},\n\t\texample.ExaFileUploadAndDownload{},\n\t\texample.ExaAttachmentCategory{},\n\t)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"register table failed\", zap.Error(err))\n\t\tos.Exit(0)\n\t}\n\n\terr = bizModel()\n\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"register biz_table failed\", zap.Error(err))\n\t\tos.Exit(0)\n\t}\n\tglobal.GVA_LOG.Info(\"register table success\")\n}\n"
  },
  {
    "path": "server/initialize/gorm_biz.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\nfunc bizModel() error {\n\tdb := global.GVA_DB\n\terr := db.AutoMigrate()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/initialize/gorm_mssql.go",
    "content": "package initialize\n\n/*\n * @Author: 逆光飞翔 191180776@qq.com\n * @Date: 2022-12-08 17:25:49\n * @LastEditors: 逆光飞翔 191180776@qq.com\n * @LastEditTime: 2022-12-08 18:00:00\n * @FilePath: \\server\\initialize\\gorm_mssql.go\n * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE\n */\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal\"\n\t\"gorm.io/driver/sqlserver\"\n\t\"gorm.io/gorm\"\n)\n\n// GormMssql 初始化Mssql数据库\n// Author [LouisZhang](191180776@qq.com)\nfunc GormMssql() *gorm.DB {\n\tm := global.GVA_CONFIG.Mssql\n\tif m.Dbname == \"\" {\n\t\treturn nil\n\t}\n\tmssqlConfig := sqlserver.Config{\n\t\tDSN:               m.Dsn(), // DSN data source name\n\t\tDefaultStringSize: 191,     // string 类型字段的默认长度\n\t}\n\t// 数据库配置\n\tgeneral := m.GeneralDB\n\tif db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {\n\t\treturn nil\n\t} else {\n\t\tdb.InstanceSet(\"gorm:table_options\", \"ENGINE=\"+m.Engine)\n\t\tsqlDB, _ := db.DB()\n\t\tsqlDB.SetMaxIdleConns(m.MaxIdleConns)\n\t\tsqlDB.SetMaxOpenConns(m.MaxOpenConns)\n\t\treturn db\n\t}\n}\n\n// GormMssqlByConfig 初始化Mysql数据库用过传入配置\nfunc GormMssqlByConfig(m config.Mssql) *gorm.DB {\n\tif m.Dbname == \"\" {\n\t\treturn nil\n\t}\n\tmssqlConfig := sqlserver.Config{\n\t\tDSN:               m.Dsn(), // DSN data source name\n\t\tDefaultStringSize: 191,     // string 类型字段的默认长度\n\t}\n\t// 数据库配置\n\tgeneral := m.GeneralDB\n\tif db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {\n\t\tpanic(err)\n\t} else {\n\t\tdb.InstanceSet(\"gorm:table_options\", \"ENGINE=InnoDB\")\n\t\tsqlDB, _ := db.DB()\n\t\tsqlDB.SetMaxIdleConns(m.MaxIdleConns)\n\t\tsqlDB.SetMaxOpenConns(m.MaxOpenConns)\n\t\treturn db\n\t}\n}\n"
  },
  {
    "path": "server/initialize/gorm_mysql.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal\"\n\t_ \"github.com/go-sql-driver/mysql\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n)\n\n// GormMysql 初始化Mysql数据库\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [ByteZhou-2018](https://github.com/ByteZhou-2018)\nfunc GormMysql() *gorm.DB {\n\tm := global.GVA_CONFIG.Mysql\n\treturn initMysqlDatabase(m)\n}\n\n// GormMysqlByConfig 通过传入配置初始化Mysql数据库\nfunc GormMysqlByConfig(m config.Mysql) *gorm.DB {\n\treturn initMysqlDatabase(m)\n}\n\n// initMysqlDatabase 初始化Mysql数据库的辅助函数\nfunc initMysqlDatabase(m config.Mysql) *gorm.DB {\n\tif m.Dbname == \"\" {\n\t\treturn nil\n\t}\n\n\tmysqlConfig := mysql.Config{\n\t\tDSN:                       m.Dsn(), // DSN data source name\n\t\tDefaultStringSize:         191,     // string 类型字段的默认长度\n\t\tSkipInitializeWithVersion: false,   // 根据版本自动配置\n\t}\n\t// 数据库配置\n\tgeneral := m.GeneralDB\n\tif db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(general)); err != nil {\n\t\tpanic(err)\n\t} else {\n\t\tdb.InstanceSet(\"gorm:table_options\", \"ENGINE=\"+m.Engine)\n\t\tsqlDB, _ := db.DB()\n\t\tsqlDB.SetMaxIdleConns(m.MaxIdleConns)\n\t\tsqlDB.SetMaxOpenConns(m.MaxOpenConns)\n\t\treturn db\n\t}\n}\n"
  },
  {
    "path": "server/initialize/gorm_oracle.go",
    "content": "package initialize\n\nimport (\n\toracle \"github.com/dzwvip/gorm-oracle\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal\"\n\t\"gorm.io/gorm\"\n)\n\n// GormOracle 初始化oracle数据库\nfunc GormOracle() *gorm.DB {\n\tm := global.GVA_CONFIG.Oracle\n\treturn initOracleDatabase(m)\n}\n\n// GormOracleByConfig 初始化Oracle数据库用过传入配置\nfunc GormOracleByConfig(m config.Oracle) *gorm.DB {\n\treturn initOracleDatabase(m)\n}\n\n// initOracleDatabase 初始化Oracle数据库的辅助函数\nfunc initOracleDatabase(m config.Oracle) *gorm.DB {\n\tif m.Dbname == \"\" {\n\t\treturn nil\n\t}\n\t// 数据库配置\n\tgeneral := m.GeneralDB\n\tif db, err := gorm.Open(oracle.Open(m.Dsn()), internal.Gorm.Config(general)); err != nil {\n\t\tpanic(err)\n\t} else {\n\t\tsqlDB, _ := db.DB()\n\t\tsqlDB.SetMaxIdleConns(m.MaxIdleConns)\n\t\tsqlDB.SetMaxOpenConns(m.MaxOpenConns)\n\t\treturn db\n\t}\n}\n"
  },
  {
    "path": "server/initialize/gorm_pgsql.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal\"\n\t\"gorm.io/driver/postgres\"\n\t\"gorm.io/gorm\"\n)\n\n// GormPgSql 初始化 Postgresql 数据库\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc GormPgSql() *gorm.DB {\n\tp := global.GVA_CONFIG.Pgsql\n\treturn initPgSqlDatabase(p)\n}\n\n// GormPgSqlByConfig 初始化 Postgresql 数据库 通过指定参数\nfunc GormPgSqlByConfig(p config.Pgsql) *gorm.DB {\n\treturn initPgSqlDatabase(p)\n}\n\n// initPgSqlDatabase 初始化 Postgresql 数据库的辅助函数\nfunc initPgSqlDatabase(p config.Pgsql) *gorm.DB {\n\tif p.Dbname == \"\" {\n\t\treturn nil\n\t}\n\tpgsqlConfig := postgres.Config{\n\t\tDSN:                  p.Dsn(), // DSN data source name\n\t\tPreferSimpleProtocol: false,\n\t}\n\t// 数据库配置\n\tgeneral := p.GeneralDB\n\tif db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(general)); err != nil {\n\t\tpanic(err)\n\t} else {\n\t\tsqlDB, _ := db.DB()\n\t\tsqlDB.SetMaxIdleConns(p.MaxIdleConns)\n\t\tsqlDB.SetMaxOpenConns(p.MaxOpenConns)\n\t\treturn db\n\t}\n}\n"
  },
  {
    "path": "server/initialize/gorm_sqlite.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal\"\n\t\"github.com/glebarez/sqlite\"\n\t\"gorm.io/gorm\"\n)\n\n// GormSqlite 初始化Sqlite数据库\nfunc GormSqlite() *gorm.DB {\n\ts := global.GVA_CONFIG.Sqlite\n\treturn initSqliteDatabase(s)\n}\n\n// GormSqliteByConfig 初始化Sqlite数据库用过传入配置\nfunc GormSqliteByConfig(s config.Sqlite) *gorm.DB {\n\treturn initSqliteDatabase(s)\n}\n\n// initSqliteDatabase 初始化Sqlite数据库辅助函数\nfunc initSqliteDatabase(s config.Sqlite) *gorm.DB {\n\tif s.Dbname == \"\" {\n\t\treturn nil\n\t}\n\n\t// 数据库配置\n\tgeneral := s.GeneralDB\n\tif db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(general)); err != nil {\n\t\tpanic(err)\n\t} else {\n\t\tsqlDB, _ := db.DB()\n\t\tsqlDB.SetMaxIdleConns(s.MaxIdleConns)\n\t\tsqlDB.SetMaxOpenConns(s.MaxOpenConns)\n\t\treturn db\n\t}\n}\n"
  },
  {
    "path": "server/initialize/init.go",
    "content": "// 假设这是初始化逻辑的一部分\n\npackage initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n)\n\n// 初始化全局函数\nfunc SetupHandlers() {\n\t// 注册系统重载处理函数\n\tutils.GlobalSystemEvents.RegisterReloadHandler(func() error {\n\t\treturn Reload()\n\t})\n}\n"
  },
  {
    "path": "server/initialize/internal/gorm.go",
    "content": "package internal\n\nimport (\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/logger\"\n\t\"gorm.io/gorm/schema\"\n)\n\nvar Gorm = new(_gorm)\n\ntype _gorm struct{}\n\n// Config gorm 自定义配置\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (g *_gorm) Config(general config.GeneralDB) *gorm.Config {\n\treturn &gorm.Config{\n\t\tLogger: logger.New(NewWriter(general), logger.Config{\n\t\t\tSlowThreshold: 200 * time.Millisecond,\n\t\t\tLogLevel:      general.LogLevel(),\n\t\t\tColorful:      true,\n\t\t}),\n\t\tNamingStrategy: schema.NamingStrategy{\n\t\t\tTablePrefix:   general.Prefix,\n\t\t\tSingularTable: general.Singular,\n\t\t},\n\t\tDisableForeignKeyConstraintWhenMigrating: true,\n\t}\n}\n"
  },
  {
    "path": "server/initialize/internal/gorm_logger_writer.go",
    "content": "package internal\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"gorm.io/gorm/logger\"\n)\n\ntype Writer struct {\n\tconfig config.GeneralDB\n\twriter logger.Writer\n}\n\nfunc NewWriter(config config.GeneralDB) *Writer {\n\treturn &Writer{config: config}\n}\n\n// Printf 格式化打印日志\nfunc (c *Writer) Printf(message string, data ...any) {\n\n\t// 当有日志时候均需要输出到控制台\n\tfmt.Printf(message, data...)\n\n\t// 当开启了zap的情况，会打印到日志记录\n\tif c.config.LogZap {\n\t\tswitch c.config.LogLevel() {\n\t\tcase logger.Silent:\n\t\t\tglobal.GVA_LOG.Debug(fmt.Sprintf(message, data...))\n\t\tcase logger.Error:\n\t\t\tglobal.GVA_LOG.Error(fmt.Sprintf(message, data...))\n\t\tcase logger.Warn:\n\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(message, data...))\n\t\tcase logger.Info:\n\t\t\tglobal.GVA_LOG.Info(fmt.Sprintf(message, data...))\n\t\tdefault:\n\t\t\tglobal.GVA_LOG.Info(fmt.Sprintf(message, data...))\n\t\t}\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "server/initialize/internal/mongo.go",
    "content": "package internal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/qiniu/qmgo/options\"\n\t\"go.mongodb.org/mongo-driver/event\"\n\topt \"go.mongodb.org/mongo-driver/mongo/options\"\n\t\"go.uber.org/zap\"\n)\n\nvar Mongo = new(mongo)\n\ntype mongo struct{}\n\nfunc (m *mongo) GetClientOptions() []options.ClientOptions {\n\tcmdMonitor := &event.CommandMonitor{\n\t\tStarted: func(ctx context.Context, event *event.CommandStartedEvent) {\n\t\t\tzap.L().Info(fmt.Sprintf(\"[MongoDB][RequestID:%d][database:%s] %s\\n\", event.RequestID, event.DatabaseName, event.Command), zap.String(\"business\", \"mongo\"))\n\t\t},\n\t\tSucceeded: func(ctx context.Context, event *event.CommandSucceededEvent) {\n\t\t\tzap.L().Info(fmt.Sprintf(\"[MongoDB][RequestID:%d] [%s] %s\\n\", event.RequestID, event.Duration.String(), event.Reply), zap.String(\"business\", \"mongo\"))\n\t\t},\n\t\tFailed: func(ctx context.Context, event *event.CommandFailedEvent) {\n\t\t\tzap.L().Error(fmt.Sprintf(\"[MongoDB][RequestID:%d] [%s] %s\\n\", event.RequestID, event.Duration.String(), event.Failure), zap.String(\"business\", \"mongo\"))\n\t\t},\n\t}\n\treturn []options.ClientOptions{{ClientOptions: &opt.ClientOptions{Monitor: cmdMonitor}}}\n}\n"
  },
  {
    "path": "server/initialize/mcp.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tmcpTool \"github.com/flipped-aurora/gin-vue-admin/server/mcp\"\n\t\"github.com/mark3labs/mcp-go/server\"\n)\n\nfunc McpRun() *server.SSEServer {\n\tconfig := global.GVA_CONFIG.MCP\n\n\ts := server.NewMCPServer(\n\t\tconfig.Name,\n\t\tconfig.Version,\n\t)\n\n\tglobal.GVA_MCP_SERVER = s\n\n\tmcpTool.RegisterAllTools(s)\n\n\treturn server.NewSSEServer(s,\n\t\tserver.WithSSEEndpoint(config.SSEPath),\n\t\tserver.WithMessageEndpoint(config.MessagePath),\n\t\tserver.WithBaseURL(config.UrlPrefix))\n}\n"
  },
  {
    "path": "server/initialize/mongo.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/qiniu/qmgo\"\n\t\"github.com/qiniu/qmgo/options\"\n\t\"go.mongodb.org/mongo-driver/bson\"\n\toption \"go.mongodb.org/mongo-driver/mongo/options\"\n\t\"sort\"\n\t\"strings\"\n)\n\nvar Mongo = new(mongo)\n\ntype (\n\tmongo struct{}\n\tIndex struct {\n\t\tV    any      `bson:\"v\"`\n\t\tNs   any      `bson:\"ns\"`\n\t\tKey  []bson.E `bson:\"key\"`\n\t\tName string   `bson:\"name\"`\n\t}\n)\n\nfunc (m *mongo) Indexes(ctx context.Context) error {\n\t// 表名:索引列表 列: \"表名\": [][]string{{\"index1\", \"index2\"}}\n\tindexMap := map[string][][]string{}\n\tfor collection, indexes := range indexMap {\n\t\terr := m.CreateIndexes(ctx, collection, indexes)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *mongo) Initialization() error {\n\tvar opts []options.ClientOptions\n\tif global.GVA_CONFIG.Mongo.IsZap {\n\t\topts = internal.Mongo.GetClientOptions()\n\t}\n\tctx := context.Background()\n\tconfig := &qmgo.Config{\n\t\tUri:              global.GVA_CONFIG.Mongo.Uri(),\n\t\tColl:             global.GVA_CONFIG.Mongo.Coll,\n\t\tDatabase:         global.GVA_CONFIG.Mongo.Database,\n\t\tMinPoolSize:      &global.GVA_CONFIG.Mongo.MinPoolSize,\n\t\tMaxPoolSize:      &global.GVA_CONFIG.Mongo.MaxPoolSize,\n\t\tSocketTimeoutMS:  &global.GVA_CONFIG.Mongo.SocketTimeoutMs,\n\t\tConnectTimeoutMS: &global.GVA_CONFIG.Mongo.ConnectTimeoutMs,\n\t}\n\tif global.GVA_CONFIG.Mongo.Username != \"\" && global.GVA_CONFIG.Mongo.Password != \"\" {\n\t\tconfig.Auth = &qmgo.Credential{\n\t\t\tUsername:   global.GVA_CONFIG.Mongo.Username,\n\t\t\tPassword:   global.GVA_CONFIG.Mongo.Password,\n\t\t\tAuthSource: global.GVA_CONFIG.Mongo.AuthSource,\n\t\t}\n\t}\n\tclient, err := qmgo.Open(ctx, config, opts...)\n\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"链接mongodb数据库失败!\")\n\t}\n\tglobal.GVA_MONGO = client\n\terr = m.Indexes(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *mongo) CreateIndexes(ctx context.Context, name string, indexes [][]string) error {\n\tcollection, err := global.GVA_MONGO.Database.Collection(name).CloneCollection()\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"获取[%s]的表对象失败!\", name)\n\t}\n\tlist, err := collection.Indexes().List(ctx)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"获取[%s]的索引对象失败!\", name)\n\t}\n\tvar entities []Index\n\terr = list.All(ctx, &entities)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"获取[%s]的索引列表失败!\", name)\n\t}\n\tlength := len(indexes)\n\tindexMap1 := make(map[string][]string, length)\n\tfor i := 0; i < length; i++ {\n\t\tsort.Strings(indexes[i]) // 对索引key进行排序, 在使用bson.M搜索时, bson会自动按照key的字母顺序进行排序\n\t\tlength1 := len(indexes[i])\n\t\tkeys := make([]string, 0, length1)\n\t\tfor j := 0; j < length1; j++ {\n\t\t\tif indexes[i][i][0] == '-' {\n\t\t\t\tkeys = append(keys, indexes[i][j], \"-1\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeys = append(keys, indexes[i][j], \"1\")\n\t\t}\n\t\tkey := strings.Join(keys, \"_\")\n\t\t_, o1 := indexMap1[key]\n\t\tif o1 {\n\t\t\treturn errors.Errorf(\"索引[%s]重复!\", key)\n\t\t}\n\t\tindexMap1[key] = indexes[i]\n\t}\n\tlength = len(entities)\n\tindexMap2 := make(map[string]map[string]string, length)\n\tfor i := 0; i < length; i++ {\n\t\tv1, o1 := indexMap2[entities[i].Name]\n\t\tif !o1 {\n\t\t\tkeyLength := len(entities[i].Key)\n\t\t\tv1 = make(map[string]string, keyLength)\n\t\t\tfor j := 0; j < keyLength; j++ {\n\t\t\t\tv2, o2 := v1[entities[i].Key[j].Key]\n\t\t\t\tif !o2 {\n\t\t\t\t\tv1 = make(map[string]string)\n\t\t\t\t}\n\t\t\t\tv2 = entities[i].Key[j].Key\n\t\t\t\tv1[entities[i].Key[j].Key] = v2\n\t\t\t\tindexMap2[entities[i].Name] = v1\n\t\t\t}\n\t\t}\n\t}\n\tfor k1, v1 := range indexMap1 {\n\t\t_, o2 := indexMap2[k1]\n\t\tif o2 {\n\t\t\tcontinue\n\t\t} // 索引存在\n\t\tif len(fmt.Sprintf(\"%s.%s.$%s\", collection.Name(), name, v1)) > 127 {\n\t\t\terr = global.GVA_MONGO.Database.Collection(name).CreateOneIndex(ctx, options.IndexModel{\n\t\t\t\tKey:          v1,\n\t\t\t\tIndexOptions: option.Index().SetName(utils.MD5V([]byte(k1))),\n\t\t\t\t// IndexOptions: option.Index().SetName(utils.MD5V([]byte(k1))).SetExpireAfterSeconds(86400), // SetExpireAfterSeconds(86400) 设置索引过期时间, 86400 = 1天\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"创建索引[%s]失败!\", k1)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\terr = global.GVA_MONGO.Database.Collection(name).CreateOneIndex(ctx, options.IndexModel{\n\t\t\tKey:          v1,\n\t\t\tIndexOptions: option.Index().SetExpireAfterSeconds(86400),\n\t\t\t// IndexOptions: option.Index().SetName(utils.MD5V([]byte(k1))).SetExpireAfterSeconds(86400), // SetExpireAfterSeconds(86400) 设置索引过期时间(秒), 86400 = 1天\n\t\t})\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"创建索引[%s]失败!\", k1)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/initialize/other.go",
    "content": "package initialize\n\nimport (\n\t\"bufio\"\n\t\"github.com/songzhibin97/gkit/cache/local_cache\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n)\n\nfunc OtherInit() {\n\tdr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t_, err = utils.ParseDuration(global.GVA_CONFIG.JWT.BufferTime)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tglobal.BlackCache = local_cache.NewCache(\n\t\tlocal_cache.SetDefaultExpire(dr),\n\t)\n\tfile, err := os.Open(\"go.mod\")\n\tif err == nil && global.GVA_CONFIG.AutoCode.Module == \"\" {\n\t\tscanner := bufio.NewScanner(file)\n\t\tscanner.Scan()\n\t\tglobal.GVA_CONFIG.AutoCode.Module = strings.TrimPrefix(scanner.Text(), \"module \")\n\t}\n}\n"
  },
  {
    "path": "server/initialize/plugin.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc InstallPlugin(PrivateGroup *gin.RouterGroup, PublicRouter *gin.RouterGroup, engine *gin.Engine) {\n\tif global.GVA_DB == nil {\n\t\tglobal.GVA_LOG.Info(\"项目暂未初始化，无法安装插件，初始化后重启项目即可完成插件安装\")\n\t\treturn\n\t}\n\tbizPluginV1(PrivateGroup, PublicRouter)\n\tbizPluginV2(engine)\n}\n"
  },
  {
    "path": "server/initialize/plugin_biz_v1.go",
    "content": "package initialize\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc PluginInit(group *gin.RouterGroup, Plugin ...plugin.Plugin) {\n\tfor i := range Plugin {\n\t\tfmt.Println(Plugin[i].RouterPath(), \"注册开始!\")\n\t\tPluginGroup := group.Group(Plugin[i].RouterPath())\n\t\tPlugin[i].Register(PluginGroup)\n\t\tfmt.Println(Plugin[i].RouterPath(), \"注册成功!\")\n\t}\n}\n\nfunc bizPluginV1(group ...*gin.RouterGroup) {\n\tprivate := group[0]\n\tpublic := group[1]\n\t//  添加跟角色挂钩权限的插件 示例 本地示例模式于在线仓库模式注意上方的import 可以自行切换 效果相同\n\tPluginInit(private, email.CreateEmailPlug(\n\t\tglobal.GVA_CONFIG.Email.To,\n\t\tglobal.GVA_CONFIG.Email.From,\n\t\tglobal.GVA_CONFIG.Email.Host,\n\t\tglobal.GVA_CONFIG.Email.Secret,\n\t\tglobal.GVA_CONFIG.Email.Nickname,\n\t\tglobal.GVA_CONFIG.Email.Port,\n\t\tglobal.GVA_CONFIG.Email.IsSSL,\n\t\tglobal.GVA_CONFIG.Email.IsLoginAuth,\n\t))\n\tholder(public, private)\n}\n"
  },
  {
    "path": "server/initialize/plugin_biz_v2.go",
    "content": "package initialize\n\nimport (\n\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc PluginInitV2(group *gin.Engine, plugins ...plugin.Plugin) {\n\tfor i := 0; i < len(plugins); i++ {\n\t\tplugins[i].Register(group)\n\t}\n}\nfunc bizPluginV2(engine *gin.Engine) {\n\tPluginInitV2(engine, plugin.Registered()...)\n}\n"
  },
  {
    "path": "server/initialize/redis.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\n\t\"github.com/redis/go-redis/v9\"\n\t\"go.uber.org/zap\"\n)\n\nfunc initRedisClient(redisCfg config.Redis) (redis.UniversalClient, error) {\n\tvar client redis.UniversalClient\n\t// 使用集群模式\n\tif redisCfg.UseCluster {\n\t\tclient = redis.NewClusterClient(&redis.ClusterOptions{\n\t\t\tAddrs:    redisCfg.ClusterAddrs,\n\t\t\tPassword: redisCfg.Password,\n\t\t})\n\t} else {\n\t\t// 使用单例模式\n\t\tclient = redis.NewClient(&redis.Options{\n\t\t\tAddr:     redisCfg.Addr,\n\t\t\tPassword: redisCfg.Password,\n\t\t\tDB:       redisCfg.DB,\n\t\t})\n\t}\n\tpong, err := client.Ping(context.Background()).Result()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"redis connect ping failed, err:\", zap.String(\"name\", redisCfg.Name), zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\tglobal.GVA_LOG.Info(\"redis connect ping response:\", zap.String(\"name\", redisCfg.Name), zap.String(\"pong\", pong))\n\treturn client, nil\n}\n\nfunc Redis() {\n\tredisClient, err := initRedisClient(global.GVA_CONFIG.Redis)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tglobal.GVA_REDIS = redisClient\n}\n\nfunc RedisList() {\n\tredisMap := make(map[string]redis.UniversalClient)\n\n\tfor _, redisCfg := range global.GVA_CONFIG.RedisList {\n\t\tclient, err := initRedisClient(redisCfg)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tredisMap[redisCfg.Name] = client\n\t}\n\n\tglobal.GVA_REDISList = redisMap\n}\n"
  },
  {
    "path": "server/initialize/register_init.go",
    "content": "package initialize\n\nimport (\n\t_ \"github.com/flipped-aurora/gin-vue-admin/server/source/example\"\n\t_ \"github.com/flipped-aurora/gin-vue-admin/server/source/system\"\n)\n\nfunc init() {\n\t// do nothing,only import source package so that inits can be registered\n}\n"
  },
  {
    "path": "server/initialize/reload.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\n// Reload 优雅地重新加载系统配置\nfunc Reload() error {\n\tglobal.GVA_LOG.Info(\"正在重新加载系统配置...\")\n\n\t// 重新加载配置文件\n\tif err := global.GVA_VP.ReadInConfig(); err != nil {\n\t\tglobal.GVA_LOG.Error(\"重新读取配置文件失败!\", zap.Error(err))\n\t\treturn err\n\t}\n\n\t// 重新初始化数据库连接\n\tif global.GVA_DB != nil {\n\t\tdb, _ := global.GVA_DB.DB()\n\t\terr := db.Close()\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"关闭原数据库连接失败!\", zap.Error(err))\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// 重新建立数据库连接\n\tglobal.GVA_DB = Gorm()\n\n\t// 重新初始化其他配置\n\tOtherInit()\n\tDBList()\n\n\tif global.GVA_DB != nil {\n\t\t// 确保数据库表结构是最新的\n\t\tRegisterTables()\n\t}\n\n\t// 重新初始化定时任务\n\tTimer()\n\n\tglobal.GVA_LOG.Info(\"系统配置重新加载完成\")\n\treturn nil\n}\n"
  },
  {
    "path": "server/initialize/router.go",
    "content": "package initialize\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/docs\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/router\"\n\t\"github.com/gin-gonic/gin\"\n\tswaggerFiles \"github.com/swaggo/files\"\n\tginSwagger \"github.com/swaggo/gin-swagger\"\n)\n\ntype justFilesFilesystem struct {\n\tfs http.FileSystem\n}\n\nfunc (fs justFilesFilesystem) Open(name string) (http.File, error) {\n\tf, err := fs.fs.Open(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstat, err := f.Stat()\n\tif stat.IsDir() {\n\t\treturn nil, os.ErrPermission\n\t}\n\n\treturn f, nil\n}\n\n// 初始化总路由\n\nfunc Routers() *gin.Engine {\n\tRouter := gin.New()\n\t// 使用自定义的 Recovery 中间件，记录 panic 并入库\n\tRouter.Use(middleware.GinRecovery(true))\n\tif gin.Mode() == gin.DebugMode {\n\t\tRouter.Use(gin.Logger())\n\t}\n\n\tif !global.GVA_CONFIG.MCP.Separate {\n\n\t\tsseServer := McpRun()\n\n\t\t// 注册mcp服务\n\t\tRouter.GET(global.GVA_CONFIG.MCP.SSEPath, func(c *gin.Context) {\n\t\t\tsseServer.SSEHandler().ServeHTTP(c.Writer, c.Request)\n\t\t})\n\n\t\tRouter.POST(global.GVA_CONFIG.MCP.MessagePath, func(c *gin.Context) {\n\t\t\tsseServer.MessageHandler().ServeHTTP(c.Writer, c.Request)\n\t\t})\n\t}\n\n\tsystemRouter := router.RouterGroupApp.System\n\texampleRouter := router.RouterGroupApp.Example\n\t// 如果想要不使用nginx代理前端网页，可以修改 web/.env.production 下的\n\t// VUE_APP_BASE_API = /\n\t// VUE_APP_BASE_PATH = http://localhost\n\t// 然后执行打包命令 npm run build。在打开下面3行注释\n\t// Router.StaticFile(\"/favicon.ico\", \"./dist/favicon.ico\")\n\t// Router.Static(\"/assets\", \"./dist/assets\")   // dist里面的静态资源\n\t// Router.StaticFile(\"/\", \"./dist/index.html\") // 前端网页入口页面\n\n\tRouter.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls())  // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS(\"端口\",\"你的cre/pem文件\",\"你的key文件\")\n\t// 跨域，如需跨域可以打开下面的注释\n\t// Router.Use(middleware.Cors()) // 直接放行全部跨域请求\n\t// Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求\n\t// global.GVA_LOG.Info(\"use middleware cors\")\n\tdocs.SwaggerInfo.BasePath = global.GVA_CONFIG.System.RouterPrefix\n\tRouter.GET(global.GVA_CONFIG.System.RouterPrefix+\"/swagger/*any\", ginSwagger.WrapHandler(swaggerFiles.Handler))\n\tglobal.GVA_LOG.Info(\"register swagger handler\")\n\t// 方便统一添加路由组前缀 多服务器上线使用\n\n\tPublicGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)\n\tPrivateGroup := Router.Group(global.GVA_CONFIG.System.RouterPrefix)\n\n\tPrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())\n\n\t{\n\t\t// 健康监测\n\t\tPublicGroup.GET(\"/health\", func(c *gin.Context) {\n\t\t\tc.JSON(http.StatusOK, \"ok\")\n\t\t})\n\t}\n\t{\n\t\tsystemRouter.InitBaseRouter(PublicGroup) // 注册基础功能路由 不做鉴权\n\t\tsystemRouter.InitInitRouter(PublicGroup) // 自动初始化相关\n\t}\n\n\t{\n\t\tsystemRouter.InitApiRouter(PrivateGroup, PublicGroup)               // 注册功能api路由\n\t\tsystemRouter.InitJwtRouter(PrivateGroup)                            // jwt相关路由\n\t\tsystemRouter.InitUserRouter(PrivateGroup)                           // 注册用户路由\n\t\tsystemRouter.InitMenuRouter(PrivateGroup)                           // 注册menu路由\n\t\tsystemRouter.InitSystemRouter(PrivateGroup)                         // system相关路由\n\t\tsystemRouter.InitSysVersionRouter(PrivateGroup)                     // 发版相关路由\n\t\tsystemRouter.InitCasbinRouter(PrivateGroup)                         // 权限相关路由\n\t\tsystemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup)          // 创建自动化代码\n\t\tsystemRouter.InitAuthorityRouter(PrivateGroup)                      // 注册角色路由\n\t\tsystemRouter.InitSysDictionaryRouter(PrivateGroup)                  // 字典管理\n\t\tsystemRouter.InitAutoCodeHistoryRouter(PrivateGroup)                // 自动化代码历史\n\t\tsystemRouter.InitSysOperationRecordRouter(PrivateGroup)             // 操作记录\n\t\tsystemRouter.InitSysDictionaryDetailRouter(PrivateGroup)            // 字典详情管理\n\t\tsystemRouter.InitAuthorityBtnRouterRouter(PrivateGroup)             // 按钮权限管理\n\t\tsystemRouter.InitSysExportTemplateRouter(PrivateGroup, PublicGroup) // 导出模板\n\t\tsystemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup)         // 参数管理\n\t\tsystemRouter.InitSysErrorRouter(PrivateGroup, PublicGroup)          // 错误日志\n\t\tsystemRouter.InitLoginLogRouter(PrivateGroup)                       // 登录日志\n\t\tsystemRouter.InitApiTokenRouter(PrivateGroup)                       // apiToken签发\n\t\tsystemRouter.InitSkillsRouter(PrivateGroup,PublicGroup)                         // Skills 定义器\n\t\texampleRouter.InitCustomerRouter(PrivateGroup)                      // 客户路由\n\t\texampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup)         // 文件上传下载功能路由\n\t\texampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup)      // 文件上传下载分类\n\n\t}\n\n\t//插件路由安装\n\tInstallPlugin(PrivateGroup, PublicGroup, Router)\n\n\t// 注册业务路由\n\tinitBizRouter(PrivateGroup, PublicGroup)\n\n\tglobal.GVA_ROUTERS = Router.Routes()\n\n\tglobal.GVA_LOG.Info(\"router register success\")\n\treturn Router\n}\n"
  },
  {
    "path": "server/initialize/router_biz.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/router\"\n\t\"github.com/gin-gonic/gin\"\n)\n\n// 占位方法，保证文件可以正确加载，避免go空变量检测报错，请勿删除。\nfunc holder(routers ...*gin.RouterGroup) {\n\t_ = routers\n\t_ = router.RouterGroupApp\n}\n\nfunc initBizRouter(routers ...*gin.RouterGroup) {\n\tprivateGroup := routers[0]\n\tpublicGroup := routers[1]\n\n\tholder(publicGroup, privateGroup)\n}\n"
  },
  {
    "path": "server/initialize/timer.go",
    "content": "package initialize\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/task\"\n\n\t\"github.com/robfig/cron/v3\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\nfunc Timer() {\n\tgo func() {\n\t\tvar option []cron.Option\n\t\toption = append(option, cron.WithSeconds())\n\t\t// 清理DB定时任务\n\t\t_, err := global.GVA_Timer.AddTaskByFunc(\"ClearDB\", \"@daily\", func() {\n\t\t\terr := task.ClearTable(global.GVA_DB) // 定时任务方法定在task文件包中\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"timer error:\", err)\n\t\t\t}\n\t\t}, \"定时清理数据库【日志，黑名单】内容\", option...)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"add timer error:\", err)\n\t\t}\n\n\t\t// 其他定时任务定在这里 参考上方使用方法\n\n\t\t//_, err := global.GVA_Timer.AddTaskByFunc(\"定时任务标识\", \"corn表达式\", func() {\n\t\t//\t具体执行内容...\n\t\t//  ......\n\t\t//}, option...)\n\t\t//if err != nil {\n\t\t//\tfmt.Println(\"add timer error:\", err)\n\t\t//}\n\t}()\n}\n"
  },
  {
    "path": "server/initialize/validator.go",
    "content": "package initialize\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\nfunc init() {\n\t_ = utils.RegisterRule(\"PageVerify\",\n\t\tutils.Rules{\n\t\t\t\"Page\":     {utils.NotEmpty()},\n\t\t\t\"PageSize\": {utils.NotEmpty()},\n\t\t},\n\t)\n\t_ = utils.RegisterRule(\"IdVerify\",\n\t\tutils.Rules{\n\t\t\t\"Id\": {utils.NotEmpty()},\n\t\t},\n\t)\n\t_ = utils.RegisterRule(\"AuthorityIdVerify\",\n\t\tutils.Rules{\n\t\t\t\"AuthorityId\": {utils.NotEmpty()},\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "server/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/core\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/initialize\"\n\t_ \"go.uber.org/automaxprocs\"\n\t\"go.uber.org/zap\"\n)\n\n//go:generate go env -w GO111MODULE=on\n//go:generate go env -w GOPROXY=https://goproxy.cn,direct\n//go:generate go mod tidy\n//go:generate go mod download\n\n// 这部分 @Tag 设置用于排序, 需要排序的接口请按照下面的格式添加\n// swag init 对 @Tag 只会从入口文件解析, 默认 main.go\n// 也可通过 --generalInfo flag 指定其他文件\n// @Tag.Name        Base\n// @Tag.Name        SysUser\n// @Tag.Description 用户\n\n// @title                       Gin-Vue-Admin Swagger API接口文档\n// @version                     v2.9.0\n// @description                 使用gin+vue进行极速开发的全栈开发基础平台\n// @securityDefinitions.apikey  ApiKeyAuth\n// @in                          header\n// @name                        x-token\n// @BasePath                    /\nfunc main() {\n\t// 初始化系统\n\tinitializeSystem()\n\t// 运行服务器\n\tcore.RunServer()\n}\n\n// initializeSystem 初始化系统所有组件\n// 提取为单独函数以便于系统重载时调用\nfunc initializeSystem() {\n\tglobal.GVA_VP = core.Viper() // 初始化Viper\n\tinitialize.OtherInit()\n\tglobal.GVA_LOG = core.Zap() // 初始化zap日志库\n\tzap.ReplaceGlobals(global.GVA_LOG)\n\tglobal.GVA_DB = initialize.Gorm() // gorm连接数据库\n\tinitialize.Timer()\n\tinitialize.DBList()\n\tinitialize.SetupHandlers() // 注册全局函数\n\tif global.GVA_DB != nil {\n\t\tinitialize.RegisterTables() // 初始化表\n\t}\n}\n"
  },
  {
    "path": "server/mcp/api_creator.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n)\n\n// 注册工具\nfunc init() {\n\tRegisterTool(&ApiCreator{})\n}\n\n// ApiCreateRequest API创建请求结构\ntype ApiCreateRequest struct {\n\tPath        string `json:\"path\"`        // API路径\n\tDescription string `json:\"description\"` // API中文描述\n\tApiGroup    string `json:\"apiGroup\"`    // API组\n\tMethod      string `json:\"method\"`      // HTTP方法\n}\n\n// ApiCreateResponse API创建响应结构\ntype ApiCreateResponse struct {\n\tSuccess bool   `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tApiID   uint   `json:\"apiId\"`\n\tPath    string `json:\"path\"`\n\tMethod  string `json:\"method\"`\n}\n\n// ApiCreator API创建工具\ntype ApiCreator struct{}\n\n// New 创建API创建工具\nfunc (a *ApiCreator) New() mcp.Tool {\n\treturn mcp.NewTool(\"create_api\",\n\t\tmcp.WithDescription(`创建后端API记录，用于AI编辑器自动添加API接口时自动创建对应的API权限记录。\n\n**重要限制：**\n- 当使用gva_auto_generate工具且needCreatedModules=true时，模块创建会自动生成API权限，不应调用此工具\n- 仅在以下情况使用：1) 单独创建API（不涉及模块创建）；2) AI编辑器自动添加API；3) router下的文件产生路径变化时`),\n\t\tmcp.WithString(\"path\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"API路径，如：/user/create\"),\n\t\t),\n\t\tmcp.WithString(\"description\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"API中文描述，如：创建用户\"),\n\t\t),\n\t\tmcp.WithString(\"apiGroup\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"API组名称，用于分类管理，如：用户管理\"),\n\t\t),\n\t\tmcp.WithString(\"method\",\n\t\t\tmcp.Description(\"HTTP方法\"),\n\t\t\tmcp.DefaultString(\"POST\"),\n\t\t),\n\t\tmcp.WithString(\"apis\",\n\t\t\tmcp.Description(\"批量创建API的JSON字符串，格式：[{\\\"path\\\":\\\"/user/create\\\",\\\"description\\\":\\\"创建用户\\\",\\\"apiGroup\\\":\\\"用户管理\\\",\\\"method\\\":\\\"POST\\\"}]\"),\n\t\t),\n\t)\n}\n\n// Handle 处理API创建请求\nfunc (a *ApiCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\targs := request.GetArguments()\n\n\tvar apis []ApiCreateRequest\n\n\t// 检查是否是批量创建\n\tif apisStr, ok := args[\"apis\"].(string); ok && apisStr != \"\" {\n\t\tif err := json.Unmarshal([]byte(apisStr), &apis); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"apis 参数格式错误: %v\", err)\n\t\t}\n\t} else {\n\t\t// 单个API创建\n\t\tpath, ok := args[\"path\"].(string)\n\t\tif !ok || path == \"\" {\n\t\t\treturn nil, errors.New(\"path 参数是必需的\")\n\t\t}\n\n\t\tdescription, ok := args[\"description\"].(string)\n\t\tif !ok || description == \"\" {\n\t\t\treturn nil, errors.New(\"description 参数是必需的\")\n\t\t}\n\n\t\tapiGroup, ok := args[\"apiGroup\"].(string)\n\t\tif !ok || apiGroup == \"\" {\n\t\t\treturn nil, errors.New(\"apiGroup 参数是必需的\")\n\t\t}\n\n\t\tmethod := \"POST\"\n\t\tif val, ok := args[\"method\"].(string); ok && val != \"\" {\n\t\t\tmethod = val\n\t\t}\n\n\t\tapis = append(apis, ApiCreateRequest{\n\t\t\tPath:        path,\n\t\t\tDescription: description,\n\t\t\tApiGroup:    apiGroup,\n\t\t\tMethod:      method,\n\t\t})\n\t}\n\n\tif len(apis) == 0 {\n\t\treturn nil, errors.New(\"没有要创建的API\")\n\t}\n\n\t// 创建API记录\n\tapiService := service.ServiceGroupApp.SystemServiceGroup.ApiService\n\tvar responses []ApiCreateResponse\n\tsuccessCount := 0\n\n\tfor _, apiReq := range apis {\n\t\tapi := system.SysApi{\n\t\t\tPath:        apiReq.Path,\n\t\t\tDescription: apiReq.Description,\n\t\t\tApiGroup:    apiReq.ApiGroup,\n\t\t\tMethod:      apiReq.Method,\n\t\t}\n\n\t\terr := apiService.CreateApi(api)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Warn(\"创建API失败\",\n\t\t\t\tzap.String(\"path\", apiReq.Path),\n\t\t\t\tzap.String(\"method\", apiReq.Method),\n\t\t\t\tzap.Error(err))\n\n\t\t\tresponses = append(responses, ApiCreateResponse{\n\t\t\t\tSuccess: false,\n\t\t\t\tMessage: fmt.Sprintf(\"创建API失败: %v\", err),\n\t\t\t\tPath:    apiReq.Path,\n\t\t\t\tMethod:  apiReq.Method,\n\t\t\t})\n\t\t} else {\n\t\t\t// 获取创建的API ID\n\t\t\tvar createdApi system.SysApi\n\t\t\terr = global.GVA_DB.Where(\"path = ? AND method = ?\", apiReq.Path, apiReq.Method).First(&createdApi).Error\n\t\t\tif err != nil {\n\t\t\t\tglobal.GVA_LOG.Warn(\"获取创建的API ID失败\", zap.Error(err))\n\t\t\t}\n\n\t\t\tresponses = append(responses, ApiCreateResponse{\n\t\t\t\tSuccess: true,\n\t\t\t\tMessage: fmt.Sprintf(\"成功创建API %s %s\", apiReq.Method, apiReq.Path),\n\t\t\t\tApiID:   createdApi.ID,\n\t\t\t\tPath:    apiReq.Path,\n\t\t\t\tMethod:  apiReq.Method,\n\t\t\t})\n\t\t\tsuccessCount++\n\t\t}\n\t}\n\n\t// 构建总体响应\n\tvar resultMessage string\n\tif len(apis) == 1 {\n\t\tresultMessage = responses[0].Message\n\t} else {\n\t\tresultMessage = fmt.Sprintf(\"批量创建API完成，成功 %d 个，失败 %d 个\", successCount, len(apis)-successCount)\n\t}\n\n\tresult := map[string]interface{}{\n\t\t\"success\":      successCount > 0,\n\t\t\"message\":      resultMessage,\n\t\t\"totalCount\":   len(apis),\n\t\t\"successCount\": successCount,\n\t\t\"failedCount\":  len(apis) - successCount,\n\t\t\"details\":      responses,\n\t}\n\n\tresultJSON, err := json.MarshalIndent(result, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化结果失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.TextContent{\n\t\t\t\tType: \"text\",\n\t\t\t\tText: fmt.Sprintf(\"API创建结果：\\n\\n%s\", string(resultJSON)),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "server/mcp/api_lister.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n)\n\n// 注册工具\nfunc init() {\n\t// 注册工具将在enter.go中统一处理\n\tRegisterTool(&ApiLister{})\n}\n\n// ApiInfo API信息结构\ntype ApiInfo struct {\n\tID          uint   `json:\"id,omitempty\"`          // 数据库ID（仅数据库API有）\n\tPath        string `json:\"path\"`                  // API路径\n\tDescription string `json:\"description,omitempty\"` // API描述\n\tApiGroup    string `json:\"apiGroup,omitempty\"`    // API组\n\tMethod      string `json:\"method\"`                // HTTP方法\n\tSource      string `json:\"source\"`                // 来源：database 或 gin\n}\n\n// ApiListResponse API列表响应结构\ntype ApiListResponse struct {\n\tSuccess      bool      `json:\"success\"`\n\tMessage      string    `json:\"message\"`\n\tDatabaseApis []ApiInfo `json:\"databaseApis\"` // 数据库中的API\n\tGinApis      []ApiInfo `json:\"ginApis\"`      // gin框架中的API\n\tTotalCount   int       `json:\"totalCount\"`   // 总数量\n}\n\n// ApiLister API列表工具\ntype ApiLister struct{}\n\n// New 创建API列表工具\nfunc (a *ApiLister) New() mcp.Tool {\n\treturn mcp.NewTool(\"list_all_apis\",\n\t\tmcp.WithDescription(`获取系统中所有的API接口，分为两组：\n\n**功能说明：**\n- 返回数据库中已注册的API列表\n- 返回gin框架中实际注册的路由API列表\n- 帮助前端判断是使用现有API还是需要创建新的API,如果api在前端未使用且需要前端调用的时候，请到api文件夹下对应模块的js中添加方法并暴露给当前业务调用\n\n**返回数据结构：**\n- databaseApis: 数据库中的API记录（包含ID、描述、分组等完整信息）\n- ginApis: gin路由中的API（仅包含路径和方法），需要AI根据路径自行揣摩路径的业务含义，例如：/api/user/:id 表示根据用户ID获取用户信息`),\n\t\tmcp.WithString(\"_placeholder\",\n\t\t\tmcp.Description(\"占位符，防止json schema校验失败\"),\n\t\t),\t\n\t)\n}\n\n// Handle 处理API列表请求\nfunc (a *ApiLister) Handle(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\n\t// 获取数据库中的API\n\tdatabaseApis, err := a.getDatabaseApis()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取数据库API失败\", zap.Error(err))\n\t\terrorResponse := ApiListResponse{\n\t\t\tSuccess: false,\n\t\t\tMessage: \"获取数据库API失败: \" + err.Error(),\n\t\t}\n\t\tresultJSON, _ := json.Marshal(errorResponse)\n\t\treturn &mcp.CallToolResult{\n\t\t\tContent: []mcp.Content{\n\t\t\t\tmcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: string(resultJSON),\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// 获取gin路由中的API\n\tginApis, err := a.getGinApis()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取gin路由API失败\", zap.Error(err))\n\t\terrorResponse := ApiListResponse{\n\t\t\tSuccess: false,\n\t\t\tMessage: \"获取gin路由API失败: \" + err.Error(),\n\t\t}\n\t\tresultJSON, _ := json.Marshal(errorResponse)\n\t\treturn &mcp.CallToolResult{\n\t\t\tContent: []mcp.Content{\n\t\t\t\tmcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: string(resultJSON),\n\t\t\t\t},\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// 构建响应\n\tresponse := ApiListResponse{\n\t\tSuccess:      true,\n\t\tMessage:      \"获取API列表成功\",\n\t\tDatabaseApis: databaseApis,\n\t\tGinApis:      ginApis,\n\t\tTotalCount:   len(databaseApis) + len(ginApis),\n\t}\n\n\tglobal.GVA_LOG.Info(\"API列表获取成功\",\n\t\tzap.Int(\"数据库API数量\", len(databaseApis)),\n\t\tzap.Int(\"gin路由API数量\", len(ginApis)),\n\t\tzap.Int(\"总数量\", response.TotalCount))\n\n\tresultJSON, err := json.Marshal(response)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化结果失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.TextContent{\n\t\t\t\tType: \"text\",\n\t\t\t\tText: string(resultJSON),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// getDatabaseApis 获取数据库中的所有API\nfunc (a *ApiLister) getDatabaseApis() ([]ApiInfo, error) {\n\tvar apis []system.SysApi\n\terr := global.GVA_DB.Model(&system.SysApi{}).Order(\"api_group ASC, path ASC\").Find(&apis).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 转换为ApiInfo格式\n\tvar result []ApiInfo\n\tfor _, api := range apis {\n\t\tresult = append(result, ApiInfo{\n\t\t\tID:          api.ID,\n\t\t\tPath:        api.Path,\n\t\t\tDescription: api.Description,\n\t\t\tApiGroup:    api.ApiGroup,\n\t\t\tMethod:      api.Method,\n\t\t\tSource:      \"database\",\n\t\t})\n\t}\n\n\treturn result, nil\n}\n\n// getGinApis 获取gin路由中的所有API（包含被忽略的API）\nfunc (a *ApiLister) getGinApis() ([]ApiInfo, error) {\n\t// 从gin路由信息中获取所有API\n\tvar result []ApiInfo\n\tfor _, route := range global.GVA_ROUTERS {\n\t\tresult = append(result, ApiInfo{\n\t\t\tPath:   route.Path,\n\t\t\tMethod: route.Method,\n\t\t\tSource: \"gin\",\n\t\t})\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "server/mcp/client/client.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\tmcpClient \"github.com/mark3labs/mcp-go/client\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\nfunc NewClient(baseUrl, name, version, serverName string) (*mcpClient.Client, error) {\n\tclient, err := mcpClient.NewSSEMCPClient(baseUrl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx := context.Background()\n\n\t// 启动client\n\tif err := client.Start(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 初始化\n\tinitRequest := mcp.InitializeRequest{}\n\tinitRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION\n\tinitRequest.Params.ClientInfo = mcp.Implementation{\n\t\tName:    name,\n\t\tVersion: version,\n\t}\n\n\tresult, err := client.Initialize(ctx, initRequest)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif result.ServerInfo.Name != serverName {\n\t\treturn nil, errors.New(\"server name mismatch\")\n\t}\n\treturn client, nil\n}\n"
  },
  {
    "path": "server/mcp/client/client_test.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"testing\"\n)\n\n// 测试 MCP 客户端连接\nfunc TestMcpClientConnection(t *testing.T) {\n\tc, err := NewClient(\"http://localhost:8888/sse\", \"test-client\", \"1.0.0\", \"gin-vue-admin MCP服务\")\n\tdefer c.Close()\n\tif err != nil {\n\t\tt.Fatalf(err.Error())\n\t}\n}\n\nfunc TestTools(t *testing.T) {\n\tt.Run(\"currentTime\", func(t *testing.T) {\n\t\tc, err := NewClient(\"http://localhost:8888/sse\", \"test-client\", \"1.0.0\", \"gin-vue-admin MCP服务\")\n\t\tdefer c.Close()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create client: %v\", err)\n\t\t}\n\t\tctx := context.Background()\n\n\t\trequest := mcp.CallToolRequest{}\n\t\trequest.Params.Name = \"currentTime\"\n\t\trequest.Params.Arguments = map[string]interface{}{\n\t\t\t\"timezone\": \"UTC+8\",\n\t\t}\n\n\t\tresult, err := c.CallTool(ctx, request)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"方法调用错误: %v\", err)\n\t\t}\n\n\t\tif len(result.Content) != 1 {\n\t\t\tt.Errorf(\"应该有且仅返回1条信息，但是现在有 %d\", len(result.Content))\n\t\t}\n\t\tif content, ok := result.Content[0].(mcp.TextContent); ok {\n\t\t\tt.Logf(\"成功返回信息%s\", content.Text)\n\t\t} else {\n\t\t\tt.Logf(\"返回为止类型信息%+v\", content)\n\t\t}\n\t})\n\n\tt.Run(\"getNickname\", func(t *testing.T) {\n\n\t\tc, err := NewClient(\"http://localhost:8888/sse\", \"test-client\", \"1.0.0\", \"gin-vue-admin MCP服务\")\n\t\tdefer c.Close()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create client: %v\", err)\n\t\t}\n\t\tctx := context.Background()\n\n\t\t// Initialize\n\t\tinitRequest := mcp.InitializeRequest{}\n\t\tinitRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION\n\t\tinitRequest.Params.ClientInfo = mcp.Implementation{\n\t\t\tName:    \"test-client\",\n\t\t\tVersion: \"1.0.0\",\n\t\t}\n\n\t\t_, err = c.Initialize(ctx, initRequest)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"初始化失败: %v\", err)\n\t\t}\n\n\t\trequest := mcp.CallToolRequest{}\n\t\trequest.Params.Name = \"getNickname\"\n\t\trequest.Params.Arguments = map[string]interface{}{\n\t\t\t\"username\": \"admin\",\n\t\t}\n\n\t\tresult, err := c.CallTool(ctx, request)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"方法调用错误: %v\", err)\n\t\t}\n\n\t\tif len(result.Content) != 1 {\n\t\t\tt.Errorf(\"应该有且仅返回1条信息，但是现在有 %d\", len(result.Content))\n\t\t}\n\t\tif content, ok := result.Content[0].(mcp.TextContent); ok {\n\t\t\tt.Logf(\"成功返回信息%s\", content.Text)\n\t\t} else {\n\t\t\tt.Logf(\"返回为止类型信息%+v\", content)\n\t\t}\n\t})\n}\n\nfunc TestGetTools(t *testing.T) {\n\tc, err := NewClient(\"http://localhost:8888/sse\", \"test-client\", \"1.0.0\", \"gin-vue-admin MCP服务\")\n\tdefer c.Close()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create client: %v\", err)\n\t}\n\tctx := context.Background()\n\n\ttoolsRequest := mcp.ListToolsRequest{}\n\n\ttoolListResult, err := c.ListTools(ctx, toolsRequest)\n\tif err != nil {\n\t\tt.Fatalf(\"获取工具列表失败: %v\", err)\n\t}\n\tfor i := range toolListResult.Tools {\n\t\ttool := toolListResult.Tools[i]\n\t\tfmt.Printf(\"工具名称: %s\\n\", tool.Name)\n\t\tfmt.Printf(\"工具描述: %s\\n\", tool.Description)\n\n\t\t// 打印参数信息\n\t\tif tool.InputSchema.Properties != nil {\n\t\t\tfmt.Println(\"参数列表:\")\n\t\t\tfor paramName, prop := range tool.InputSchema.Properties {\n\t\t\t\trequired := \"否\"\n\t\t\t\t// 检查参数是否在必填列表中\n\t\t\t\tfor _, reqField := range tool.InputSchema.Required {\n\t\t\t\t\tif reqField == paramName {\n\t\t\t\t\t\trequired = \"是\"\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfmt.Printf(\"  - %s (类型: %s, 描述: %s, 必填: %s)\\n\",\n\t\t\t\t\tparamName, prop.(map[string]any)[\"type\"], prop.(map[string]any)[\"description\"], required)\n\t\t\t}\n\t\t} else {\n\t\t\tfmt.Println(\"该工具没有参数\")\n\t\t}\n\t\tfmt.Println(\"-------------------\")\n\t}\n}\n"
  },
  {
    "path": "server/mcp/dictionary_generator.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n\t\"gorm.io/gorm\"\n)\n\nfunc init() {\n\tRegisterTool(&DictionaryOptionsGenerator{})\n}\n\n// DictionaryOptionsGenerator 字典选项生成器\ntype DictionaryOptionsGenerator struct{}\n\n// DictionaryOption 字典选项结构\ntype DictionaryOption struct {\n\tLabel string `json:\"label\"`\n\tValue string `json:\"value\"`\n\tSort  int    `json:\"sort\"`\n}\n\n// DictionaryGenerateRequest 字典生成请求\ntype DictionaryGenerateRequest struct {\n\tDictType    string             `json:\"dictType\"`    // 字典类型\n\tFieldDesc   string             `json:\"fieldDesc\"`   // 字段描述\n\tOptions     []DictionaryOption `json:\"options\"`     // AI生成的字典选项\n\tDictName    string             `json:\"dictName\"`    // 字典名称（可选）\n\tDescription string             `json:\"description\"` // 字典描述（可选）\n}\n\n// DictionaryGenerateResponse 字典生成响应\ntype DictionaryGenerateResponse struct {\n\tSuccess      bool   `json:\"success\"`\n\tMessage      string `json:\"message\"`\n\tDictType     string `json:\"dictType\"`\n\tOptionsCount int    `json:\"optionsCount\"`\n}\n\n// New 返回工具注册信息\nfunc (d *DictionaryOptionsGenerator) New() mcp.Tool {\n\treturn mcp.NewTool(\"generate_dictionary_options\",\n\t\tmcp.WithDescription(\"智能生成字典选项并自动创建字典和字典详情\"),\n\t\tmcp.WithString(\"dictType\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"字典类型，用于标识字典的唯一性\"),\n\t\t),\n\t\tmcp.WithString(\"fieldDesc\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"字段描述，用于AI理解字段含义\"),\n\t\t),\n\t\tmcp.WithString(\"options\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"字典选项JSON字符串，格式：[{\\\"label\\\":\\\"显示名\\\",\\\"value\\\":\\\"值\\\",\\\"sort\\\":1}]\"),\n\t\t),\n\t\tmcp.WithString(\"dictName\",\n\t\t\tmcp.Description(\"字典名称，如果不提供将自动生成\"),\n\t\t),\n\t\tmcp.WithString(\"description\",\n\t\t\tmcp.Description(\"字典描述\"),\n\t\t),\n\t)\n}\n\n// Handle 处理工具调用\nfunc (d *DictionaryOptionsGenerator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// 解析请求参数\n\targs := request.GetArguments()\n\n\tdictType, ok := args[\"dictType\"].(string)\n\tif !ok || dictType == \"\" {\n\t\treturn nil, errors.New(\"dictType 参数是必需的\")\n\t}\n\n\tfieldDesc, ok := args[\"fieldDesc\"].(string)\n\tif !ok || fieldDesc == \"\" {\n\t\treturn nil, errors.New(\"fieldDesc 参数是必需的\")\n\t}\n\n\toptionsStr, ok := args[\"options\"].(string)\n\tif !ok || optionsStr == \"\" {\n\t\treturn nil, errors.New(\"options 参数是必需的\")\n\t}\n\n\t// 解析options JSON字符串\n\tvar options []DictionaryOption\n\tif err := json.Unmarshal([]byte(optionsStr), &options); err != nil {\n\t\treturn nil, fmt.Errorf(\"options 参数格式错误: %v\", err)\n\t}\n\n\tif len(options) == 0 {\n\t\treturn nil, errors.New(\"options 不能为空\")\n\t}\n\n\tdictName, _ := args[\"dictName\"].(string)\n\tdescription, _ := args[\"description\"].(string)\n\n\t// 构建请求对象\n\treq := &DictionaryGenerateRequest{\n\t\tDictType:    dictType,\n\t\tFieldDesc:   fieldDesc,\n\t\tOptions:     options,\n\t\tDictName:    dictName,\n\t\tDescription: description,\n\t}\n\n\t// 创建字典\n\tresponse, err := d.createDictionaryWithOptions(ctx, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 构建响应\n\tresultJSON, err := json.MarshalIndent(response, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化结果失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.TextContent{\n\t\t\t\tType: \"text\",\n\t\t\t\tText: fmt.Sprintf(\"字典选项生成结果：\\n\\n%s\", string(resultJSON)),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// createDictionaryWithOptions 创建字典和字典选项\nfunc (d *DictionaryOptionsGenerator) createDictionaryWithOptions(ctx context.Context, req *DictionaryGenerateRequest) (*DictionaryGenerateResponse, error) {\n\t// 检查字典是否已存在\n\texists, err := d.checkDictionaryExists(req.DictType)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"检查字典是否存在失败: %v\", err)\n\t}\n\n\tif exists {\n\t\treturn &DictionaryGenerateResponse{\n\t\t\tSuccess:      false,\n\t\t\tMessage:      fmt.Sprintf(\"字典 %s 已存在，跳过创建\", req.DictType),\n\t\t\tDictType:     req.DictType,\n\t\t\tOptionsCount: 0,\n\t\t}, nil\n\t}\n\n\t// 生成字典名称\n\tdictName := req.DictName\n\tif dictName == \"\" {\n\t\tdictName = d.generateDictionaryName(req.DictType, req.FieldDesc)\n\t}\n\n\t// 创建字典\n\tdictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService\n\tdictionary := system.SysDictionary{\n\t\tName:   dictName,\n\t\tType:   req.DictType,\n\t\tStatus: &[]bool{true}[0], // 默认启用\n\t\tDesc:   req.Description,\n\t}\n\n\terr = dictionaryService.CreateSysDictionary(dictionary)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"创建字典失败: %v\", err)\n\t}\n\n\t// 获取刚创建的字典ID\n\tvar createdDict system.SysDictionary\n\terr = global.GVA_DB.Where(\"type = ?\", req.DictType).First(&createdDict).Error\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"获取创建的字典失败: %v\", err)\n\t}\n\n\t// 创建字典详情项\n\tdictionaryDetailService := service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService\n\tsuccessCount := 0\n\n\tfor _, option := range req.Options {\n\t\tdictionaryDetail := system.SysDictionaryDetail{\n\t\t\tLabel:           option.Label,\n\t\t\tValue:           option.Value,\n\t\t\tStatus:          &[]bool{true}[0], // 默认启用\n\t\t\tSort:            option.Sort,\n\t\t\tSysDictionaryID: int(createdDict.ID),\n\t\t}\n\n\t\terr = dictionaryDetailService.CreateSysDictionaryDetail(dictionaryDetail)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Warn(\"创建字典详情项失败\", zap.Error(err))\n\t\t} else {\n\t\t\tsuccessCount++\n\t\t}\n\t}\n\n\treturn &DictionaryGenerateResponse{\n\t\tSuccess:      true,\n\t\tMessage:      fmt.Sprintf(\"成功创建字典 %s，包含 %d 个选项\", req.DictType, successCount),\n\t\tDictType:     req.DictType,\n\t\tOptionsCount: successCount,\n\t}, nil\n}\n\n// checkDictionaryExists 检查字典是否存在\nfunc (d *DictionaryOptionsGenerator) checkDictionaryExists(dictType string) (bool, error) {\n\tvar dictionary system.SysDictionary\n\terr := global.GVA_DB.Where(\"type = ?\", dictType).First(&dictionary).Error\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\treturn false, nil // 字典不存在\n\t\t}\n\t\treturn false, err // 其他错误\n\t}\n\treturn true, nil // 字典存在\n}\n\n// generateDictionaryName 生成字典名称\nfunc (d *DictionaryOptionsGenerator) generateDictionaryName(dictType, fieldDesc string) string {\n\tif fieldDesc != \"\" {\n\t\treturn fmt.Sprintf(\"%s字典\", fieldDesc)\n\t}\n\treturn fmt.Sprintf(\"%s字典\", dictType)\n}\n"
  },
  {
    "path": "server/mcp/dictionary_query.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n\t\"gorm.io/gorm\"\n)\n\n// 注册工具\nfunc init() {\n\tRegisterTool(&DictionaryQuery{})\n}\n\ntype DictionaryPre struct {\n\tType string `json:\"type\"` // 字典名（英）\n\tDesc string `json:\"desc\"` // 描述\n}\n\n// DictionaryInfo 字典信息结构\ntype DictionaryInfo struct {\n\tID      uint                   `json:\"id\"`\n\tName    string                 `json:\"name\"`    // 字典名（中）\n\tType    string                 `json:\"type\"`    // 字典名（英）\n\tStatus  *bool                  `json:\"status\"`  // 状态\n\tDesc    string                 `json:\"desc\"`    // 描述\n\tDetails []DictionaryDetailInfo `json:\"details\"` // 字典详情\n}\n\n// DictionaryDetailInfo 字典详情信息结构\ntype DictionaryDetailInfo struct {\n\tID     uint   `json:\"id\"`\n\tLabel  string `json:\"label\"`  // 展示值\n\tValue  string `json:\"value\"`  // 字典值\n\tExtend string `json:\"extend\"` // 扩展值\n\tStatus *bool  `json:\"status\"` // 启用状态\n\tSort   int    `json:\"sort\"`   // 排序标记\n}\n\n// DictionaryQueryResponse 字典查询响应结构\ntype DictionaryQueryResponse struct {\n\tSuccess      bool             `json:\"success\"`\n\tMessage      string           `json:\"message\"`\n\tTotal        int              `json:\"total\"`\n\tDictionaries []DictionaryInfo `json:\"dictionaries\"`\n}\n\n// DictionaryQuery 字典查询工具\ntype DictionaryQuery struct{}\n\n// New 创建字典查询工具\nfunc (d *DictionaryQuery) New() mcp.Tool {\n\treturn mcp.NewTool(\"query_dictionaries\",\n\t\tmcp.WithDescription(\"查询系统中所有的字典和字典属性，用于AI生成逻辑时了解可用的字典选项\"),\n\t\tmcp.WithString(\"dictType\",\n\t\t\tmcp.Description(\"可选：指定字典类型进行精确查询，如果不提供则返回所有字典\"),\n\t\t),\n\t\tmcp.WithBoolean(\"includeDisabled\",\n\t\t\tmcp.Description(\"是否包含已禁用的字典和字典项，默认为false（只返回启用的）\"),\n\t\t),\n\t\tmcp.WithBoolean(\"detailsOnly\",\n\t\t\tmcp.Description(\"是否只返回字典详情信息（不包含字典基本信息），默认为false\"),\n\t\t),\n\t)\n}\n\n// Handle 处理字典查询请求\nfunc (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\targs := request.GetArguments()\n\n\t// 获取参数\n\tdictType := \"\"\n\tif val, ok := args[\"dictType\"].(string); ok {\n\t\tdictType = val\n\t}\n\n\tincludeDisabled := false\n\tif val, ok := args[\"includeDisabled\"].(bool); ok {\n\t\tincludeDisabled = val\n\t}\n\n\tdetailsOnly := false\n\tif val, ok := args[\"detailsOnly\"].(bool); ok {\n\t\tdetailsOnly = val\n\t}\n\n\t// 获取字典服务\n\tdictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService\n\n\tvar dictionaries []DictionaryInfo\n\tvar err error\n\n\tif dictType != \"\" {\n\t\t// 查询指定类型的字典\n\t\tvar status *bool\n\t\tif !includeDisabled {\n\t\t\tstatus = &[]bool{true}[0]\n\t\t}\n\n\t\tsysDictionary, err := dictionaryService.GetSysDictionary(dictType, 0, status)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"查询字典失败\", zap.Error(err))\n\t\t\treturn &mcp.CallToolResult{\n\t\t\t\tContent: []mcp.Content{\n\t\t\t\t\tmcp.NewTextContent(fmt.Sprintf(`{\"success\": false, \"message\": \"查询字典失败: %v\", \"total\": 0, \"dictionaries\": []}`, err.Error())),\n\t\t\t\t},\n\t\t\t}, nil\n\t\t}\n\n\t\t// 转换为响应格式\n\t\tdictInfo := DictionaryInfo{\n\t\t\tID:     sysDictionary.ID,\n\t\t\tName:   sysDictionary.Name,\n\t\t\tType:   sysDictionary.Type,\n\t\t\tStatus: sysDictionary.Status,\n\t\t\tDesc:   sysDictionary.Desc,\n\t\t}\n\n\t\t// 获取字典详情\n\t\tfor _, detail := range sysDictionary.SysDictionaryDetails {\n\t\t\tif includeDisabled || (detail.Status != nil && *detail.Status) {\n\t\t\t\tdictInfo.Details = append(dictInfo.Details, DictionaryDetailInfo{\n\t\t\t\t\tID:     detail.ID,\n\t\t\t\t\tLabel:  detail.Label,\n\t\t\t\t\tValue:  detail.Value,\n\t\t\t\t\tExtend: detail.Extend,\n\t\t\t\t\tStatus: detail.Status,\n\t\t\t\t\tSort:   detail.Sort,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tdictionaries = append(dictionaries, dictInfo)\n\t} else {\n\t\t// 查询所有字典\n\t\tvar sysDictionaries []system.SysDictionary\n\t\tdb := global.GVA_DB.Model(&system.SysDictionary{})\n\n\t\tif !includeDisabled {\n\t\t\tdb = db.Where(\"status = ?\", true)\n\t\t}\n\n\t\terr = db.Preload(\"SysDictionaryDetails\", func(db *gorm.DB) *gorm.DB {\n\t\t\tif includeDisabled {\n\t\t\t\treturn db.Order(\"sort\")\n\t\t\t} else {\n\t\t\t\treturn db.Where(\"status = ?\", true).Order(\"sort\")\n\t\t\t}\n\t\t}).Find(&sysDictionaries).Error\n\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"查询字典列表失败\", zap.Error(err))\n\t\t\treturn &mcp.CallToolResult{\n\t\t\t\tContent: []mcp.Content{\n\t\t\t\t\tmcp.NewTextContent(fmt.Sprintf(`{\"success\": false, \"message\": \"查询字典列表失败: %v\", \"total\": 0, \"dictionaries\": []}`, err.Error())),\n\t\t\t\t},\n\t\t\t}, nil\n\t\t}\n\n\t\t// 转换为响应格式\n\t\tfor _, dict := range sysDictionaries {\n\t\t\tdictInfo := DictionaryInfo{\n\t\t\t\tID:     dict.ID,\n\t\t\t\tName:   dict.Name,\n\t\t\t\tType:   dict.Type,\n\t\t\t\tStatus: dict.Status,\n\t\t\t\tDesc:   dict.Desc,\n\t\t\t}\n\n\t\t\t// 获取字典详情\n\t\t\tfor _, detail := range dict.SysDictionaryDetails {\n\t\t\t\tif includeDisabled || (detail.Status != nil && *detail.Status) {\n\t\t\t\t\tdictInfo.Details = append(dictInfo.Details, DictionaryDetailInfo{\n\t\t\t\t\t\tID:     detail.ID,\n\t\t\t\t\t\tLabel:  detail.Label,\n\t\t\t\t\t\tValue:  detail.Value,\n\t\t\t\t\t\tExtend: detail.Extend,\n\t\t\t\t\t\tStatus: detail.Status,\n\t\t\t\t\t\tSort:   detail.Sort,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdictionaries = append(dictionaries, dictInfo)\n\t\t}\n\t}\n\n\t// 如果只需要详情信息，则提取所有详情\n\tif detailsOnly {\n\t\tvar allDetails []DictionaryDetailInfo\n\t\tfor _, dict := range dictionaries {\n\t\t\tallDetails = append(allDetails, dict.Details...)\n\t\t}\n\n\t\tresponse := map[string]interface{}{\n\t\t\t\"success\": true,\n\t\t\t\"message\": \"查询字典详情成功\",\n\t\t\t\"total\":   len(allDetails),\n\t\t\t\"details\": allDetails,\n\t\t}\n\n\t\tresponseJSON, _ := json.Marshal(response)\n\t\treturn &mcp.CallToolResult{\n\t\t\tContent: []mcp.Content{\n\t\t\t\tmcp.NewTextContent(string(responseJSON)),\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// 构建响应\n\tresponse := DictionaryQueryResponse{\n\t\tSuccess:      true,\n\t\tMessage:      \"查询字典成功\",\n\t\tTotal:        len(dictionaries),\n\t\tDictionaries: dictionaries,\n\t}\n\n\tresponseJSON, err := json.Marshal(response)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"序列化响应失败\", zap.Error(err))\n\t\treturn &mcp.CallToolResult{\n\t\t\tContent: []mcp.Content{\n\t\t\t\tmcp.NewTextContent(fmt.Sprintf(`{\"success\": false, \"message\": \"序列化响应失败: %v\", \"total\": 0, \"dictionaries\": []}`, err.Error())),\n\t\t\t},\n\t\t}, nil\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.NewTextContent(string(responseJSON)),\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "server/mcp/enter.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"github.com/mark3labs/mcp-go/server\"\n)\n\n// McpTool 定义了MCP工具必须实现的接口\ntype McpTool interface {\n\t// Handle 返回工具调用信息\n\tHandle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)\n\t// New 返回工具注册信息\n\tNew() mcp.Tool\n}\n\n// 工具注册表\nvar toolRegister = make(map[string]McpTool)\n\n// RegisterTool 供工具在init时调用，将自己注册到工具注册表中\nfunc RegisterTool(tool McpTool) {\n\tmcpTool := tool.New()\n\ttoolRegister[mcpTool.Name] = tool\n}\n\n// RegisterAllTools 将所有注册的工具注册到MCP服务中\nfunc RegisterAllTools(mcpServer *server.MCPServer) {\n\tfor _, tool := range toolRegister {\n\t\tmcpServer.AddTool(tool.New(), tool.Handle)\n\t}\n}\n"
  },
  {
    "path": "server/mcp/gva_analyze.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\n// 注册工具\nfunc init() {\n\tRegisterTool(&GVAAnalyzer{})\n}\n\n// GVAAnalyzer GVA分析器 - 用于分析当前功能是否需要创建独立的package和module\ntype GVAAnalyzer struct{}\n\n// AnalyzeRequest 分析请求结构体\ntype AnalyzeRequest struct {\n\tRequirement string `json:\"requirement\" binding:\"required\"` // 用户需求描述\n}\n\n// AnalyzeResponse 分析响应结构体\ntype AnalyzeResponse struct {\n\tExistingPackages   []PackageInfo           `json:\"existingPackages\"`   // 现有包信息\n\tPredesignedModules []PredesignedModuleInfo `json:\"predesignedModules\"` // 预设计模块信息\n\tDictionaries       []DictionaryPre         `json:\"dictionaries\"`       // 字典信息\n\tCleanupInfo        *CleanupInfo            `json:\"cleanupInfo\"`        // 清理信息（如果有）\n}\n\n// ModuleInfo 模块信息\ntype ModuleInfo struct {\n\tModuleName  string   `json:\"moduleName\"`  // 模块名称\n\tPackageName string   `json:\"packageName\"` // 包名\n\tTemplate    string   `json:\"template\"`    // 模板类型\n\tStructName  string   `json:\"structName\"`  // 结构体名称\n\tTableName   string   `json:\"tableName\"`   // 表名\n\tDescription string   `json:\"description\"` // 描述\n\tFilePaths   []string `json:\"filePaths\"`   // 相关文件路径\n}\n\n// PackageInfo 包信息\ntype PackageInfo struct {\n\tPackageName string `json:\"packageName\"` // 包名\n\tTemplate    string `json:\"template\"`    // 模板类型\n\tLabel       string `json:\"label\"`       // 标签\n\tDesc        string `json:\"desc\"`        // 描述\n\tModule      string `json:\"module\"`      // 模块\n\tIsEmpty     bool   `json:\"isEmpty\"`     // 是否为空包\n}\n\n// PredesignedModuleInfo 预设计模块信息\ntype PredesignedModuleInfo struct {\n\tModuleName  string   `json:\"moduleName\"`  // 模块名称\n\tPackageName string   `json:\"packageName\"` // 包名\n\tTemplate    string   `json:\"template\"`    // 模板类型\n\tFilePaths   []string `json:\"filePaths\"`   // 文件路径列表\n\tDescription string   `json:\"description\"` // 描述\n}\n\n// CleanupInfo 清理信息\ntype CleanupInfo struct {\n\tDeletedPackages []string `json:\"deletedPackages\"` // 已删除的包\n\tDeletedModules  []string `json:\"deletedModules\"`  // 已删除的模块\n\tCleanupMessage  string   `json:\"cleanupMessage\"`  // 清理消息\n}\n\n// New 创建GVA分析器工具\nfunc (g *GVAAnalyzer) New() mcp.Tool {\n\treturn mcp.NewTool(\"gva_analyze\",\n\t\tmcp.WithDescription(\"返回当前系统中有效的包和模块信息，并分析用户需求是否需要创建新的包、模块和字典。同时检查并清理空包，确保系统整洁。\"),\n\t\tmcp.WithString(\"requirement\",\n\t\t\tmcp.Description(\"用户需求描述，用于分析是否需要创建新的包和模块\"),\n\t\t\tmcp.Required(),\n\t\t),\n\t)\n}\n\n// Handle 处理分析请求\nfunc (g *GVAAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// 解析请求参数\n\trequirementStr, ok := request.GetArguments()[\"requirement\"].(string)\n\tif !ok || requirementStr == \"\" {\n\t\treturn nil, errors.New(\"参数错误：requirement 必须是非空字符串\")\n\t}\n\n\t// 创建分析请求\n\tanalyzeReq := AnalyzeRequest{\n\t\tRequirement: requirementStr,\n\t}\n\n\t// 执行分析逻辑\n\tresponse, err := g.performAnalysis(ctx, analyzeReq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"分析失败: %v\", err)\n\t}\n\n\t// 序列化响应\n\tresponseJSON, err := json.Marshal(response)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化响应失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.NewTextContent(string(responseJSON)),\n\t\t},\n\t}, nil\n}\n\n// performAnalysis 执行分析逻辑\nfunc (g *GVAAnalyzer) performAnalysis(ctx context.Context, req AnalyzeRequest) (*AnalyzeResponse, error) {\n\t// 1. 获取数据库中的包信息\n\tvar packages []model.SysAutoCodePackage\n\tif err := global.GVA_DB.Find(&packages).Error; err != nil {\n\t\treturn nil, fmt.Errorf(\"获取包信息失败: %v\", err)\n\t}\n\n\t// 2. 获取历史记录\n\tvar histories []model.SysAutoCodeHistory\n\tif err := global.GVA_DB.Find(&histories).Error; err != nil {\n\t\treturn nil, fmt.Errorf(\"获取历史记录失败: %v\", err)\n\t}\n\n\t// 3. 检查空包并进行清理\n\tcleanupInfo := &CleanupInfo{\n\t\tDeletedPackages: []string{},\n\t\tDeletedModules:  []string{},\n\t}\n\n\tvar validPackages []model.SysAutoCodePackage\n\tvar emptyPackageHistoryIDs []uint\n\n\tfor _, pkg := range packages {\n\t\tisEmpty, err := g.isPackageFolderEmpty(pkg.PackageName, pkg.Template)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"检查包 %s 是否为空时出错: %v\", pkg.PackageName, err))\n\t\t\tcontinue\n\t\t}\n\n\t\tif isEmpty {\n\t\t\t// 删除空包文件夹\n\t\t\tif err := g.removeEmptyPackageFolder(pkg.PackageName, pkg.Template); err != nil {\n\t\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"删除空包文件夹 %s 失败: %v\", pkg.PackageName, err))\n\t\t\t} else {\n\t\t\t\tcleanupInfo.DeletedPackages = append(cleanupInfo.DeletedPackages, pkg.PackageName)\n\t\t\t}\n\n\t\t\t// 删除数据库记录\n\t\t\tif err := global.GVA_DB.Delete(&pkg).Error; err != nil {\n\t\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"删除包数据库记录 %s 失败: %v\", pkg.PackageName, err))\n\t\t\t}\n\n\t\t\t// 收集相关的历史记录ID\n\t\t\tfor _, history := range histories {\n\t\t\t\tif history.Package == pkg.PackageName {\n\t\t\t\t\temptyPackageHistoryIDs = append(emptyPackageHistoryIDs, history.ID)\n\t\t\t\t\tcleanupInfo.DeletedModules = append(cleanupInfo.DeletedModules, history.StructName)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tvalidPackages = append(validPackages, pkg)\n\t\t}\n\t}\n\n\t// 5. 清理空包相关的历史记录和脏历史记录\n\tvar dirtyHistoryIDs []uint\n\tfor _, history := range histories {\n\t\t// 检查是否为空包相关的历史记录\n\t\tfor _, emptyID := range emptyPackageHistoryIDs {\n\t\t\tif history.ID == emptyID {\n\t\t\t\tdirtyHistoryIDs = append(dirtyHistoryIDs, history.ID)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// 删除脏历史记录\n\tif len(dirtyHistoryIDs) > 0 {\n\t\tif err := global.GVA_DB.Delete(&model.SysAutoCodeHistory{}, \"id IN ?\", dirtyHistoryIDs).Error; err != nil {\n\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"删除脏历史记录失败: %v\", err))\n\t\t} else {\n\t\t\tglobal.GVA_LOG.Info(fmt.Sprintf(\"成功删除 %d 条脏历史记录\", len(dirtyHistoryIDs)))\n\t\t}\n\n\t\t// 清理相关的API和菜单记录\n\t\tif err := g.cleanupRelatedApiAndMenus(dirtyHistoryIDs); err != nil {\n\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"清理相关API和菜单记录失败: %v\", err))\n\t\t}\n\t}\n\n\t// 6. 扫描预设计模块\n\tpredesignedModules, err := g.scanPredesignedModules()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"扫描预设计模块失败: %v\", err))\n\t\tpredesignedModules = []PredesignedModuleInfo{} // 设置为空列表，不影响主流程\n\t}\n\n\t// 7. 过滤掉与已删除包相关的模块\n\tfilteredModules := []PredesignedModuleInfo{}\n\tfor _, module := range predesignedModules {\n\t\tisDeleted := false\n\t\tfor _, deletedPkg := range cleanupInfo.DeletedPackages {\n\t\t\tif module.PackageName == deletedPkg {\n\t\t\t\tisDeleted = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !isDeleted {\n\t\t\tfilteredModules = append(filteredModules, module)\n\t\t}\n\t}\n\n\t// 8. 构建分析结果消息\n\tvar analysisMessage strings.Builder\n\tif len(cleanupInfo.DeletedPackages) > 0 || len(cleanupInfo.DeletedModules) > 0 {\n\t\tanalysisMessage.WriteString(\"**系统清理完成**\\n\\n\")\n\t\tif len(cleanupInfo.DeletedPackages) > 0 {\n\t\t\tanalysisMessage.WriteString(fmt.Sprintf(\"- 删除了 %d 个空包: %s\\n\", len(cleanupInfo.DeletedPackages), strings.Join(cleanupInfo.DeletedPackages, \", \")))\n\t\t}\n\t\tif len(cleanupInfo.DeletedModules) > 0 {\n\t\t\tanalysisMessage.WriteString(fmt.Sprintf(\"- 删除了 %d 个相关模块: %s\\n\", len(cleanupInfo.DeletedModules), strings.Join(cleanupInfo.DeletedModules, \", \")))\n\t\t}\n\t\tanalysisMessage.WriteString(\"\\n\")\n\t\tcleanupInfo.CleanupMessage = analysisMessage.String()\n\t}\n\n\tanalysisMessage.WriteString(\" **分析结果**\\n\\n\")\n\tanalysisMessage.WriteString(fmt.Sprintf(\"- **现有包数量**: %d\\n\", len(validPackages)))\n\tanalysisMessage.WriteString(fmt.Sprintf(\"- **预设计模块数量**: %d\\n\\n\", len(filteredModules)))\n\n\t// 9. 转换包信息\n\texistingPackages := make([]PackageInfo, len(validPackages))\n\tfor i, pkg := range validPackages {\n\t\texistingPackages[i] = PackageInfo{\n\t\t\tPackageName: pkg.PackageName,\n\t\t\tTemplate:    pkg.Template,\n\t\t\tLabel:       pkg.Label,\n\t\t\tDesc:        pkg.Desc,\n\t\t\tModule:      pkg.Module,\n\t\t\tIsEmpty:     false, // 已经过滤掉空包\n\t\t}\n\t}\n\n\tdictionaries := []DictionaryPre{} // 这里可以根据需要填充字典信息\n\terr = global.GVA_DB.Table(\"sys_dictionaries\").Find(&dictionaries, \"deleted_at is null\").Error\n\tif err != nil {\n\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"获取字典信息失败: %v\", err))\n\t\tdictionaries = []DictionaryPre{} // 设置为空列表，不影响主流程\n\t}\n\n\t// 10. 构建响应\n\tresponse := &AnalyzeResponse{\n\t\tExistingPackages:   existingPackages,\n\t\tPredesignedModules: filteredModules,\n\t\tDictionaries:       dictionaries,\n\t}\n\n\treturn response, nil\n}\n\n// isPackageFolderEmpty 检查包文件夹是否为空\nfunc (g *GVAAnalyzer) isPackageFolderEmpty(packageName, template string) (bool, error) {\n\t// 根据模板类型确定基础路径\n\tvar basePath string\n\tif template == \"plugin\" {\n\t\tbasePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", packageName)\n\t} else {\n\t\tbasePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", packageName)\n\t}\n\n\t// 检查文件夹是否存在\n\tif _, err := os.Stat(basePath); os.IsNotExist(err) {\n\t\treturn true, nil // 文件夹不存在，视为空\n\t} else if err != nil {\n\t\treturn false, err // 其他错误\n\t}\n\t// 递归检查是否有.go文件\n\treturn g.hasGoFilesRecursive(basePath)\n}\n\n// hasGoFilesRecursive 递归检查目录及其子目录中是否有.go文件\nfunc (g *GVAAnalyzer) hasGoFilesRecursive(dirPath string) (bool, error) {\n\tentries, err := os.ReadDir(dirPath)\n\tif err != nil {\n\t\treturn true, err // 读取失败，返回空\n\t}\n\n\t// 检查当前目录下的.go文件\n\tfor _, entry := range entries {\n\t\tif !entry.IsDir() && strings.HasSuffix(entry.Name(), \".go\") {\n\t\t\treturn false, nil // 找到.go文件，不为空\n\t\t}\n\t}\n\n\t// 递归检查子目录\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\tsubDirPath := filepath.Join(dirPath, entry.Name())\n\t\t\tisEmpty, err := g.hasGoFilesRecursive(subDirPath)\n\t\t\tif err != nil {\n\t\t\t\tcontinue // 忽略子目录的错误，继续检查其他目录\n\t\t\t}\n\t\t\tif !isEmpty {\n\t\t\t\treturn false, nil // 子目录中找到.go文件，不为空\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true, nil // 没有找到.go文件，为空\n}\n\n// removeEmptyPackageFolder 删除空包文件夹\nfunc (g *GVAAnalyzer) removeEmptyPackageFolder(packageName, template string) error {\n\tvar basePath string\n\tif template == \"plugin\" {\n\t\tbasePath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", packageName)\n\t} else {\n\t\t// 对于package类型，需要删除多个目录\n\t\tpaths := []string{\n\t\t\tfilepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", packageName),\n\t\t\tfilepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"model\", packageName),\n\t\t\tfilepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", packageName),\n\t\t\tfilepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", packageName),\n\t\t}\n\t\tfor _, path := range paths {\n\t\t\tif err := g.removeDirectoryIfExists(path); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn g.removeDirectoryIfExists(basePath)\n}\n\n// removeDirectoryIfExists 删除目录（如果存在）\nfunc (g *GVAAnalyzer) removeDirectoryIfExists(dirPath string) error {\n\tif _, err := os.Stat(dirPath); os.IsNotExist(err) {\n\t\treturn nil // 目录不存在，无需删除\n\t} else if err != nil {\n\t\treturn err // 其他错误\n\t}\n\n\t// 检查目录中是否包含go文件\n\tnoGoFiles, err := g.hasGoFilesRecursive(dirPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// hasGoFilesRecursive 返回 false 表示发现了 go 文件\n\tif noGoFiles {\n\t\treturn os.RemoveAll(dirPath)\n\t}\n\treturn nil\n}\n\n// cleanupRelatedApiAndMenus 清理相关的API和菜单记录\nfunc (g *GVAAnalyzer) cleanupRelatedApiAndMenus(historyIDs []uint) error {\n\tif len(historyIDs) == 0 {\n\t\treturn nil\n\t}\n\n\t// 这里可以根据需要实现具体的API和菜单清理逻辑\n\t// 由于涉及到具体的业务逻辑，这里只做日志记录\n\tglobal.GVA_LOG.Info(fmt.Sprintf(\"清理历史记录ID %v 相关的API和菜单记录\", historyIDs))\n\n\t// 可以调用service层的相关方法进行清理\n\t// 例如：service.ServiceGroupApp.SystemApiService.DeleteApisByIds(historyIDs)\n\t// 例如：service.ServiceGroupApp.MenuService.DeleteMenusByIds(historyIDs)\n\n\treturn nil\n}\n\n// scanPredesignedModules 扫描预设计模块\nfunc (g *GVAAnalyzer) scanPredesignedModules() ([]PredesignedModuleInfo, error) {\n\t// 获取autocode配置路径\n\tautocodeRoot := global.GVA_CONFIG.AutoCode.Root\n\tif autocodeRoot == \"\" {\n\t\treturn nil, errors.New(\"autocode根路径未配置\")\n\t}\n\n\tvar modules []PredesignedModuleInfo\n\n\t// 扫描plugin目录\n\tpluginModules, err := g.scanPluginModules(filepath.Join(autocodeRoot, global.GVA_CONFIG.AutoCode.Server, \"plugin\"))\n\tif err != nil {\n\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"扫描plugin模块失败: %v\", err))\n\t} else {\n\t\tmodules = append(modules, pluginModules...)\n\t}\n\n\t// 扫描model目录\n\tmodelModules, err := g.scanModelModules(filepath.Join(autocodeRoot, global.GVA_CONFIG.AutoCode.Server, \"model\"))\n\tif err != nil {\n\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"扫描model模块失败: %v\", err))\n\t} else {\n\t\tmodules = append(modules, modelModules...)\n\t}\n\n\treturn modules, nil\n}\n\n// scanPluginModules 扫描插件模块\nfunc (g *GVAAnalyzer) scanPluginModules(pluginDir string) ([]PredesignedModuleInfo, error) {\n\tvar modules []PredesignedModuleInfo\n\n\tif _, err := os.Stat(pluginDir); os.IsNotExist(err) {\n\t\treturn modules, nil // 目录不存在，返回空列表\n\t}\n\n\tentries, err := os.ReadDir(pluginDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\tpluginName := entry.Name()\n\t\t\tpluginPath := filepath.Join(pluginDir, pluginName)\n\n\t\t\t// 查找model目录\n\t\t\tmodelDir := filepath.Join(pluginPath, \"model\")\n\t\t\tif _, err := os.Stat(modelDir); err == nil {\n\t\t\t\t// 扫描model目录下的模块\n\t\t\t\tpluginModules, err := g.scanModulesInDirectory(modelDir, pluginName, \"plugin\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"扫描插件 %s 的模块失败: %v\", pluginName, err))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tmodules = append(modules, pluginModules...)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn modules, nil\n}\n\n// scanModelModules 扫描模型模块\nfunc (g *GVAAnalyzer) scanModelModules(modelDir string) ([]PredesignedModuleInfo, error) {\n\tvar modules []PredesignedModuleInfo\n\n\tif _, err := os.Stat(modelDir); os.IsNotExist(err) {\n\t\treturn modules, nil // 目录不存在，返回空列表\n\t}\n\n\tentries, err := os.ReadDir(modelDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\tpackageName := entry.Name()\n\t\t\tpackagePath := filepath.Join(modelDir, packageName)\n\n\t\t\t// 扫描包目录下的模块\n\t\t\tpackageModules, err := g.scanModulesInDirectory(packagePath, packageName, \"package\")\n\t\t\tif err != nil {\n\t\t\t\tglobal.GVA_LOG.Warn(fmt.Sprintf(\"扫描包 %s 的模块失败: %v\", packageName, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmodules = append(modules, packageModules...)\n\t\t}\n\t}\n\n\treturn modules, nil\n}\n\n// scanModulesInDirectory 扫描目录中的模块\nfunc (g *GVAAnalyzer) scanModulesInDirectory(dir, packageName, template string) ([]PredesignedModuleInfo, error) {\n\tvar modules []PredesignedModuleInfo\n\n\tentries, err := os.ReadDir(dir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, entry := range entries {\n\t\tif !entry.IsDir() && strings.HasSuffix(entry.Name(), \".go\") {\n\t\t\tmoduleName := strings.TrimSuffix(entry.Name(), \".go\")\n\t\t\tfilePath := filepath.Join(dir, entry.Name())\n\n\t\t\tmodule := PredesignedModuleInfo{\n\t\t\t\tModuleName:  moduleName,\n\t\t\t\tPackageName: packageName,\n\t\t\t\tTemplate:    template,\n\t\t\t\tFilePaths:   []string{filePath},\n\t\t\t\tDescription: fmt.Sprintf(\"%s模块中的%s\", packageName, moduleName),\n\t\t\t}\n\t\t\tmodules = append(modules, module)\n\t\t}\n\t}\n\n\treturn modules, nil\n}\n"
  },
  {
    "path": "server/mcp/gva_execute.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n)\n\n// 注册工具\nfunc init() {\n\tRegisterTool(&GVAExecutor{})\n}\n\n// GVAExecutor GVA代码生成器\ntype GVAExecutor struct{}\n\n// ExecuteRequest 执行请求结构\ntype ExecuteRequest struct {\n\tExecutionPlan ExecutionPlan `json:\"executionPlan\"` // 执行计划\n\tRequirement   string        `json:\"requirement\"`   // 原始需求（可选，用于日志记录）\n}\n\n// ExecuteResponse 执行响应结构\ntype ExecuteResponse struct {\n\tSuccess        bool              `json:\"success\"`\n\tMessage        string            `json:\"message\"`\n\tPackageID      uint              `json:\"packageId,omitempty\"`\n\tHistoryID      uint              `json:\"historyId,omitempty\"`\n\tPaths          map[string]string `json:\"paths,omitempty\"`\n\tGeneratedPaths []string          `json:\"generatedPaths,omitempty\"`\n\tNextActions    []string          `json:\"nextActions,omitempty\"`\n}\n\n// ExecutionPlan 执行计划结构\ntype ExecutionPlan struct {\n\tPackageName             string                            `json:\"packageName\"`\n\tPackageType             string                            `json:\"packageType\"` // \"plugin\" 或 \"package\"\n\tNeedCreatedPackage      bool                              `json:\"needCreatedPackage\"`\n\tNeedCreatedModules      bool                              `json:\"needCreatedModules\"`\n\tNeedCreatedDictionaries bool                              `json:\"needCreatedDictionaries\"`\n\tPackageInfo             *request.SysAutoCodePackageCreate `json:\"packageInfo,omitempty\"`\n\tModulesInfo             []*request.AutoCode               `json:\"modulesInfo,omitempty\"`\n\tPaths                   map[string]string                 `json:\"paths,omitempty\"`\n\tDictionariesInfo        []*DictionaryGenerateRequest      `json:\"dictionariesInfo,omitempty\"`\n}\n\n// New 创建GVA代码生成执行器工具\nfunc (g *GVAExecutor) New() mcp.Tool {\n    return mcp.NewTool(\"gva_execute\",\n\t\tmcp.WithDescription(`**GVA代码生成执行器：直接执行代码生成，无需确认步骤**\n\n**核心功能：**\n根据需求分析和当前的包信息判断是否调用，直接生成代码。支持批量创建多个模块、自动创建包、模块、字典等。\n\n**使用场景：**\n在gva_analyze获取了当前的包信息和字典信息之后，如果已经包含了可以使用的包和模块，那就不要调用本mcp。根据分析结果直接生成代码，适用于自动化代码生成流程。\n\n**重要提示：**\n- 当needCreatedModules=true时，模块创建会自动生成API和菜单，不应再调用api_creator和menu_creator工具\n- 字段使用字典类型时，系统会自动检查并创建字典\n- 字典创建会在模块创建之前执行\n- 当字段配置了dataSource且association=2（一对多关联）时，系统会自动将fieldType修改为'array'`),\n        mcp.WithObject(\"executionPlan\",\n            mcp.Description(\"执行计划，包含包信息、模块与字典信息\"),\n            mcp.Required(),\n            mcp.Properties(map[string]interface{}{\n                \"packageName\": map[string]interface{}{\n                    \"type\":        \"string\",\n                    \"description\": \"包名（小写开头）\",\n                },\n                \"packageType\": map[string]interface{}{\n                    \"type\":        \"string\",\n                    \"description\": \"package 或 plugin，如果用户提到了使用插件则创建plugin，如果用户没有特定说明则一律选用package\",\n                    \"enum\":        []string{\"package\", \"plugin\"},\n                },\n                \"needCreatedPackage\": map[string]interface{}{\n                    \"type\":        \"boolean\",\n                    \"description\": \"是否需要创建包，为true时packageInfo必需\",\n                },\n                \"needCreatedModules\": map[string]interface{}{\n                    \"type\":        \"boolean\",\n                    \"description\": \"是否需要创建模块，为true时modulesInfo必需\",\n                },\n                \"needCreatedDictionaries\": map[string]interface{}{\n                    \"type\":        \"boolean\",\n                    \"description\": \"是否需要创建字典，为true时dictionariesInfo必需\",\n                },\n                \"packageInfo\": map[string]interface{}{\n                    \"type\":        \"object\",\n                    \"description\": \"包创建信息，当needCreatedPackage=true时必需\",\n                    \"properties\": map[string]interface{}{\n                        \"desc\":        map[string]interface{}{\"type\": \"string\", \"description\": \"包描述\"},\n                        \"label\":       map[string]interface{}{\"type\": \"string\", \"description\": \"展示名\"},\n                        \"template\":    map[string]interface{}{\"type\": \"string\", \"description\": \"package 或 plugin，如果用户提到了使用插件则创建plugin，如果用户没有特定说明则一律选用package\", \"enum\": []string{\"package\", \"plugin\"}},\n                        \"packageName\": map[string]interface{}{\"type\": \"string\", \"description\": \"包名\"},\n                    },\n                },\n                \"modulesInfo\": map[string]interface{}{\n                    \"type\":        \"array\",\n                    \"description\": \"模块配置列表，支持批量创建多个模块\",\n                    \"items\": map[string]interface{}{\n                        \"type\": \"object\",\n                        \"properties\": map[string]interface{}{\n                            \"package\":            map[string]interface{}{\"type\": \"string\", \"description\": \"包名（小写开头，示例: userInfo）\"},\n                            \"tableName\":          map[string]interface{}{\"type\": \"string\", \"description\": \"数据库表名（蛇形命名法,示例:user_info）\"},\n                            \"businessDB\":         map[string]interface{}{\"type\": \"string\", \"description\": \"业务数据库（可留空表示默认）\"},\n                            \"structName\":         map[string]interface{}{\"type\": \"string\", \"description\": \"结构体名（大驼峰示例:UserInfo）\"},\n                            \"packageName\":        map[string]interface{}{\"type\": \"string\", \"description\": \"文件名称\"},\n                            \"description\":        map[string]interface{}{\"type\": \"string\", \"description\": \"中文描述\"},\n                            \"abbreviation\":       map[string]interface{}{\"type\": \"string\", \"description\": \"简称\"},\n                            \"humpPackageName\":    map[string]interface{}{\"type\": \"string\", \"description\": \"文件名称（小驼峰），一般是结构体名的小驼峰示例:userInfo\"},\n                            \"gvaModel\":           map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否使用GVA模型（固定为true），自动包含ID、CreatedAt、UpdatedAt、DeletedAt字段\"},\n                            \"autoMigrate\":        map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否自动迁移数据库\"},\n                            \"autoCreateResource\": map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否创建资源（默认为false）\"},\n                            \"autoCreateApiToSql\": map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否创建API（默认为true）\"},\n                            \"autoCreateMenuToSql\": map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否创建菜单（默认为true）\"},\n                            \"autoCreateBtnAuth\":  map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否创建按钮权限（默认为false）\"},\n                            \"onlyTemplate\":       map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否仅模板（默认为false）\"},\n                            \"isTree\":             map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否树形结构（默认为false）\"},\n                            \"treeJson\":           map[string]interface{}{\"type\": \"string\", \"description\": \"树形JSON字段\"},\n                            \"isAdd\":              map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否新增（固定为false）\"},\n                            \"generateWeb\":        map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否生成前端代码\"},\n                            \"generateServer\":     map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否生成后端代码\"},\n                            \"fields\": map[string]interface{}{\n                                \"type\":        \"array\",\n                                \"description\": \"字段列表\",\n                                \"items\": map[string]interface{}{\n                                    \"type\": \"object\",\n                                    \"properties\": map[string]interface{}{\n                                        \"fieldName\":   map[string]interface{}{\"type\": \"string\", \"description\": \"字段名（必须大写开头示例:UserName）\"},\n                                        \"fieldDesc\":   map[string]interface{}{\"type\": \"string\", \"description\": \"字段描述\"},\n                                        \"fieldType\":   map[string]interface{}{\"type\": \"string\", \"description\": \"字段类型：string（字符串）、richtext（富文本）、int（整型）、bool（布尔值）、float64（浮点型）、time.Time（时间）、enum（枚举）、picture（单图片）、pictures（多图片）、video（视频）、file（文件）、json（JSON）、array（数组）\"},\n                                        \"fieldJson\":   map[string]interface{}{\"type\": \"string\", \"description\": \"JSON标签,示例: userName\"},\n                                        \"dataTypeLong\": map[string]interface{}{\"type\": \"string\", \"description\": \"数据长度\"},\n                                        \"comment\":     map[string]interface{}{\"type\": \"string\", \"description\": \"注释\"},\n                                        \"columnName\":  map[string]interface{}{\"type\": \"string\", \"description\": \"数据库列名,示例: user_name\"},\n                                        \"fieldSearchType\": map[string]interface{}{\"type\": \"string\", \"description\": \"搜索类型：=、!=、>、>=、<、<=、LIKE、BETWEEN、IN、NOT IN、NOT BETWEEN\"},\n                                        \"fieldSearchHide\": map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否隐藏搜索\"},\n                                        \"dictType\":        map[string]interface{}{\"type\": \"string\", \"description\": \"字典类型，使用字典类型时系统会自动检查并创建字典\"},\n                                        \"form\":            map[string]interface{}{\"type\": \"boolean\", \"description\": \"表单显示\"},\n                                        \"table\":           map[string]interface{}{\"type\": \"boolean\", \"description\": \"表格显示\"},\n                                        \"desc\":            map[string]interface{}{\"type\": \"boolean\", \"description\": \"详情显示\"},\n                                        \"excel\":           map[string]interface{}{\"type\": \"boolean\", \"description\": \"导入导出\"},\n                                        \"require\":         map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否必填\"},\n                                        \"defaultValue\":    map[string]interface{}{\"type\": \"string\", \"description\": \"默认值\"},\n                                        \"errorText\":       map[string]interface{}{\"type\": \"string\", \"description\": \"错误提示\"},\n                                        \"clearable\":       map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否可清空\"},\n                                        \"sort\":            map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否排序\"},\n                                        \"primaryKey\":      map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否主键（gvaModel=false时必须有一个字段为true）\"},\n                                        \"dataSource\": map[string]interface{}{\n                                            \"type\":        \"object\",\n                                            \"description\": \"数据源配置，用于配置字段的关联表信息。获取表名提示：可在 server/model 和 plugin/xxx/model 目录下查看对应模块的 TableName() 接口实现获取实际表名（如 SysUser 的表名为 sys_users）。获取数据库名提示：主数据库通常使用 gva（默认数据库标识），多数据库可在 config.yaml 的 db-list 配置中查看可用数据库的 alias-name 字段，如果用户未提及关联多数据库信息则使用默认数据库，默认数据库的情况下 dbName填写为空\",\n                                            \"properties\": map[string]interface{}{\n                                                \"dbName\":       map[string]interface{}{\"type\": \"string\", \"description\": \"关联的数据库名称（默认数据库留空）\"},\n                                                \"table\":        map[string]interface{}{\"type\": \"string\", \"description\": \"关联的表名\"},\n                                                \"label\":        map[string]interface{}{\"type\": \"string\", \"description\": \"用于显示的字段名（如name、title等）\"},\n                                                \"value\":        map[string]interface{}{\"type\": \"string\", \"description\": \"用于存储的值字段名（通常是id）\"},\n                                                \"association\":  map[string]interface{}{\"type\": \"integer\", \"description\": \"关联关系类型：1=一对一关联，2=一对多关联。一对一和一对多的前面的一是当前的实体，如果他只能关联另一个实体的一个则选用一对一，如果他需要关联多个他的关联实体则选用一对多\"},\n                                                \"hasDeletedAt\": map[string]interface{}{\"type\": \"boolean\", \"description\": \"关联表是否有软删除字段\"},\n                                            },\n                                        },\n                                        \"checkDataSource\": map[string]interface{}{\"type\": \"boolean\", \"description\": \"是否检查数据源，启用后会验证关联表的存在性\"},\n                                        \"fieldIndexType\":  map[string]interface{}{\"type\": \"string\", \"description\": \"索引类型\"},\n                                    },\n                                },\n                            },\n                        },\n                    },\n                },\n                \"paths\": map[string]interface{}{\n                    \"type\":        \"object\",\n                    \"description\": \"生成的文件路径映射\",\n                    \"additionalProperties\": map[string]interface{}{\"type\": \"string\"},\n                },\n                \"dictionariesInfo\": map[string]interface{}{\n                    \"type\":        \"array\",\n                    \"description\": \"字典创建信息，字典创建会在模块创建之前执行\",\n                    \"items\": map[string]interface{}{\n                        \"type\": \"object\",\n                        \"properties\": map[string]interface{}{\n                            \"dictType\":    map[string]interface{}{\"type\": \"string\", \"description\": \"字典类型，用于标识字典的唯一性\"},\n                            \"dictName\":    map[string]interface{}{\"type\": \"string\", \"description\": \"字典名称，必须生成，字典的中文名称\"},\n                            \"description\": map[string]interface{}{\"type\": \"string\", \"description\": \"字典描述，字典的用途说明\"},\n                            \"status\":      map[string]interface{}{\"type\": \"boolean\", \"description\": \"字典状态：true启用，false禁用\"},\n                            \"fieldDesc\":   map[string]interface{}{\"type\": \"string\", \"description\": \"字段描述，用于AI理解字段含义并生成合适的选项\"},\n                            \"options\": map[string]interface{}{\n                                \"type\":        \"array\",\n                                \"description\": \"字典选项列表（可选，如果不提供将根据fieldDesc自动生成默认选项）\",\n                                \"items\": map[string]interface{}{\n                                    \"type\": \"object\",\n                                    \"properties\": map[string]interface{}{\n                                        \"label\": map[string]interface{}{\"type\": \"string\", \"description\": \"显示名称，用户看到的选项名\"},\n                                        \"value\": map[string]interface{}{\"type\": \"string\", \"description\": \"选项值，实际存储的值\"},\n                                        \"sort\":  map[string]interface{}{\"type\": \"integer\", \"description\": \"排序号，数字越小越靠前\"},\n                                    },\n                                },\n                            },\n                        },\n                    },\n                },\n            }),\n            mcp.AdditionalProperties(false),\n        ),\n\t\tmcp.WithString(\"requirement\",\n\t\t\tmcp.Description(\"原始需求描述（可选，用于日志记录）\"),\n\t\t),\n\t)\n}\n\n// Handle 处理执行请求（移除确认步骤）\nfunc (g *GVAExecutor) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\texecutionPlanData, ok := request.GetArguments()[\"executionPlan\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"参数错误：executionPlan 必须提供\")\n\t}\n\n\t// 解析执行计划\n\tplanJSON, err := json.Marshal(executionPlanData)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"解析执行计划失败: %v\", err)\n\t}\n\n\tvar plan ExecutionPlan\n\terr = json.Unmarshal(planJSON, &plan)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"解析执行计划失败: %v\\n\\n请确保ExecutionPlan格式正确，参考工具描述中的结构体格式要求\", err)\n\t}\n\n\t// 验证执行计划的完整性\n\tif err := g.validateExecutionPlan(&plan); err != nil {\n\t\treturn nil, fmt.Errorf(\"执行计划验证失败: %v\", err)\n\t}\n\n\t// 获取原始需求（可选）\n\tvar originalRequirement string\n\tif reqData, ok := request.GetArguments()[\"requirement\"]; ok {\n\t\tif reqStr, ok := reqData.(string); ok {\n\t\t\toriginalRequirement = reqStr\n\t\t}\n\t}\n\n\t// 直接执行创建操作（无确认步骤）\n\tresult := g.executeCreation(ctx, &plan)\n\n\t// 如果执行成功且有原始需求，提供代码复检建议\n\tvar reviewMessage string\n\tif result.Success && originalRequirement != \"\" {\n\t\tglobal.GVA_LOG.Info(\"执行完成，返回生成的文件路径供AI进行代码复检...\")\n\n\t\t// 构建文件路径信息供AI使用\n\t\tvar pathsInfo []string\n\t\tfor _, path := range result.GeneratedPaths {\n\t\t\tpathsInfo = append(pathsInfo, fmt.Sprintf(\"- %s\", path))\n\t\t}\n\n\t\treviewMessage = fmt.Sprintf(\"\\n\\n📁 已生成以下文件：\\n%s\\n\\n💡 提示：可以检查生成的代码是否满足原始需求。\", strings.Join(pathsInfo, \"\\n\"))\n\t} else if originalRequirement == \"\" {\n\t\treviewMessage = \"\\n\\n💡 提示：如需代码复检，请提供原始需求描述。\"\n\t}\n\n\t// 序列化响应\n\tresponse := ExecuteResponse{\n\t\tSuccess:        result.Success,\n\t\tMessage:        result.Message,\n\t\tPackageID:      result.PackageID,\n\t\tHistoryID:      result.HistoryID,\n\t\tPaths:          result.Paths,\n\t\tGeneratedPaths: result.GeneratedPaths,\n\t\tNextActions:    result.NextActions,\n\t}\n\n\tresponseJSON, err := json.MarshalIndent(response, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化结果失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.NewTextContent(fmt.Sprintf(\"执行结果：\\n\\n%s%s\", string(responseJSON), reviewMessage)),\n\t\t},\n\t}, nil\n}\n\n// validateExecutionPlan 验证执行计划的完整性\nfunc (g *GVAExecutor) validateExecutionPlan(plan *ExecutionPlan) error {\n\t// 验证基本字段\n\tif plan.PackageName == \"\" {\n\t\treturn errors.New(\"packageName 不能为空\")\n\t}\n\tif plan.PackageType != \"package\" && plan.PackageType != \"plugin\" {\n\t\treturn errors.New(\"packageType 必须是 'package' 或 'plugin'\")\n\t}\n\n\t// 验证packageType和template字段的一致性\n\tif plan.NeedCreatedPackage && plan.PackageInfo != nil {\n\t\tif plan.PackageType != plan.PackageInfo.Template {\n\t\t\treturn errors.New(\"packageType 和 packageInfo.template 必须保持一致\")\n\t\t}\n\t}\n\n\t// 验证包信息\n\tif plan.NeedCreatedPackage {\n\t\tif plan.PackageInfo == nil {\n\t\t\treturn errors.New(\"当 needCreatedPackage=true 时，packageInfo 不能为空\")\n\t\t}\n\t\tif plan.PackageInfo.PackageName == \"\" {\n\t\t\treturn errors.New(\"packageInfo.packageName 不能为空\")\n\t\t}\n\t\tif plan.PackageInfo.Template != \"package\" && plan.PackageInfo.Template != \"plugin\" {\n\t\t\treturn errors.New(\"packageInfo.template 必须是 'package' 或 'plugin'\")\n\t\t}\n\t\tif plan.PackageInfo.Label == \"\" {\n\t\t\treturn errors.New(\"packageInfo.label 不能为空\")\n\t\t}\n\t\tif plan.PackageInfo.Desc == \"\" {\n\t\t\treturn errors.New(\"packageInfo.desc 不能为空\")\n\t\t}\n\t}\n\n\t// 验证模块信息（批量验证）\n\tif plan.NeedCreatedModules {\n\t\tif len(plan.ModulesInfo) == 0 {\n\t\t\treturn errors.New(\"当 needCreatedModules=true 时，modulesInfo 不能为空\")\n\t\t}\n\n\t\t// 遍历验证每个模块\n\t\tfor moduleIndex, moduleInfo := range plan.ModulesInfo {\n\t\t\tif moduleInfo.Package == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 package 不能为空\", moduleIndex+1)\n\t\t\t}\n\t\t\tif moduleInfo.StructName == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 structName 不能为空\", moduleIndex+1)\n\t\t\t}\n\t\t\tif moduleInfo.TableName == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 tableName 不能为空\", moduleIndex+1)\n\t\t\t}\n\t\t\tif moduleInfo.Description == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 description 不能为空\", moduleIndex+1)\n\t\t\t}\n\t\t\tif moduleInfo.Abbreviation == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 abbreviation 不能为空\", moduleIndex+1)\n\t\t\t}\n\t\t\tif moduleInfo.PackageName == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 packageName 不能为空\", moduleIndex+1)\n\t\t\t}\n\t\t\tif moduleInfo.HumpPackageName == \"\" {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 humpPackageName 不能为空\", moduleIndex+1)\n\t\t\t}\n\n\t\t\t// 验证字段信息\n\t\t\tif len(moduleInfo.Fields) == 0 {\n\t\t\t\treturn fmt.Errorf(\"模块 %d 的 fields 不能为空，至少需要一个字段\", moduleIndex+1)\n\t\t\t}\n\n\t\t\tfor i, field := range moduleInfo.Fields {\n\t\t\t\tif field.FieldName == \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 fieldName 不能为空\", moduleIndex+1, i+1)\n\t\t\t\t}\n\n\t\t\t\t// 确保字段名首字母大写\n\t\t\t\tif len(field.FieldName) > 0 {\n\t\t\t\t\tfirstChar := string(field.FieldName[0])\n\t\t\t\t\tif firstChar >= \"a\" && firstChar <= \"z\" {\n\t\t\t\t\t\tmoduleInfo.Fields[i].FieldName = strings.ToUpper(firstChar) + field.FieldName[1:]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif field.FieldDesc == \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 fieldDesc 不能为空\", moduleIndex+1, i+1)\n\t\t\t\t}\n\t\t\t\tif field.FieldType == \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 fieldType 不能为空\", moduleIndex+1, i+1)\n\t\t\t\t}\n\t\t\t\tif field.FieldJson == \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 fieldJson 不能为空\", moduleIndex+1, i+1)\n\t\t\t\t}\n\t\t\t\tif field.ColumnName == \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 columnName 不能为空\", moduleIndex+1, i+1)\n\t\t\t\t}\n\n\t\t\t\t// 验证字段类型\n\t\t\t\tvalidFieldTypes := []string{\"string\", \"int\", \"int64\", \"float64\", \"bool\", \"time.Time\", \"enum\", \"picture\", \"video\", \"file\", \"pictures\", \"array\", \"richtext\", \"json\"}\n\t\t\t\tvalidType := false\n\t\t\t\tfor _, validFieldType := range validFieldTypes {\n\t\t\t\t\tif field.FieldType == validFieldType {\n\t\t\t\t\t\tvalidType = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !validType {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 fieldType '%s' 不支持，支持的类型：%v\", moduleIndex+1, i+1, field.FieldType, validFieldTypes)\n\t\t\t\t}\n\n\t\t\t\t// 验证搜索类型（如果设置了）\n\t\t\t\tif field.FieldSearchType != \"\" {\n\t\t\t\t\tvalidSearchTypes := []string{\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"LIKE\", \"BETWEEN\", \"IN\", \"NOT IN\"}\n\t\t\t\t\tvalidSearchType := false\n\t\t\t\t\tfor _, validType := range validSearchTypes {\n\t\t\t\t\t\tif field.FieldSearchType == validType {\n\t\t\t\t\t\t\tvalidSearchType = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !validSearchType {\n\t\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 fieldSearchType '%s' 不支持，支持的类型：%v\", moduleIndex+1, i+1, field.FieldSearchType, validSearchTypes)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// 验证 dataSource 字段配置\n\t\t\t\tif field.DataSource != nil {\n\t\t\t\t\tassociationValue := field.DataSource.Association\n\t\t\t\t\t// 当 association 为 2（一对多关联）时，强制修改 fieldType 为 array\n\t\t\t\t\tif associationValue == 2 {\n\t\t\t\t\t\tif field.FieldType != \"array\" {\n\t\t\t\t\t\t\tglobal.GVA_LOG.Info(fmt.Sprintf(\"模块 %d 字段 %d：检测到一对多关联(association=2)，自动将 fieldType 从 '%s' 修改为 'array'\", moduleIndex+1, i+1, field.FieldType))\n\t\t\t\t\t\t\tmoduleInfo.Fields[i].FieldType = \"array\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// 验证 association 值的有效性\n\t\t\t\t\tif associationValue != 1 && associationValue != 2 {\n\t\t\t\t\t\treturn fmt.Errorf(\"模块 %d 字段 %d 的 dataSource.association 必须是 1（一对一）或 2（一对多）\", moduleIndex+1, i+1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 验证主键设置\n\t\t\tif !moduleInfo.GvaModel {\n\t\t\t\t// 当不使用GVA模型时，必须有且仅有一个字段设置为主键\n\t\t\t\tprimaryKeyCount := 0\n\t\t\t\tfor _, field := range moduleInfo.Fields {\n\t\t\t\t\tif field.PrimaryKey {\n\t\t\t\t\t\tprimaryKeyCount++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif primaryKeyCount == 0 {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d：当 gvaModel=false 时，必须有一个字段的 primaryKey=true\", moduleIndex+1)\n\t\t\t\t}\n\t\t\t\tif primaryKeyCount > 1 {\n\t\t\t\t\treturn fmt.Errorf(\"模块 %d：当 gvaModel=false 时，只能有一个字段的 primaryKey=true\", moduleIndex+1)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// 当使用GVA模型时，所有字段的primaryKey都应该为false\n\t\t\t\tfor i, field := range moduleInfo.Fields {\n\t\t\t\t\tif field.PrimaryKey {\n\t\t\t\t\t\treturn fmt.Errorf(\"模块 %d：当 gvaModel=true 时，字段 %d 的 primaryKey 应该为 false，系统会自动创建ID主键\", moduleIndex+1, i+1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// executeCreation 执行创建操作\nfunc (g *GVAExecutor) executeCreation(ctx context.Context, plan *ExecutionPlan) *ExecuteResponse {\n\tresult := &ExecuteResponse{\n\t\tSuccess:        false,\n\t\tPaths:          make(map[string]string),\n\t\tGeneratedPaths: []string{}, // 初始化生成文件路径列表\n\t}\n\n\t// 无论如何都先构建目录结构信息，确保paths始终返回\n\tresult.Paths = g.buildDirectoryStructure(plan)\n\n\t// 记录预期生成的文件路径\n\tresult.GeneratedPaths = g.collectExpectedFilePaths(plan)\n\n\tif !plan.NeedCreatedModules {\n\t\tresult.Success = true\n\t\tresult.Message += \"已列出当前功能所涉及的目录结构信息; 请在paths中查看; 并且在对应指定文件中实现相关的业务逻辑; \"\n\t\treturn result\n\t}\n\n\t// 创建包（如果需要）\n\tif plan.NeedCreatedPackage && plan.PackageInfo != nil {\n\t\tpackageService := service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage\n\t\terr := packageService.Create(ctx, plan.PackageInfo)\n\t\tif err != nil {\n\t\t\tresult.Message = fmt.Sprintf(\"创建包失败: %v\", err)\n\t\t\t// 即使创建包失败，也要返回paths信息\n\t\t\treturn result\n\t\t}\n\t\tresult.Message += \"包创建成功; \"\n\t}\n\n\t// 创建指定字典（如果需要）\n\tif plan.NeedCreatedDictionaries && len(plan.DictionariesInfo) > 0 {\n\t\tdictResult := g.createDictionariesFromInfo(ctx, plan.DictionariesInfo)\n\t\tresult.Message += dictResult\n\t}\n\n\t// 批量创建字典和模块（如果需要）\n\tif plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {\n\t\ttemplateService := service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate\n\n\t\t// 遍历所有模块进行创建\n\t\tfor _, moduleInfo := range plan.ModulesInfo {\n\n\t\t\t// 创建模块\n\t\t\terr := moduleInfo.Pretreatment()\n\t\t\tif err != nil {\n\t\t\t\tresult.Message += fmt.Sprintf(\"模块 %s 信息预处理失败: %v; \", moduleInfo.StructName, err)\n\t\t\t\tcontinue // 继续处理下一个模块\n\t\t\t}\n\n\t\t\terr = templateService.Create(ctx, *moduleInfo)\n\t\t\tif err != nil {\n\t\t\t\tresult.Message += fmt.Sprintf(\"创建模块 %s 失败: %v; \", moduleInfo.StructName, err)\n\t\t\t\tcontinue // 继续处理下一个模块\n\t\t\t}\n\t\t\tresult.Message += fmt.Sprintf(\"模块 %s 创建成功; \", moduleInfo.StructName)\n\t\t}\n\n\t\tresult.Message += fmt.Sprintf(\"批量创建完成，共处理 %d 个模块; \", len(plan.ModulesInfo))\n\n\t\t// 添加重要提醒：不要使用其他MCP工具\n\t\tresult.Message += \"\\n\\n⚠️ 重要提醒：\\n\"\n\t\tresult.Message += \"模块创建已完成，API和菜单已自动生成。请不要再调用以下MCP工具：\\n\"\n\t\tresult.Message += \"- api_creator：API权限已在模块创建时自动生成\\n\"\n\t\tresult.Message += \"- menu_creator：前端菜单已在模块创建时自动生成\\n\"\n\t\tresult.Message += \"如需修改API或菜单，请直接在系统管理界面中进行配置。\\n\"\n\t}\n\n\tresult.Message += \"已构建目录结构信息; \"\n\tresult.Success = true\n\n\tif result.Message == \"\" {\n\t\tresult.Message = \"执行计划完成\"\n\t}\n\n\treturn result\n}\n\n// buildDirectoryStructure 构建目录结构信息\nfunc (g *GVAExecutor) buildDirectoryStructure(plan *ExecutionPlan) map[string]string {\n\tpaths := make(map[string]string)\n\n\t// 获取配置信息\n\tautoCodeConfig := global.GVA_CONFIG.AutoCode\n\n\t// 构建基础路径\n\trootPath := autoCodeConfig.Root\n\tserverPath := autoCodeConfig.Server\n\twebPath := autoCodeConfig.Web\n\tmoduleName := autoCodeConfig.Module\n\n\t// 如果计划中有包名，使用计划中的包名，否则使用默认\n\tpackageName := \"example\"\n\tif plan.PackageName != \"\" {\n\t\tpackageName = plan.PackageName\n\t}\n\n\t// 如果计划中有模块信息，获取第一个模块的结构名作为默认值\n\tstructName := \"ExampleStruct\"\n\tif len(plan.ModulesInfo) > 0 && plan.ModulesInfo[0].StructName != \"\" {\n\t\tstructName = plan.ModulesInfo[0].StructName\n\t}\n\n\t// 根据包类型构建不同的路径结构\n\tpackageType := plan.PackageType\n\tif packageType == \"\" {\n\t\tpackageType = \"package\" // 默认为package模式\n\t}\n\n\t// 构建服务端路径\n\tif serverPath != \"\" {\n\t\tserverBasePath := fmt.Sprintf(\"%s/%s\", rootPath, serverPath)\n\n\t\tif packageType == \"plugin\" {\n\t\t\t// Plugin 模式：所有文件都在 /plugin/packageName/ 目录下\n\t\t\tplugingBasePath := fmt.Sprintf(\"%s/plugin/%s\", serverBasePath, packageName)\n\n\t\t\t// API 路径\n\t\t\tpaths[\"api\"] = fmt.Sprintf(\"%s/api\", plugingBasePath)\n\n\t\t\t// Service 路径\n\t\t\tpaths[\"service\"] = fmt.Sprintf(\"%s/service\", plugingBasePath)\n\n\t\t\t// Model 路径\n\t\t\tpaths[\"model\"] = fmt.Sprintf(\"%s/model\", plugingBasePath)\n\n\t\t\t// Router 路径\n\t\t\tpaths[\"router\"] = fmt.Sprintf(\"%s/router\", plugingBasePath)\n\n\t\t\t// Request 路径\n\t\t\tpaths[\"request\"] = fmt.Sprintf(\"%s/model/request\", plugingBasePath)\n\n\t\t\t// Response 路径\n\t\t\tpaths[\"response\"] = fmt.Sprintf(\"%s/model/response\", plugingBasePath)\n\n\t\t\t// Plugin 特有文件\n\t\t\tpaths[\"plugin_main\"] = fmt.Sprintf(\"%s/main.go\", plugingBasePath)\n\t\t\tpaths[\"plugin_config\"] = fmt.Sprintf(\"%s/plugin.go\", plugingBasePath)\n\t\t\tpaths[\"plugin_initialize\"] = fmt.Sprintf(\"%s/initialize\", plugingBasePath)\n\t\t} else {\n\t\t\t// Package 模式：传统的目录结构\n\t\t\t// API 路径\n\t\t\tpaths[\"api\"] = fmt.Sprintf(\"%s/api/v1/%s\", serverBasePath, packageName)\n\n\t\t\t// Service 路径\n\t\t\tpaths[\"service\"] = fmt.Sprintf(\"%s/service/%s\", serverBasePath, packageName)\n\n\t\t\t// Model 路径\n\t\t\tpaths[\"model\"] = fmt.Sprintf(\"%s/model/%s\", serverBasePath, packageName)\n\n\t\t\t// Router 路径\n\t\t\tpaths[\"router\"] = fmt.Sprintf(\"%s/router/%s\", serverBasePath, packageName)\n\n\t\t\t// Request 路径\n\t\t\tpaths[\"request\"] = fmt.Sprintf(\"%s/model/%s/request\", serverBasePath, packageName)\n\n\t\t\t// Response 路径\n\t\t\tpaths[\"response\"] = fmt.Sprintf(\"%s/model/%s/response\", serverBasePath, packageName)\n\t\t}\n\t}\n\n\t// 构建前端路径（两种模式相同）\n\tif webPath != \"\" {\n\t\twebBasePath := fmt.Sprintf(\"%s/%s\", rootPath, webPath)\n\n\t\tif packageType == \"plugin\" {\n\t\t\t// Plugin 模式：前端文件也在 /plugin/packageName/ 目录下\n\t\t\tpluginWebBasePath := fmt.Sprintf(\"%s/plugin/%s\", webBasePath, packageName)\n\n\t\t\t// Vue 页面路径\n\t\t\tpaths[\"vue_page\"] = fmt.Sprintf(\"%s/view\", pluginWebBasePath)\n\n\t\t\t// API 路径\n\t\t\tpaths[\"vue_api\"] = fmt.Sprintf(\"%s/api\", pluginWebBasePath)\n\t\t} else {\n\t\t\t// Package 模式：传统的目录结构\n\t\t\t// Vue 页面路径\n\t\t\tpaths[\"vue_page\"] = fmt.Sprintf(\"%s/view/%s\", webBasePath, packageName)\n\n\t\t\t// API 路径\n\t\t\tpaths[\"vue_api\"] = fmt.Sprintf(\"%s/api/%s\", webBasePath, packageName)\n\t\t}\n\t}\n\n\t// 添加模块信息\n\tpaths[\"module\"] = moduleName\n\tpaths[\"package_name\"] = packageName\n\tpaths[\"package_type\"] = packageType\n\tpaths[\"struct_name\"] = structName\n\tpaths[\"root_path\"] = rootPath\n\tpaths[\"server_path\"] = serverPath\n\tpaths[\"web_path\"] = webPath\n\n\treturn paths\n}\n\n// collectExpectedFilePaths 收集预期生成的文件路径\nfunc (g *GVAExecutor) collectExpectedFilePaths(plan *ExecutionPlan) []string {\n\tvar paths []string\n\n\t// 获取目录结构\n\tdirPaths := g.buildDirectoryStructure(plan)\n\n\t// 如果需要创建模块，添加预期的文件路径\n\tif plan.NeedCreatedModules && len(plan.ModulesInfo) > 0 {\n\t\tfor _, moduleInfo := range plan.ModulesInfo {\n\t\t\tstructName := moduleInfo.StructName\n\n\t\t\t// 后端文件\n\t\t\tif apiPath, ok := dirPaths[\"api\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.go\", apiPath, strings.ToLower(structName)))\n\t\t\t}\n\t\t\tif servicePath, ok := dirPaths[\"service\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.go\", servicePath, strings.ToLower(structName)))\n\t\t\t}\n\t\t\tif modelPath, ok := dirPaths[\"model\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.go\", modelPath, strings.ToLower(structName)))\n\t\t\t}\n\t\t\tif routerPath, ok := dirPaths[\"router\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.go\", routerPath, strings.ToLower(structName)))\n\t\t\t}\n\t\t\tif requestPath, ok := dirPaths[\"request\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.go\", requestPath, strings.ToLower(structName)))\n\t\t\t}\n\t\t\tif responsePath, ok := dirPaths[\"response\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.go\", responsePath, strings.ToLower(structName)))\n\t\t\t}\n\n\t\t\t// 前端文件\n\t\t\tif vuePage, ok := dirPaths[\"vue_page\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.vue\", vuePage, strings.ToLower(structName)))\n\t\t\t}\n\t\t\tif vueApi, ok := dirPaths[\"vue_api\"]; ok {\n\t\t\t\tpaths = append(paths, fmt.Sprintf(\"%s/%s.js\", vueApi, strings.ToLower(structName)))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn paths\n}\n\n// checkDictionaryExists 检查字典是否存在\nfunc (g *GVAExecutor) checkDictionaryExists(dictType string) (bool, error) {\n\tdictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService\n\t_, err := dictionaryService.GetSysDictionary(dictType, 0, nil)\n\tif err != nil {\n\t\t// 如果是记录不存在的错误，返回false\n\t\tif strings.Contains(err.Error(), \"record not found\") {\n\t\t\treturn false, nil\n\t\t}\n\t\t// 其他错误返回错误信息\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// createDictionariesFromInfo 根据 DictionariesInfo 创建字典\nfunc (g *GVAExecutor) createDictionariesFromInfo(ctx context.Context, dictionariesInfo []*DictionaryGenerateRequest) string {\n\tvar messages []string\n\tdictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService\n\tdictionaryDetailService := service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService\n\n\tmessages = append(messages, fmt.Sprintf(\"开始创建 %d 个指定字典: \", len(dictionariesInfo)))\n\n\tfor _, dictInfo := range dictionariesInfo {\n\t\t// 检查字典是否存在\n\t\texists, err := g.checkDictionaryExists(dictInfo.DictType)\n\t\tif err != nil {\n\t\t\tmessages = append(messages, fmt.Sprintf(\"检查字典 %s 时出错: %v; \", dictInfo.DictType, err))\n\t\t\tcontinue\n\t\t}\n\n\t\tif !exists {\n\t\t\t// 字典不存在，创建字典\n\t\t\tdictionary := model.SysDictionary{\n\t\t\t\tName:   dictInfo.DictName,\n\t\t\t\tType:   dictInfo.DictType,\n\t\t\t\tStatus: utils.Pointer(true),\n\t\t\t\tDesc:   dictInfo.Description,\n\t\t\t}\n\n\t\t\terr = dictionaryService.CreateSysDictionary(dictionary)\n\t\t\tif err != nil {\n\t\t\t\tmessages = append(messages, fmt.Sprintf(\"创建字典 %s 失败: %v; \", dictInfo.DictType, err))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmessages = append(messages, fmt.Sprintf(\"成功创建字典 %s (%s); \", dictInfo.DictType, dictInfo.DictName))\n\n\t\t\t// 获取刚创建的字典ID\n\t\t\tvar createdDict model.SysDictionary\n\t\t\terr = global.GVA_DB.Where(\"type = ?\", dictInfo.DictType).First(&createdDict).Error\n\t\t\tif err != nil {\n\t\t\t\tmessages = append(messages, fmt.Sprintf(\"获取创建的字典失败: %v; \", err))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 创建字典选项\n\t\t\tif len(dictInfo.Options) > 0 {\n\t\t\t\tsuccessCount := 0\n\t\t\t\tfor _, option := range dictInfo.Options {\n\t\t\t\t\tdictionaryDetail := model.SysDictionaryDetail{\n\t\t\t\t\t\tLabel:           option.Label,\n\t\t\t\t\t\tValue:           option.Value,\n\t\t\t\t\t\tStatus:          &[]bool{true}[0], // 默认启用\n\t\t\t\t\t\tSort:            option.Sort,\n\t\t\t\t\t\tSysDictionaryID: int(createdDict.ID),\n\t\t\t\t\t}\n\n\t\t\t\t\terr = dictionaryDetailService.CreateSysDictionaryDetail(dictionaryDetail)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tglobal.GVA_LOG.Warn(\"创建字典详情项失败\", zap.Error(err))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsuccessCount++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmessages = append(messages, fmt.Sprintf(\"创建了 %d 个字典选项; \", successCount))\n\t\t\t}\n\t\t} else {\n\t\t\tmessages = append(messages, fmt.Sprintf(\"字典 %s 已存在，跳过创建; \", dictInfo.DictType))\n\t\t}\n\t}\n\n\treturn strings.Join(messages, \"\")\n}\n"
  },
  {
    "path": "server/mcp/gva_review.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\n// GVAReviewer GVA代码审查工具\ntype GVAReviewer struct{}\n\n// init 注册工具\nfunc init() {\n\tRegisterTool(&GVAReviewer{})\n}\n\n// ReviewRequest 审查请求结构\ntype ReviewRequest struct {\n\tUserRequirement string   `json:\"userRequirement\"` // 经过requirement_analyze后的用户需求\n\tGeneratedFiles  []string `json:\"generatedFiles\"`  // gva_execute创建的文件列表\n}\n\n// ReviewResponse 审查响应结构\ntype ReviewResponse struct {\n\tSuccess          bool   `json:\"success\"`          // 是否审查成功\n\tMessage          string `json:\"message\"`          // 审查结果消息\n\tAdjustmentPrompt string `json:\"adjustmentPrompt\"` // 调整代码的提示\n\tReviewDetails    string `json:\"reviewDetails\"`    // 详细的审查结果\n}\n\n// New 创建GVA代码审查工具\nfunc (g *GVAReviewer) New() mcp.Tool {\n\treturn mcp.NewTool(\"gva_review\",\n\t\tmcp.WithDescription(`**GVA代码审查工具 - 在gva_execute调用后使用**\n\n**核心功能：**\n- 接收经过requirement_analyze处理的用户需求和gva_execute生成的文件列表\n- 分析生成的代码是否满足用户的原始需求\n- 检查是否涉及到关联、交互等复杂功能\n- 如果代码不满足需求，提供调整建议和新的prompt\n\n**使用场景：**\n- 在gva_execute成功执行后调用\n- 用于验证生成的代码是否完整满足用户需求\n- 检查模块间的关联关系是否正确实现\n- 发现缺失的交互功能或业务逻辑\n\n**工作流程：**\n1. 接收用户原始需求和生成的文件列表\n2. 分析需求中的关键功能点\n3. 检查生成的文件是否覆盖所有功能\n4. 识别缺失的关联关系、交互功能等\n5. 生成调整建议和新的开发prompt\n\n**输出内容：**\n- 审查结果和是否需要调整\n- 详细的缺失功能分析\n- 针对性的代码调整建议\n- 可直接使用的开发prompt\n\n**重要提示：**\n- 本工具专门用于代码质量审查，不执行实际的代码修改\n- 重点关注模块间关联、用户交互、业务流程完整性\n- 提供的调整建议应该具体可执行`),\n\t\tmcp.WithString(\"userRequirement\",\n\t\t\tmcp.Description(\"经过requirement_analyze处理后的用户需求描述，包含详细的功能要求和字段信息\"),\n\t\t\tmcp.Required(),\n\t\t),\n\t\tmcp.WithString(\"generatedFiles\",\n\t\t\tmcp.Description(\"gva_execute创建的文件列表，JSON字符串格式，包含所有生成的后端和前端文件路径\"),\n\t\t\tmcp.Required(),\n\t\t),\n\t)\n}\n\n// Handle 处理审查请求\nfunc (g *GVAReviewer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// 获取用户需求\n\tuserRequirementData, ok := request.GetArguments()[\"userRequirement\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"参数错误：userRequirement 必须提供\")\n\t}\n\n\tuserRequirement, ok := userRequirementData.(string)\n\tif !ok {\n\t\treturn nil, errors.New(\"参数错误：userRequirement 必须是字符串类型\")\n\t}\n\n\t// 获取生成的文件列表\n\tgeneratedFilesData, ok := request.GetArguments()[\"generatedFiles\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"参数错误：generatedFiles 必须提供\")\n\t}\n\n\tgeneratedFilesStr, ok := generatedFilesData.(string)\n\tif !ok {\n\t\treturn nil, errors.New(\"参数错误：generatedFiles 必须是JSON字符串\")\n\t}\n\n\t// 解析JSON字符串为字符串数组\n\tvar generatedFiles []string\n\terr := json.Unmarshal([]byte(generatedFilesStr), &generatedFiles)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"解析generatedFiles失败: %v\", err)\n\t}\n\n\tif len(generatedFiles) == 0 {\n\t\treturn nil, errors.New(\"参数错误：generatedFiles 不能为空\")\n\t}\n\n\t// 直接生成调整提示，不进行复杂分析\n\tadjustmentPrompt := g.generateAdjustmentPrompt(userRequirement, generatedFiles)\n\n\t// 构建简化的审查详情\n\treviewDetails := fmt.Sprintf(\"📋 **代码审查报告**\\n\\n **用户原始需求：**\\n%s\\n\\n **已生成文件数量：** %d\\n\\n **建议进行代码优化和完善**\", userRequirement, len(generatedFiles))\n\n\t// 构建审查结果\n\treviewResult := &ReviewResponse{\n\t\tSuccess:          true,\n\t\tMessage:          \"代码审查完成\",\n\t\tAdjustmentPrompt: adjustmentPrompt,\n\t\tReviewDetails:    reviewDetails,\n\t}\n\n\t// 序列化响应\n\tresponseJSON, err := json.MarshalIndent(reviewResult, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化审查结果失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.NewTextContent(fmt.Sprintf(\"代码审查结果：\\n\\n%s\", string(responseJSON))),\n\t\t},\n\t}, nil\n}\n\n// generateAdjustmentPrompt 生成调整代码的提示\nfunc (g *GVAReviewer) generateAdjustmentPrompt(userRequirement string, generatedFiles []string) string {\n\tvar prompt strings.Builder\n\n\tprompt.WriteString(\"🔧 **代码调整指导 Prompt：**\\n\\n\")\n\tprompt.WriteString(fmt.Sprintf(\"**用户的原始需求为：** %s\\n\\n\", userRequirement))\n\tprompt.WriteString(\"**经过GVA生成后的文件有如下内容：**\\n\")\n\tfor _, file := range generatedFiles {\n\t\tprompt.WriteString(fmt.Sprintf(\"- %s\\n\", file))\n\t}\n\tprompt.WriteString(\"\\n\")\n\n\tprompt.WriteString(\"**请帮我优化和完善代码，确保：**\\n\")\n\tprompt.WriteString(\"1. 代码完全满足用户的原始需求\\n\")\n\tprompt.WriteString(\"2. 完善模块间的关联关系，确保数据一致性\\n\")\n\tprompt.WriteString(\"3. 实现所有必要的用户交互功能\\n\")\n\tprompt.WriteString(\"4. 保持代码的完整性和可维护性\\n\")\n\tprompt.WriteString(\"5. 遵循GVA框架的开发规范和最佳实践\\n\")\n\tprompt.WriteString(\"6. 确保前后端功能完整对接\\n\")\n\tprompt.WriteString(\"7. 添加必要的错误处理和数据验证\\n\\n\")\n\tprompt.WriteString(\"8. 如果需要vue路由跳转，请使用 menu_lister获取完整路由表，并且路由跳转使用 router.push({\\\"name\\\":从menu_lister中获取的name})\\n\\n\")\n\tprompt.WriteString(\"9. 如果当前所有的vue页面内容无法满足需求，则自行书写vue文件，并且调用 menu_creator创建菜单记录\\n\\n\")\n\tprompt.WriteString(\"10. 如果需要API调用，请使用 api_lister获取api表，根据需求调用对应接口\\n\\n\")\n\tprompt.WriteString(\"11. 如果当前所有API无法满足则自行书写接口，补全前后端代码，并使用 api_creator创建api记录\\n\\n\")\n\tprompt.WriteString(\"12. 无论前后端都不要随意删除import的内容\\n\\n\")\n\tprompt.WriteString(\"**请基于用户需求和现有文件，提供完整的代码优化方案。**\")\n\n\treturn prompt.String()\n}\n"
  },
  {
    "path": "server/mcp/menu_creator.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n)\n\n// 注册工具\nfunc init() {\n\tRegisterTool(&MenuCreator{})\n}\n\n// MenuCreateRequest 菜单创建请求结构\ntype MenuCreateRequest struct {\n\tParentId    uint                   `json:\"parentId\"`    // 父菜单ID，0表示根菜单\n\tPath        string                 `json:\"path\"`        // 路由path\n\tName        string                 `json:\"name\"`        // 路由name\n\tHidden      bool                   `json:\"hidden\"`      // 是否在列表隐藏\n\tComponent   string                 `json:\"component\"`   // 对应前端文件路径\n\tSort        int                    `json:\"sort\"`        // 排序标记\n\tTitle       string                 `json:\"title\"`       // 菜单名\n\tIcon        string                 `json:\"icon\"`        // 菜单图标\n\tKeepAlive   bool                   `json:\"keepAlive\"`   // 是否缓存\n\tDefaultMenu bool                   `json:\"defaultMenu\"` // 是否是基础路由\n\tCloseTab    bool                   `json:\"closeTab\"`    // 自动关闭tab\n\tActiveName  string                 `json:\"activeName\"`  // 高亮菜单\n\tParameters  []MenuParameterRequest `json:\"parameters\"`  // 路由参数\n\tMenuBtn     []MenuButtonRequest    `json:\"menuBtn\"`     // 菜单按钮\n}\n\n// MenuParameterRequest 菜单参数请求结构\ntype MenuParameterRequest struct {\n\tType  string `json:\"type\"`  // 参数类型：params或query\n\tKey   string `json:\"key\"`   // 参数key\n\tValue string `json:\"value\"` // 参数值\n}\n\n// MenuButtonRequest 菜单按钮请求结构\ntype MenuButtonRequest struct {\n\tName string `json:\"name\"` // 按钮名称\n\tDesc string `json:\"desc\"` // 按钮描述\n}\n\n// MenuCreateResponse 菜单创建响应结构\ntype MenuCreateResponse struct {\n\tSuccess bool   `json:\"success\"`\n\tMessage string `json:\"message\"`\n\tMenuID  uint   `json:\"menuId\"`\n\tName    string `json:\"name\"`\n\tPath    string `json:\"path\"`\n}\n\n// MenuCreator 菜单创建工具\ntype MenuCreator struct{}\n\n// New 创建菜单创建工具\nfunc (m *MenuCreator) New() mcp.Tool {\n\treturn mcp.NewTool(\"create_menu\",\n\t\tmcp.WithDescription(`创建前端菜单记录，用于AI编辑器自动添加前端页面时自动创建对应的菜单项。\n\n**重要限制：**\n- 当使用gva_auto_generate工具且needCreatedModules=true时，模块创建会自动生成菜单项，不应调用此工具\n- 仅在以下情况使用：1) 单独创建菜单（不涉及模块创建）；2) AI编辑器自动添加前端页面时`),\n\t\tmcp.WithNumber(\"parentId\",\n\t\t\tmcp.Description(\"父菜单ID，0表示根菜单\"),\n\t\t\tmcp.DefaultNumber(0),\n\t\t),\n\t\tmcp.WithString(\"path\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"路由path，如：userList\"),\n\t\t),\n\t\tmcp.WithString(\"name\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"路由name，用于Vue Router，如：userList\"),\n\t\t),\n\t\tmcp.WithBoolean(\"hidden\",\n\t\t\tmcp.Description(\"是否在菜单列表中隐藏\"),\n\t\t),\n\t\tmcp.WithString(\"component\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"对应的前端Vue组件路径，如：view/user/list.vue\"),\n\t\t),\n\t\tmcp.WithNumber(\"sort\",\n\t\t\tmcp.Description(\"菜单排序号，数字越小越靠前\"),\n\t\t\tmcp.DefaultNumber(1),\n\t\t),\n\t\tmcp.WithString(\"title\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"菜单显示标题\"),\n\t\t),\n\t\tmcp.WithString(\"icon\",\n\t\t\tmcp.Description(\"菜单图标名称\"),\n\t\t\tmcp.DefaultString(\"menu\"),\n\t\t),\n\t\tmcp.WithBoolean(\"keepAlive\",\n\t\t\tmcp.Description(\"是否缓存页面\"),\n\t\t),\n\t\tmcp.WithBoolean(\"defaultMenu\",\n\t\t\tmcp.Description(\"是否是基础路由\"),\n\t\t),\n\t\tmcp.WithBoolean(\"closeTab\",\n\t\t\tmcp.Description(\"是否自动关闭tab\"),\n\t\t),\n\t\tmcp.WithString(\"activeName\",\n\t\t\tmcp.Description(\"高亮菜单名称\"),\n\t\t),\n\t\tmcp.WithString(\"parameters\",\n\t\t\tmcp.Description(\"路由参数JSON字符串，格式：[{\\\"type\\\":\\\"params\\\",\\\"key\\\":\\\"id\\\",\\\"value\\\":\\\"1\\\"}]\"),\n\t\t),\n\t\tmcp.WithString(\"menuBtn\",\n\t\t\tmcp.Description(\"菜单按钮JSON字符串，格式：[{\\\"name\\\":\\\"add\\\",\\\"desc\\\":\\\"新增\\\"}]\"),\n\t\t),\n\t)\n}\n\n// Handle 处理菜单创建请求\nfunc (m *MenuCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// 解析请求参数\n\targs := request.GetArguments()\n\n\t// 必需参数\n\tpath, ok := args[\"path\"].(string)\n\tif !ok || path == \"\" {\n\t\treturn nil, errors.New(\"path 参数是必需的\")\n\t}\n\n\tname, ok := args[\"name\"].(string)\n\tif !ok || name == \"\" {\n\t\treturn nil, errors.New(\"name 参数是必需的\")\n\t}\n\n\tcomponent, ok := args[\"component\"].(string)\n\tif !ok || component == \"\" {\n\t\treturn nil, errors.New(\"component 参数是必需的\")\n\t}\n\n\ttitle, ok := args[\"title\"].(string)\n\tif !ok || title == \"\" {\n\t\treturn nil, errors.New(\"title 参数是必需的\")\n\t}\n\n\t// 可选参数\n\tparentId := uint(0)\n\tif val, ok := args[\"parentId\"].(float64); ok {\n\t\tparentId = uint(val)\n\t}\n\n\thidden := false\n\tif val, ok := args[\"hidden\"].(bool); ok {\n\t\thidden = val\n\t}\n\n\tsort := 1\n\tif val, ok := args[\"sort\"].(float64); ok {\n\t\tsort = int(val)\n\t}\n\n\ticon := \"menu\"\n\tif val, ok := args[\"icon\"].(string); ok && val != \"\" {\n\t\ticon = val\n\t}\n\n\tkeepAlive := false\n\tif val, ok := args[\"keepAlive\"].(bool); ok {\n\t\tkeepAlive = val\n\t}\n\n\tdefaultMenu := false\n\tif val, ok := args[\"defaultMenu\"].(bool); ok {\n\t\tdefaultMenu = val\n\t}\n\n\tcloseTab := false\n\tif val, ok := args[\"closeTab\"].(bool); ok {\n\t\tcloseTab = val\n\t}\n\n\tactiveName := \"\"\n\tif val, ok := args[\"activeName\"].(string); ok {\n\t\tactiveName = val\n\t}\n\n\t// 解析参数和按钮\n\tvar parameters []system.SysBaseMenuParameter\n\tif parametersStr, ok := args[\"parameters\"].(string); ok && parametersStr != \"\" {\n\t\tvar paramReqs []MenuParameterRequest\n\t\tif err := json.Unmarshal([]byte(parametersStr), &paramReqs); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parameters 参数格式错误: %v\", err)\n\t\t}\n\t\tfor _, param := range paramReqs {\n\t\t\tparameters = append(parameters, system.SysBaseMenuParameter{\n\t\t\t\tType:  param.Type,\n\t\t\t\tKey:   param.Key,\n\t\t\t\tValue: param.Value,\n\t\t\t})\n\t\t}\n\t}\n\n\tvar menuBtn []system.SysBaseMenuBtn\n\tif menuBtnStr, ok := args[\"menuBtn\"].(string); ok && menuBtnStr != \"\" {\n\t\tvar btnReqs []MenuButtonRequest\n\t\tif err := json.Unmarshal([]byte(menuBtnStr), &btnReqs); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"menuBtn 参数格式错误: %v\", err)\n\t\t}\n\t\tfor _, btn := range btnReqs {\n\t\t\tmenuBtn = append(menuBtn, system.SysBaseMenuBtn{\n\t\t\t\tName: btn.Name,\n\t\t\t\tDesc: btn.Desc,\n\t\t\t})\n\t\t}\n\t}\n\n\t// 构建菜单对象\n\tmenu := system.SysBaseMenu{\n\t\tParentId:  parentId,\n\t\tPath:      path,\n\t\tName:      name,\n\t\tHidden:    hidden,\n\t\tComponent: component,\n\t\tSort:      sort,\n\t\tMeta: system.Meta{\n\t\t\tTitle:       title,\n\t\t\tIcon:        icon,\n\t\t\tKeepAlive:   keepAlive,\n\t\t\tDefaultMenu: defaultMenu,\n\t\t\tCloseTab:    closeTab,\n\t\t\tActiveName:  activeName,\n\t\t},\n\t\tParameters: parameters,\n\t\tMenuBtn:    menuBtn,\n\t}\n\n\t// 创建菜单\n\tmenuService := service.ServiceGroupApp.SystemServiceGroup.MenuService\n\terr := menuService.AddBaseMenu(menu)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"创建菜单失败: %v\", err)\n\t}\n\n\t// 获取创建的菜单ID\n\tvar createdMenu system.SysBaseMenu\n\terr = global.GVA_DB.Where(\"name = ? AND path = ?\", name, path).First(&createdMenu).Error\n\tif err != nil {\n\t\tglobal.GVA_LOG.Warn(\"获取创建的菜单ID失败\", zap.Error(err))\n\t}\n\n\t// 构建响应\n\tresponse := &MenuCreateResponse{\n\t\tSuccess: true,\n\t\tMessage: fmt.Sprintf(\"成功创建菜单 %s\", title),\n\t\tMenuID:  createdMenu.ID,\n\t\tName:    name,\n\t\tPath:    path,\n\t}\n\n\tresultJSON, err := json.MarshalIndent(response, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化结果失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.TextContent{\n\t\t\t\tType: \"text\",\n\t\t\t\tText: fmt.Sprintf(\"菜单创建结果：\\n\\n%s\", string(resultJSON)),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "server/mcp/menu_lister.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"go.uber.org/zap\"\n)\n\n// 注册工具\nfunc init() {\n\t// 注册工具将在enter.go中统一处理\n\tRegisterTool(&MenuLister{})\n}\n\n// MenuListResponse 菜单列表响应结构\ntype MenuListResponse struct {\n\tSuccess     bool                  `json:\"success\"`\n\tMessage     string                `json:\"message\"`\n\tMenus       []system.SysBaseMenu  `json:\"menus\"`\n\tTotalCount  int                   `json:\"totalCount\"`\n\tDescription string                `json:\"description\"`\n}\n\n// MenuLister 菜单列表工具\ntype MenuLister struct{}\n\n// New 创建菜单列表工具\nfunc (m *MenuLister) New() mcp.Tool {\n\treturn mcp.NewTool(\"list_all_menus\",\n\t\tmcp.WithDescription(`获取系统中所有菜单信息，包括菜单树结构、路由信息、组件路径等，用于前端编写vue-router时正确跳转\n\n**功能说明：**\n- 返回完整的菜单树形结构\n- 包含路由配置信息（path、name、component）\n- 包含菜单元数据（title、icon、keepAlive等）\n- 包含菜单参数和按钮配置\n- 支持父子菜单关系展示\n\n**使用场景：**\n- 前端路由配置：获取所有菜单信息用于配置vue-router\n- 菜单权限管理：了解系统中所有可用的菜单项\n- 导航组件开发：构建动态导航菜单\n- 系统架构分析：了解系统的菜单结构和页面组织`),\nmcp.WithString(\"_placeholder\",\n\t\t\tmcp.Description(\"占位符，防止json schema校验失败\"),\n\t\t),\t\n\t)\n}\n\n// Handle 处理菜单列表请求\nfunc (m *MenuLister) Handle(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// 获取所有基础菜单\n\tallMenus, err := m.getAllMenus()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取菜单列表失败\", zap.Error(err))\n\t\treturn &mcp.CallToolResult{\n\t\t\tContent: []mcp.Content{\n\t\t\t\tmcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: fmt.Sprintf(\"获取菜单列表失败: %v\", err),\n\t\t\t\t},\n\t\t\t},\n\t\t\tIsError: true,\n\t\t}, nil\n\t}\n\n\t// 构建返回结果\n\tresponse := MenuListResponse{\n\t\tSuccess:     true,\n\t\tMessage:     \"获取菜单列表成功\",\n\t\tMenus:       allMenus,\n\t\tTotalCount:  len(allMenus),\n\t\tDescription: \"系统中所有菜单信息的标准列表，包含路由配置和组件信息\",\n\t}\n\n\t// 序列化响应\n\tresponseJSON, err := json.MarshalIndent(response, \"\", \"  \")\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"序列化菜单响应失败\", zap.Error(err))\n\t\treturn &mcp.CallToolResult{\n\t\t\tContent: []mcp.Content{\n\t\t\t\tmcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: fmt.Sprintf(\"序列化响应失败: %v\", err),\n\t\t\t\t},\n\t\t\t},\n\t\t\tIsError: true,\n\t\t}, nil\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.TextContent{\n\t\t\t\tType: \"text\",\n\t\t\t\tText: string(responseJSON),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// getAllMenus 获取所有基础菜单\nfunc (m *MenuLister) getAllMenus() ([]system.SysBaseMenu, error) {\n\tvar menus []system.SysBaseMenu\n\terr := global.GVA_DB.Order(\"sort\").Preload(\"Parameters\").Preload(\"MenuBtn\").Find(&menus).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn menus, nil\n}\n\n"
  },
  {
    "path": "server/mcp/requirement_analyzer.go",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\nfunc init() {\n\tRegisterTool(&RequirementAnalyzer{})\n}\n\ntype RequirementAnalyzer struct{}\n\n// RequirementAnalysisRequest 需求分析请求\ntype RequirementAnalysisRequest struct {\n\tUserRequirement string `json:\"userRequirement\"`\n}\n\n// RequirementAnalysisResponse 需求分析响应\ntype RequirementAnalysisResponse struct {\n\tAIPrompt string `json:\"aiPrompt\"` // 给AI的提示词\n}\n\n// New 返回工具注册信息\nfunc (t *RequirementAnalyzer) New() mcp.Tool {\n\treturn mcp.NewTool(\"requirement_analyzer\",\n\t\tmcp.WithDescription(`** 智能需求分析与模块设计工具 - 首选入口工具（最高优先级）**\n\n** 重要提示：这是所有MCP工具的首选入口，请优先使用！**\n\n** 核心能力：**\n作为资深系统架构师，智能分析用户需求并自动设计完整的模块架构\n\n** 核心功能：**\n1. **智能需求解构**：深度分析用户需求，识别核心业务实体、业务流程、数据关系\n2. **自动模块设计**：基于需求分析，智能确定需要多少个模块及各模块功能\n3. **字段智能推导**：为每个模块自动设计详细字段，包含数据类型、关联关系、字典需求\n4. **架构优化建议**：提供模块拆分、关联设计、扩展性等专业建议\n\n** 输出内容：**\n- 模块数量和架构设计\n- 每个模块的详细字段清单\n- 数据类型和关联关系设计\n- 字典需求和类型定义\n- 模块间关系图和扩展建议\n\n** 适用场景：**\n- 用户需求描述不完整，需要智能补全\n- 复杂业务系统的模块架构设计\n- 需要专业的数据库设计建议\n- 想要快速搭建生产级业务系统\n\n** 推荐工作流：**\n requirement_analyzer → gva_analyze → gva_execute → 其他辅助工具\n \n `),\n\t\tmcp.WithString(\"userRequirement\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"用户的需求描述，支持自然语言，如：'我要做一个猫舍管理系统，用来录入猫的信息，并且记录每只猫每天的活动信息'\"),\n\t\t),\n\t)\n}\n\n// Handle 处理工具调用\nfunc (t *RequirementAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tuserRequirement, ok := request.GetArguments()[\"userRequirement\"].(string)\n\tif !ok || userRequirement == \"\" {\n\t\treturn nil, errors.New(\"参数错误：userRequirement 必须是非空字符串\")\n\t}\n\n\t// 分析用户需求\n\tanalysisResponse, err := t.analyzeRequirement(userRequirement)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"需求分析失败: %v\", err)\n\t}\n\n\t// 序列化响应\n\tresponseData, err := json.Marshal(analysisResponse)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"序列化响应失败: %v\", err)\n\t}\n\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\tmcp.NewTextContent(string(responseData)),\n\t\t},\n\t}, nil\n}\n\n// analyzeRequirement 分析用户需求 - 专注于AI需求传递\nfunc (t *RequirementAnalyzer) analyzeRequirement(userRequirement string) (*RequirementAnalysisResponse, error) {\n\t// 生成AI提示词 - 这是唯一功能\n\taiPrompt := t.generateAIPrompt(userRequirement)\n\n\treturn &RequirementAnalysisResponse{\n\t\tAIPrompt: aiPrompt,\n\t}, nil\n}\n\n// generateAIPrompt 生成AI提示词 - 智能分析需求并确定模块结构\nfunc (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string {\n\tprompt := fmt.Sprintf(`# 智能需求分析与模块设计任务\n\n## 用户原始需求\n%s\n\n## 核心任务\n你需要作为一个资深的系统架构师，深度分析用户需求，智能设计出完整的模块架构。\n\n## 分析步骤\n\n### 第一步：需求解构分析\n请仔细分析用户需求，识别出：\n1. **核心业务实体**（如：用户、商品、订单、疫苗、宠物等）\n2. **业务流程**（如：注册、购买、记录、管理等）\n3. **数据关系**（实体间的关联关系）\n4. **功能模块**（需要哪些独立的管理模块）\n\n### 第二步：模块架构设计\n基于需求分析，设计出模块架构，格式如下：\n\n**模块1：[模块名称]**\n- 功能描述：[该模块的核心功能]\n- 主要字段：[列出关键字段，注明数据类型]\n- 关联关系：[与其他模块的关系，明确一对一/一对多]\n- 字典需求：[需要哪些字典类型]\n\n**模块2：[模块名称]**\n- 功能描述：[该模块的核心功能]\n- 主要字段：[列出关键字段，注明数据类型]\n- 关联关系：[与其他模块的关系]\n- 字典需求：[需要哪些字典类型]\n\n**...**\n\n### 第三步：字段详细设计\n为每个模块详细设计字段：\n\n#### 模块1字段清单：\n- 字段名1 (数据类型) - 字段描述 [是否必填] [关联信息/字典类型]\n- 字段名2 (数据类型) - 字段描述 [是否必填] [关联信息/字典类型]\n- ...\n\n#### 模块2字段清单：\n- 字段名1 (数据类型) - 字段描述 [是否必填] [关联信息/字典类型]\n- ...\n\n## 智能分析指导原则\n\n### 模块拆分原则\n1. **单一职责**：每个模块只负责一个核心业务实体\n2. **数据完整性**：相关数据应该在同一模块中\n3. **业务独立性**：模块应该能够独立完成特定业务功能\n4. **扩展性考虑**：为未来功能扩展预留空间\n\n### 字段设计原则\n1. **必要性**：只包含业务必需的字段\n2. **规范性**：遵循数据库设计规范\n3. **关联性**：正确识别实体间关系\n4. **字典化**：状态、类型等枚举值使用字典\n\n### 关联关系识别\n- **一对一**：一个实体只能关联另一个实体的一个记录\n- **一对多**：一个实体可以关联另一个实体的多个记录\n- **多对多**：通过中间表实现复杂关联\n\n## 特殊场景处理\n\n### 复杂实体识别\n当用户提到某个概念时，要判断它是否需要独立模块：\n- **字典处理**：简单的常见的状态、类型（如：开关、性别、完成状态等）\n- **独立模块**：复杂实体（如：疫苗管理、宠物档案、注射记录）\n\n## 输出要求\n\n### 必须包含的信息\n1. **模块数量**：明确需要几个模块\n2. **模块关系图**：用文字描述模块间关系\n3. **核心字段**：每个模块的关键字段（至少5-10个）\n4. **数据类型**：string、int、bool、time.Time、float64等\n5. **关联设计**：明确哪些字段是关联字段\n6. **字典需求**：列出需要创建的字典类型\n\n### 严格遵循用户输入\n- 如果用户提供了具体字段，**必须使用**用户提供的字段\n- 如果用户提供了SQL文件，**严格按照**SQL结构设计\n- **不要**随意发散，**不要**添加用户未提及的功能\n---\n\n**现在请开始深度分析用户需求：\"%s\"**\n\n请按照上述框架进行系统性分析，确保输出的模块设计既满足当前需求，又具备良好的扩展性。`, userRequirement, userRequirement)\n\n\treturn prompt\n}\n"
  },
  {
    "path": "server/middleware/casbin_rbac.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/gin-gonic/gin\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// CasbinHandler 拦截器\nfunc CasbinHandler() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\twaitUse, _ := utils.GetClaims(c)\n\t\t//获取请求的PATH\n\t\tpath := c.Request.URL.Path\n\t\tobj := strings.TrimPrefix(path, global.GVA_CONFIG.System.RouterPrefix)\n\t\t// 获取请求方法\n\t\tact := c.Request.Method\n\t\t// 获取用户的角色\n\t\tsub := strconv.Itoa(int(waitUse.AuthorityId))\n\t\te := utils.GetCasbin() // 判断策略中是否存在\n\t\tsuccess, _ := e.Enforce(sub, obj, act)\n\t\tif !success {\n\t\t\tresponse.FailWithDetailed(gin.H{}, \"权限不足\", c)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "server/middleware/cors.go",
    "content": "package middleware\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/gin-gonic/gin\"\n\t\"net/http\"\n)\n\n// Cors 直接放行所有跨域请求并放行所有 OPTIONS 方法\nfunc Cors() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tmethod := c.Request.Method\n\t\torigin := c.Request.Header.Get(\"Origin\")\n\t\tc.Header(\"Access-Control-Allow-Origin\", origin)\n\t\tc.Header(\"Access-Control-Allow-Headers\", \"Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id\")\n\t\tc.Header(\"Access-Control-Allow-Methods\", \"POST, GET, OPTIONS,DELETE,PUT\")\n\t\tc.Header(\"Access-Control-Expose-Headers\", \"Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, New-Token, New-Expires-At\")\n\t\tc.Header(\"Access-Control-Allow-Credentials\", \"true\")\n\n\t\t// 放行所有OPTIONS方法\n\t\tif method == \"OPTIONS\" {\n\t\t\tc.AbortWithStatus(http.StatusNoContent)\n\t\t}\n\t\t// 处理请求\n\t\tc.Next()\n\t}\n}\n\n// CorsByRules 按照配置处理跨域请求\nfunc CorsByRules() gin.HandlerFunc {\n\t// 放行全部\n\tif global.GVA_CONFIG.Cors.Mode == \"allow-all\" {\n\t\treturn Cors()\n\t}\n\treturn func(c *gin.Context) {\n\t\twhitelist := checkCors(c.GetHeader(\"origin\"))\n\n\t\t// 通过检查, 添加请求头\n\t\tif whitelist != nil {\n\t\t\tc.Header(\"Access-Control-Allow-Origin\", whitelist.AllowOrigin)\n\t\t\tc.Header(\"Access-Control-Allow-Headers\", whitelist.AllowHeaders)\n\t\t\tc.Header(\"Access-Control-Allow-Methods\", whitelist.AllowMethods)\n\t\t\tc.Header(\"Access-Control-Expose-Headers\", whitelist.ExposeHeaders)\n\t\t\tif whitelist.AllowCredentials {\n\t\t\t\tc.Header(\"Access-Control-Allow-Credentials\", \"true\")\n\t\t\t}\n\t\t}\n\n\t\t// 严格白名单模式且未通过检查，直接拒绝处理请求\n\t\tif whitelist == nil && global.GVA_CONFIG.Cors.Mode == \"strict-whitelist\" && !(c.Request.Method == \"GET\" && c.Request.URL.Path == \"/health\") {\n\t\t\tc.AbortWithStatus(http.StatusForbidden)\n\t\t} else {\n\t\t\t// 非严格白名单模式，无论是否通过检查均放行所有 OPTIONS 方法\n\t\t\tif c.Request.Method == http.MethodOptions {\n\t\t\t\tc.AbortWithStatus(http.StatusNoContent)\n\t\t\t}\n\t\t}\n\n\t\t// 处理请求\n\t\tc.Next()\n\t}\n}\n\nfunc checkCors(currentOrigin string) *config.CORSWhitelist {\n\tfor _, whitelist := range global.GVA_CONFIG.Cors.Whitelist {\n\t\t// 遍历配置中的跨域头，寻找匹配项\n\t\tif currentOrigin == whitelist.AllowOrigin {\n\t\t\treturn &whitelist\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/middleware/email.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/utils\"\n\tutils2 \"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\nfunc ErrorToEmail() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tvar username string\n\t\tclaims, _ := utils2.GetClaims(c)\n\t\tif claims.Username != \"\" {\n\t\t\tusername = claims.Username\n\t\t} else {\n\t\t\tid, _ := strconv.Atoi(c.Request.Header.Get(\"x-user-id\"))\n\t\t\tvar u system.SysUser\n\t\t\terr := global.GVA_DB.Where(\"id = ?\", id).First(&u).Error\n\t\t\tif err != nil {\n\t\t\t\tusername = \"Unknown\"\n\t\t\t}\n\t\t\tusername = u.Username\n\t\t}\n\t\tbody, _ := io.ReadAll(c.Request.Body)\n\t\t// 再重新写回请求体body中，ioutil.ReadAll会清空c.Request.Body中的数据\n\t\tc.Request.Body = io.NopCloser(bytes.NewBuffer(body))\n\t\trecord := system.SysOperationRecord{\n\t\t\tIp:     c.ClientIP(),\n\t\t\tMethod: c.Request.Method,\n\t\t\tPath:   c.Request.URL.Path,\n\t\t\tAgent:  c.Request.UserAgent(),\n\t\t\tBody:   string(body),\n\t\t}\n\t\tnow := time.Now()\n\n\t\tc.Next()\n\n\t\tlatency := time.Since(now)\n\t\tstatus := c.Writer.Status()\n\t\trecord.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()\n\t\tstr := \"接收到的请求为\" + record.Body + \"\\n\" + \"请求方式为\" + record.Method + \"\\n\" + \"报错信息如下\" + record.ErrorMessage + \"\\n\" + \"耗时\" + latency.String() + \"\\n\"\n\t\tif status != 200 {\n\t\t\tsubject := username + \"\" + record.Ip + \"调用了\" + record.Path + \"报错了\"\n\t\t\tif err := utils.ErrorToEmail(subject, str); err != nil {\n\t\t\t\tglobal.GVA_LOG.Error(\"ErrorToEmail Failed, err:\", zap.Error(err))\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/middleware/error.go",
    "content": "package middleware\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\n// GinRecovery recover掉项目可能出现的panic，并使用zap记录相关日志\nfunc GinRecovery(stack bool) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\t// Check for a broken connection, as it is not really a\n\t\t\t\t// condition that warrants a panic stack trace.\n\t\t\t\tvar brokenPipe bool\n\t\t\t\tif ne, ok := err.(*net.OpError); ok {\n\t\t\t\t\tif se, ok := ne.Err.(*os.SyscallError); ok {\n\t\t\t\t\t\tif strings.Contains(strings.ToLower(se.Error()), \"broken pipe\") || strings.Contains(strings.ToLower(se.Error()), \"connection reset by peer\") {\n\t\t\t\t\t\t\tbrokenPipe = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\thttpRequest, _ := httputil.DumpRequest(c.Request, false)\n\t\t\t\tif brokenPipe {\n\t\t\t\t\tglobal.GVA_LOG.Error(c.Request.URL.Path,\n\t\t\t\t\t\tzap.Any(\"error\", err),\n\t\t\t\t\t\tzap.String(\"request\", string(httpRequest)),\n\t\t\t\t\t)\n\t\t\t\t\t// If the connection is dead, we can't write a status to it.\n\t\t\t\t\t_ = c.Error(err.(error)) // nolint: errcheck\n\t\t\t\t\tc.Abort()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif stack {\n\t\t\t\t\tform := \"后端\"\n\t\t\t\t\tinfo := fmt.Sprintf(\"Panic: %v\\nRequest: %s\\nStack: %s\", err, string(httpRequest), string(debug.Stack()))\n\t\t\t\t\tlevel := \"error\"\n\t\t\t\t\t_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(context.Background(), &system.SysError{\n\t\t\t\t\t\tForm:  &form,\n\t\t\t\t\t\tInfo:  &info,\n\t\t\t\t\t\tLevel: level,\n\t\t\t\t\t})\n\t\t\t\t\tglobal.GVA_LOG.Error(\"[Recovery from panic]\",\n\t\t\t\t\t\tzap.Any(\"error\", err),\n\t\t\t\t\t\tzap.String(\"request\", string(httpRequest)),\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tform := \"后端\"\n\t\t\t\t\tinfo := fmt.Sprintf(\"Panic: %v\\nRequest: %s\", err, string(httpRequest))\n\t\t\t\t\tlevel := \"error\"\n\t\t\t\t\t_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(context.Background(), &system.SysError{\n\t\t\t\t\t\tForm:  &form,\n\t\t\t\t\t\tInfo:  &info,\n\t\t\t\t\t\tLevel: level,\n\t\t\t\t\t})\n\t\t\t\t\tglobal.GVA_LOG.Error(\"[Recovery from panic]\",\n\t\t\t\t\t\tzap.Any(\"error\", err),\n\t\t\t\t\t\tzap.String(\"request\", string(httpRequest)),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tc.AbortWithStatus(http.StatusInternalServerError)\n\t\t\t}\n\t\t}()\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "server/middleware/jwt.go",
    "content": "package middleware\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/golang-jwt/jwt/v5\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc JWTAuth() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\t// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录\n\t\ttoken := utils.GetToken(c)\n\t\tif token == \"\" {\n\t\t\tresponse.NoAuth(\"未登录或非法访问，请登录\", c)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tif isBlacklist(token) {\n\t\t\tresponse.NoAuth(\"您的帐户异地登陆或令牌失效\", c)\n\t\t\tutils.ClearToken(c)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tj := utils.NewJWT()\n\t\t// parseToken 解析token包含的信息\n\t\tclaims, err := j.ParseToken(token)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, utils.TokenExpired) {\n\t\t\t\tresponse.NoAuth(\"登录已过期，请重新登录\", c)\n\t\t\t\tutils.ClearToken(c)\n\t\t\t\tc.Abort()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresponse.NoAuth(err.Error(), c)\n\t\t\tutils.ClearToken(c)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\t// 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开\n\t\t// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开\n\n\t\t//if user, err := userService.FindUserByUuid(claims.UUID.String()); err != nil || user.Enable == 2 {\n\t\t//\t_ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})\n\t\t//\tresponse.FailWithDetailed(gin.H{\"reload\": true}, err.Error(), c)\n\t\t//\tc.Abort()\n\t\t//}\n\t\tc.Set(\"claims\", claims)\n\t\tif claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {\n\t\t\tdr, _ := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)\n\t\t\tclaims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(dr))\n\t\t\tnewToken, _ := j.CreateTokenByOldToken(token, *claims)\n\t\t\tnewClaims, _ := j.ParseToken(newToken)\n\t\t\tc.Header(\"new-token\", newToken)\n\t\t\tc.Header(\"new-expires-at\", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))\n\t\t\tutils.SetToken(c, newToken, int(dr.Seconds()/60))\n\t\t\tif global.GVA_CONFIG.System.UseMultipoint {\n\t\t\t\t// 记录新的活跃jwt\n\t\t\t\t_ = utils.SetRedisJWT(newToken, newClaims.Username)\n\t\t\t}\n\t\t}\n\t\tc.Next()\n\n\t\tif newToken, exists := c.Get(\"new-token\"); exists {\n\t\t\tc.Header(\"new-token\", newToken.(string))\n\t\t}\n\t\tif newExpiresAt, exists := c.Get(\"new-expires-at\"); exists {\n\t\t\tc.Header(\"new-expires-at\", newExpiresAt.(string))\n\t\t}\n\t}\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: IsBlacklist\n//@description: 判断JWT是否在黑名单内部\n//@param: jwt string\n//@return: bool\n\nfunc isBlacklist(jwt string) bool {\n\t_, ok := global.BlackCache.Get(jwt)\n\treturn ok\n}\n"
  },
  {
    "path": "server/middleware/limit_ip.go",
    "content": "package middleware\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype LimitConfig struct {\n\t// GenerationKey 根据业务生成key 下面CheckOrMark查询生成\n\tGenerationKey func(c *gin.Context) string\n\t// 检查函数,用户可修改具体逻辑,更加灵活\n\tCheckOrMark func(key string, expire int, limit int) error\n\t// Expire key 过期时间\n\tExpire int\n\t// Limit 周期时间\n\tLimit int\n}\n\nfunc (l LimitConfig) LimitWithTime() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tif err := l.CheckOrMark(l.GenerationKey(c), l.Expire, l.Limit); err != nil {\n\t\t\tc.JSON(http.StatusOK, gin.H{\"code\": response.ERROR, \"msg\": err.Error()})\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t} else {\n\t\t\tc.Next()\n\t\t}\n\t}\n}\n\n// DefaultGenerationKey 默认生成key\nfunc DefaultGenerationKey(c *gin.Context) string {\n\treturn \"GVA_Limit\" + c.ClientIP()\n}\n\nfunc DefaultCheckOrMark(key string, expire int, limit int) (err error) {\n\t// 判断是否开启redis\n\tif global.GVA_REDIS == nil {\n\t\treturn err\n\t}\n\tif err = SetLimitWithTime(key, limit, time.Duration(expire)*time.Second); err != nil {\n\t\tglobal.GVA_LOG.Error(\"limit\", zap.Error(err))\n\t}\n\treturn err\n}\n\nfunc DefaultLimit() gin.HandlerFunc {\n\treturn LimitConfig{\n\t\tGenerationKey: DefaultGenerationKey,\n\t\tCheckOrMark:   DefaultCheckOrMark,\n\t\tExpire:        global.GVA_CONFIG.System.LimitTimeIP,\n\t\tLimit:         global.GVA_CONFIG.System.LimitCountIP,\n\t}.LimitWithTime()\n}\n\n// SetLimitWithTime 设置访问次数\nfunc SetLimitWithTime(key string, limit int, expiration time.Duration) error {\n\tcount, err := global.GVA_REDIS.Exists(context.Background(), key).Result()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif count == 0 {\n\t\tpipe := global.GVA_REDIS.TxPipeline()\n\t\tpipe.Incr(context.Background(), key)\n\t\tpipe.Expire(context.Background(), key, expiration)\n\t\t_, err = pipe.Exec(context.Background())\n\t\treturn err\n\t} else {\n\t\t// 次数\n\t\tif times, err := global.GVA_REDIS.Get(context.Background(), key).Int(); err != nil {\n\t\t\treturn err\n\t\t} else {\n\t\t\tif times >= limit {\n\t\t\t\tif t, err := global.GVA_REDIS.PTTL(context.Background(), key).Result(); err != nil {\n\t\t\t\t\treturn errors.New(\"请求太过频繁，请稍后再试\")\n\t\t\t\t} else {\n\t\t\t\t\treturn errors.New(\"请求太过频繁, 请 \" + t.String() + \" 秒后尝试\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn global.GVA_REDIS.Incr(context.Background(), key).Err()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/middleware/loadtls.go",
    "content": "package middleware\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/unrolled/secure\"\n)\n\n// 用https把这个中间件在router里面use一下就好\n\nfunc LoadTls() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tmiddleware := secure.New(secure.Options{\n\t\t\tSSLRedirect: true,\n\t\t\tSSLHost:     \"localhost:443\",\n\t\t})\n\t\terr := middleware.Process(c.Writer, c.Request)\n\t\tif err != nil {\n\t\t\t// 如果出现错误，请不要继续\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\t\t// 继续往下处理\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "server/middleware/logger.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// LogLayout 日志layout\ntype LogLayout struct {\n\tTime      time.Time\n\tMetadata  map[string]interface{} // 存储自定义原数据\n\tPath      string                 // 访问路径\n\tQuery     string                 // 携带query\n\tBody      string                 // 携带body数据\n\tIP        string                 // ip地址\n\tUserAgent string                 // 代理\n\tError     string                 // 错误\n\tCost      time.Duration          // 花费时间\n\tSource    string                 // 来源\n}\n\ntype Logger struct {\n\t// Filter 用户自定义过滤\n\tFilter func(c *gin.Context) bool\n\t// FilterKeyword 关键字过滤(key)\n\tFilterKeyword func(layout *LogLayout) bool\n\t// AuthProcess 鉴权处理\n\tAuthProcess func(c *gin.Context, layout *LogLayout)\n\t// 日志处理\n\tPrint func(LogLayout)\n\t// Source 服务唯一标识\n\tSource string\n}\n\nfunc (l Logger) SetLoggerMiddleware() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tstart := time.Now()\n\t\tpath := c.Request.URL.Path\n\t\tquery := c.Request.URL.RawQuery\n\t\tvar body []byte\n\t\tif l.Filter != nil && !l.Filter(c) {\n\t\t\tbody, _ = c.GetRawData()\n\t\t\t// 将原body塞回去\n\t\t\tc.Request.Body = io.NopCloser(bytes.NewBuffer(body))\n\t\t}\n\t\tc.Next()\n\t\tcost := time.Since(start)\n\t\tlayout := LogLayout{\n\t\t\tTime:      time.Now(),\n\t\t\tPath:      path,\n\t\t\tQuery:     query,\n\t\t\tIP:        c.ClientIP(),\n\t\t\tUserAgent: c.Request.UserAgent(),\n\t\t\tError:     strings.TrimRight(c.Errors.ByType(gin.ErrorTypePrivate).String(), \"\\n\"),\n\t\t\tCost:      cost,\n\t\t\tSource:    l.Source,\n\t\t}\n\t\tif l.Filter != nil && !l.Filter(c) {\n\t\t\tlayout.Body = string(body)\n\t\t}\n\t\tif l.AuthProcess != nil {\n\t\t\t// 处理鉴权需要的信息\n\t\t\tl.AuthProcess(c, &layout)\n\t\t}\n\t\tif l.FilterKeyword != nil {\n\t\t\t// 自行判断key/value 脱敏等\n\t\t\tl.FilterKeyword(&layout)\n\t\t}\n\t\t// 自行处理日志\n\t\tl.Print(layout)\n\t}\n}\n\nfunc DefaultLogger() gin.HandlerFunc {\n\treturn Logger{\n\t\tPrint: func(layout LogLayout) {\n\t\t\t// 标准输出,k8s做收集\n\t\t\tv, _ := json.Marshal(layout)\n\t\t\tfmt.Println(string(v))\n\t\t},\n\t\tSource: \"GVA\",\n\t}.SetLoggerMiddleware()\n}\n"
  },
  {
    "path": "server/middleware/operation.go",
    "content": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\nvar respPool sync.Pool\nvar bufferSize = 1024\n\nfunc init() {\n\trespPool.New = func() interface{} {\n\t\treturn make([]byte, bufferSize)\n\t}\n}\n\nfunc OperationRecord() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tvar body []byte\n\t\tvar userId int\n\t\tif c.Request.Method != http.MethodGet {\n\t\t\tvar err error\n\t\t\tbody, err = io.ReadAll(c.Request.Body)\n\t\t\tif err != nil {\n\t\t\t\tglobal.GVA_LOG.Error(\"read body from request error:\", zap.Error(err))\n\t\t\t} else {\n\t\t\t\tc.Request.Body = io.NopCloser(bytes.NewBuffer(body))\n\t\t\t}\n\t\t} else {\n\t\t\tquery := c.Request.URL.RawQuery\n\t\t\tquery, _ = url.QueryUnescape(query)\n\t\t\tsplit := strings.Split(query, \"&\")\n\t\t\tm := make(map[string]string)\n\t\t\tfor _, v := range split {\n\t\t\t\tkv := strings.Split(v, \"=\")\n\t\t\t\tif len(kv) == 2 {\n\t\t\t\t\tm[kv[0]] = kv[1]\n\t\t\t\t}\n\t\t\t}\n\t\t\tbody, _ = json.Marshal(&m)\n\t\t}\n\t\tclaims, _ := utils.GetClaims(c)\n\t\tif claims != nil && claims.BaseClaims.ID != 0 {\n\t\t\tuserId = int(claims.BaseClaims.ID)\n\t\t} else {\n\t\t\tid, err := strconv.Atoi(c.Request.Header.Get(\"x-user-id\"))\n\t\t\tif err != nil {\n\t\t\t\tuserId = 0\n\t\t\t}\n\t\t\tuserId = id\n\t\t}\n\t\trecord := system.SysOperationRecord{\n\t\t\tIp:     c.ClientIP(),\n\t\t\tMethod: c.Request.Method,\n\t\t\tPath:   c.Request.URL.Path,\n\t\t\tAgent:  c.Request.UserAgent(),\n\t\t\tBody:   \"\",\n\t\t\tUserID: userId,\n\t\t}\n\n\t\t// 上传文件时候 中间件日志进行裁断操作\n\t\tif strings.Contains(c.GetHeader(\"Content-Type\"), \"multipart/form-data\") {\n\t\t\trecord.Body = \"[文件]\"\n\t\t} else {\n\t\t\tif len(body) > bufferSize {\n\t\t\t\trecord.Body = \"[超出记录长度]\"\n\t\t\t} else {\n\t\t\t\trecord.Body = string(body)\n\t\t\t}\n\t\t}\n\n\t\twriter := responseBodyWriter{\n\t\t\tResponseWriter: c.Writer,\n\t\t\tbody:           &bytes.Buffer{},\n\t\t}\n\t\tc.Writer = writer\n\t\tnow := time.Now()\n\n\t\tc.Next()\n\n\t\tlatency := time.Since(now)\n\t\trecord.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()\n\t\trecord.Status = c.Writer.Status()\n\t\trecord.Latency = latency\n\t\trecord.Resp = writer.body.String()\n\n\t\tif strings.Contains(c.Writer.Header().Get(\"Pragma\"), \"public\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Expires\"), \"0\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Cache-Control\"), \"must-revalidate, post-check=0, pre-check=0\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Content-Type\"), \"application/force-download\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Content-Type\"), \"application/octet-stream\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Content-Type\"), \"application/vnd.ms-excel\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Content-Type\"), \"application/download\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Content-Disposition\"), \"attachment\") ||\n\t\t\tstrings.Contains(c.Writer.Header().Get(\"Content-Transfer-Encoding\"), \"binary\") {\n\t\t\tif len(record.Resp) > bufferSize {\n\t\t\t\t// 截断\n\t\t\t\trecord.Body = \"超出记录长度\"\n\t\t\t}\n\t\t}\n\t\tif err := global.GVA_DB.Create(&record).Error; err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"create operation record error:\", zap.Error(err))\n\t\t}\n\t}\n}\n\ntype responseBodyWriter struct {\n\tgin.ResponseWriter\n\tbody *bytes.Buffer\n}\n\nfunc (r responseBodyWriter) Write(b []byte) (int, error) {\n\tr.body.Write(b)\n\treturn r.ResponseWriter.Write(b)\n}\n"
  },
  {
    "path": "server/middleware/timeout.go",
    "content": "package middleware\n\nimport (\n\t\"context\"\n\t\"github.com/gin-gonic/gin\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// TimeoutMiddleware 创建超时中间件\n// 入参 timeout 设置超时时间（例如：time.Second * 5）\n// 使用示例 xxx.Get(\"path\",middleware.TimeoutMiddleware(30*time.Second),HandleFunc)\nfunc TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tctx, cancel := context.WithTimeout(c.Request.Context(), timeout)\n\t\tdefer cancel()\n\n\t\tc.Request = c.Request.WithContext(ctx)\n\n\t\t// 使用 buffered channel 避免 goroutine 泄漏\n\t\tdone := make(chan struct{}, 1)\n\t\tpanicChan := make(chan interface{}, 1)\n\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tif p := recover(); p != nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase panicChan <- p:\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tselect {\n\t\t\t\tcase done <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}()\n\t\t\tc.Next()\n\t\t}()\n\n\t\tselect {\n\t\tcase p := <-panicChan:\n\t\t\tpanic(p)\n\t\tcase <-done:\n\t\t\treturn\n\t\tcase <-ctx.Done():\n\t\t\t// 确保服务器超时设置足够长\n\t\t\tc.Header(\"Connection\", \"close\")\n\t\t\tc.AbortWithStatusJSON(http.StatusGatewayTimeout, gin.H{\n\t\t\t\t\"code\": 504,\n\t\t\t\t\"msg\":  \"请求超时\",\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/model/common/basetypes.go",
    "content": "package common\n\nimport (\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"errors\"\n)\n\ntype JSONMap map[string]interface{}\n\nfunc (m JSONMap) Value() (driver.Value, error) {\n\tif m == nil {\n\t\treturn nil, nil\n\t}\n\treturn json.Marshal(m)\n}\n\nfunc (m *JSONMap) Scan(value interface{}) error {\n\tif value == nil {\n\t\t*m = make(map[string]interface{})\n\t\treturn nil\n\t}\n\tvar err error\n\tswitch value.(type) {\n\tcase []byte:\n\t\terr = json.Unmarshal(value.([]byte), m)\n\tcase string:\n\t\terr = json.Unmarshal([]byte(value.(string)), m)\n\tdefault:\n\t\terr = errors.New(\"basetypes.JSONMap.Scan: invalid value type\")\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype TreeNode[T any] interface {\n\tGetChildren() []T\n\tSetChildren(children T)\n\tGetID() int\n\tGetParentID() int\n}\n"
  },
  {
    "path": "server/model/common/clearDB.go",
    "content": "package common\n\ntype ClearDB struct {\n\tTableName    string\n\tCompareField string\n\tInterval     string\n}\n"
  },
  {
    "path": "server/model/common/request/common.go",
    "content": "package request\n\nimport (\n\t\"gorm.io/gorm\"\n)\n\n// PageInfo Paging common input parameter structure\ntype PageInfo struct {\n\tPage     int    `json:\"page\" form:\"page\"`         // 页码\n\tPageSize int    `json:\"pageSize\" form:\"pageSize\"` // 每页大小\n\tKeyword  string `json:\"keyword\" form:\"keyword\"`   // 关键字\n}\n\nfunc (r *PageInfo) Paginate() func(db *gorm.DB) *gorm.DB {\n\treturn func(db *gorm.DB) *gorm.DB {\n\t\tif r.Page <= 0 {\n\t\t\tr.Page = 1\n\t\t}\n\t\tswitch {\n\t\tcase r.PageSize > 100:\n\t\t\tr.PageSize = 100\n\t\tcase r.PageSize <= 0:\n\t\t\tr.PageSize = 10\n\t\t}\n\t\toffset := (r.Page - 1) * r.PageSize\n\t\treturn db.Offset(offset).Limit(r.PageSize)\n\t}\n}\n\n// GetById Find by id structure\ntype GetById struct {\n\tID int `json:\"id\" form:\"id\"` // 主键ID\n}\n\nfunc (r *GetById) Uint() uint {\n\treturn uint(r.ID)\n}\n\ntype IdsReq struct {\n\tIds []int `json:\"ids\" form:\"ids\"`\n}\n\n// GetAuthorityId Get role by id structure\ntype GetAuthorityId struct {\n\tAuthorityId uint `json:\"authorityId\" form:\"authorityId\"` // 角色ID\n}\n\ntype Empty struct{}\n"
  },
  {
    "path": "server/model/common/response/common.go",
    "content": "package response\n\ntype PageResult struct {\n\tList     interface{} `json:\"list\"`\n\tTotal    int64       `json:\"total\"`\n\tPage     int         `json:\"page\"`\n\tPageSize int         `json:\"pageSize\"`\n}\n"
  },
  {
    "path": "server/model/common/response/response.go",
    "content": "package response\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype Response struct {\n\tCode int         `json:\"code\"`\n\tData interface{} `json:\"data\"`\n\tMsg  string      `json:\"msg\"`\n}\n\nconst (\n\tERROR   = 7\n\tSUCCESS = 0\n)\n\nfunc Result(code int, data interface{}, msg string, c *gin.Context) {\n\tc.JSON(http.StatusOK, Response{\n\t\tcode,\n\t\tdata,\n\t\tmsg,\n\t})\n}\n\nfunc Ok(c *gin.Context) {\n\tResult(SUCCESS, map[string]interface{}{}, \"操作成功\", c)\n}\n\nfunc OkWithMessage(message string, c *gin.Context) {\n\tResult(SUCCESS, map[string]interface{}{}, message, c)\n}\n\nfunc OkWithData(data interface{}, c *gin.Context) {\n\tResult(SUCCESS, data, \"成功\", c)\n}\n\nfunc OkWithDetailed(data interface{}, message string, c *gin.Context) {\n\tResult(SUCCESS, data, message, c)\n}\n\nfunc Fail(c *gin.Context) {\n\tResult(ERROR, map[string]interface{}{}, \"操作失败\", c)\n}\n\nfunc FailWithMessage(message string, c *gin.Context) {\n\tResult(ERROR, map[string]interface{}{}, message, c)\n}\n\nfunc NoAuth(message string, c *gin.Context) {\n\tc.JSON(http.StatusUnauthorized, Response{\n\t\t7,\n\t\tnil,\n\t\tmessage,\n\t})\n}\n\nfunc FailWithDetailed(data interface{}, message string, c *gin.Context) {\n\tResult(ERROR, data, message, c)\n}\n"
  },
  {
    "path": "server/model/example/exa_attachment_category.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype ExaAttachmentCategory struct {\n\tglobal.GVA_MODEL\n\tName     string                   `json:\"name\" form:\"name\" gorm:\"default:null;type:varchar(255);column:name;comment:分类名称;\"`\n\tPid      uint                     `json:\"pid\" form:\"pid\" gorm:\"default:0;type:int;column:pid;comment:父节点ID;\"`\n\tChildren []*ExaAttachmentCategory `json:\"children\" gorm:\"-\"`\n}\n\nfunc (ExaAttachmentCategory) TableName() string {\n\treturn \"exa_attachment_category\"\n}\n"
  },
  {
    "path": "server/model/example/exa_breakpoint_continue.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// file struct, 文件结构体\ntype ExaFile struct {\n\tglobal.GVA_MODEL\n\tFileName     string\n\tFileMd5      string\n\tFilePath     string\n\tExaFileChunk []ExaFileChunk\n\tChunkTotal   int\n\tIsFinish     bool\n}\n\n// file chunk struct, 切片结构体\ntype ExaFileChunk struct {\n\tglobal.GVA_MODEL\n\tExaFileID       uint\n\tFileChunkNumber int\n\tFileChunkPath   string\n}\n"
  },
  {
    "path": "server/model/example/exa_customer.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype ExaCustomer struct {\n\tglobal.GVA_MODEL\n\tCustomerName       string         `json:\"customerName\" form:\"customerName\" gorm:\"comment:客户名\"`                // 客户名\n\tCustomerPhoneData  string         `json:\"customerPhoneData\" form:\"customerPhoneData\" gorm:\"comment:客户手机号\"`    // 客户手机号\n\tSysUserID          uint           `json:\"sysUserId\" form:\"sysUserId\" gorm:\"comment:管理ID\"`                     // 管理ID\n\tSysUserAuthorityID uint           `json:\"sysUserAuthorityID\" form:\"sysUserAuthorityID\" gorm:\"comment:管理角色ID\"` // 管理角色ID\n\tSysUser            system.SysUser `json:\"sysUser\" form:\"sysUser\" gorm:\"comment:管理详情\"`                         // 管理详情\n}\n"
  },
  {
    "path": "server/model/example/exa_file_upload_download.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype ExaFileUploadAndDownload struct {\n\tglobal.GVA_MODEL\n\tName    string `json:\"name\" form:\"name\" gorm:\"column:name;comment:文件名\"`                                // 文件名\n\tClassId int    `json:\"classId\" form:\"classId\" gorm:\"default:0;type:int;column:class_id;comment:分类id;\"` // 分类id\n\tUrl     string `json:\"url\" form:\"url\" gorm:\"column:url;comment:文件地址\"`                                  // 文件地址\n\tTag     string `json:\"tag\" form:\"tag\" gorm:\"column:tag;comment:文件标签\"`                                  // 文件标签\n\tKey     string `json:\"key\" form:\"key\" gorm:\"column:key;comment:编号\"`                                    // 编号\n}\n\nfunc (ExaFileUploadAndDownload) TableName() string {\n\treturn \"exa_file_upload_and_downloads\"\n}\n"
  },
  {
    "path": "server/model/example/request/exa_file_upload_and_downloads.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n)\n\ntype ExaAttachmentCategorySearch struct {\n\tClassId int `json:\"classId\" form:\"classId\"`\n\trequest.PageInfo\n}\n"
  },
  {
    "path": "server/model/example/response/exa_breakpoint_continue.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\ntype FilePathResponse struct {\n\tFilePath string `json:\"filePath\"`\n}\n\ntype FileResponse struct {\n\tFile example.ExaFile `json:\"file\"`\n}\n"
  },
  {
    "path": "server/model/example/response/exa_customer.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\ntype ExaCustomerResponse struct {\n\tCustomer example.ExaCustomer `json:\"customer\"`\n}\n"
  },
  {
    "path": "server/model/example/response/exa_file_upload_download.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\ntype ExaFileResponse struct {\n\tFile example.ExaFileUploadAndDownload `json:\"file\"`\n}\n"
  },
  {
    "path": "server/model/system/request/jwt.go",
    "content": "package request\n\nimport (\n\tjwt \"github.com/golang-jwt/jwt/v5\"\n\t\"github.com/google/uuid\"\n)\n\n// CustomClaims structure\ntype CustomClaims struct {\n\tBaseClaims\n\tBufferTime int64\n\tjwt.RegisteredClaims\n}\n\ntype BaseClaims struct {\n\tUUID        uuid.UUID\n\tID          uint\n\tUsername    string\n\tNickName    string\n\tAuthorityId uint\n}\n"
  },
  {
    "path": "server/model/system/request/sys_api.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\n// api分页条件查询及排序结构体\ntype SearchApiParams struct {\n\tsystem.SysApi\n\trequest.PageInfo\n\tOrderKey string `json:\"orderKey\"` // 排序\n\tDesc     bool   `json:\"desc\"`     // 排序方式:升序false(默认)|降序true\n}\n\n// SetApiAuthorities 通过API路径和方法全量覆盖关联角色列表\ntype SetApiAuthorities struct {\n\tPath         string `json:\"path\" form:\"path\"`                     // API路径\n\tMethod       string `json:\"method\" form:\"method\"`                 // 请求方法\n\tAuthorityIds []uint `json:\"authorityIds\" form:\"authorityIds\"`     // 角色ID列表\n}\n"
  },
  {
    "path": "server/model/system/request/sys_api_token.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysApiTokenSearch struct {\n\tsystem.SysApiToken\n\trequest.PageInfo\n    Status *bool `json:\"status\" form:\"status\"`\n}\n"
  },
  {
    "path": "server/model/system/request/sys_authority_btn.go",
    "content": "package request\n\ntype SysAuthorityBtnReq struct {\n\tMenuID      uint   `json:\"menuID\"`\n\tAuthorityId uint   `json:\"authorityId\"`\n\tSelected    []uint `json:\"selected\"`\n}\n"
  },
  {
    "path": "server/model/system/request/sys_auto_code.go",
    "content": "package request\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/pkg/errors\"\n\t\"go/token\"\n\t\"strings\"\n)\n\ntype AutoCode struct {\n\tPackage             string                 `json:\"package\"`\n\tPackageT            string                 `json:\"-\"`\n\tTableName           string                 `json:\"tableName\" example:\"表名\"`              // 表名\n\tBusinessDB          string                 `json:\"businessDB\" example:\"业务数据库\"`          // 业务数据库\n\tStructName          string                 `json:\"structName\" example:\"Struct名称\"`       // Struct名称\n\tPackageName         string                 `json:\"packageName\" example:\"文件名称\"`          // 文件名称\n\tDescription         string                 `json:\"description\" example:\"Struct中文名称\"`    // Struct中文名称\n\tAbbreviation        string                 `json:\"abbreviation\" example:\"Struct简称\"`     // Struct简称\n\tHumpPackageName     string                 `json:\"humpPackageName\" example:\"go文件名称\"`    // go文件名称\n\tGvaModel            bool                   `json:\"gvaModel\" example:\"false\"`            // 是否使用gva默认Model\n\tAutoMigrate         bool                   `json:\"autoMigrate\" example:\"false\"`         // 是否自动迁移表结构\n\tAutoCreateResource  bool                   `json:\"autoCreateResource\" example:\"false\"`  // 是否自动创建资源标识\n\tAutoCreateApiToSql  bool                   `json:\"autoCreateApiToSql\" example:\"false\"`  // 是否自动创建api\n\tAutoCreateMenuToSql bool                   `json:\"autoCreateMenuToSql\" example:\"false\"` // 是否自动创建menu\n\tAutoCreateBtnAuth   bool                   `json:\"autoCreateBtnAuth\" example:\"false\"`   // 是否自动创建按钮权限\n\tOnlyTemplate        bool                   `json:\"onlyTemplate\" example:\"false\"`        // 是否只生成模板\n\tIsTree              bool                   `json:\"isTree\" example:\"false\"`              // 是否树形结构\n\tTreeJson            string                 `json:\"treeJson\" example:\"展示的树json字段\"`       // 展示的树json字段\n\tIsAdd               bool                   `json:\"isAdd\" example:\"false\"`               // 是否新增\n\tFields              []*AutoCodeField       `json:\"fields\"`\n\tGenerateWeb         bool                   `json:\"generateWeb\" example:\"true\"`    // 是否生成web\n\tGenerateServer      bool                   `json:\"generateServer\" example:\"true\"` // 是否生成server\n\tModule              string                 `json:\"-\"`\n\tDictTypes           []string               `json:\"-\"`\n\tPrimaryField        *AutoCodeField         `json:\"primaryField\"`\n\tDataSourceMap       map[string]*DataSource `json:\"-\"`\n\tHasPic              bool                   `json:\"-\"`\n\tHasFile             bool                   `json:\"-\"`\n\tHasTimer            bool                   `json:\"-\"`\n\tNeedSort            bool                   `json:\"-\"`\n\tNeedJSON            bool                   `json:\"-\"`\n\tHasRichText         bool                   `json:\"-\"`\n\tHasDataSource       bool                   `json:\"-\"`\n\tHasSearchTimer      bool                   `json:\"-\"`\n\tHasArray            bool                   `json:\"-\"`\n\tHasExcel            bool                   `json:\"-\"`\n}\n\ntype DataSource struct {\n\tDBName       string `json:\"dbName\"`\n\tTable        string `json:\"table\"`\n\tLabel        string `json:\"label\"`\n\tValue        string `json:\"value\"`\n\tAssociation  int    `json:\"association\"` // 关联关系 1 一对一 2 一对多\n\tHasDeletedAt bool   `json:\"hasDeletedAt\"`\n}\n\nfunc (r *AutoCode) Apis() []model.SysApi {\n\treturn []model.SysApi{\n\t\t{\n\t\t\tPath:        \"/\" + r.Abbreviation + \"/\" + \"create\" + r.StructName,\n\t\t\tDescription: \"新增\" + r.Description,\n\t\t\tApiGroup:    r.Description,\n\t\t\tMethod:      \"POST\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/\" + r.Abbreviation + \"/\" + \"delete\" + r.StructName,\n\t\t\tDescription: \"删除\" + r.Description,\n\t\t\tApiGroup:    r.Description,\n\t\t\tMethod:      \"DELETE\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/\" + r.Abbreviation + \"/\" + \"delete\" + r.StructName + \"ByIds\",\n\t\t\tDescription: \"批量删除\" + r.Description,\n\t\t\tApiGroup:    r.Description,\n\t\t\tMethod:      \"DELETE\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/\" + r.Abbreviation + \"/\" + \"update\" + r.StructName,\n\t\t\tDescription: \"更新\" + r.Description,\n\t\t\tApiGroup:    r.Description,\n\t\t\tMethod:      \"PUT\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/\" + r.Abbreviation + \"/\" + \"find\" + r.StructName,\n\t\t\tDescription: \"根据ID获取\" + r.Description,\n\t\t\tApiGroup:    r.Description,\n\t\t\tMethod:      \"GET\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/\" + r.Abbreviation + \"/\" + \"get\" + r.StructName + \"List\",\n\t\t\tDescription: \"获取\" + r.Description + \"列表\",\n\t\t\tApiGroup:    r.Description,\n\t\t\tMethod:      \"GET\",\n\t\t},\n\t}\n}\n\nfunc (r *AutoCode) Menu(template string) model.SysBaseMenu {\n\tcomponent := fmt.Sprintf(\"view/%s/%s/%s.vue\", r.Package, r.PackageName, r.PackageName)\n\tif template != \"package\" {\n\t\tcomponent = fmt.Sprintf(\"plugin/%s/view/%s.vue\", r.Package, r.PackageName)\n\t}\n\treturn model.SysBaseMenu{\n\t\tParentId:  0,\n\t\tPath:      r.Abbreviation,\n\t\tName:      r.Abbreviation,\n\t\tComponent: component,\n\t\tMeta: model.Meta{\n\t\t\tTitle: r.Description,\n\t\t},\n\t}\n}\n\n// Pretreatment 预处理\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (r *AutoCode) Pretreatment() error {\n\tr.Module = global.GVA_CONFIG.AutoCode.Module\n\tif token.IsKeyword(r.Abbreviation) {\n\t\tr.Abbreviation = r.Abbreviation + \"_\"\n\t} // go 关键字处理\n\tif strings.HasSuffix(r.HumpPackageName, \"test\") {\n\t\tr.HumpPackageName = r.HumpPackageName + \"_\"\n\t} // test\n\tlength := len(r.Fields)\n\tdict := make(map[string]string, length)\n\tr.DataSourceMap = make(map[string]*DataSource, length)\n\tfor i := 0; i < length; i++ {\n\t\tif r.Fields[i].Excel {\n\t\t\tr.HasExcel = true\n\t\t}\n\t\tif r.Fields[i].DictType != \"\" {\n\t\t\tdict[r.Fields[i].DictType] = \"\"\n\t\t}\n\t\tif r.Fields[i].Sort {\n\t\t\tr.NeedSort = true\n\t\t}\n\t\tswitch r.Fields[i].FieldType {\n\t\tcase \"file\":\n\t\t\tr.HasFile = true\n\t\t\tr.NeedJSON = true\n\t\tcase \"json\":\n\t\t\tr.NeedJSON = true\n\t\tcase \"array\":\n\t\t\tr.NeedJSON = true\n\t\t\tr.HasArray = true\n\t\tcase \"video\":\n\t\t\tr.HasPic = true\n\t\tcase \"richtext\":\n\t\t\tr.HasRichText = true\n\t\tcase \"picture\":\n\t\t\tr.HasPic = true\n\t\tcase \"pictures\":\n\t\t\tr.HasPic = true\n\t\t\tr.NeedJSON = true\n\t\tcase \"time.Time\":\n\t\t\tr.HasTimer = true\n\t\t\tif r.Fields[i].FieldSearchType != \"\" && r.Fields[i].FieldSearchType != \"BETWEEN\" && r.Fields[i].FieldSearchType != \"NOT BETWEEN\" {\n\t\t\t\tr.HasSearchTimer = true\n\t\t\t}\n\t\t}\n\t\tif r.Fields[i].DataSource != nil {\n\t\t\tif r.Fields[i].DataSource.Table != \"\" && r.Fields[i].DataSource.Label != \"\" && r.Fields[i].DataSource.Value != \"\" {\n\t\t\t\tr.HasDataSource = true\n\t\t\t\tr.Fields[i].CheckDataSource = true\n\t\t\t\tr.DataSourceMap[r.Fields[i].FieldJson] = r.Fields[i].DataSource\n\t\t\t}\n\t\t}\n\t\tif !r.GvaModel && r.PrimaryField == nil && r.Fields[i].PrimaryKey {\n\t\t\tr.PrimaryField = r.Fields[i]\n\t\t} // 自定义主键\n\t}\n\t{\n\t\tfor key := range dict {\n\t\t\tr.DictTypes = append(r.DictTypes, key)\n\t\t}\n\t} // DictTypes => 字典\n\t{\n\t\tif r.GvaModel {\n\t\t\tr.PrimaryField = &AutoCodeField{\n\t\t\t\tFieldName:    \"ID\",\n\t\t\t\tFieldType:    \"uint\",\n\t\t\t\tFieldDesc:    \"ID\",\n\t\t\t\tFieldJson:    \"ID\",\n\t\t\t\tDataTypeLong: \"20\",\n\t\t\t\tComment:      \"主键ID\",\n\t\t\t\tColumnName:   \"id\",\n\t\t\t}\n\t\t}\n\t} // GvaModel\n\t{\n\t\tif r.IsAdd && r.PrimaryField == nil {\n\t\t\tr.PrimaryField = new(AutoCodeField)\n\t\t}\n\t} // 新增字段模式下不关注主键\n\tif r.Package == \"\" {\n\t\treturn errors.New(\"Package为空!\")\n\t} // 增加判断：Package不为空\n\tpackages := []rune(r.Package)\n\tif len(packages) > 0 {\n\t\tif packages[0] >= 97 && packages[0] <= 122 {\n\t\t\tpackages[0] = packages[0] - 32\n\t\t}\n\t\tr.PackageT = string(packages)\n\t} // PackageT 是 Package 的首字母大写\n\treturn nil\n}\n\nfunc (r *AutoCode) History() SysAutoHistoryCreate {\n\tbytes, _ := json.Marshal(r)\n\treturn SysAutoHistoryCreate{\n\t\tTable:       r.TableName,\n\t\tPackage:     r.Package,\n\t\tRequest:     string(bytes),\n\t\tStructName:  r.StructName,\n\t\tBusinessDB:  r.BusinessDB,\n\t\tDescription: r.Description,\n\t}\n}\n\ntype AutoCodeField struct {\n\tFieldName       string `json:\"fieldName\"`       // Field名\n\tFieldDesc       string `json:\"fieldDesc\"`       // 中文名\n\tFieldType       string `json:\"fieldType\"`       // Field数据类型\n\tFieldJson       string `json:\"fieldJson\"`       // FieldJson\n\tDataTypeLong    string `json:\"dataTypeLong\"`    // 数据库字段长度\n\tComment         string `json:\"comment\"`         // 数据库字段描述\n\tColumnName      string `json:\"columnName\"`      // 数据库字段\n\tFieldSearchType string `json:\"fieldSearchType\"` // 搜索条件\n\tFieldSearchHide bool   `json:\"fieldSearchHide\"` // 是否隐藏查询条件\n\tDictType        string `json:\"dictType\"`        // 字典\n\t//Front           bool        `json:\"front\"`           // 是否前端可见\n\tForm            bool        `json:\"form\"`            // 是否前端新建/编辑\n\tTable           bool        `json:\"table\"`           // 是否前端表格列\n\tDesc            bool        `json:\"desc\"`            // 是否前端详情\n\tExcel           bool        `json:\"excel\"`           // 是否导入/导出\n\tRequire         bool        `json:\"require\"`         // 是否必填\n\tDefaultValue    string      `json:\"defaultValue\"`    // 是否必填\n\tErrorText       string      `json:\"errorText\"`       // 校验失败文字\n\tClearable       bool        `json:\"clearable\"`       // 是否可清空\n\tSort            bool        `json:\"sort\"`            // 是否增加排序\n\tPrimaryKey      bool        `json:\"primaryKey\"`      // 是否主键\n\tDataSource      *DataSource `json:\"dataSource\"`      // 数据源\n\tCheckDataSource bool        `json:\"checkDataSource\"` // 是否检查数据源\n\tFieldIndexType  string      `json:\"fieldIndexType\"`  // 索引类型\n}\n\ntype AutoFunc struct {\n\tPackage         string `json:\"package\"`\n\tFuncName        string `json:\"funcName\"`        // 方法名称\n\tRouter          string `json:\"router\"`          // 路由名称\n\tFuncDesc        string `json:\"funcDesc\"`        // 方法介绍\n\tBusinessDB      string `json:\"businessDB\"`      // 业务库\n\tStructName      string `json:\"structName\"`      // Struct名称\n\tPackageName     string `json:\"packageName\"`     // 文件名称\n\tDescription     string `json:\"description\"`     // Struct中文名称\n\tAbbreviation    string `json:\"abbreviation\"`    // Struct简称\n\tHumpPackageName string `json:\"humpPackageName\"` // go文件名称\n\tMethod          string `json:\"method\"`          // 方法\n\tIsPlugin        bool   `json:\"isPlugin\"`        // 是否插件\n\tIsAuth          bool   `json:\"isAuth\"`          // 是否鉴权\n\tIsPreview       bool   `json:\"isPreview\"`       // 是否预览\n\tIsAi            bool   `json:\"isAi\"`            // 是否AI\n\tApiFunc         string `json:\"apiFunc\"`         // API方法\n\tServerFunc      string `json:\"serverFunc\"`      // 服务方法\n\tJsFunc          string `json:\"jsFunc\"`          // JS方法\n}\n\ntype InitMenu struct {\n\tPlugName   string `json:\"plugName\"`\n\tParentMenu string `json:\"parentMenu\"`\n\tMenus      []uint `json:\"menus\"`\n}\n\ntype InitApi struct {\n\tPlugName string `json:\"plugName\"`\n\tAPIs     []uint `json:\"apis\"`\n}\n\ntype InitDictionary struct {\n\tPlugName     string `json:\"plugName\"`\n\tDictionaries []uint `json:\"dictionaries\"`\n}\n\ntype LLMAutoCode struct {\n\tPrompt string `json:\"prompt\" form:\"prompt\" gorm:\"column:prompt;comment:提示语;type:text;\"` //提示语\n\tMode   string `json:\"mode\" form:\"mode\" gorm:\"column:mode;comment:模式;type:text;\"`        //模式\n}\n"
  },
  {
    "path": "server/model/system/request/sys_auto_code_mcp.go",
    "content": "package request\n\ntype AutoMcpTool struct {\n\tName        string `json:\"name\" form:\"name\" binding:\"required\"`\n\tDescription string `json:\"description\" form:\"description\" binding:\"required\"`\n\tParams      []struct {\n\t\tName        string `json:\"name\" form:\"name\" binding:\"required\"`\n\t\tDescription string `json:\"description\" form:\"description\" binding:\"required\"`\n\t\tType        string `json:\"type\" form:\"type\" binding:\"required\"` // string, number, boolean, object, array\n\t\tRequired    bool   `json:\"required\" form:\"required\"`\n\t\tDefault     string `json:\"default\" form:\"default\"`\n\t} `json:\"params\" form:\"params\"`\n\tResponse []struct {\n\t\tType string `json:\"type\" form:\"type\" binding:\"required\"` // text, image\n\t} `json:\"response\" form:\"response\"`\n}\n"
  },
  {
    "path": "server/model/system/request/sys_auto_code_package.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysAutoCodePackageCreate struct {\n\tDesc        string `json:\"desc\" example:\"描述\"`\n\tLabel       string `json:\"label\" example:\"展示名\"`\n\tTemplate    string `json:\"template\"  example:\"模版\"`\n\tPackageName string `json:\"packageName\" example:\"包名\"`\n\tModule      string `json:\"-\" example:\"模块\"`\n}\n\nfunc (r *SysAutoCodePackageCreate) AutoCode() AutoCode {\n\treturn AutoCode{\n\t\tPackage: r.PackageName,\n\t\tModule:  global.GVA_CONFIG.AutoCode.Module,\n\t}\n}\n\nfunc (r *SysAutoCodePackageCreate) Create() model.SysAutoCodePackage {\n\treturn model.SysAutoCodePackage{\n\t\tDesc:        r.Desc,\n\t\tLabel:       r.Label,\n\t\tTemplate:    r.Template,\n\t\tPackageName: r.PackageName,\n\t\tModule:      global.GVA_CONFIG.AutoCode.Module,\n\t}\n}\n"
  },
  {
    "path": "server/model/system/request/sys_auto_history.go",
    "content": "package request\n\nimport (\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysAutoHistoryCreate struct {\n\tTable            string            // 表名\n\tPackage          string            // 模块名/插件名\n\tRequest          string            // 前端传入的结构化信息\n\tStructName       string            // 结构体名称\n\tBusinessDB       string            // 业务库\n\tDescription      string            // Struct中文名称\n\tInjections       map[string]string // 注入路径\n\tTemplates        map[string]string // 模板信息\n\tApiIDs           []uint            // api表注册内容\n\tMenuID           uint              // 菜单ID\n\tExportTemplateID uint              // 导出模板ID\n}\n\nfunc (r *SysAutoHistoryCreate) Create() model.SysAutoCodeHistory {\n\tentity := model.SysAutoCodeHistory{\n\t\tPackage:          r.Package,\n\t\tRequest:          r.Request,\n\t\tTable:            r.Table,\n\t\tStructName:       r.StructName,\n\t\tAbbreviation:     r.StructName,\n\t\tBusinessDB:       r.BusinessDB,\n\t\tDescription:      r.Description,\n\t\tInjections:       r.Injections,\n\t\tTemplates:        r.Templates,\n\t\tApiIDs:           r.ApiIDs,\n\t\tMenuID:           r.MenuID,\n\t\tExportTemplateID: r.ExportTemplateID,\n\t}\n\tif entity.Table == \"\" {\n\t\tentity.Table = r.StructName\n\t}\n\treturn entity\n}\n\ntype SysAutoHistoryRollBack struct {\n\tcommon.GetById\n\tDeleteApi   bool `json:\"deleteApi\" form:\"deleteApi\"`     // 是否删除接口\n\tDeleteMenu  bool `json:\"deleteMenu\" form:\"deleteMenu\"`   // 是否删除菜单\n\tDeleteTable bool `json:\"deleteTable\" form:\"deleteTable\"` // 是否删除表\n}\n\nfunc (r *SysAutoHistoryRollBack) ApiIds(entity model.SysAutoCodeHistory) common.IdsReq {\n\tlength := len(entity.ApiIDs)\n\tids := make([]int, 0)\n\tfor i := 0; i < length; i++ {\n\t\tids = append(ids, int(entity.ApiIDs[i]))\n\t}\n\treturn common.IdsReq{Ids: ids}\n}\n"
  },
  {
    "path": "server/model/system/request/sys_casbin.go",
    "content": "package request\n\n// CasbinInfo Casbin info structure\ntype CasbinInfo struct {\n\tPath   string `json:\"path\"`   // 路径\n\tMethod string `json:\"method\"` // 方法\n}\n\n// CasbinInReceive Casbin structure for input parameters\ntype CasbinInReceive struct {\n\tAuthorityId uint         `json:\"authorityId\"` // 权限id\n\tCasbinInfos []CasbinInfo `json:\"casbinInfos\"`\n}\n\nfunc DefaultCasbin() []CasbinInfo {\n\treturn []CasbinInfo{\n\t\t{Path: \"/menu/getMenu\", Method: \"POST\"},\n\t\t{Path: \"/jwt/jsonInBlacklist\", Method: \"POST\"},\n\t\t{Path: \"/base/login\", Method: \"POST\"},\n\t\t{Path: \"/user/changePassword\", Method: \"POST\"},\n\t\t{Path: \"/user/setUserAuthority\", Method: \"POST\"},\n\t\t{Path: \"/user/getUserInfo\", Method: \"GET\"},\n\t\t{Path: \"/user/setSelfInfo\", Method: \"PUT\"},\n\t\t{Path: \"/fileUploadAndDownload/upload\", Method: \"POST\"},\n\t\t{Path: \"/sysDictionary/findSysDictionary\", Method: \"GET\"},\n\t}\n}\n"
  },
  {
    "path": "server/model/system/request/sys_dictionary.go",
    "content": "package request\n\ntype SysDictionarySearch struct {\n\tName string `json:\"name\" form:\"name\" gorm:\"column:name;comment:字典名（中）\"` // 字典名（中）\n}\n\ntype ImportSysDictionaryRequest struct {\n\tJson string `json:\"json\" binding:\"required\"` // JSON字符串\n}\n"
  },
  {
    "path": "server/model/system/request/sys_dictionary_detail.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysDictionaryDetailSearch struct {\n\tsystem.SysDictionaryDetail\n\trequest.PageInfo\n\tParentID *uint `json:\"parentID\" form:\"parentID\"` // 父级字典详情ID，用于查询指定父级下的子项\n\tLevel    *int  `json:\"level\" form:\"level\"`       // 层级深度，用于查询指定层级的数据\n}\n\n// CreateSysDictionaryDetailRequest 创建字典详情请求\ntype CreateSysDictionaryDetailRequest struct {\n\tLabel           string `json:\"label\" form:\"label\" binding:\"required\"`                     // 展示值\n\tValue           string `json:\"value\" form:\"value\" binding:\"required\"`                     // 字典值\n\tExtend          string `json:\"extend\" form:\"extend\"`                                      // 扩展值\n\tStatus          *bool  `json:\"status\" form:\"status\"`                                      // 启用状态\n\tSort            int    `json:\"sort\" form:\"sort\"`                                          // 排序标记\n\tSysDictionaryID int    `json:\"sysDictionaryID\" form:\"sysDictionaryID\" binding:\"required\"` // 关联标记\n\tParentID        *uint  `json:\"parentID\" form:\"parentID\"`                                  // 父级字典详情ID\n}\n\n// UpdateSysDictionaryDetailRequest 更新字典详情请求\ntype UpdateSysDictionaryDetailRequest struct {\n\tID              uint   `json:\"ID\" form:\"ID\" binding:\"required\"`                           // 主键ID\n\tLabel           string `json:\"label\" form:\"label\" binding:\"required\"`                     // 展示值\n\tValue           string `json:\"value\" form:\"value\" binding:\"required\"`                     // 字典值\n\tExtend          string `json:\"extend\" form:\"extend\"`                                      // 扩展值\n\tStatus          *bool  `json:\"status\" form:\"status\"`                                      // 启用状态\n\tSort            int    `json:\"sort\" form:\"sort\"`                                          // 排序标记\n\tSysDictionaryID int    `json:\"sysDictionaryID\" form:\"sysDictionaryID\" binding:\"required\"` // 关联标记\n\tParentID        *uint  `json:\"parentID\" form:\"parentID\"`                                  // 父级字典详情ID\n}\n\n// GetDictionaryDetailsByParentRequest 根据父级ID获取字典详情请求\ntype GetDictionaryDetailsByParentRequest struct {\n\tSysDictionaryID int   `json:\"sysDictionaryID\" form:\"sysDictionaryID\" binding:\"required\"` // 字典ID\n\tParentID        *uint `json:\"parentID\" form:\"parentID\"`                                  // 父级字典详情ID，为空时获取顶级\n\tIncludeChildren bool  `json:\"includeChildren\" form:\"includeChildren\"`                    // 是否包含子级数据\n}\n"
  },
  {
    "path": "server/model/system/request/sys_error.go",
    "content": "\npackage request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"time\"\n)\n\ntype SysErrorSearch struct{\n    CreatedAtRange []time.Time `json:\"createdAtRange\" form:\"createdAtRange[]\"`\n      Form  *string `json:\"form\" form:\"form\"` \n      Info  *string `json:\"info\" form:\"info\"` \n    request.PageInfo\n}\n"
  },
  {
    "path": "server/model/system/request/sys_export_template.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"time\"\n)\n\ntype SysExportTemplateSearch struct {\n\tsystem.SysExportTemplate\n\tStartCreatedAt *time.Time `json:\"startCreatedAt\" form:\"startCreatedAt\"`\n\tEndCreatedAt   *time.Time `json:\"endCreatedAt\" form:\"endCreatedAt\"`\n\trequest.PageInfo\n}\n"
  },
  {
    "path": "server/model/system/request/sys_init.go",
    "content": "package request\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"os\"\n)\n\ntype InitDB struct {\n\tAdminPassword string `json:\"adminPassword\" binding:\"required\"`\n\tDBType        string `json:\"dbType\"`                    // 数据库类型\n\tHost          string `json:\"host\"`                      // 服务器地址\n\tPort          string `json:\"port\"`                      // 数据库连接端口\n\tUserName      string `json:\"userName\"`                  // 数据库用户名\n\tPassword      string `json:\"password\"`                  // 数据库密码\n\tDBName        string `json:\"dbName\" binding:\"required\"` // 数据库名\n\tDBPath        string `json:\"dbPath\"`                    // sqlite数据库文件路径\n\tTemplate      string `json:\"template\"`                  // postgresql指定template\n}\n\n// MysqlEmptyDsn msyql 空数据库 建库链接\n// Author SliverHorn\nfunc (i *InitDB) MysqlEmptyDsn() string {\n\tif i.Host == \"\" {\n\t\ti.Host = \"127.0.0.1\"\n\t}\n\tif i.Port == \"\" {\n\t\ti.Port = \"3306\"\n\t}\n\treturn fmt.Sprintf(\"%s:%s@tcp(%s:%s)/\", i.UserName, i.Password, i.Host, i.Port)\n}\n\n// PgsqlEmptyDsn pgsql 空数据库 建库链接\n// Author SliverHorn\nfunc (i *InitDB) PgsqlEmptyDsn() string {\n\tif i.Host == \"\" {\n\t\ti.Host = \"127.0.0.1\"\n\t}\n\tif i.Port == \"\" {\n\t\ti.Port = \"5432\"\n\t}\n\treturn \"host=\" + i.Host + \" user=\" + i.UserName + \" password=\" + i.Password + \" port=\" + i.Port + \" dbname=\" + \"postgres\" + \" \" + \"sslmode=disable TimeZone=Asia/Shanghai\"\n}\n\n// SqliteEmptyDsn sqlite 空数据库 建库链接\n// Author Kafumio\nfunc (i *InitDB) SqliteEmptyDsn() string {\n\tseparator := string(os.PathSeparator)\n\treturn i.DBPath + separator + i.DBName + \".db\"\n}\n\nfunc (i *InitDB) MssqlEmptyDsn() string {\n\treturn \"sqlserver://\" + i.UserName + \":\" + i.Password + \"@\" + i.Host + \":\" + i.Port + \"?database=\" + i.DBName + \"&encrypt=disable\"\n}\n\n// ToMysqlConfig 转换 config.Mysql\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (i *InitDB) ToMysqlConfig() config.Mysql {\n\treturn config.Mysql{\n\t\tGeneralDB: config.GeneralDB{\n\t\t\tPath:         i.Host,\n\t\t\tPort:         i.Port,\n\t\t\tDbname:       i.DBName,\n\t\t\tUsername:     i.UserName,\n\t\t\tPassword:     i.Password,\n\t\t\tMaxIdleConns: 10,\n\t\t\tMaxOpenConns: 100,\n\t\t\tLogMode:      \"error\",\n\t\t\tConfig:       \"charset=utf8mb4&parseTime=True&loc=Local\",\n\t\t},\n\t}\n}\n\n// ToPgsqlConfig 转换 config.Pgsql\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (i *InitDB) ToPgsqlConfig() config.Pgsql {\n\treturn config.Pgsql{\n\t\tGeneralDB: config.GeneralDB{\n\t\t\tPath:         i.Host,\n\t\t\tPort:         i.Port,\n\t\t\tDbname:       i.DBName,\n\t\t\tUsername:     i.UserName,\n\t\t\tPassword:     i.Password,\n\t\t\tMaxIdleConns: 10,\n\t\t\tMaxOpenConns: 100,\n\t\t\tLogMode:      \"error\",\n\t\t\tConfig:       \"sslmode=disable TimeZone=Asia/Shanghai\",\n\t\t},\n\t}\n}\n\n// ToSqliteConfig 转换 config.Sqlite\n// Author [Kafumio](https://github.com/Kafumio)\nfunc (i *InitDB) ToSqliteConfig() config.Sqlite {\n\treturn config.Sqlite{\n\t\tGeneralDB: config.GeneralDB{\n\t\t\tPath:         i.DBPath,\n\t\t\tPort:         i.Port,\n\t\t\tDbname:       i.DBName,\n\t\t\tUsername:     i.UserName,\n\t\t\tPassword:     i.Password,\n\t\t\tMaxIdleConns: 10,\n\t\t\tMaxOpenConns: 100,\n\t\t\tLogMode:      \"error\",\n\t\t\tConfig:       \"\",\n\t\t},\n\t}\n}\n\nfunc (i *InitDB) ToMssqlConfig() config.Mssql {\n\treturn config.Mssql{\n\t\tGeneralDB: config.GeneralDB{\n\t\t\tPath:         i.DBPath,\n\t\t\tPort:         i.Port,\n\t\t\tDbname:       i.DBName,\n\t\t\tUsername:     i.UserName,\n\t\t\tPassword:     i.Password,\n\t\t\tMaxIdleConns: 10,\n\t\t\tMaxOpenConns: 100,\n\t\t\tLogMode:      \"error\",\n\t\t\tConfig:       \"\",\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "server/model/system/request/sys_login_log.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysLoginLogSearch struct {\n\tsystem.SysLoginLog\n\trequest.PageInfo\n}\n"
  },
  {
    "path": "server/model/system/request/sys_menu.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\n// AddMenuAuthorityInfo Add menu authority info structure\ntype AddMenuAuthorityInfo struct {\n\tMenus       []system.SysBaseMenu `json:\"menus\"`\n\tAuthorityId uint                 `json:\"authorityId\"` // 角色ID\n}\n\n// SetMenuAuthorities 通过菜单ID全量覆盖关联角色列表\ntype SetMenuAuthorities struct {\n\tMenuId       uint   `json:\"menuId\" form:\"menuId\"`             // 菜单ID\n\tAuthorityIds []uint `json:\"authorityIds\" form:\"authorityIds\"` // 角色ID列表\n}\n\nfunc DefaultMenu() []system.SysBaseMenu {\n\treturn []system.SysBaseMenu{{\n\t\tGVA_MODEL: global.GVA_MODEL{ID: 1},\n\t\tParentId:  0,\n\t\tPath:      \"dashboard\",\n\t\tName:      \"dashboard\",\n\t\tComponent: \"view/dashboard/index.vue\",\n\t\tSort:      1,\n\t\tMeta: system.Meta{\n\t\t\tTitle: \"仪表盘\",\n\t\t\tIcon:  \"setting\",\n\t\t},\n\t}}\n}\n"
  },
  {
    "path": "server/model/system/request/sys_operation_record.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysOperationRecordSearch struct {\n\tsystem.SysOperationRecord\n\trequest.PageInfo\n}\n"
  },
  {
    "path": "server/model/system/request/sys_params.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"time\"\n)\n\ntype SysParamsSearch struct {\n\tStartCreatedAt *time.Time `json:\"startCreatedAt\" form:\"startCreatedAt\"`\n\tEndCreatedAt   *time.Time `json:\"endCreatedAt\" form:\"endCreatedAt\"`\n\tName           string     `json:\"name\" form:\"name\" `\n\tKey            string     `json:\"key\" form:\"key\" `\n\trequest.PageInfo\n}\n"
  },
  {
    "path": "server/model/system/request/sys_skills.go",
    "content": "package request\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\ntype SkillToolRequest struct {\n\tTool string `json:\"tool\"`\n}\n\ntype SkillDetailRequest struct {\n\tTool  string `json:\"tool\"`\n\tSkill string `json:\"skill\"`\n}\n\ntype SkillDeleteRequest struct {\n\tTool  string `json:\"tool\"`\n\tSkill string `json:\"skill\"`\n}\n\ntype SkillPackageRequest struct {\n\tTool  string `json:\"tool\"`\n\tSkill string `json:\"skill\"`\n}\n\ntype SkillSaveRequest struct {\n\tTool      string           `json:\"tool\"`\n\tSkill     string           `json:\"skill\"`\n\tMeta      system.SkillMeta `json:\"meta\"`\n\tMarkdown  string           `json:\"markdown\"`\n\tSyncTools []string         `json:\"syncTools\"`\n}\n\ntype SkillScriptCreateRequest struct {\n\tTool       string `json:\"tool\"`\n\tSkill      string `json:\"skill\"`\n\tFileName   string `json:\"fileName\"`\n\tScriptType string `json:\"scriptType\"`\n}\n\ntype SkillResourceCreateRequest struct {\n\tTool     string `json:\"tool\"`\n\tSkill    string `json:\"skill\"`\n\tFileName string `json:\"fileName\"`\n}\n\ntype SkillReferenceCreateRequest struct {\n\tTool     string `json:\"tool\"`\n\tSkill    string `json:\"skill\"`\n\tFileName string `json:\"fileName\"`\n}\n\ntype SkillTemplateCreateRequest struct {\n\tTool     string `json:\"tool\"`\n\tSkill    string `json:\"skill\"`\n\tFileName string `json:\"fileName\"`\n}\n\ntype SkillFileRequest struct {\n\tTool     string `json:\"tool\"`\n\tSkill    string `json:\"skill\"`\n\tFileName string `json:\"fileName\"`\n}\n\ntype SkillFileSaveRequest struct {\n\tTool     string `json:\"tool\"`\n\tSkill    string `json:\"skill\"`\n\tFileName string `json:\"fileName\"`\n\tContent  string `json:\"content\"`\n}\n\ntype SkillGlobalConstraintSaveRequest struct {\n\tTool      string   `json:\"tool\"`\n\tContent   string   `json:\"content\"`\n\tSyncTools []string `json:\"syncTools\"`\n}\n\ntype DownloadOnlineSkillReq struct {\n\tTool    string `json:\"tool\" binding:\"required\"`\n\tID      uint   `json:\"id\" binding:\"required\"`\n\tVersion string `json:\"version\" binding:\"required\"`\n}\n"
  },
  {
    "path": "server/model/system/request/sys_user.go",
    "content": "package request\n\nimport (\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\n// Register User register structure\ntype Register struct {\n\tUsername     string `json:\"userName\" example:\"用户名\"`\n\tPassword     string `json:\"passWord\" example:\"密码\"`\n\tNickName     string `json:\"nickName\" example:\"昵称\"`\n\tHeaderImg    string `json:\"headerImg\" example:\"头像链接\"`\n\tAuthorityId  uint   `json:\"authorityId\" swaggertype:\"string\" example:\"int 角色id\"`\n\tEnable       int    `json:\"enable\" swaggertype:\"string\" example:\"int 是否启用\"`\n\tAuthorityIds []uint `json:\"authorityIds\" swaggertype:\"string\" example:\"[]uint 角色id\"`\n\tPhone        string `json:\"phone\" example:\"电话号码\"`\n\tEmail        string `json:\"email\" example:\"电子邮箱\"`\n}\n\n// Login User login structure\ntype Login struct {\n\tUsername  string `json:\"username\"`  // 用户名\n\tPassword  string `json:\"password\"`  // 密码\n\tCaptcha   string `json:\"captcha\"`   // 验证码\n\tCaptchaId string `json:\"captchaId\"` // 验证码ID\n}\n\n// ChangePasswordReq Modify password structure\ntype ChangePasswordReq struct {\n\tID          uint   `json:\"-\"`           // 从 JWT 中提取 user id，避免越权\n\tPassword    string `json:\"password\"`    // 密码\n\tNewPassword string `json:\"newPassword\"` // 新密码\n}\n\ntype ResetPassword struct {\n\tID       uint   `json:\"ID\" form:\"ID\"`\n\tPassword string `json:\"password\" form:\"password\" gorm:\"comment:用户登录密码\"` // 用户登录密码\n}\n\n// SetUserAuth Modify user's auth structure\ntype SetUserAuth struct {\n\tAuthorityId uint `json:\"authorityId\"` // 角色ID\n}\n\n// SetUserAuthorities Modify user's auth structure\ntype SetUserAuthorities struct {\n\tID           uint\n\tAuthorityIds []uint `json:\"authorityIds\"` // 角色ID\n}\n\ntype ChangeUserInfo struct {\n\tID           uint                  `gorm:\"primarykey\"`                                                                           // 主键ID\n\tNickName     string                `json:\"nickName\" gorm:\"default:系统用户;comment:用户昵称\"`                                            // 用户昵称\n\tPhone        string                `json:\"phone\"  gorm:\"comment:用户手机号\"`                                                          // 用户手机号\n\tAuthorityIds []uint                `json:\"authorityIds\" gorm:\"-\"`                                                                // 角色ID\n\tEmail        string                `json:\"email\"  gorm:\"comment:用户邮箱\"`                                                           // 用户邮箱\n\tHeaderImg    string                `json:\"headerImg\" gorm:\"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像\"` // 用户头像\n\tEnable       int                   `json:\"enable\" gorm:\"comment:冻结用户\"`                                                           //冻结用户\n\tAuthorities  []system.SysAuthority `json:\"-\" gorm:\"many2many:sys_user_authority;\"`\n}\n\ntype GetUserList struct {\n\tcommon.PageInfo\n\tUsername string `json:\"username\" form:\"username\"`\n\tNickName string `json:\"nickName\" form:\"nickName\"`\n\tPhone    string `json:\"phone\" form:\"phone\"`\n\tEmail    string `json:\"email\" form:\"email\"`\n\tOrderKey string `json:\"orderKey\" form:\"orderKey\"` // 排序\n\tDesc     bool   `json:\"desc\" form:\"desc\"`         // 排序方式:升序false(默认)|降序true\n}\n\n// SetRoleUsers 通过角色ID全量覆盖关联用户列表\ntype SetRoleUsers struct {\n\tAuthorityId uint   `json:\"authorityId\" form:\"authorityId\"` // 角色ID\n\tUserIds     []uint `json:\"userIds\" form:\"userIds\"`         // 用户ID列表\n}\n"
  },
  {
    "path": "server/model/system/request/sys_version.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"time\"\n)\n\ntype SysVersionSearch struct {\n\tCreatedAtRange []time.Time `json:\"createdAtRange\" form:\"createdAtRange[]\"`\n\tVersionName    *string     `json:\"versionName\" form:\"versionName\"`\n\tVersionCode    *string     `json:\"versionCode\" form:\"versionCode\"`\n\trequest.PageInfo\n}\n\n// ExportVersionRequest 导出版本请求结构体\ntype ExportVersionRequest struct {\n\tVersionName string `json:\"versionName\" binding:\"required\"` // 版本名称\n\tVersionCode string `json:\"versionCode\" binding:\"required\"` // 版本号\n\tDescription string `json:\"description\"`                    // 版本描述\n\tMenuIds     []uint `json:\"menuIds\"`                        // 选中的菜单ID列表\n\tApiIds      []uint `json:\"apiIds\"`                         // 选中的API ID列表\n\tDictIds     []uint `json:\"dictIds\"`                        // 选中的字典ID列表\n}\n\n// ImportVersionRequest 导入版本请求结构体\ntype ImportVersionRequest struct {\n\tVersionInfo      VersionInfo            `json:\"version\" binding:\"required\"` // 版本信息\n\tExportMenu       []system.SysBaseMenu   `json:\"menus\"`                      // 菜单数据，直接复用SysBaseMenu\n\tExportApi        []system.SysApi        `json:\"apis\"`                       // API数据，直接复用SysApi\n\tExportDictionary []system.SysDictionary `json:\"dictionaries\"`               // 字典数据，直接复用SysDictionary\n}\n\n// VersionInfo 版本信息结构体\ntype VersionInfo struct {\n\tName        string `json:\"name\" binding:\"required\"`        // 版本名称\n\tCode        string `json:\"code\" binding:\"required\"`        // 版本号\n\tDescription string `json:\"description\"`                    // 版本描述\n\tExportTime  string `json:\"exportTime\"`                     // 导出时间\n}\n"
  },
  {
    "path": "server/model/system/response/sys_api.go",
    "content": "package response\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysAPIResponse struct {\n\tApi system.SysApi `json:\"api\"`\n}\n\ntype SysAPIListResponse struct {\n\tApis []system.SysApi `json:\"apis\"`\n}\n\ntype SysSyncApis struct {\n\tNewApis    []system.SysApi `json:\"newApis\"`\n\tDeleteApis []system.SysApi `json:\"deleteApis\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_authority.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\ntype SysAuthorityResponse struct {\n\tAuthority system.SysAuthority `json:\"authority\"`\n}\n\ntype SysAuthorityCopyResponse struct {\n\tAuthority      system.SysAuthority `json:\"authority\"`\n\tOldAuthorityId uint                `json:\"oldAuthorityId\"` // 旧角色ID\n}\n"
  },
  {
    "path": "server/model/system/response/sys_authority_btn.go",
    "content": "package response\n\ntype SysAuthorityBtnRes struct {\n\tSelected []uint `json:\"selected\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_auto_code.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\ntype Db struct {\n\tDatabase string `json:\"database\" gorm:\"column:database\"`\n}\n\ntype Table struct {\n\tTableName string `json:\"tableName\" gorm:\"column:table_name\"`\n}\n\ntype Column struct {\n\tDataType      string `json:\"dataType\" gorm:\"column:data_type\"`\n\tColumnName    string `json:\"columnName\" gorm:\"column:column_name\"`\n\tDataTypeLong  string `json:\"dataTypeLong\" gorm:\"column:data_type_long\"`\n\tColumnComment string `json:\"columnComment\" gorm:\"column:column_comment\"`\n\tPrimaryKey    bool   `json:\"primaryKey\" gorm:\"column:primary_key\"`\n}\n\ntype PluginInfo struct {\n\tPluginName   string                 `json:\"pluginName\"`\n\tPluginType   string                 `json:\"pluginType\"` // web, server, full\n\tApis         []system.SysApi        `json:\"apis\"`\n\tMenus        []system.SysBaseMenu   `json:\"menus\"`\n\tDictionaries []system.SysDictionary `json:\"dictionaries\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_captcha.go",
    "content": "package response\n\ntype SysCaptchaResponse struct {\n\tCaptchaId     string `json:\"captchaId\"`\n\tPicPath       string `json:\"picPath\"`\n\tCaptchaLength int    `json:\"captchaLength\"`\n\tOpenCaptcha   bool   `json:\"openCaptcha\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_casbin.go",
    "content": "package response\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\ntype PolicyPathResponse struct {\n\tPaths []request.CasbinInfo `json:\"paths\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_menu.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\ntype SysMenusResponse struct {\n\tMenus []system.SysMenu `json:\"menus\"`\n}\n\ntype SysBaseMenusResponse struct {\n\tMenus []system.SysBaseMenu `json:\"menus\"`\n}\n\ntype SysBaseMenuResponse struct {\n\tMenu system.SysBaseMenu `json:\"menu\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_system.go",
    "content": "package response\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\ntype SysConfigResponse struct {\n\tConfig config.Server `json:\"config\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_user.go",
    "content": "package response\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype SysUserResponse struct {\n\tUser system.SysUser `json:\"user\"`\n}\n\ntype LoginResponse struct {\n\tUser      system.SysUser `json:\"user\"`\n\tToken     string         `json:\"token\"`\n\tExpiresAt int64          `json:\"expiresAt\"`\n}\n"
  },
  {
    "path": "server/model/system/response/sys_version.go",
    "content": "package response\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\n// ExportVersionResponse 导出版本响应结构体\ntype ExportVersionResponse struct {\n\tVersion      request.VersionInfo    `json:\"version\"`      // 版本信息\n\tMenus        []system.SysBaseMenu   `json:\"menus\"`        // 菜单数据，直接复用SysBaseMenu\n\tApis         []system.SysApi        `json:\"apis\"`         // API数据，直接复用SysApi\n\tDictionaries []system.SysDictionary `json:\"dictionaries\"` // 字典数据，直接复用SysDictionary\n}\n"
  },
  {
    "path": "server/model/system/sys_api.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype SysApi struct {\n\tglobal.GVA_MODEL\n\tPath        string `json:\"path\" gorm:\"comment:api路径\"`             // api路径\n\tDescription string `json:\"description\" gorm:\"comment:api中文描述\"`    // api中文描述\n\tApiGroup    string `json:\"apiGroup\" gorm:\"comment:api组\"`          // api组\n\tMethod      string `json:\"method\" gorm:\"default:POST;comment:方法\"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\n}\n\nfunc (SysApi) TableName() string {\n\treturn \"sys_apis\"\n}\n\ntype SysIgnoreApi struct {\n\tglobal.GVA_MODEL\n\tPath   string `json:\"path\" gorm:\"comment:api路径\"`             // api路径\n\tMethod string `json:\"method\" gorm:\"default:POST;comment:方法\"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE\n\tFlag   bool   `json:\"flag\" gorm:\"-\"`                         // 是否忽略\n}\n\nfunc (SysIgnoreApi) TableName() string {\n\treturn \"sys_ignore_apis\"\n}\n"
  },
  {
    "path": "server/model/system/sys_api_token.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"time\"\n)\n\ntype SysApiToken struct {\n\tglobal.GVA_MODEL\n\tUserID      uint      `json:\"userId\" gorm:\"comment:用户ID\"`\n\tUser        SysUser   `json:\"user\" gorm:\"foreignKey:UserID;\"`\n\tAuthorityID uint      `json:\"authorityId\" gorm:\"comment:角色ID\"`\n\tToken       string    `json:\"token\" gorm:\"type:text;comment:Token\"`\n\tStatus      bool      `json:\"status\" gorm:\"default:true;comment:状态\"` // true有效 false无效\n\tExpiresAt   time.Time `json:\"expiresAt\" gorm:\"comment:过期时间\"`\n\tRemark      string    `json:\"remark\" gorm:\"comment:备注\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_authority.go",
    "content": "package system\n\nimport (\n\t\"time\"\n)\n\ntype SysAuthority struct {\n\tCreatedAt       time.Time       // 创建时间\n\tUpdatedAt       time.Time       // 更新时间\n\tDeletedAt       *time.Time      `sql:\"index\"`\n\tAuthorityId     uint            `json:\"authorityId\" gorm:\"not null;unique;primary_key;comment:角色ID;size:90\"` // 角色ID\n\tAuthorityName   string          `json:\"authorityName\" gorm:\"comment:角色名\"`                                    // 角色名\n\tParentId        *uint           `json:\"parentId\" gorm:\"comment:父角色ID\"`                                       // 父角色ID\n\tDataAuthorityId []*SysAuthority `json:\"dataAuthorityId\" gorm:\"many2many:sys_data_authority_id;\"`\n\tChildren        []SysAuthority  `json:\"children\" gorm:\"-\"`\n\tSysBaseMenus    []SysBaseMenu   `json:\"menus\" gorm:\"many2many:sys_authority_menus;\"`\n\tUsers           []SysUser       `json:\"-\" gorm:\"many2many:sys_user_authority;\"`\n\tDefaultRouter   string          `json:\"defaultRouter\" gorm:\"comment:默认菜单;default:dashboard\"` // 默认菜单(默认dashboard)\n}\n\nfunc (SysAuthority) TableName() string {\n\treturn \"sys_authorities\"\n}\n"
  },
  {
    "path": "server/model/system/sys_authority_btn.go",
    "content": "package system\n\ntype SysAuthorityBtn struct {\n\tAuthorityId      uint           `gorm:\"comment:角色ID\"`\n\tSysMenuID        uint           `gorm:\"comment:菜单ID\"`\n\tSysBaseMenuBtnID uint           `gorm:\"comment:菜单按钮ID\"`\n\tSysBaseMenuBtn   SysBaseMenuBtn ` gorm:\"comment:按钮详情\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_authority_menu.go",
    "content": "package system\n\ntype SysMenu struct {\n\tSysBaseMenu\n\tMenuId      uint                   `json:\"menuId\" gorm:\"comment:菜单ID\"`\n\tAuthorityId uint                   `json:\"-\" gorm:\"comment:角色ID\"`\n\tChildren    []SysMenu              `json:\"children\" gorm:\"-\"`\n\tParameters  []SysBaseMenuParameter `json:\"parameters\" gorm:\"foreignKey:SysBaseMenuID;references:MenuId\"`\n\tBtns        map[string]uint        `json:\"btns\" gorm:\"-\"`\n}\n\ntype SysAuthorityMenu struct {\n\tMenuId      string `json:\"menuId\" gorm:\"comment:菜单ID;column:sys_base_menu_id\"`\n\tAuthorityId string `json:\"-\" gorm:\"comment:角色ID;column:sys_authority_authority_id\"`\n}\n\nfunc (s SysAuthorityMenu) TableName() string {\n\treturn \"sys_authority_menus\"\n}\n"
  },
  {
    "path": "server/model/system/sys_auto_code_history.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"gorm.io/gorm\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// SysAutoCodeHistory 自动迁移代码记录,用于回滚,重放使用\ntype SysAutoCodeHistory struct {\n\tglobal.GVA_MODEL\n\tTable            string             `json:\"tableName\" gorm:\"column:table_name;comment:表名\"`\n\tPackage          string             `json:\"package\" gorm:\"column:package;comment:模块名/插件名\"`\n\tRequest          string             `json:\"request\" gorm:\"type:text;column:request;comment:前端传入的结构化信息\"`\n\tStructName       string             `json:\"structName\" gorm:\"column:struct_name;comment:结构体名称\"`\n\tAbbreviation     string             `json:\"abbreviation\" gorm:\"column:abbreviation;comment:结构体名称缩写\"`\n\tBusinessDB       string             `json:\"businessDb\" gorm:\"column:business_db;comment:业务库\"`\n\tDescription      string             `json:\"description\" gorm:\"column:description;comment:Struct中文名称\"`\n\tTemplates        map[string]string  `json:\"template\" gorm:\"serializer:json;type:text;column:templates;comment:模板信息\"`\n\tInjections       map[string]string  `json:\"injections\" gorm:\"serializer:json;type:text;column:Injections;comment:注入路径\"`\n\tFlag             int                `json:\"flag\" gorm:\"column:flag;comment:[0:创建,1:回滚]\"`\n\tApiIDs           []uint             `json:\"apiIDs\" gorm:\"serializer:json;column:api_ids;comment:api表注册内容\"`\n\tMenuID           uint               `json:\"menuId\" gorm:\"column:menu_id;comment:菜单ID\"`\n\tExportTemplateID uint               `json:\"exportTemplateID\" gorm:\"column:export_template_id;comment:导出模板ID\"`\n\tAutoCodePackage  SysAutoCodePackage `json:\"autoCodePackage\" gorm:\"foreignKey:ID;references:PackageID\"`\n\tPackageID        uint               `json:\"packageID\" gorm:\"column:package_id;comment:包ID\"`\n}\n\nfunc (s *SysAutoCodeHistory) BeforeCreate(db *gorm.DB) error {\n\ttemplates := make(map[string]string, len(s.Templates))\n\tfor key, value := range s.Templates {\n\t\tserver := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)\n\t\t{\n\t\t\thasServer := strings.Index(key, server)\n\t\t\tif hasServer != -1 {\n\t\t\t\tkey = strings.TrimPrefix(key, server)\n\t\t\t\tkeys := strings.Split(key, string(os.PathSeparator))\n\t\t\t\tkey = path.Join(keys...)\n\t\t\t}\n\t\t} // key\n\t\tweb := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot())\n\t\thasWeb := strings.Index(value, web)\n\t\tif hasWeb != -1 {\n\t\t\tvalue = strings.TrimPrefix(value, web)\n\t\t\tvalues := strings.Split(value, string(os.PathSeparator))\n\t\t\tvalue = path.Join(values...)\n\t\t\ttemplates[key] = value\n\t\t\tcontinue\n\t\t}\n\t\thasServer := strings.Index(value, server)\n\t\tif hasServer != -1 {\n\t\t\tvalue = strings.TrimPrefix(value, server)\n\t\t\tvalues := strings.Split(value, string(os.PathSeparator))\n\t\t\tvalue = path.Join(values...)\n\t\t\ttemplates[key] = value\n\t\t\tcontinue\n\t\t}\n\t}\n\ts.Templates = templates\n\treturn nil\n}\n\nfunc (s *SysAutoCodeHistory) TableName() string {\n\treturn \"sys_auto_code_histories\"\n}\n"
  },
  {
    "path": "server/model/system/sys_auto_code_package.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype SysAutoCodePackage struct {\n\tglobal.GVA_MODEL\n\tDesc        string `json:\"desc\" gorm:\"comment:描述\"`\n\tLabel       string `json:\"label\" gorm:\"comment:展示名\"`\n\tTemplate    string `json:\"template\"  gorm:\"comment:模版\"`\n\tPackageName string `json:\"packageName\" gorm:\"comment:包名\"`\n\tModule      string `json:\"-\" example:\"模块\"`\n}\n\nfunc (s *SysAutoCodePackage) TableName() string {\n\treturn \"sys_auto_code_packages\"\n}\n"
  },
  {
    "path": "server/model/system/sys_base_menu.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype SysBaseMenu struct {\n\tglobal.GVA_MODEL\n\tMenuLevel     uint                   `json:\"-\"`\n\tParentId      uint                   `json:\"parentId\" gorm:\"comment:父菜单ID\"`          // 父菜单ID\n\tPath          string                 `json:\"path\" gorm:\"comment:路由path\"`              // 路由path\n\tName          string                 `json:\"name\" gorm:\"comment:路由name\"`              // 路由name\n\tHidden        bool                   `json:\"hidden\" gorm:\"comment:是否在列表隐藏\"`      // 是否在列表隐藏\n\tComponent     string                 `json:\"component\" gorm:\"comment:对应前端文件路径\"` // 对应前端文件路径\n\tSort          int                    `json:\"sort\" gorm:\"comment:排序标记\"`              // 排序标记\n\tMeta          `json:\"meta\" gorm:\"embedded\"`                                             // 附加属性\n\tSysAuthoritys []SysAuthority         `json:\"authoritys\" gorm:\"many2many:sys_authority_menus;\"`\n\tChildren      []SysBaseMenu          `json:\"children\" gorm:\"-\"`\n\tParameters    []SysBaseMenuParameter `json:\"parameters\"`\n\tMenuBtn       []SysBaseMenuBtn       `json:\"menuBtn\"`\n}\n\ntype Meta struct {\n\tActiveName     string `json:\"activeName\" gorm:\"comment:高亮菜单\"`\n\tKeepAlive      bool   `json:\"keepAlive\" gorm:\"comment:是否缓存\"`                 // 是否缓存\n\tDefaultMenu    bool   `json:\"defaultMenu\" gorm:\"comment:是否是基础路由（开发中）\"` // 是否是基础路由（开发中）\n\tTitle          string `json:\"title\" gorm:\"comment:菜单名\"`                       // 菜单名\n\tIcon           string `json:\"icon\" gorm:\"comment:菜单图标\"`                      // 菜单图标\n\tCloseTab       bool   `json:\"closeTab\" gorm:\"comment:自动关闭tab\"`               // 自动关闭tab\n\tTransitionType string `json:\"transitionType\" gorm:\"comment:路由切换动画\"`        // 路由切换动画\n}\n\ntype SysBaseMenuParameter struct {\n\tglobal.GVA_MODEL\n\tSysBaseMenuID uint\n\tType          string `json:\"type\" gorm:\"comment:地址栏携带参数为params还是query\"` // 地址栏携带参数为params还是query\n\tKey           string `json:\"key\" gorm:\"comment:地址栏携带参数的key\"`              // 地址栏携带参数的key\n\tValue         string `json:\"value\" gorm:\"comment:地址栏携带参数的值\"`             // 地址栏携带参数的值\n}\n\nfunc (SysBaseMenu) TableName() string {\n\treturn \"sys_base_menus\"\n}\n"
  },
  {
    "path": "server/model/system/sys_dictionary.go",
    "content": "// 自动生成模板SysDictionary\npackage system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 如果含有time.Time 请自行import time包\ntype SysDictionary struct {\n\tglobal.GVA_MODEL\n\tName                 string                `json:\"name\" form:\"name\" gorm:\"column:name;comment:字典名（中）\"`              // 字典名（中）\n\tType                 string                `json:\"type\" form:\"type\" gorm:\"column:type;comment:字典名（英）\"`              // 字典名（英）\n\tStatus               *bool                 `json:\"status\" form:\"status\" gorm:\"column:status;comment:状态\"`            // 状态\n\tDesc                 string                `json:\"desc\" form:\"desc\" gorm:\"column:desc;comment:描述\"`                  // 描述\n\tParentID             *uint                 `json:\"parentID\" form:\"parentID\" gorm:\"column:parent_id;comment:父级字典ID\"` // 父级字典ID\n\tChildren             []SysDictionary       `json:\"children\" gorm:\"foreignKey:ParentID\"`                             // 子字典\n\tSysDictionaryDetails []SysDictionaryDetail `json:\"sysDictionaryDetails\" form:\"sysDictionaryDetails\"`\n}\n\nfunc (SysDictionary) TableName() string {\n\treturn \"sys_dictionaries\"\n}\n"
  },
  {
    "path": "server/model/system/sys_dictionary_detail.go",
    "content": "// 自动生成模板SysDictionaryDetail\npackage system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 如果含有time.Time 请自行import time包\ntype SysDictionaryDetail struct {\n\tglobal.GVA_MODEL\n\tLabel           string                `json:\"label\" form:\"label\" gorm:\"column:label;comment:展示值\"`                                  // 展示值\n\tValue           string                `json:\"value\" form:\"value\" gorm:\"column:value;comment:字典值\"`                                  // 字典值\n\tExtend          string                `json:\"extend\" form:\"extend\" gorm:\"column:extend;comment:扩展值\"`                               // 扩展值\n\tStatus          *bool                 `json:\"status\" form:\"status\" gorm:\"column:status;comment:启用状态\"`                              // 启用状态\n\tSort            int                   `json:\"sort\" form:\"sort\" gorm:\"column:sort;comment:排序标记\"`                                    // 排序标记\n\tSysDictionaryID int                   `json:\"sysDictionaryID\" form:\"sysDictionaryID\" gorm:\"column:sys_dictionary_id;comment:关联标记\"` // 关联标记\n\tParentID        *uint                 `json:\"parentID\" form:\"parentID\" gorm:\"column:parent_id;comment:父级字典详情ID\"`                   // 父级字典详情ID\n\tChildren        []SysDictionaryDetail `json:\"children\" gorm:\"foreignKey:ParentID\"`                                                 // 子字典详情\n\tLevel           int                   `json:\"level\" form:\"level\" gorm:\"column:level;comment:层级深度\"`                                 // 层级深度，从0开始\n\tPath            string                `json:\"path\" form:\"path\" gorm:\"column:path;comment:层级路径\"`                                    // 层级路径，如 \"1,2,3\"\n\tDisabled        bool                  `json:\"disabled\" gorm:\"-\"`                                                                   // 禁用状态，根据status字段动态计算\n}\n\nfunc (SysDictionaryDetail) TableName() string {\n\treturn \"sys_dictionary_details\"\n}\n"
  },
  {
    "path": "server/model/system/sys_error.go",
    "content": "// 自动生成模板SysError\npackage system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 错误日志 结构体  SysError\ntype SysError struct {\n\tglobal.GVA_MODEL\n\tForm     *string `json:\"form\" form:\"form\" gorm:\"comment:错误来源;column:form;type:text;\" binding:\"required\"` //错误来源\n\tInfo     *string `json:\"info\" form:\"info\" gorm:\"comment:错误内容;column:info;type:text;\"`                    //错误内容\n\tLevel    string  `json:\"level\" form:\"level\" gorm:\"comment:日志等级;column:level;\"`\n\tSolution *string `json:\"solution\" form:\"solution\" gorm:\"comment:解决方案;column:solution;type:text\"`               //解决方案\n\tStatus   string  `json:\"status\" form:\"status\" gorm:\"comment:处理状态;column:status;type:varchar(20);default:未处理;\"` //处理状态：未处理/处理中/处理完成\n}\n\n// TableName 错误日志 SysError自定义表名 sys_error\nfunc (SysError) TableName() string {\n\treturn \"sys_error\"\n}\n"
  },
  {
    "path": "server/model/system/sys_export_template.go",
    "content": "// 自动生成模板SysExportTemplate\npackage system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 导出模板 结构体  SysExportTemplate\ntype SysExportTemplate struct {\n\tglobal.GVA_MODEL\n\tDBName       string         `json:\"dbName\" form:\"dbName\" gorm:\"column:db_name;comment:数据库名称;\"`                       //数据库名称\n\tName         string         `json:\"name\" form:\"name\" gorm:\"column:name;comment:模板名称;\"`                               //模板名称\n\tTableName    string         `json:\"tableName\" form:\"tableName\" gorm:\"column:table_name;comment:表名称;\"`                //表名称\n\tTemplateID   string         `json:\"templateID\" form:\"templateID\" gorm:\"column:template_id;comment:模板标识;\"`            //模板标识\n\tTemplateInfo string         `json:\"templateInfo\" form:\"templateInfo\" gorm:\"column:template_info;type:text;\"`         //模板信息\n\tSQL          string         `json:\"sql\" form:\"sql\" gorm:\"column:sql;type:text;comment:自定义导出SQL;\"`                    //自定义导出SQL\n\tImportSQL    string         `json:\"importSql\" form:\"importSql\" gorm:\"column:import_sql;type:text;comment:自定义导入SQL;\"` //自定义导入SQL\n\tLimit        *int           `json:\"limit\" form:\"limit\" gorm:\"column:limit;comment:导出限制\"`\n\tOrder        string         `json:\"order\" form:\"order\" gorm:\"column:order;comment:排序\"`\n\tConditions   []Condition    `json:\"conditions\" form:\"conditions\" gorm:\"foreignKey:TemplateID;references:TemplateID;comment:条件\"`\n\tJoinTemplate []JoinTemplate `json:\"joinTemplate\" form:\"joinTemplate\" gorm:\"foreignKey:TemplateID;references:TemplateID;comment:关联\"`\n}\n\ntype JoinTemplate struct {\n\tglobal.GVA_MODEL\n\tTemplateID string `json:\"templateID\" form:\"templateID\" gorm:\"column:template_id;comment:模板标识\"`\n\tJOINS      string `json:\"joins\" form:\"joins\" gorm:\"column:joins;comment:关联\"`\n\tTable      string `json:\"table\" form:\"table\" gorm:\"column:table;comment:关联表\"`\n\tON         string `json:\"on\" form:\"on\" gorm:\"column:on;comment:关联条件\"`\n}\n\nfunc (JoinTemplate) TableName() string {\n\treturn \"sys_export_template_join\"\n}\n\ntype Condition struct {\n\tglobal.GVA_MODEL\n\tTemplateID string `json:\"templateID\" form:\"templateID\" gorm:\"column:template_id;comment:模板标识\"`\n\tFrom       string `json:\"from\" form:\"from\" gorm:\"column:from;comment:条件取的key\"`\n\tColumn     string `json:\"column\" form:\"column\" gorm:\"column:column;comment:作为查询条件的字段\"`\n\tOperator   string `json:\"operator\" form:\"operator\" gorm:\"column:operator;comment:操作符\"`\n}\n\nfunc (Condition) TableName() string {\n\treturn \"sys_export_template_condition\"\n}\n"
  },
  {
    "path": "server/model/system/sys_jwt_blacklist.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype JwtBlacklist struct {\n\tglobal.GVA_MODEL\n\tJwt string `gorm:\"type:text;comment:jwt\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_login_log.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\ntype SysLoginLog struct {\n\tglobal.GVA_MODEL\n\tUsername      string  `json:\"username\" gorm:\"column:username;comment:用户名\"`\n\tIp            string  `json:\"ip\" gorm:\"column:ip;comment:请求ip\"`\n\tStatus        bool    `json:\"status\" gorm:\"column:status;comment:登录状态\"`\n\tErrorMessage  string  `json:\"errorMessage\" gorm:\"column:error_message;comment:错误信息\"`\n\tAgent         string  `json:\"agent\" gorm:\"column:agent;comment:代理\"`\n\tUserID        uint    `json:\"userId\" gorm:\"column:user_id;comment:用户id\"`\n\tUser          SysUser `json:\"user\" gorm:\"foreignKey:UserID\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_menu_btn.go",
    "content": "package system\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\ntype SysBaseMenuBtn struct {\n\tglobal.GVA_MODEL\n\tName          string `json:\"name\" gorm:\"comment:按钮关键key\"`\n\tDesc          string `json:\"desc\" gorm:\"按钮备注\"`\n\tSysBaseMenuID uint   `json:\"sysBaseMenuID\" gorm:\"comment:菜单ID\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_operation_record.go",
    "content": "// 自动生成模板SysOperationRecord\npackage system\n\nimport (\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 如果含有time.Time 请自行import time包\ntype SysOperationRecord struct {\n\tglobal.GVA_MODEL\n\tIp           string        `json:\"ip\" form:\"ip\" gorm:\"column:ip;comment:请求ip\"`                                   // 请求ip\n\tMethod       string        `json:\"method\" form:\"method\" gorm:\"column:method;comment:请求方法\"`                       // 请求方法\n\tPath         string        `json:\"path\" form:\"path\" gorm:\"column:path;comment:请求路径\"`                             // 请求路径\n\tStatus       int           `json:\"status\" form:\"status\" gorm:\"column:status;comment:请求状态\"`                       // 请求状态\n\tLatency      time.Duration `json:\"latency\" form:\"latency\" gorm:\"column:latency;comment:延迟\" swaggertype:\"string\"` // 延迟\n\tAgent        string        `json:\"agent\" form:\"agent\" gorm:\"type:text;column:agent;comment:代理\"`                  // 代理\n\tErrorMessage string        `json:\"error_message\" form:\"error_message\" gorm:\"column:error_message;comment:错误信息\"`  // 错误信息\n\tBody         string        `json:\"body\" form:\"body\" gorm:\"type:text;column:body;comment:请求Body\"`                 // 请求Body\n\tResp         string        `json:\"resp\" form:\"resp\" gorm:\"type:text;column:resp;comment:响应Body\"`                 // 响应Body\n\tUserID       int           `json:\"user_id\" form:\"user_id\" gorm:\"column:user_id;comment:用户id\"`                    // 用户id\n\tUser         SysUser       `json:\"user\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_params.go",
    "content": "// 自动生成模板SysParams\npackage system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 参数 结构体  SysParams\ntype SysParams struct {\n\tglobal.GVA_MODEL\n\tName  string `json:\"name\" form:\"name\" gorm:\"column:name;comment:参数名称;\" binding:\"required\"`   //参数名称\n\tKey   string `json:\"key\" form:\"key\" gorm:\"column:key;comment:参数键;\" binding:\"required\"`       //参数键\n\tValue string `json:\"value\" form:\"value\" gorm:\"column:value;comment:参数值;\" binding:\"required\"` //参数值\n\tDesc  string `json:\"desc\" form:\"desc\" gorm:\"column:desc;comment:参数说明;\"`                      //参数说明\n}\n\n// TableName 参数 SysParams自定义表名 sys_params\nfunc (SysParams) TableName() string {\n\treturn \"sys_params\"\n}\n"
  },
  {
    "path": "server/model/system/sys_skills.go",
    "content": "package system\n\ntype SkillMeta struct {\n\tName         string `json:\"name\" yaml:\"name\"`\n\tDescription  string `json:\"description\" yaml:\"description\"`\n\tAllowedTools string `json:\"allowedTools\" yaml:\"allowed-tools,omitempty\"`\n\tContext      string `json:\"context\" yaml:\"context,omitempty\"`\n\tAgent        string `json:\"agent\" yaml:\"agent,omitempty\"`\n}\n\ntype SkillDetail struct {\n\tTool       string    `json:\"tool\"`\n\tSkill      string    `json:\"skill\"`\n\tMeta       SkillMeta `json:\"meta\"`\n\tMarkdown   string    `json:\"markdown\"`\n\tScripts    []string  `json:\"scripts\"`\n\tResources  []string  `json:\"resources\"`\n\tReferences []string  `json:\"references\"`\n\tTemplates  []string  `json:\"templates\"`\n}\n\ntype SkillTool struct {\n\tKey   string `json:\"key\"`\n\tLabel string `json:\"label\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_system.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n)\n\n// 配置文件结构体\ntype System struct {\n\tConfig config.Server `json:\"config\"`\n}\n"
  },
  {
    "path": "server/model/system/sys_user.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\t\"github.com/google/uuid\"\n)\n\ntype Login interface {\n\tGetUsername() string\n\tGetNickname() string\n\tGetUUID() uuid.UUID\n\tGetUserId() uint\n\tGetAuthorityId() uint\n\tGetUserInfo() any\n}\n\nvar _ Login = new(SysUser)\n\ntype SysUser struct {\n\tglobal.GVA_MODEL\n\tUUID          uuid.UUID      `json:\"uuid\" gorm:\"index;comment:用户UUID\"`                                                                   // 用户UUID\n\tUsername      string         `json:\"userName\" gorm:\"index;comment:用户登录名\"`                                                                // 用户登录名\n\tPassword      string         `json:\"-\"  gorm:\"comment:用户登录密码\"`                                                                           // 用户登录密码\n\tNickName      string         `json:\"nickName\" gorm:\"default:系统用户;comment:用户昵称\"`                                                          // 用户昵称\n\tHeaderImg     string         `json:\"headerImg\" gorm:\"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像\"`               // 用户头像\n\tAuthorityId   uint           `json:\"authorityId\" gorm:\"default:888;comment:用户角色ID\"`                                                      // 用户角色ID\n\tAuthority     SysAuthority   `json:\"authority\" gorm:\"foreignKey:AuthorityId;references:AuthorityId;comment:用户角色\"`                        // 用户角色\n\tAuthorities   []SysAuthority `json:\"authorities\" gorm:\"many2many:sys_user_authority;\"`                                                   // 多用户角色\n\tPhone         string         `json:\"phone\"  gorm:\"comment:用户手机号\"`                                                                        // 用户手机号\n\tEmail         string         `json:\"email\"  gorm:\"comment:用户邮箱\"`                                                                         // 用户邮箱\n\tEnable        int            `json:\"enable\" gorm:\"default:1;comment:用户是否被冻结 1正常 2冻结\"`                                                    //用户是否被冻结 1正常 2冻结\n\tOriginSetting common.JSONMap `json:\"originSetting\" form:\"originSetting\" gorm:\"type:text;default:null;column:origin_setting;comment:配置;\"` //配置\n}\n\nfunc (SysUser) TableName() string {\n\treturn \"sys_users\"\n}\n\nfunc (s *SysUser) GetUsername() string {\n\treturn s.Username\n}\n\nfunc (s *SysUser) GetNickname() string {\n\treturn s.NickName\n}\n\nfunc (s *SysUser) GetUUID() uuid.UUID {\n\treturn s.UUID\n}\n\nfunc (s *SysUser) GetUserId() uint {\n\treturn s.ID\n}\n\nfunc (s *SysUser) GetAuthorityId() uint {\n\treturn s.AuthorityId\n}\n\nfunc (s *SysUser) GetUserInfo() any {\n\treturn *s\n}\n"
  },
  {
    "path": "server/model/system/sys_user_authority.go",
    "content": "package system\n\n// SysUserAuthority 是 sysUser 和 sysAuthority 的连接表\ntype SysUserAuthority struct {\n\tSysUserId               uint `gorm:\"column:sys_user_id\"`\n\tSysAuthorityAuthorityId uint `gorm:\"column:sys_authority_authority_id\"`\n}\n\nfunc (s *SysUserAuthority) TableName() string {\n\treturn \"sys_user_authority\"\n}\n"
  },
  {
    "path": "server/model/system/sys_version.go",
    "content": "// 自动生成模板SysVersion\npackage system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// 版本管理 结构体  SysVersion\ntype SysVersion struct {\n\tglobal.GVA_MODEL\n\tVersionName *string `json:\"versionName\" form:\"versionName\" gorm:\"comment:版本名称;column:version_name;size:255;\" binding:\"required\"`                           //版本名称\n\tVersionCode *string `json:\"versionCode\" form:\"versionCode\" gorm:\"comment:版本号;column:version_code;size:100;\" binding:\"required\"`                            //版本号\n\tDescription *string `json:\"description\" form:\"description\" gorm:\"comment:版本描述;column:description;size:500;\"`                                               //版本描述\n\tVersionData *string `json:\"versionData\" form:\"versionData\" gorm:\"comment:版本数据JSON;column:version_data;type:text;\"` //版本数据\n}\n\n// TableName 版本管理 SysVersion自定义表名 sys_versions\nfunc (SysVersion) TableName() string {\n\treturn \"sys_versions\"\n}\n"
  },
  {
    "path": "server/plugin/announcement/api/enter.go",
    "content": "package api\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/service\"\n\nvar (\n\tApi         = new(api)\n\tserviceInfo = service.Service.Info\n)\n\ntype api struct{ Info info }\n"
  },
  {
    "path": "server/plugin/announcement/api/info.go",
    "content": "package api\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\nvar Info = new(info)\n\ntype info struct{}\n\n// CreateInfo 创建公告\n// @Tags Info\n// @Summary 创建公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.Info true \"创建公告\"\n// @Success 200 {object} response.Response{msg=string} \"创建成功\"\n// @Router /info/createInfo [post]\nfunc (a *info) CreateInfo(c *gin.Context) {\n\tvar info model.Info\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = serviceInfo.CreateInfo(&info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"创建成功\", c)\n}\n\n// DeleteInfo 删除公告\n// @Tags Info\n// @Summary 删除公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.Info true \"删除公告\"\n// @Success 200 {object} response.Response{msg=string} \"删除成功\"\n// @Router /info/deleteInfo [delete]\nfunc (a *info) DeleteInfo(c *gin.Context) {\n\tID := c.Query(\"ID\")\n\terr := serviceInfo.DeleteInfo(ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// DeleteInfoByIds 批量删除公告\n// @Tags Info\n// @Summary 批量删除公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{msg=string} \"批量删除成功\"\n// @Router /info/deleteInfoByIds [delete]\nfunc (a *info) DeleteInfoByIds(c *gin.Context) {\n\tIDs := c.QueryArray(\"IDs[]\")\n\tif err := serviceInfo.DeleteInfoByIds(IDs); err != nil {\n\t\tglobal.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\n// UpdateInfo 更新公告\n// @Tags Info\n// @Summary 更新公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.Info true \"更新公告\"\n// @Success 200 {object} response.Response{msg=string} \"更新成功\"\n// @Router /info/updateInfo [put]\nfunc (a *info) UpdateInfo(c *gin.Context) {\n\tvar info model.Info\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = serviceInfo.UpdateInfo(info)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// FindInfo 用id查询公告\n// @Tags Info\n// @Summary 用id查询公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query model.Info true \"用id查询公告\"\n// @Success 200 {object} response.Response{data=model.Info,msg=string} \"查询成功\"\n// @Router /info/findInfo [get]\nfunc (a *info) FindInfo(c *gin.Context) {\n\tID := c.Query(\"ID\")\n\treinfo, err := serviceInfo.GetInfo(ID)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithData(reinfo, c)\n}\n\n// GetInfoList 分页获取公告列表\n// @Tags Info\n// @Summary 分页获取公告列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query request.InfoSearch true \"分页获取公告列表\"\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /info/getInfoList [get]\nfunc (a *info) GetInfoList(c *gin.Context) {\n\tvar pageInfo request.InfoSearch\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := serviceInfo.GetInfoInfoList(pageInfo)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"获取失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithDetailed(response.PageResult{\n\t\tList:     list,\n\t\tTotal:    total,\n\t\tPage:     pageInfo.Page,\n\t\tPageSize: pageInfo.PageSize,\n\t}, \"获取成功\", c)\n}\n\n// GetInfoDataSource 获取Info的数据源\n// @Tags Info\n// @Summary 获取Info的数据源\n// @accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"查询成功\"\n// @Router /info/getInfoDataSource [get]\nfunc (a *info) GetInfoDataSource(c *gin.Context) {\n\t// 此接口为获取数据源定义的数据\n\tdataSource, err := serviceInfo.GetInfoDataSource()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithData(dataSource, c)\n}\n\n// GetInfoPublic 不需要鉴权的公告接口\n// @Tags Info\n// @Summary 不需要鉴权的公告接口\n// @accept application/json\n// @Produce application/json\n// @Param data query request.InfoSearch true \"分页获取公告列表\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /info/getInfoPublic [get]\nfunc (a *info) GetInfoPublic(c *gin.Context) {\n\t// 此接口不需要鉴权 示例为返回了一个固定的消息接口，一般本接口用于C端服务，需要自己实现业务逻辑\n\tresponse.OkWithDetailed(gin.H{\"info\": \"不需要鉴权的公告接口信息\"}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/plugin/announcement/config/config.go",
    "content": "package config\n\ntype Config struct {\n}\n"
  },
  {
    "path": "server/plugin/announcement/gen/gen.go",
    "content": "package main\n\nimport (\n\t\"gorm.io/gen\"\n\t\"path/filepath\" //go:generate go mod tidy\n\t//go:generate go mod download\n\t//go:generate go run gen.go\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model\"\n)\n\nfunc main() {\n\tg := gen.NewGenerator(gen.Config{OutPath: filepath.Join(\"..\", \"..\", \"..\", \"announcement\", \"blender\", \"model\", \"dao\"), Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface})\n\tg.ApplyBasic(\n\t\tnew(model.Info),\n\t)\n\tg.Execute()\n}\n"
  },
  {
    "path": "server/plugin/announcement/initialize/api.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils\"\n)\n\nfunc Api(ctx context.Context) {\n\tentities := []model.SysApi{\n\t\t{\n\t\t\tPath:        \"/info/createInfo\",\n\t\t\tDescription: \"新建公告\",\n\t\t\tApiGroup:    \"公告\",\n\t\t\tMethod:      \"POST\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/info/deleteInfo\",\n\t\t\tDescription: \"删除公告\",\n\t\t\tApiGroup:    \"公告\",\n\t\t\tMethod:      \"DELETE\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/info/deleteInfoByIds\",\n\t\t\tDescription: \"批量删除公告\",\n\t\t\tApiGroup:    \"公告\",\n\t\t\tMethod:      \"DELETE\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/info/updateInfo\",\n\t\t\tDescription: \"更新公告\",\n\t\t\tApiGroup:    \"公告\",\n\t\t\tMethod:      \"PUT\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/info/findInfo\",\n\t\t\tDescription: \"根据ID获取公告\",\n\t\t\tApiGroup:    \"公告\",\n\t\t\tMethod:      \"GET\",\n\t\t},\n\t\t{\n\t\t\tPath:        \"/info/getInfoList\",\n\t\t\tDescription: \"获取公告列表\",\n\t\t\tApiGroup:    \"公告\",\n\t\t\tMethod:      \"GET\",\n\t\t},\n\t}\n\tutils.RegisterApis(entities...)\n}\n"
  },
  {
    "path": "server/plugin/announcement/initialize/dictionary.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils\"\n)\n\nfunc Dictionary(ctx context.Context) {\n\tentities := []model.SysDictionary{}\n\tutils.RegisterDictionaries(entities...)\n}\n"
  },
  {
    "path": "server/plugin/announcement/initialize/gorm.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model\"\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/zap\"\n)\n\nfunc Gorm(ctx context.Context) {\n\terr := global.GVA_DB.WithContext(ctx).AutoMigrate(\n\t\tnew(model.Info),\n\t)\n\tif err != nil {\n\t\terr = errors.Wrap(err, \"注册表失败!\")\n\t\tzap.L().Error(fmt.Sprintf(\"%+v\", err))\n\t}\n}\n"
  },
  {
    "path": "server/plugin/announcement/initialize/menu.go",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils\"\n)\n\nfunc Menu(ctx context.Context) {\n\tentities := []model.SysBaseMenu{\n\t\t{\n\t\t\tParentId:  9,\n\t\t\tPath:      \"anInfo\",\n\t\t\tName:      \"anInfo\",\n\t\t\tHidden:    false,\n\t\t\tComponent: \"plugin/announcement/view/info.vue\",\n\t\t\tSort:      5,\n\t\t\tMeta:      model.Meta{Title: \"公告管理\", Icon: \"box\"},\n\t\t},\n\t}\n\tutils.RegisterMenus(entities...)\n}\n"
  },
  {
    "path": "server/plugin/announcement/initialize/router.go",
    "content": "package initialize\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/router\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc Router(engine *gin.Engine) {\n\tpublic := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group(\"\")\n\tprivate := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group(\"\")\n\tprivate.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())\n\trouter.Router.Info.Init(public, private)\n}\n"
  },
  {
    "path": "server/plugin/announcement/initialize/viper.go",
    "content": "package initialize\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/plugin\"\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/zap\"\n)\n\nfunc Viper() {\n\terr := global.GVA_VP.UnmarshalKey(\"announcement\", &plugin.Config)\n\tif err != nil {\n\t\terr = errors.Wrap(err, \"初始化配置文件失败!\")\n\t\tzap.L().Error(fmt.Sprintf(\"%+v\", err))\n\t}\n}\n"
  },
  {
    "path": "server/plugin/announcement/model/info.go",
    "content": "package model\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"gorm.io/datatypes\"\n)\n\n// Info 公告 结构体\ntype Info struct {\n\tglobal.GVA_MODEL\n\tTitle       string         `json:\"title\" form:\"title\" gorm:\"column:title;comment:公告标题;\"`                                              //标题\n\tContent     string         `json:\"content\" form:\"content\" gorm:\"column:content;comment:公告内容;type:text;\"`                              //内容\n\tUserID      *int           `json:\"userID\" form:\"userID\" gorm:\"column:user_id;comment:发布者;\"`                                           //作者\n\tAttachments datatypes.JSON `json:\"attachments\" form:\"attachments\" gorm:\"column:attachments;comment:相关附件;\" swaggertype:\"array,object\"` //附件\n}\n\n// TableName 公告 Info自定义表名 gva_announcements_info\nfunc (Info) TableName() string {\n\treturn \"gva_announcements_info\"\n}\n"
  },
  {
    "path": "server/plugin/announcement/model/request/info.go",
    "content": "package request\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"time\"\n)\n\ntype InfoSearch struct {\n\tStartCreatedAt *time.Time `json:\"startCreatedAt\" form:\"startCreatedAt\"`\n\tEndCreatedAt   *time.Time `json:\"endCreatedAt\" form:\"endCreatedAt\"`\n\trequest.PageInfo\n}\n"
  },
  {
    "path": "server/plugin/announcement/plugin/plugin.go",
    "content": "package plugin\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/config\"\n\nvar Config config.Config\n"
  },
  {
    "path": "server/plugin/announcement/plugin.go",
    "content": "package announcement\n\nimport (\n\t\"context\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/initialize\"\n\tinterfaces \"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nvar _ interfaces.Plugin = (*plugin)(nil)\n\nvar Plugin = new(plugin)\n\ntype plugin struct{}\n\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n\nfunc (p *plugin) Register(group *gin.Engine) {\n\tctx := context.Background()\n\t// 如果需要配置文件，请到config.Config中填充配置结构，且到下方发放中填入其在config.yaml中的key\n\t// initialize.Viper()\n\t// 安装插件时候自动注册的api数据请到下方法.Api方法中实现\n\tinitialize.Api(ctx)\n\t// 安装插件时候自动注册的Menu数据请到下方法.Menu方法中实现\n\tinitialize.Menu(ctx)\n\t// 安装插件时候自动注册的Dictionary数据请到下方法.Dictionary方法中实现\n\tinitialize.Dictionary(ctx)\n\tinitialize.Gorm(ctx)\n\tinitialize.Router(group)\n}\n"
  },
  {
    "path": "server/plugin/announcement/router/enter.go",
    "content": "package router\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/api\"\n\nvar (\n\tRouter  = new(router)\n\tapiInfo = api.Api.Info\n)\n\ntype router struct{ Info info }\n"
  },
  {
    "path": "server/plugin/announcement/router/info.go",
    "content": "package router\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nvar Info = new(info)\n\ntype info struct{}\n\n// Init 初始化 公告 路由信息\nfunc (r *info) Init(public *gin.RouterGroup, private *gin.RouterGroup) {\n\t{\n\t\tgroup := private.Group(\"info\").Use(middleware.OperationRecord())\n\t\tgroup.POST(\"createInfo\", apiInfo.CreateInfo)             // 新建公告\n\t\tgroup.DELETE(\"deleteInfo\", apiInfo.DeleteInfo)           // 删除公告\n\t\tgroup.DELETE(\"deleteInfoByIds\", apiInfo.DeleteInfoByIds) // 批量删除公告\n\t\tgroup.PUT(\"updateInfo\", apiInfo.UpdateInfo)              // 更新公告\n\t}\n\t{\n\t\tgroup := private.Group(\"info\")\n\t\tgroup.GET(\"findInfo\", apiInfo.FindInfo)       // 根据ID获取公告\n\t\tgroup.GET(\"getInfoList\", apiInfo.GetInfoList) // 获取公告列表\n\t}\n\t{\n\t\tgroup := public.Group(\"info\")\n\t\tgroup.GET(\"getInfoDataSource\", apiInfo.GetInfoDataSource) // 获取公告数据源\n\t\tgroup.GET(\"getInfoPublic\", apiInfo.GetInfoPublic)         // 获取公告列表\n\t}\n}\n"
  },
  {
    "path": "server/plugin/announcement/service/enter.go",
    "content": "package service\n\nvar Service = new(service)\n\ntype service struct{ Info info }\n"
  },
  {
    "path": "server/plugin/announcement/service/info.go",
    "content": "package service\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/model/request\"\n)\n\nvar Info = new(info)\n\ntype info struct{}\n\n// CreateInfo 创建公告记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (s *info) CreateInfo(info *model.Info) (err error) {\n\terr = global.GVA_DB.Create(info).Error\n\treturn err\n}\n\n// DeleteInfo 删除公告记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (s *info) DeleteInfo(ID string) (err error) {\n\terr = global.GVA_DB.Delete(&model.Info{}, \"id = ?\", ID).Error\n\treturn err\n}\n\n// DeleteInfoByIds 批量删除公告记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (s *info) DeleteInfoByIds(IDs []string) (err error) {\n\terr = global.GVA_DB.Delete(&[]model.Info{}, \"id in ?\", IDs).Error\n\treturn err\n}\n\n// UpdateInfo 更新公告记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (s *info) UpdateInfo(info model.Info) (err error) {\n\terr = global.GVA_DB.Model(&model.Info{}).Where(\"id = ?\", info.ID).Updates(&info).Error\n\treturn err\n}\n\n// GetInfo 根据ID获取公告记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (s *info) GetInfo(ID string) (info model.Info, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", ID).First(&info).Error\n\treturn\n}\n\n// GetInfoInfoList 分页获取公告记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (s *info) GetInfoInfoList(info request.InfoSearch) (list []model.Info, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&model.Info{})\n\tvar infos []model.Info\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif info.StartCreatedAt != nil && info.EndCreatedAt != nil {\n\t\tdb = db.Where(\"created_at BETWEEN ? AND ?\", info.StartCreatedAt, info.EndCreatedAt)\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif limit != 0 {\n\t\tdb = db.Limit(limit).Offset(offset)\n\t}\n\terr = db.Find(&infos).Error\n\treturn infos, total, err\n}\nfunc (s *info) GetInfoDataSource() (res map[string][]map[string]any, err error) {\n\tres = make(map[string][]map[string]any)\n\n\tuserID := make([]map[string]any, 0)\n\tglobal.GVA_DB.Table(\"sys_users\").Select(\"nick_name as label,id as value\").Scan(&userID)\n\tres[\"userID\"] = userID\n\treturn\n}\n"
  },
  {
    "path": "server/plugin/email/README.MD",
    "content": "## GVA 邮件发送功能插件\n#### 开发者：GIN-VUE-ADMIN 官方\n\n### 使用步骤\n\n#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件\n    例：\n    本插件可以采用gva的配置文件 也可以直接写死内容作为配置 建议为gva添加配置文件结构 然后将配置传入\n\tPluginInit(PrivateGroup, email.CreateEmailPlug(\n\t\tglobal.GVA_CONFIG.Email.To,\n\t\tglobal.GVA_CONFIG.Email.From,\n\t\tglobal.GVA_CONFIG.Email.Host,\n\t\tglobal.GVA_CONFIG.Email.Secret,\n\t\tglobal.GVA_CONFIG.Email.Nickname,\n\t\tglobal.GVA_CONFIG.Email.Port,\n\t\tglobal.GVA_CONFIG.Email.IsSSL,\n\t\tglobal.GVA_CONFIG.Email.IsLoginAuth,\n\t\t))\n\n    同样也可以再传入时写死\n\n    PluginInit(PrivateGroup, email.CreateEmailPlug(\n    \"a@qq.com\",\n    \"b@qq.com\",\n    \"smtp.qq.com\",\n    \"global.GVA_CONFIG.Email.Secret\",\n    \"登录密钥\",\n    465,\n    true,\n    true,\n    ))\n\n### 2. 配置说明\n\n#### 2-1 全局配置结构体说明\n    //其中 Form 和 Secret 通常来说就是用户名和密码\n\n    type Email struct {\n\t    To          string  // 收件人:多个以英文逗号分隔 例：a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 此处配置主要用于发送错误监控邮件\n\t    From        string  // 发件人  你自己要发邮件的邮箱\n\t    Host        string  // 服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议\n\t    Secret      string  // 密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥\n\t    Nickname    string  // 昵称    发件人昵称 自定义即可 可以不填\n\t    Port        int     // 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465\n\t    IsSSL       bool    // 是否SSL   是否开启SSL\n\t    IsLoginAuth bool    // 是否LoginAuth   是否使用LoginAuth认证方式（适用于IBM、微软邮箱服务器等）\n    }\n#### 2-2 入参结构说明\n    //其中 Form 和 Secret 通常来说就是用户名和密码\n\n    type Email struct {\n        To      string `json:\"to\"`      // 邮件发送给谁\n        Subject string `json:\"subject\"` // 邮件标题\n        Body    string `json:\"body\"`    // 邮件内容\n    }\n\n\n### 3. 方法API\n\n    utils.EmailTest(邮件标题，邮件主体) 发送测试邮件\n    例:utils.EmailTest(\"测试邮件\"，\"测试邮件\")\n    utils.ErrorToEmail(邮件标题,邮件主体) 错误监控\n    例:utils.ErrorToEmail(\"测试邮件\"，\"测试邮件\")\n    utils.Email(目标邮箱多个的话用逗号分隔，邮件标题，邮件主体) 发送测试邮件\n    例:utils.Email(”a.qq.com,b.qq.com“,\"测试邮件\"，\"测试邮件\")\n\n### 4. 可直接调用的接口\n\n    测试接口： /email/emailTest [post] 已配置swagger\n\n    发送邮件接口接口： /email/emailSend [post] 已配置swagger\n    入参：\n    type Email struct {\n        To      string `json:\"to\"`      // 邮件发送给谁\n        Subject string `json:\"subject\"` // 邮件标题\n        Body    string `json:\"body\"`    // 邮件内容\n    }\n   \n"
  },
  {
    "path": "server/plugin/email/api/enter.go",
    "content": "package api\n\ntype ApiGroup struct {\n\tEmailApi\n}\n\nvar ApiGroupApp = new(ApiGroup)\n"
  },
  {
    "path": "server/plugin/email/api/sys_email.go",
    "content": "package api\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\temail_response \"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/model/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/service\"\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n)\n\ntype EmailApi struct{}\n\n// EmailTest\n// @Tags      System\n// @Summary   发送测试邮件\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Success   200  {string}  string  \"{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}\"\n// @Router    /email/emailTest [post]\nfunc (s *EmailApi) EmailTest(c *gin.Context) {\n\terr := service.ServiceGroupApp.EmailTest()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"发送失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"发送失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"发送成功\", c)\n}\n\n// SendEmail\n// @Tags      System\n// @Summary   发送邮件\n// @Security  ApiKeyAuth\n// @Produce   application/json\n// @Param     data  body      email_response.Email  true  \"发送邮件必须的参数\"\n// @Success   200   {string}  string                \"{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}\"\n// @Router    /email/sendEmail [post]\nfunc (s *EmailApi) SendEmail(c *gin.Context) {\n\tvar email email_response.Email\n\terr := c.ShouldBindJSON(&email)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\terr = service.ServiceGroupApp.SendEmail(email.To, email.Subject, email.Body)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"发送失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"发送失败\", c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"发送成功\", c)\n}\n"
  },
  {
    "path": "server/plugin/email/config/email.go",
    "content": "package config\n\ntype Email struct {\n\tTo          string `mapstructure:\"to\" json:\"to\" yaml:\"to\"`                               // 收件人:多个以英文逗号分隔 例：a@qq.com b@qq.com 正式开发中请把此项目作为参数使用\n\tFrom        string `mapstructure:\"from\" json:\"from\" yaml:\"from\"`                         // 发件人  你自己要发邮件的邮箱\n\tHost        string `mapstructure:\"host\" json:\"host\" yaml:\"host\"`                         // 服务器地址 例如 smtp.qq.com  请前往QQ或者你要发邮件的邮箱查看其smtp协议\n\tSecret      string `mapstructure:\"secret\" json:\"secret\" yaml:\"secret\"`                   // 密钥    用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥\n\tNickname    string `mapstructure:\"nickname\" json:\"nickname\" yaml:\"nickname\"`             // 昵称    发件人昵称 通常为自己的邮箱\n\tPort        int    `mapstructure:\"port\" json:\"port\" yaml:\"port\"`                         // 端口     请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465\n\tIsSSL       bool   `mapstructure:\"is-ssl\" json:\"isSSL\" yaml:\"is-ssl\"`                    // 是否SSL   是否开启SSL\n\tIsLoginAuth bool   `mapstructure:\"is-loginauth\" json:\"is-loginauth\" yaml:\"is-loginauth\"` // 是否LoginAuth   是否使用LoginAuth认证\n}\n"
  },
  {
    "path": "server/plugin/email/global/gloabl.go",
    "content": "package global\n\nimport \"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/config\"\n\nvar GlobalConfig = new(config.Email)\n"
  },
  {
    "path": "server/plugin/email/main.go",
    "content": "package email\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/router\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype emailPlugin struct{}\n\nfunc CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool, IsLoginAuth bool) *emailPlugin {\n\tglobal.GlobalConfig.To = To\n\tglobal.GlobalConfig.From = From\n\tglobal.GlobalConfig.Host = Host\n\tglobal.GlobalConfig.Secret = Secret\n\tglobal.GlobalConfig.Nickname = Nickname\n\tglobal.GlobalConfig.Port = Port\n\tglobal.GlobalConfig.IsSSL = IsSSL\n\tglobal.GlobalConfig.IsLoginAuth = IsLoginAuth\n\treturn &emailPlugin{}\n}\n\nfunc (*emailPlugin) Register(group *gin.RouterGroup) {\n\trouter.RouterGroupApp.InitEmailRouter(group)\n}\n\nfunc (*emailPlugin) RouterPath() string {\n\treturn \"email\"\n}\n"
  },
  {
    "path": "server/plugin/email/model/response/email.go",
    "content": "package response\n\ntype Email struct {\n\tTo      string `json:\"to\"`      // 邮件发送给谁\n\tSubject string `json:\"subject\"` // 邮件标题\n\tBody    string `json:\"body\"`    // 邮件内容\n}\n"
  },
  {
    "path": "server/plugin/email/router/enter.go",
    "content": "package router\n\ntype RouterGroup struct {\n\tEmailRouter\n}\n\nvar RouterGroupApp = new(RouterGroup)\n"
  },
  {
    "path": "server/plugin/email/router/sys_email.go",
    "content": "package router\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/api\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype EmailRouter struct{}\n\nfunc (s *EmailRouter) InitEmailRouter(Router *gin.RouterGroup) {\n\temailRouter := Router.Use(middleware.OperationRecord())\n\tEmailApi := api.ApiGroupApp.EmailApi.EmailTest\n\tSendEmail := api.ApiGroupApp.EmailApi.SendEmail\n\t{\n\t\temailRouter.POST(\"emailTest\", EmailApi)  // 发送测试邮件\n\t\temailRouter.POST(\"sendEmail\", SendEmail) // 发送邮件\n\t}\n}\n"
  },
  {
    "path": "server/plugin/email/service/enter.go",
    "content": "package service\n\ntype ServiceGroup struct {\n\tEmailService\n}\n\nvar ServiceGroupApp = new(ServiceGroup)\n"
  },
  {
    "path": "server/plugin/email/service/sys_email.go",
    "content": "package service\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/utils\"\n)\n\ntype EmailService struct{}\n\n//@author: [maplepie](https://github.com/maplepie)\n//@function: EmailTest\n//@description: 发送邮件测试\n//@return: err error\n\nfunc (e *EmailService) EmailTest() (err error) {\n\tsubject := \"test\"\n\tbody := \"test\"\n\terr = utils.EmailTest(subject, body)\n\treturn err\n}\n\n//@author: [maplepie](https://github.com/maplepie)\n//@function: EmailTest\n//@description: 发送邮件测试\n//@return: err error\n//@params to string \t 收件人\n//@params subject string   标题（主题）\n//@params body  string \t 邮件内容\n\nfunc (e *EmailService) SendEmail(to, subject, body string) (err error) {\n\terr = utils.Email(to, subject, body)\n\treturn err\n}\n"
  },
  {
    "path": "server/plugin/email/utils/email.go",
    "content": "package utils\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/smtp\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/plugin/email/global\"\n\n\t\"github.com/jordan-wright/email\"\n)\n\n//@author: [maplepie](https://github.com/maplepie)\n//@function: Email\n//@description: Email发送方法\n//@param: subject string, body string\n//@return: error\n\nfunc Email(To, subject string, body string) error {\n\tto := strings.Split(To, \",\")\n\treturn send(to, subject, body)\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: ErrorToEmail\n//@description: 给email中间件错误发送邮件到指定邮箱\n//@param: subject string, body string\n//@return: error\n\nfunc ErrorToEmail(subject string, body string) error {\n\tto := strings.Split(global.GlobalConfig.To, \",\")\n\tif to[len(to)-1] == \"\" { // 判断切片的最后一个元素是否为空,为空则移除\n\t\tto = to[:len(to)-1]\n\t}\n\treturn send(to, subject, body)\n}\n\n//@author: [maplepie](https://github.com/maplepie)\n//@function: EmailTest\n//@description: Email测试方法\n//@param: subject string, body string\n//@return: error\n\nfunc EmailTest(subject string, body string) error {\n\tto := []string{global.GlobalConfig.To}\n\treturn send(to, subject, body)\n}\n\n//@author: [maplepie](https://github.com/maplepie)\n//@function: send\n//@description: Email发送方法\n//@param: subject string, body string\n//@return: error\n\nfunc send(to []string, subject string, body string) error {\n\tfrom := global.GlobalConfig.From\n\tnickname := global.GlobalConfig.Nickname\n\tsecret := global.GlobalConfig.Secret\n\thost := global.GlobalConfig.Host\n\tport := global.GlobalConfig.Port\n\tisSSL := global.GlobalConfig.IsSSL\n\tisLoginAuth := global.GlobalConfig.IsLoginAuth\n\n\tvar auth smtp.Auth\n\tif isLoginAuth {\n\t\tauth = LoginAuth(from, secret)\n\t} else {\n\t\tauth = smtp.PlainAuth(\"\", from, secret, host)\n\t}\n\te := email.NewEmail()\n\tif nickname != \"\" {\n\t\te.From = fmt.Sprintf(\"%s <%s>\", nickname, from)\n\t} else {\n\t\te.From = from\n\t}\n\te.To = to\n\te.Subject = subject\n\te.HTML = []byte(body)\n\tvar err error\n\thostAddr := fmt.Sprintf(\"%s:%d\", host, port)\n\tif isSSL {\n\t\terr = e.SendWithTLS(hostAddr, auth, &tls.Config{ServerName: host})\n\t} else {\n\t\terr = e.Send(hostAddr, auth)\n\t}\n\treturn err\n}\n\n// LoginAuth 用于IBM、微软邮箱服务器的LOGIN认证方式\ntype loginAuth struct {\n\tusername, password string\n}\n\nfunc LoginAuth(username, password string) smtp.Auth {\n\treturn &loginAuth{username, password}\n}\n\nfunc (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {\n\treturn \"LOGIN\", []byte{}, nil\n}\n\nfunc (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {\n\tif more {\n\t\tswitch string(fromServer) {\n\t\tcase \"Username:\":\n\t\t\treturn []byte(a.username), nil\n\t\tcase \"Password:\":\n\t\t\treturn []byte(a.password), nil\n\t\tdefault:\n\t\t\t// 邮箱服务器可能发送的其他提示信息\n\t\t\tprompt := strings.ToLower(string(fromServer))\n\t\t\tif strings.Contains(prompt, \"username\") || strings.Contains(prompt, \"user\") {\n\t\t\t\treturn []byte(a.username), nil\n\t\t\t}\n\t\t\tif strings.Contains(prompt, \"password\") || strings.Contains(prompt, \"pass\") {\n\t\t\t\treturn []byte(a.password), nil\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, nil\n}\n"
  },
  {
    "path": "server/plugin/plugin-tool/utils/check.go",
    "content": "package utils\n\nimport (\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/zap\"\n\t\"gorm.io/gorm\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\nvar (\n\tApiMap  = make(map[string][]system.SysApi)\n\tMenuMap = make(map[string][]system.SysBaseMenu)\n\tDictMap = make(map[string][]system.SysDictionary)\n\trw      sync.Mutex\n)\n\nfunc getPluginName() string {\n\t_, file, _, ok := runtime.Caller(2)\n\tpluginName := \"\"\n\tif ok {\n\t\tfile = filepath.ToSlash(file)\n\t\tconst key = \"server/plugin/\"\n\t\tif idx := strings.Index(file, key); idx != -1 {\n\t\t\tremain := file[idx+len(key):]\n\t\t\tparts := strings.Split(remain, \"/\")\n\t\t\tif len(parts) > 0 {\n\t\t\t\tpluginName = parts[0]\n\t\t\t}\n\t\t}\n\t}\n\treturn pluginName\n}\n\nfunc RegisterApis(apis ...system.SysApi) {\n\tname := getPluginName()\n\tif name != \"\" {\n\t\trw.Lock()\n\t\tApiMap[name] = apis\n\t\trw.Unlock()\n\t}\n\n\terr := global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tfor _, api := range apis {\n\t\t\terr := tx.Model(system.SysApi{}).Where(\"path = ? AND method = ? AND api_group = ? \", api.Path, api.Method, api.ApiGroup).FirstOrCreate(&api).Error\n\t\t\tif err != nil {\n\t\t\t\tzap.L().Error(\"注册API失败\", zap.Error(err), zap.String(\"api\", api.Path), zap.String(\"method\", api.Method), zap.String(\"apiGroup\", api.ApiGroup))\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tzap.L().Error(\"注册API失败\", zap.Error(err))\n\t}\n}\n\nfunc RegisterMenus(menus ...system.SysBaseMenu) {\n\tname := getPluginName()\n\tif name != \"\" {\n\t\trw.Lock()\n\t\tMenuMap[name] = menus\n\t\trw.Unlock()\n\t}\n\n\tparentMenu := menus[0]\n\totherMenus := menus[1:]\n\terr := global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\terr := tx.Model(system.SysBaseMenu{}).Where(\"name = ? \", parentMenu.Name).FirstOrCreate(&parentMenu).Error\n\t\tif err != nil {\n\t\t\tzap.L().Error(\"注册菜单失败\", zap.Error(err))\n\t\t\treturn errors.Wrap(err, \"注册菜单失败\")\n\t\t}\n\t\tpid := parentMenu.ID\n\t\tfor i := range otherMenus {\n\t\t\totherMenus[i].ParentId = pid\n\t\t\terr = tx.Model(system.SysBaseMenu{}).Where(\"name = ? \", otherMenus[i].Name).FirstOrCreate(&otherMenus[i]).Error\n\t\t\tif err != nil {\n\t\t\t\tzap.L().Error(\"注册菜单失败\", zap.Error(err))\n\t\t\t\treturn errors.Wrap(err, \"注册菜单失败\")\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tzap.L().Error(\"注册菜单失败\", zap.Error(err))\n\t}\n\n}\n\nfunc RegisterDictionaries(dictionaries ...system.SysDictionary) {\n\tname := getPluginName()\n\tif name != \"\" {\n\t\trw.Lock()\n\t\tDictMap[name] = dictionaries\n\t\trw.Unlock()\n\t}\n\n\terr := global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tfor _, dict := range dictionaries {\n\t\t\tdetails := dict.SysDictionaryDetails\n\t\t\tdict.SysDictionaryDetails = nil\n\t\t\terr := tx.Model(system.SysDictionary{}).Where(\"type = ?\", dict.Type).FirstOrCreate(&dict).Error\n\t\t\tif err != nil {\n\t\t\t\tzap.L().Error(\"注册字典失败\", zap.Error(err), zap.String(\"type\", dict.Type))\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, detail := range details {\n\t\t\t\tdetail.SysDictionaryID = int(dict.ID)\n\t\t\t\terr = tx.Model(system.SysDictionaryDetail{}).Where(\"sys_dictionary_id = ? AND value = ?\", dict.ID, detail.Value).FirstOrCreate(&detail).Error\n\t\t\t\tif err != nil {\n\t\t\t\t\tzap.L().Error(\"注册字典详情失败\", zap.Error(err), zap.String(\"value\", detail.Value))\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tzap.L().Error(\"注册字典失败\", zap.Error(err))\n\t}\n}\n\nfunc Pointer[T any](in T) *T {\n\treturn &in\n}\n\nfunc GetPluginData(pluginName string) ([]system.SysApi, []system.SysBaseMenu, []system.SysDictionary) {\n\trw.Lock()\n\tdefer rw.Unlock()\n\treturn ApiMap[pluginName], MenuMap[pluginName], DictMap[pluginName]\n}\n\n"
  },
  {
    "path": "server/plugin/register.go",
    "content": "package plugin\n\nimport (\n\t_ \"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement\"\n)\n"
  },
  {
    "path": "server/resource/function/api.go.tpl",
    "content": "{{if .IsPlugin}}\n// {{.FuncName}} {{.FuncDesc}}\n// @Tags {{.StructName}}\n// @Summary {{.FuncDesc}}\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}]\nfunc (a *{{.Abbreviation}}) {{.FuncName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n    // 请添加自己的业务逻辑\n    err := service{{ .StructName }}.{{.FuncName}}(ctx)\n       if err != nil {\n    \t\tglobal.GVA_LOG.Error(\"失败!\", zap.Error(err))\n            response.FailWithMessage(\"失败\", c)\n    \t\treturn\n       }\n    response.OkWithData(\"返回数据\",c)\n}\n\n{{- else -}}\n\n// {{.FuncName}} {{.FuncDesc}}\n// @Tags {{.StructName}}\n// @Summary {{.FuncDesc}}\n// @Accept application/json\n// @Produce application/json\n// @Param data query {{.Package}}Req.{{.StructName}}Search true \"成功\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"成功\"\n// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api){{.FuncName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n    // 请添加自己的业务逻辑\n    err := {{.Abbreviation}}Service.{{.FuncName}}(ctx)\n    if err != nil {\n        global.GVA_LOG.Error(\"失败!\", zap.Error(err))\n   \t\tresponse.FailWithMessage(\"失败\", c)\n   \t\treturn\n   \t}\n   \tresponse.OkWithData(\"返回数据\",c)\n}\n{{end}}\n"
  },
  {
    "path": "server/resource/function/api.js.tpl",
    "content": "{{if .IsPlugin}}\n// {{.FuncName}} {{.FuncDesc}}\n// @Tags {{.StructName}}\n// @Summary {{.FuncDesc}}\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}]\nexport const {{.Router}} = () => {\n  return service({\n    url: '/{{.Abbreviation}}/{{.Router}}',\n    method: '{{.Method}}'\n  })\n}\n\n{{- else -}}\n\n// {{.FuncName}} {{.FuncDesc}}\n// @Tags {{.StructName}}\n// @Summary {{.FuncDesc}}\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"成功\"\n// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}]\nexport const {{.Router}} = () => {\n  return service({\n    url: '/{{.Abbreviation}}/{{.Router}}',\n    method: '{{.Method}}'\n  })\n}\n\n{{- end -}}\n"
  },
  {
    "path": "server/resource/function/server.go.tpl",
    "content": "{{- $db := \"\" }}\n{{- if eq .BusinessDB \"\" }}\n {{- $db = \"global.GVA_DB\" }}\n{{- else}}\n {{- $db =  printf \"global.MustGetGlobalDBByDBName(\\\"%s\\\")\" .BusinessDB   }}\n{{- end}}\n{{if .IsPlugin}}\n\n// {{.FuncName}} {{.FuncDesc}}\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) {{.FuncName}}(ctx context.Context) (err error) {\n\tdb := {{$db}}.Model(&model.{{.StructName}}{})\n    return db.Error\n}\n\n{{- else -}}\n\n// {{.FuncName}} {{.FuncDesc}}\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service){{.FuncName}}(ctx context.Context) (err error) {\n\t// 请在这里实现自己的业务逻辑\n\tdb := {{$db}}.Model(&{{.Package}}.{{.StructName}}{})\n    return db.Error\n}\n{{end}}\n"
  },
  {
    "path": "server/resource/mcp/tools.tpl",
    "content": "package mcpTool\n\nimport (\n\t\"context\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\nfunc init() {\n\tRegisterTool(&{{.Name | title}}{})\n}\n\ntype {{.Name | title}} struct {\n}\n\n// {{.Description}}\nfunc (t *{{.Name | title}}) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// TODO: 实现工具逻辑\n\t// 参数示例:\n\t// {{- range .Params}}\n\t// {{.Name}} := request.GetArguments()[\"{{.Name}}\"]\n\t// {{- end}}\n\treturn &mcp.CallToolResult{\n\t\tContent: []mcp.Content{\n\t\t\t{{- range .Response}}\n\t\t\tmcp.{{.Type | title}}Content{\n\t\t\t\tType: \"{{.Type}}\",\n\t\t\t\t// TODO: 填充{{.Type}}内容\n\t\t\t},\n\t\t\t{{- end}}\n\t\t},\n\t}, nil\n}\n\nfunc (t *{{.Name | title}}) New() mcp.Tool {\n\treturn mcp.NewTool(\"{{.Name}}\",\n\t\tmcp.WithDescription(\"{{.Description}}\"),\n\t\t{{- range .Params}}\n\t\tmcp.With{{.Type | title}}(\"{{.Name}}\",\n\t\t\t{{- if .Required}}mcp.Required(),{{end}}\n\t\t\tmcp.Description(\"{{.Description}}\"),\n\t\t\t{{- if .Default}}\n              {{- if eq .Type \"string\"}}\n              mcp.DefaultString(\"{{.Default}}\"),\n              {{- else if eq .Type \"number\"}}\n              mcp.DefaultNumber({{.Default}}),\n              {{- else if eq .Type \"boolean\"}}\n              mcp.DefaultBoolean({{if or (eq .Default \"true\") (eq .Default \"True\")}}true{{else}}false{{end}}),\n              {{- else if eq .Type \"array\"}}\n              // 注意：数组默认值需要在后端代码中预处理为正确的格式\n              // mcp.DefaultArray({{.Default}}),\n              {{- end}}\n            {{- end}}\n\t\t),\n\t\t{{- end}}\n\t)\n}\n"
  },
  {
    "path": "server/resource/package/readme.txt.tpl",
    "content": "代码解压后把fe的api文件内容粘贴进前端api文件夹下并修改为自己想要的名字即可\n\n后端代码解压后同理，放到自己想要的 mvc对应路径 并且到 initRouter中注册自动生成的路由 到registerTable中注册自动生成的model\n\n项目github:\"https://github.com/piexlmax/github.com/flipped-aurora/gin-vue-admin/server\"\n\n希望大家给个star多多鼓励\n"
  },
  {
    "path": "server/resource/package/server/api/api.go.tpl",
    "content": "package {{.Package}}\n\nimport (\n\t{{if not .OnlyTemplate}}\n\t\"{{.Module}}/global\"\n    \"{{.Module}}/model/common/response\"\n    \"{{.Module}}/model/{{.Package}}\"\n    {{- if not .IsTree}}\n    {{.Package}}Req \"{{.Module}}/model/{{.Package}}/request\"\n    {{- end }}\n    \"github.com/gin-gonic/gin\"\n    \"go.uber.org/zap\"\n    {{- if .AutoCreateResource}}\n    \"{{.Module}}/utils\"\n    {{- end }}\n    {{- else}}\n    \"{{.Module}}/model/common/response\"\n    \"github.com/gin-gonic/gin\"\n    {{- end}}\n)\n\ntype {{.StructName}}Api struct {}\n\n{{if not .OnlyTemplate}}\n\n// Create{{.StructName}} 创建{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 创建{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body {{.Package}}.{{.StructName}} true \"创建{{.Description}}\"\n// @Success 200 {object} response.Response{msg=string} \"创建成功\"\n// @Router /{{.Abbreviation}}/create{{.StructName}} [post]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Create{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tvar {{.Abbreviation}} {{.Package}}.{{.StructName}}\n\terr := c.ShouldBindJSON(&{{.Abbreviation}})\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\t{{- if .AutoCreateResource }}\n    {{.Abbreviation}}.CreatedBy = utils.GetUserID(c)\n\t{{- end }}\n\terr = {{.Abbreviation}}Service.Create{{.StructName}}(ctx,&{{.Abbreviation}})\n\tif err != nil {\n        global.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败:\" + err.Error(), c)\n\t\treturn\n\t}\n    response.OkWithMessage(\"创建成功\", c)\n}\n\n// Delete{{.StructName}} 删除{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body {{.Package}}.{{.StructName}} true \"删除{{.Description}}\"\n// @Success 200 {object} response.Response{msg=string} \"删除成功\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Delete{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\t{{.PrimaryField.FieldJson}} := c.Query(\"{{.PrimaryField.FieldJson}}\")\n\t\t{{- if .AutoCreateResource }}\n    userID := utils.GetUserID(c)\n        {{- end }}\n\terr := {{.Abbreviation}}Service.Delete{{.StructName}}(ctx,{{.PrimaryField.FieldJson}} {{- if .AutoCreateResource -}},userID{{- end -}})\n\tif err != nil {\n        global.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败:\" + err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"删除成功\", c)\n}\n\n// Delete{{.StructName}}ByIds 批量删除{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 批量删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{msg=string} \"批量删除成功\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Delete{{.StructName}}ByIds(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\t{{.PrimaryField.FieldJson}}s := c.QueryArray(\"{{.PrimaryField.FieldJson}}s[]\")\n    \t{{- if .AutoCreateResource }}\n    userID := utils.GetUserID(c)\n        {{- end }}\n\terr := {{.Abbreviation}}Service.Delete{{.StructName}}ByIds(ctx,{{.PrimaryField.FieldJson}}s{{- if .AutoCreateResource }},userID{{- end }})\n\tif err != nil {\n        global.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败:\" + err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"批量删除成功\", c)\n}\n\n// Update{{.StructName}} 更新{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 更新{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body {{.Package}}.{{.StructName}} true \"更新{{.Description}}\"\n// @Success 200 {object} response.Response{msg=string} \"更新成功\"\n// @Router /{{.Abbreviation}}/update{{.StructName}} [put]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Update{{.StructName}}(c *gin.Context) {\n    // 从ctx获取标准context进行业务行为\n    ctx := c.Request.Context()\n\n\tvar {{.Abbreviation}} {{.Package}}.{{.StructName}}\n\terr := c.ShouldBindJSON(&{{.Abbreviation}})\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\t    {{- if .AutoCreateResource }}\n    {{.Abbreviation}}.UpdatedBy = utils.GetUserID(c)\n        {{- end }}\n\terr = {{.Abbreviation}}Service.Update{{.StructName}}(ctx,{{.Abbreviation}})\n\tif err != nil {\n        global.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败:\" + err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithMessage(\"更新成功\", c)\n}\n\n// Find{{.StructName}} 用id查询{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 用id查询{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param {{.PrimaryField.FieldJson}} query {{.PrimaryField.FieldType}} true \"用id查询{{.Description}}\"\n// @Success 200 {object} response.Response{data={{.Package}}.{{.StructName}},msg=string} \"查询成功\"\n// @Router /{{.Abbreviation}}/find{{.StructName}} [get]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Find{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\t{{.PrimaryField.FieldJson}} := c.Query(\"{{.PrimaryField.FieldJson}}\")\n\tre{{.Abbreviation}}, err := {{.Abbreviation}}Service.Get{{.StructName}}(ctx,{{.PrimaryField.FieldJson}})\n\tif err != nil {\n        global.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败:\" + err.Error(), c)\n\t\treturn\n\t}\n\tresponse.OkWithData(re{{.Abbreviation}}, c)\n}\n\n{{- if .IsTree }}\n// Get{{.StructName}}List 分页获取{{.Description}}列表,Tree模式下不接受参数\n// @Tags {{.StructName}}\n// @Summary 分页获取{{.Description}}列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}List(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tlist, err := {{.Abbreviation}}Service.Get{{.StructName}}InfoList(ctx)\n\tif err != nil {\n\t    global.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n        response.FailWithMessage(\"获取失败:\" + err.Error(), c)\n        return\n    }\n    response.OkWithDetailed(list, \"获取成功\", c)\n}\n{{- else }}\n// Get{{.StructName}}List 分页获取{{.Description}}列表\n// @Tags {{.StructName}}\n// @Summary 分页获取{{.Description}}列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query {{.Package}}Req.{{.StructName}}Search true \"分页获取{{.Description}}列表\"\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}List(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tvar pageInfo {{.Package}}Req.{{.StructName}}Search\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := {{.Abbreviation}}Service.Get{{.StructName}}InfoList(ctx,pageInfo)\n\tif err != nil {\n\t    global.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n        response.FailWithMessage(\"获取失败:\" + err.Error(), c)\n        return\n    }\n    response.OkWithDetailed(response.PageResult{\n        List:     list,\n        Total:    total,\n        Page:     pageInfo.Page,\n        PageSize: pageInfo.PageSize,\n    }, \"获取成功\", c)\n}\n{{- end }}\n\n{{- if .HasDataSource }}\n// Get{{.StructName}}DataSource 获取{{.StructName}}的数据源\n// @Tags {{.StructName}}\n// @Summary 获取{{.StructName}}的数据源\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"查询成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}DataSource [get]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}DataSource(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n    // 此接口为获取数据源定义的数据\n    dataSource, err := {{.Abbreviation}}Service.Get{{.StructName}}DataSource(ctx)\n    if err != nil {\n        global.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n   \t\tresponse.FailWithMessage(\"查询失败:\" + err.Error(), c)\n   \t\treturn\n    }\n   response.OkWithData(dataSource, c)\n}\n{{- end }}\n\n{{- end }}\n\n// Get{{.StructName}}Public 不需要鉴权的{{.Description}}接口\n// @Tags {{.StructName}}\n// @Summary 不需要鉴权的{{.Description}}接口\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]\nfunc ({{.Abbreviation}}Api *{{.StructName}}Api) Get{{.StructName}}Public(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n    // 此接口不需要鉴权\n    // 示例为返回了一个固定的消息接口，一般本接口用于C端服务，需要自己实现业务逻辑\n    {{.Abbreviation}}Service.Get{{.StructName}}Public(ctx)\n    response.OkWithDetailed(gin.H{\n       \"info\": \"不需要鉴权的{{.Description}}接口信息\",\n    }, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/resource/package/server/api/enter.go.tpl",
    "content": "package {{ .Package }}\n\ntype ApiGroup struct {\n}"
  },
  {
    "path": "server/resource/package/server/model/model.go.tpl",
    "content": "{{- if .IsAdd}}\n// 在结构体中新增如下字段\n{{- range .Fields}}\n  {{ GenerateField . }}\n{{- end }}\n\n{{ else }}\n// 自动生成模板{{.StructName}}\npackage {{.Package}}\n\n{{- if not .OnlyTemplate}}\nimport (\n\t{{- if .GvaModel }}\n\t\"{{.Module}}/global\"\n\t{{- end }}\n\t{{- if or .HasTimer }}\n\t\"time\"\n\t{{- end }}\n\t{{- if .NeedJSON }}\n\t\"gorm.io/datatypes\"\n\t{{- end }}\n)\n{{- end }}\n\n// {{.Description}} 结构体  {{.StructName}}\ntype {{.StructName}} struct {\n{{- if not .OnlyTemplate}}\n{{- if .GvaModel }}\n    global.GVA_MODEL\n{{- end }}\n{{- range .Fields}}\n  {{ GenerateField . }}\n{{- end }}\n    {{- if .AutoCreateResource }}\n    CreatedBy  uint   `gorm:\"column:created_by;comment:创建者\"`\n    UpdatedBy  uint   `gorm:\"column:updated_by;comment:更新者\"`\n    DeletedBy  uint   `gorm:\"column:deleted_by;comment:删除者\"`\n    {{- end }}\n    {{- if .IsTree }}\n    Children   []*{{.StructName}} `json:\"children\" gorm:\"-\"`     //子节点\n    ParentID   int             `json:\"parentID\" gorm:\"column:parent_id;comment:父节点\"`\n    {{- end }}\n{{- end }}\n}\n\n{{ if .TableName }}\n// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}}\nfunc ({{.StructName}}) TableName() string {\n    return \"{{.TableName}}\"\n}\n{{ end }}\n\n{{if .IsTree }}\n// GetChildren 实现TreeNode接口\nfunc (s *{{.StructName}}) GetChildren() []*{{.StructName}} {\n    return s.Children\n}\n\n// SetChildren 实现TreeNode接口\nfunc (s *{{.StructName}}) SetChildren(children *{{.StructName}}) {\n\ts.Children = append(s.Children, children)\n}\n\n// GetID 实现TreeNode接口\nfunc (s *{{.StructName}}) GetID() int {\n    return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}})\n}\n\n// GetParentID 实现TreeNode接口\nfunc (s *{{.StructName}}) GetParentID() int {\n    return s.ParentID\n}\n{{ end }}\n\n{{ end }}\n"
  },
  {
    "path": "server/resource/package/server/model/request/request.go.tpl",
    "content": "{{- if .IsAdd}}\n// 在结构体中新增如下字段\n{{- range .Fields}}\n    {{- if ne .FieldSearchType \"\"}}\n      {{ GenerateSearchField . }}\n    {{- end}}\n{{- end }}\n{{- if .NeedSort}}\nSort  string `json:\"sort\" form:\"sort\"`\nOrder string `json:\"order\" form:\"order\"`\n{{- end}}\n{{- else }}\npackage request\n\nimport (\n{{- if not .OnlyTemplate }}\n\t\"{{.Module}}/model/common/request\"\n\t{{ if or .HasSearchTimer .GvaModel }}\"time\"{{ end }}\n{{- end }}\n)\n\ntype {{.StructName}}Search struct{\n{{- if not .OnlyTemplate}}\n{{- if .GvaModel }}\n    CreatedAtRange []time.Time `json:\"createdAtRange\" form:\"createdAtRange[]\"`\n{{- end }}\n{{- range .Fields}}\n    {{- if ne .FieldSearchType \"\"}}\n      {{ GenerateSearchField . }}\n    {{- end}}\n{{- end }}\n    request.PageInfo\n    {{- if .NeedSort}}\n    Sort  string `json:\"sort\" form:\"sort\"`\n    Order string `json:\"order\" form:\"order\"`\n    {{- end}}\n{{- end}}\n}\n{{- end }}\n"
  },
  {
    "path": "server/resource/package/server/router/enter.go.tpl",
    "content": "package {{ .Package }}\n\ntype RouterGroup struct {\n}"
  },
  {
    "path": "server/resource/package/server/router/router.go.tpl",
    "content": "package {{.Package}}\n\nimport (\n\t{{if .OnlyTemplate}}// {{ end}}\"{{.Module}}/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype {{.StructName}}Router struct {}\n\n// Init{{.StructName}}Router 初始化 {{.Description}} 路由信息\nfunc (s *{{.StructName}}Router) Init{{.StructName}}Router(Router *gin.RouterGroup,PublicRouter *gin.RouterGroup) {\n\t{{- if not .OnlyTemplate}}\n\t{{.Abbreviation}}Router := Router.Group(\"{{.Abbreviation}}\").Use(middleware.OperationRecord())\n\t{{.Abbreviation}}RouterWithoutRecord := Router.Group(\"{{.Abbreviation}}\")\n\t{{- else }}\n\t// {{.Abbreviation}}Router := Router.Group(\"{{.Abbreviation}}\").Use(middleware.OperationRecord())\n    // {{.Abbreviation}}RouterWithoutRecord := Router.Group(\"{{.Abbreviation}}\")\n\t{{- end}}\n\t{{.Abbreviation}}RouterWithoutAuth := PublicRouter.Group(\"{{.Abbreviation}}\")\n\t{{- if not .OnlyTemplate}}\n\t{\n\t\t{{.Abbreviation}}Router.POST(\"create{{.StructName}}\", {{.Abbreviation}}Api.Create{{.StructName}})   // 新建{{.Description}}\n\t\t{{.Abbreviation}}Router.DELETE(\"delete{{.StructName}}\", {{.Abbreviation}}Api.Delete{{.StructName}}) // 删除{{.Description}}\n\t\t{{.Abbreviation}}Router.DELETE(\"delete{{.StructName}}ByIds\", {{.Abbreviation}}Api.Delete{{.StructName}}ByIds) // 批量删除{{.Description}}\n\t\t{{.Abbreviation}}Router.PUT(\"update{{.StructName}}\", {{.Abbreviation}}Api.Update{{.StructName}})    // 更新{{.Description}}\n\t}\n\t{\n\t\t{{.Abbreviation}}RouterWithoutRecord.GET(\"find{{.StructName}}\", {{.Abbreviation}}Api.Find{{.StructName}})        // 根据ID获取{{.Description}}\n\t\t{{.Abbreviation}}RouterWithoutRecord.GET(\"get{{.StructName}}List\", {{.Abbreviation}}Api.Get{{.StructName}}List)  // 获取{{.Description}}列表\n\t}\n\t{\n\t{{- if .HasDataSource}}\n\t    {{.Abbreviation}}RouterWithoutAuth.GET(\"get{{.StructName}}DataSource\", {{.Abbreviation}}Api.Get{{.StructName}}DataSource)  // 获取{{.Description}}数据源\n\t{{- end}}\n\t    {{.Abbreviation}}RouterWithoutAuth.GET(\"get{{.StructName}}Public\", {{.Abbreviation}}Api.Get{{.StructName}}Public)  // {{.Description}}开放接口\n\t}\n\t{{- else}}\n\t{\n\t    {{.Abbreviation}}RouterWithoutAuth.GET(\"get{{.StructName}}Public\", {{.Abbreviation}}Api.Get{{.StructName}}Public)  // {{.Description}}开放接口\n\t}\n    {{ end }}\n}\n"
  },
  {
    "path": "server/resource/package/server/service/enter.go.tpl",
    "content": "package {{ .Package }}\n\ntype ServiceGroup struct {\n}"
  },
  {
    "path": "server/resource/package/server/service/service.go.tpl",
    "content": "{{- $db := \"\" }}\n{{- if eq .BusinessDB \"\" }}\n {{- $db = \"global.GVA_DB\" }}\n{{- else}}\n {{- $db =  printf \"global.MustGetGlobalDBByDBName(\\\"%s\\\")\" .BusinessDB   }}\n{{- end}}\n\n{{- if .IsAdd}}\n\n// Get{{.StructName}}InfoList 新增搜索语句\n       {{ GenerateSearchConditions .Fields }}\n// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容\n       {{- range .Fields}}\n            {{- if .Sort}}\norderMap[\"{{.ColumnName}}\"] = true\n         \t{{- end}}\n       {{- end}}\n\n\n{{- if .HasDataSource }}\n//  Get{{.StructName}}DataSource()方法新增关联语句\n\t{{range $key, $value := .DataSourceMap}}\n{{$key}} := make([]map[string]any, 0)\n{{ $dataDB := \"\" }}\n{{- if eq $value.DBName \"\" }}\n{{ $dataDB = $db }}\n{{- else}}\n{{ $dataDB = printf \"global.MustGetGlobalDBByDBName(\\\"%s\\\")\" $value.DBName }}\n{{- end}}\n{{$dataDB}}.Table(\"{{$value.Table}}\"){{- if $value.HasDeletedAt}}.Where(\"deleted_at IS NULL\"){{ end }}.Select(\"{{$value.Label}} as label,{{$value.Value}} as value\").Scan(&{{$key}})\nres[\"{{$key}}\"] = {{$key}}\n\t{{- end }}\n{{- end }}\n{{- else}}\npackage {{.Package}}\n\nimport (\n{{- if not .OnlyTemplate }}\n\t\"context\"\n\t\"{{.Module}}/global\"\n\t\"{{.Module}}/model/{{.Package}}\"\n\t{{- if not .IsTree}}\n    {{.Package}}Req \"{{.Module}}/model/{{.Package}}/request\"\n    {{- else }}\n    \"{{.Module}}/utils\"\n    \"errors\"\n    {{- end }}\n    {{- if .AutoCreateResource }}\n    \"gorm.io/gorm\"\n    {{- end}}\n{{- end }}\n)\n\ntype {{.StructName}}Service struct {}\n\n{{- if not .OnlyTemplate }}\n// Create{{.StructName}} 创建{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service) Create{{.StructName}}(ctx context.Context, {{.Abbreviation}} *{{.Package}}.{{.StructName}}) (err error) {\n\terr = {{$db}}.Create({{.Abbreviation}}).Error\n\treturn err\n}\n\n// Delete{{.StructName}} 删除{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Delete{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string{{- if .AutoCreateResource -}},userID uint{{- end -}}) (err error) {\n\t{{- if .IsTree }}\n       var count int64\n\t   err = {{$db}}.Find(&{{.Package}}.{{.StructName}}{},\"parent_id = ?\",{{.PrimaryField.FieldJson}}).Count(&count).Error\n\t   if count > 0 {\n           return errors.New(\"此节点存在子节点不允许删除\")\n       }\n       if err != nil {\n           return err\n       }\n\t{{- end }}\n\n\t{{- if .AutoCreateResource }}\n\terr = {{$db}}.Transaction(func(tx *gorm.DB) error {\n\t    if err := tx.Model(&{{.Package}}.{{.StructName}}{}).Where(\"{{.PrimaryField.ColumnName}} = ?\", {{.PrimaryField.FieldJson}}).Update(\"deleted_by\", userID).Error; err != nil {\n              return err\n        }\n        if err = tx.Delete(&{{.Package}}.{{.StructName}}{},\"{{.PrimaryField.ColumnName}} = ?\",{{.PrimaryField.FieldJson}}).Error; err != nil {\n              return err\n        }\n        return nil\n\t})\n    {{- else }}\n\terr = {{$db}}.Delete(&{{.Package}}.{{.StructName}}{},\"{{.PrimaryField.ColumnName}} = ?\",{{.PrimaryField.FieldJson}}).Error\n\t{{- end }}\n\treturn err\n}\n\n// Delete{{.StructName}}ByIds 批量删除{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Delete{{.StructName}}ByIds(ctx context.Context, {{.PrimaryField.FieldJson}}s []string {{- if .AutoCreateResource }},deleted_by uint{{- end}}) (err error) {\n\t{{- if .AutoCreateResource }}\n\terr = {{$db}}.Transaction(func(tx *gorm.DB) error {\n\t    if err := tx.Model(&{{.Package}}.{{.StructName}}{}).Where(\"{{.PrimaryField.ColumnName}} in ?\", {{.PrimaryField.FieldJson}}s).Update(\"deleted_by\", deleted_by).Error; err != nil {\n            return err\n        }\n        if err := tx.Where(\"{{.PrimaryField.ColumnName}} in ?\", {{.PrimaryField.FieldJson}}s).Delete(&{{.Package}}.{{.StructName}}{}).Error; err != nil {\n            return err\n        }\n        return nil\n    })\n    {{- else}}\n\terr = {{$db}}.Delete(&[]{{.Package}}.{{.StructName}}{},\"{{.PrimaryField.ColumnName}} in ?\",{{.PrimaryField.FieldJson}}s).Error\n    {{- end}}\n\treturn err\n}\n\n// Update{{.StructName}} 更新{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Update{{.StructName}}(ctx context.Context, {{.Abbreviation}} {{.Package}}.{{.StructName}}) (err error) {\n\terr = {{$db}}.Model(&{{.Package}}.{{.StructName}}{}).Where(\"{{.PrimaryField.ColumnName}} = ?\",{{.Abbreviation}}.{{.PrimaryField.FieldName}}).Updates(&{{.Abbreviation}}).Error\n\treturn err\n}\n\n// Get{{.StructName}} 根据{{.PrimaryField.FieldJson}}获取{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string) ({{.Abbreviation}} {{.Package}}.{{.StructName}}, err error) {\n\terr = {{$db}}.Where(\"{{.PrimaryField.ColumnName}} = ?\", {{.PrimaryField.FieldJson}}).First(&{{.Abbreviation}}).Error\n\treturn\n}\n\n\n{{- if .IsTree }}\n// Get{{.StructName}}InfoList 分页获取{{.Description}}记录,Tree模式下不添加分页和搜索\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoList(ctx context.Context) (list []*{{.Package}}.{{.StructName}},err error) {\n    // 创建db\n\tdb := {{$db}}.Model(&{{.Package}}.{{.StructName}}{})\n    var {{.Abbreviation}}s []*{{.Package}}.{{.StructName}}\n\n\terr = db.Find(&{{.Abbreviation}}s).Error\n\n\treturn utils.BuildTree({{.Abbreviation}}s), err\n}\n{{- else }}\n// Get{{.StructName}}InfoList 分页获取{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoList(ctx context.Context, info {{.Package}}Req.{{.StructName}}Search) (list []{{.Package}}.{{.StructName}}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n    // 创建db\n\tdb := {{$db}}.Model(&{{.Package}}.{{.StructName}}{})\n    var {{.Abbreviation}}s []{{.Package}}.{{.StructName}}\n    // 如果有条件搜索 下方会自动创建搜索语句\n{{- if .GvaModel }}\n    if len(info.CreatedAtRange) == 2 {\n     db = db.Where(\"created_at BETWEEN ? AND ?\", info.CreatedAtRange[0], info.CreatedAtRange[1])\n    }\n{{- end }}\n    {{ GenerateSearchConditions .Fields }}\n\terr = db.Count(&total).Error\n\tif err!=nil {\n    \treturn\n    }\n    {{- if .NeedSort}}\n        var OrderStr string\n        orderMap := make(map[string]bool)\n        {{- if .GvaModel }}\n           orderMap[\"id\"] = true\n           orderMap[\"created_at\"] = true\n        {{- end }}\n       {{- range .Fields}}\n            {{- if .Sort}}\n         \torderMap[\"{{.ColumnName}}\"] = true\n         \t{{- end}}\n       {{- end}}\n       if orderMap[info.Sort] {\n          OrderStr = info.Sort\n          if info.Order == \"descending\" {\n             OrderStr = OrderStr + \" desc\"\n          }\n          db = db.Order(OrderStr)\n       }\n    {{- end}}\n\n\tif limit != 0 {\n       db = db.Limit(limit).Offset(offset)\n    }\n\n\terr = db.Find(&{{.Abbreviation}}s).Error\n\treturn  {{.Abbreviation}}s, total, err\n}\n\n{{- end }}\n\n{{- if .HasDataSource }}\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}DataSource(ctx context.Context) (res map[string][]map[string]any, err error) {\n\tres = make(map[string][]map[string]any)\n\t{{range $key, $value := .DataSourceMap}}\n\t   {{$key}} := make([]map[string]any, 0)\n\t   {{ $dataDB := \"\" }}\n\t   {{- if eq $value.DBName \"\" }}\n       {{ $dataDB = \"global.GVA_DB\" }}\n       {{- else}}\n       {{ $dataDB = printf \"global.MustGetGlobalDBByDBName(\\\"%s\\\")\" $value.DBName }}\n       {{- end}}\n       {{$dataDB}}.Table(\"{{$value.Table}}\"){{- if $value.HasDeletedAt}}.Where(\"deleted_at IS NULL\"){{ end }}.Select(\"{{$value.Label}} as label,{{$value.Value}} as value\").Scan(&{{$key}})\n\t   res[\"{{$key}}\"] = {{$key}}\n\t{{- end }}\n\treturn\n}\n{{- end }}\n{{- end }}\nfunc ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}Public(ctx context.Context) {\n    // 此方法为获取数据源定义的数据\n    // 请自行实现\n}\n{{- end }}\n"
  },
  {
    "path": "server/resource/package/web/api/api.js.tpl",
    "content": "import service from '@/utils/request'\n\n{{- if not .OnlyTemplate}}\n// @Tags {{.StructName}}\n// @Summary 创建{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"创建{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /{{.Abbreviation}}/create{{.StructName}} [post]\nexport const create{{.StructName}} = (data) => {\n  return service({\n    url: '/{{.Abbreviation}}/create{{.StructName}}',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"删除{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]\nexport const delete{{.StructName}} = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/delete{{.StructName}}',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 批量删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]\nexport const delete{{.StructName}}ByIds = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 更新{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"更新{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /{{.Abbreviation}}/update{{.StructName}} [put]\nexport const update{{.StructName}} = (data) => {\n  return service({\n    url: '/{{.Abbreviation}}/update{{.StructName}}',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 用id查询{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query model.{{.StructName}} true \"用id查询{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /{{.Abbreviation}}/find{{.StructName}} [get]\nexport const find{{.StructName}} = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/find{{.StructName}}',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 分页获取{{.Description}}列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取{{.Description}}列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]\nexport const get{{.StructName}}List = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/get{{.StructName}}List',\n    method: 'get',\n    params\n  })\n}\n\n{{- if .HasDataSource}}\n// @Tags {{.StructName}}\n// @Summary 获取数据源\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /{{.Abbreviation}}/find{{.StructName}}DataSource [get]\nexport const get{{.StructName}}DataSource = () => {\n  return service({\n    url: '/{{.Abbreviation}}/get{{.StructName}}DataSource',\n    method: 'get',\n  })\n}\n{{- end}}\n\n{{- end}}\n\n// @Tags {{.StructName}}\n// @Summary 不需要鉴权的{{.Description}}接口\n// @Accept application/json\n// @Produce application/json\n// @Param data query {{.Package}}Req.{{.StructName}}Search true \"分页获取{{.Description}}列表\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]\nexport const get{{.StructName}}Public = () => {\n  return service({\n    url: '/{{.Abbreviation}}/get{{.StructName}}Public',\n    method: 'get',\n  })\n}\n"
  },
  {
    "path": "server/resource/package/web/view/form.vue.tpl",
    "content": "{{- if .IsAdd }}\n// 新增表单中增加如下代码\n{{- range .Fields}}\n     {{- if .Form}}\n        {{ GenerateFormItem . }}\n     {{- end }}\n{{- end }}\n\n// 字典增加如下代码\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\n\n// init方法中增加如下调用\n\n{{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n{{- end }}\n\n// 基础formData结构增加如下字段\n{{- range .Fields}}\n          {{- if .Form}}\n            {{ GenerateDefaultFormValue . }}\n          {{- end }}\n        {{- end }}\n// 验证规则中增加如下字段\n\n{{- range .Fields }}\n        {{- if .Form }}\n            {{- if eq .Require true }}\n{{.FieldJson }} : [{\n    required: true,\n    message: '{{ .ErrorText }}',\n    trigger: ['input','blur'],\n},\n               {{- if eq .FieldType \"string\" }}\n{\n    whitespace: true,\n    message: '不能只输入空格',\n    trigger: ['input', 'blur'],\n}\n              {{- end }}\n],\n            {{- end }}\n        {{- end }}\n    {{- end }}\n\n{{- if .HasDataSource }}\n// 请引用\nget{{.StructName}}DataSource,\n\n//  获取数据源\nconst dataSource = ref([])\nconst getDataSourceFunc = async()=>{\n  const res = await get{{.StructName}}DataSource()\n  if (res.code === 0) {\n    dataSource.value = res.data\n  }\n}\ngetDataSourceFunc()\n{{- end }}\n{{- else }}\n{{- if not .OnlyTemplate }}\n<template>\n  <div>\n    <div class=\"gva-form-box\">\n      <el-form :model=\"formData\" ref=\"elFormRef\" label-position=\"right\" :rules=\"rule\" label-width=\"80px\">\n        {{- if .IsTree }}\n          <el-form-item label=\"父节点:\" prop=\"parentID\" >\n              <el-tree-select\n                  v-model=\"formData.parentID\"\n                  :data=\"[rootNode,...tableData]\"\n                  check-strictly\n                  :render-after-expand=\"false\"\n                  :props=\"defaultProps\"\n                  clearable\n                  style=\"width: 240px\"\n                  placeholder=\"根节点\"\n              />\n          </el-form-item>\n        {{- end }}\n      {{- range .Fields}}\n      {{- if .Form }}\n        {{ GenerateFormItem . }}\n      {{- end }}\n      {{- end }}\n        <el-form-item>\n          <el-button :loading=\"btnLoading\" type=\"primary\" @click=\"save\">保存</el-button>\n          <el-button type=\"primary\" @click=\"back\">返回</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport {\n  {{- if .HasDataSource }}\n    get{{.StructName}}DataSource,\n  {{- end }}\n  {{- if .IsTree }}\n    get{{.StructName}}List,\n  {{- end }}\n  create{{.StructName}},\n  update{{.StructName}},\n  find{{.StructName}}\n} from '@/api/{{.Package}}/{{.PackageName}}'\n\ndefineOptions({\n    name: '{{.StructName}}Form'\n})\n\n// 自动获取字典\nimport { getDictFunc } from '@/utils/format'\nimport { useRoute, useRouter } from \"vue-router\"\nimport { ElMessage } from 'element-plus'\nimport { ref, reactive } from 'vue'\n{{- if .HasPic }}\n// 图片选择组件\nimport SelectImage from '@/components/selectImage/selectImage.vue'\n{{- end }}\n\n{{- if .HasFile }}\n// 文件选择组件\nimport SelectFile from '@/components/selectFile/selectFile.vue'\n{{- end }}\n\n{{- if .HasRichText }}\n// 富文本组件\nimport RichEdit from '@/components/richtext/rich-edit.vue'\n{{- end }}\n\n{{- if .HasArray}}\n// 数组控制组件\nimport ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'\n{{- end }}\n\n\nconst route = useRoute()\nconst router = useRouter()\n\n{{- if .IsTree }}\nconst tableData = ref([])\n\nconst defaultProps = {\n  children: \"children\",\n  label: \"{{ .TreeJson }}\",\n  value: \"{{ .PrimaryField.FieldJson }}\"\n}\n\nconst rootNode = {\n  {{ .PrimaryField.FieldJson }}: 0,\n  {{ .TreeJson }}: '根节点',\n  children: []\n}\n\nconst getTableData = async() => {\n  const table = await get{{.StructName}}List()\n  if (table.code === 0) {\n    tableData.value = table.data || []\n  }\n}\n\ngetTableData()\n\n{{- end }}\n\n// 提交按钮loading\nconst btnLoading = ref(false)\n\nconst type = ref('')\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\nconst formData = ref({\n        {{- if .IsTree }}\n            parentID: undefined,\n        {{- end }}\n        {{- range .Fields}}\n          {{- if .Form }}\n            {{ GenerateDefaultFormValue . }}\n          {{- end }}\n        {{- end }}\n        })\n// 验证规则\nconst rule = reactive({\n    {{- range .Fields }}\n            {{- if eq .Require true }}\n               {{.FieldJson }} : [{\n                   required: true,\n                   message: '{{ .ErrorText }}',\n                   trigger: ['input','blur'],\n               }],\n            {{- end }}\n    {{- end }}\n})\n\nconst elFormRef = ref()\n\n{{- if .HasDataSource }}\n  const dataSource = ref([])\n  const getDataSourceFunc = async()=>{\n    const res = await get{{.StructName}}DataSource()\n    if (res.code === 0) {\n      dataSource.value = res.data\n    }\n  }\n  getDataSourceFunc()\n{{- end }}\n\n// 初始化方法\nconst init = async () => {\n // 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例\n    if (route.query.id) {\n      const res = await find{{.StructName}}({ ID: route.query.id })\n      if (res.code === 0) {\n        formData.value = res.data\n        type.value = 'update'\n      }\n    } else {\n      type.value = 'create'\n    }\n    {{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n    {{- end }}\n}\n\ninit()\n// 保存按钮\nconst save = async() => {\n      btnLoading.value = true\n      elFormRef.value?.validate( async (valid) => {\n         if (!valid) return btnLoading.value = false\n            let res\n           switch (type.value) {\n             case 'create':\n               res = await create{{.StructName}}(formData.value)\n               break\n             case 'update':\n               res = await update{{.StructName}}(formData.value)\n               break\n             default:\n               res = await create{{.StructName}}(formData.value)\n               break\n           }\n           btnLoading.value = false\n           if (res.code === 0) {\n             ElMessage({\n               type: 'success',\n               message: '创建/更改成功'\n             })\n           }\n       })\n}\n\n// 返回按钮\nconst back = () => {\n    router.go(-1)\n}\n\n</script>\n\n<style>\n</style>\n{{- else }}\n<template>\n<div>form</div>\n</template>\n<script setup>\n</script>\n<style>\n</style>\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "server/resource/package/web/view/table.vue.tpl",
    "content": "{{- $global := . }}\n{{- $templateID := printf \"%s_%s\" .Package .StructName }}\n{{- if .IsAdd }}\n\n// 请在搜索条件中增加如下代码\n{{- range .Fields}}\n    {{- if .FieldSearchType}}\n{{ GenerateSearchFormItem .}}\n    {{ end }}\n{{ end }}\n\n\n// 表格增加如下列代码\n\n{{- range .Fields}}\n    {{- if .Table}}\n       {{ GenerateTableColumn . }}\n    {{- end }}\n{{- end }}\n\n// 新增表单中增加如下代码\n{{- range .Fields}}\n   {{- if .Form}}\n     {{ GenerateFormItem . }}\n   {{- end }}\n{{- end }}\n\n// 查看抽屉中增加如下代码\n\n{{- range .Fields}}\n              {{- if .Desc }}\n    {{ GenerateDescriptionItem . }}\n              {{- end }}\n            {{- end }}\n\n// 字典增加如下代码\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\n\n// setOptions方法中增加如下调用\n\n{{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n{{- end }}\n\n// 基础formData结构（变量处和关闭表单处）增加如下字段\n{{- range .Fields}}\n          {{- if .Form}}\n            {{ GenerateDefaultFormValue . }}\n          {{- end }}\n        {{- end }}\n// 验证规则中增加如下字段\n\n{{- range .Fields }}\n        {{- if .Form }}\n            {{- if eq .Require true }}\n{{.FieldJson }} : [{\n    required: true,\n    message: '{{ .ErrorText }}',\n    trigger: ['input','blur'],\n},\n               {{- if eq .FieldType \"string\" }}\n{\n    whitespace: true,\n    message: '不能只输入空格',\n    trigger: ['input', 'blur'],\n}\n              {{- end }}\n],\n            {{- end }}\n        {{- end }}\n    {{- end }}\n\n\n\n{{- if .HasDataSource }}\n// 请引用\nget{{.StructName}}DataSource,\n\n//  获取数据源\nconst dataSource = ref({})\nconst getDataSourceFunc = async()=>{\n  const res = await get{{.StructName}}DataSource()\n  if (res.code === 0) {\n    dataSource.value = res.data\n  }\n}\ngetDataSourceFunc()\n{{- end }}\n\n{{- else }}\n\n{{- if not .OnlyTemplate}}\n<template>\n  <div>\n  {{- if not .IsTree }}\n    <div class=\"gva-search-box\">\n      <el-form ref=\"elSearchFormRef\" :inline=\"true\" :model=\"searchInfo\" class=\"demo-form-inline\" @keyup.enter=\"onSubmit\">\n      {{- if .GvaModel }}\n      <el-form-item label=\"创建日期\" prop=\"createdAtRange\">\n      <template #label>\n        <span>\n          创建日期\n          <el-tooltip content=\"搜索范围是开始日期（包含）至结束日期（不包含）\">\n            <el-icon><QuestionFilled /></el-icon>\n          </el-tooltip>\n        </span>\n      </template>\n\n      <el-date-picker\n            v-model=\"searchInfo.createdAtRange\"\n            class=\"!w-380px\"\n            type=\"datetimerange\"\n            range-separator=\"至\"\n            start-placeholder=\"开始时间\"\n            end-placeholder=\"结束时间\"\n          />\n       </el-form-item>\n      {{ end -}}\n           {{- range .Fields}}  {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}\n            {{ GenerateSearchFormItem .}}\n            {{ end }}{{ end }}{{ end }}\n\n        <template v-if=\"showAllQuery\">\n          <!-- 将需要控制显示状态的查询条件添加到此范围内 -->\n          {{- range .Fields}}  {{- if .FieldSearchType}} {{- if .FieldSearchHide }}\n          {{ GenerateSearchFormItem .}}\n          {{ end }}{{ end }}{{ end }}\n        </template>\n\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">查询</el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n          <el-button link type=\"primary\" icon=\"arrow-down\" @click=\"showAllQuery=true\" v-if=\"!showAllQuery\">展开</el-button>\n          <el-button link type=\"primary\" icon=\"arrow-up\" @click=\"showAllQuery=false\" v-else>收起</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  {{- end }}\n    <div class=\"gva-table-box\">\n        <div class=\"gva-btn-list\">\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.add\"{{ end }} type=\"primary\" icon=\"plus\" @click=\"openDialog()\">新增</el-button>\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.batchDelete\"{{ end }} icon=\"delete\" style=\"margin-left: 10px;\" :disabled=\"!multipleSelection.length\" @click=\"onDelete\">删除</el-button>\n            {{ if .HasExcel -}}\n            <ExportTemplate {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.exportTemplate\"{{ end }} template-id=\"{{$templateID}}\" />\n            <ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.exportExcel\"{{ end }} template-id=\"{{$templateID}}\" filterDeleted/>\n            <ImportExcel {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.importExcel\"{{ end }} template-id=\"{{$templateID}}\" @on-success=\"getTableData\" />\n            {{- end }}\n        </div>\n        <el-table\n        ref=\"multipleTable\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :data=\"tableData\"\n        row-key=\"{{.PrimaryField.FieldJson}}\"\n        @selection-change=\"handleSelectionChange\"\n        {{- if .NeedSort}}\n        @sort-change=\"sortChange\"\n        {{- end}}\n        >\n        <el-table-column type=\"selection\" width=\"55\" />\n        {{ if .GvaModel }}\n        <el-table-column sortable align=\"left\" label=\"日期\" prop=\"CreatedAt\" {{ if .IsTree -}} min-{{- end -}}width=\"180\">\n            <template #default=\"scope\">{{ \"{{ formatDate(scope.row.CreatedAt) }}\" }}</template>\n        </el-table-column>\n        {{ end }}\n        {{- range .Fields}}\n        {{- if .Table}}\n            {{ GenerateTableColumn . }}\n        {{- end }}\n        {{- end }}\n        <el-table-column align=\"left\" label=\"操作\" fixed=\"right\" :min-width=\"appStore.operateMinWith\">\n            <template #default=\"scope\">\n            {{- if .IsTree }}\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.add\"{{ end }} type=\"primary\" link class=\"table-button\" @click=\"openDialog(scope.row)\"><el-icon style=\"margin-right: 5px\"><InfoFilled /></el-icon>新增子节点</el-button>\n            {{- end }}\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.info\"{{ end }} type=\"primary\" link class=\"table-button\" @click=\"getDetails(scope.row)\"><el-icon style=\"margin-right: 5px\"><InfoFilled /></el-icon>查看</el-button>\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.edit\"{{ end }} type=\"primary\" link icon=\"edit\" class=\"table-button\" @click=\"update{{.StructName}}Func(scope.row)\">编辑</el-button>\n            <el-button {{ if .IsTree }}v-if=\"!scope.row.children?.length\" {{ end }} {{if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.delete\"{{ end }} type=\"primary\" link icon=\"delete\" @click=\"deleteRow(scope.row)\">删除</el-button>\n            </template>\n        </el-table-column>\n        </el-table>\n        <div class=\"gva-pagination\">\n            <el-pagination\n            layout=\"total, sizes, prev, pager, next, jumper\"\n            :current-page=\"page\"\n            :page-size=\"pageSize\"\n            :page-sizes=\"[10, 30, 50, 100]\"\n            :total=\"total\"\n            @current-change=\"handleCurrentChange\"\n            @size-change=\"handleSizeChange\"\n            />\n        </div>\n    </div>\n    <el-drawer destroy-on-close :size=\"appStore.drawerSize\" v-model=\"dialogFormVisible\" :show-close=\"false\" :before-close=\"closeDialog\">\n       <template #header>\n              <div class=\"flex justify-between items-center\">\n                <span class=\"text-lg\">{{\"{{\"}}type==='create'?'新增':'编辑'{{\"}}\"}}</span>\n                <div>\n                  <el-button :loading=\"btnLoading\" type=\"primary\" @click=\"enterDialog\">确 定</el-button>\n                  <el-button @click=\"closeDialog\">取 消</el-button>\n                </div>\n              </div>\n            </template>\n\n          <el-form :model=\"formData\" label-position=\"top\" ref=\"elFormRef\" :rules=\"rule\" label-width=\"80px\">\n          {{- if .IsTree }}\n            <el-form-item label=\"父节点:\" prop=\"parentID\" >\n                <el-tree-select\n                    v-model=\"formData.parentID\"\n                    :data=\"[rootNode,...tableData]\"\n                    check-strictly\n                    :render-after-expand=\"false\"\n                    :props=\"defaultProps\"\n                    clearable\n                    style=\"width: 240px\"\n                    placeholder=\"根节点\"\n                />\n            </el-form-item>\n          {{- end }}\n        {{- range .Fields}}\n          {{- if .Form}}\n            {{ GenerateFormItem . }}\n          {{- end }}\n          {{- end }}\n          </el-form>\n    </el-drawer>\n\n    <el-drawer destroy-on-close :size=\"appStore.drawerSize\" v-model=\"detailShow\" :show-close=\"true\" :before-close=\"closeDetailShow\" title=\"查看\">\n            <el-descriptions :column=\"1\" border>\n            {{- if .IsTree }}\n            <el-descriptions-item label=\"父节点\">\n                <el-tree-select\n                  v-model=\"detailForm.parentID\"\n                  :data=\"[rootNode,...tableData]\"\n                  check-strictly\n                  disabled\n                  :render-after-expand=\"false\"\n                  :props=\"defaultProps\"\n                  clearable\n                  style=\"width: 240px\"\n                  placeholder=\"根节点\"\n                />\n            </el-descriptions-item>\n            {{- end }}\n            {{- range .Fields}}\n              {{- if .Desc }}\n                    {{ GenerateDescriptionItem . }}\n              {{- end }}\n            {{- end }}\n            </el-descriptions>\n        </el-drawer>\n\n  </div>\n</template>\n\n<script setup>\nimport {\n  {{- if .HasDataSource }}\n    get{{.StructName}}DataSource,\n  {{- end }}\n  create{{.StructName}},\n  delete{{.StructName}},\n  delete{{.StructName}}ByIds,\n  update{{.StructName}},\n  find{{.StructName}},\n  get{{.StructName}}List\n} from '@/api/{{.Package}}/{{.PackageName}}'\n\n{{- if or .HasPic .HasFile}}\nimport { getUrl } from '@/utils/image'\n{{- end }}\n{{- if .HasPic }}\n// 图片选择组件\nimport SelectImage from '@/components/selectImage/selectImage.vue'\n{{- end }}\n\n{{- if .HasRichText }}\n// 富文本组件\nimport RichEdit from '@/components/richtext/rich-edit.vue'\nimport RichView from '@/components/richtext/rich-view.vue'\n{{- end }}\n\n{{- if .HasFile }}\n// 文件选择组件\nimport SelectFile from '@/components/selectFile/selectFile.vue'\n{{- end }}\n\n{{- if .HasArray}}\n// 数组控制组件\nimport ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'\n{{- end }}\n\n// 全量引入格式化工具 请按需保留\nimport { getDictFunc, formatDate, formatBoolean, filterDict ,filterDataSource, returnArrImg, onDownloadFile } from '@/utils/format'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport { ref, reactive } from 'vue'\n{{- if .AutoCreateBtnAuth }}\n// 引入按钮权限标识\nimport { useBtnAuth } from '@/utils/btnAuth'\n{{- end }}\nimport { useAppStore } from \"@/pinia\"\n\n{{if .HasExcel -}}\n// 导出组件\nimport ExportExcel from '@/components/exportExcel/exportExcel.vue'\n// 导入组件\nimport ImportExcel from '@/components/exportExcel/importExcel.vue'\n// 导出模板组件\nimport ExportTemplate from '@/components/exportExcel/exportTemplate.vue'\n{{- end}}\n\n\ndefineOptions({\n    name: '{{.StructName}}'\n})\n\n{{- if .AutoCreateBtnAuth }}\n// 按钮权限实例化\n    const btnAuth = useBtnAuth()\n{{- end }}\n\n// 提交按钮loading\nconst btnLoading = ref(false)\nconst appStore = useAppStore()\n\n// 控制更多查询条件显示/隐藏状态\nconst showAllQuery = ref(false)\n\n// 自动化生成的字典（可能为空）以及字段\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\nconst formData = ref({\n        {{- if .IsTree }}\n            parentID:undefined,\n        {{- end }}\n        {{- range .Fields}}\n          {{- if .Form}}\n            {{ GenerateDefaultFormValue . }}\n          {{- end }}\n        {{- end }}\n        })\n\n{{- if .HasDataSource }}\n  const dataSource = ref([])\n  const getDataSourceFunc = async()=>{\n    const res = await get{{.StructName}}DataSource()\n    if (res.code === 0) {\n      dataSource.value = res.data\n    }\n  }\n  getDataSourceFunc()\n{{- end }}\n\n\n\n// 验证规则\nconst rule = reactive({\n    {{- range .Fields }}\n        {{- if .Form }}\n            {{- if eq .Require true }}\n               {{.FieldJson }} : [{\n                   required: true,\n                   message: '{{ .ErrorText }}',\n                   trigger: ['input','blur'],\n               },\n               {{- if eq .FieldType \"string\" }}\n               {\n                   whitespace: true,\n                   message: '不能只输入空格',\n                   trigger: ['input', 'blur'],\n              }\n              {{- end }}\n              ],\n            {{- end }}\n        {{- end }}\n    {{- end }}\n})\n\nconst elFormRef = ref()\nconst elSearchFormRef = ref()\n\n// =========== 表格控制部分 ===========\nconst page = ref(1)\nconst total = ref(0)\nconst pageSize = ref(10)\nconst tableData = ref([])\nconst searchInfo = ref({})\n\n{{- if .NeedSort}}\n// 排序\nconst sortChange = ({ prop, order }) => {\n  const sortMap = {\n    CreatedAt:\"created_at\",\n    ID:\"id\",\n    {{- range .Fields}}\n     {{- if .Table}}\n      {{- if and .Sort}}\n        {{- if not (eq .ColumnName \"\")}}\n            {{.FieldJson}}: '{{.ColumnName}}',\n        {{- end}}\n      {{- end}}\n     {{- end}}\n    {{- end}}\n  }\n\n  let sort = sortMap[prop]\n  if(!sort){\n   sort = prop.replace(/[A-Z]/g, match => `_${match.toLowerCase()}`)\n  }\n\n  searchInfo.value.sort = sort\n  searchInfo.value.order = order\n  getTableData()\n}\n{{- end}}\n\n{{- if not .IsTree }}\n// 重置\nconst onReset = () => {\n  searchInfo.value = {}\n  getTableData()\n}\n\n// 搜索\nconst onSubmit = () => {\n  elSearchFormRef.value?.validate(async(valid) => {\n    if (!valid) return\n    page.value = 1\n    {{- range .Fields}}{{- if eq .FieldType \"bool\" }}\n    if (searchInfo.value.{{.FieldJson}} === \"\"){\n        searchInfo.value.{{.FieldJson}}=null\n    }{{ end }}{{ end }}\n    getTableData()\n  })\n}\n\n// 分页\nconst handleSizeChange = (val) => {\n  pageSize.value = val\n  getTableData()\n}\n\n// 修改页面容量\nconst handleCurrentChange = (val) => {\n  page.value = val\n  getTableData()\n}\n\n// 查询\nconst getTableData = async() => {\n  const table = await get{{.StructName}}List({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })\n  if (table.code === 0) {\n    tableData.value = table.data.list\n    total.value = table.data.total\n    page.value = table.data.page\n    pageSize.value = table.data.pageSize\n  }\n}\n{{- else }}\n// 树选择器配置\nconst defaultProps = {\n  children: \"children\",\n  label: \"{{ .TreeJson }}\",\n  value: \"{{ .PrimaryField.FieldJson }}\"\n}\n\nconst rootNode = {\n  {{ .PrimaryField.FieldJson }}: 0,\n  {{ .TreeJson }}: '根节点',\n  children: []\n}\n\n// 查询\nconst getTableData = async() => {\n  const table = await get{{.StructName}}List()\n  if (table.code === 0) {\n    tableData.value = table.data || []\n  }\n}\n{{- end }}\n\ngetTableData()\n\n// ============== 表格控制部分结束 ===============\n\n// 获取需要的字典 可能为空 按需保留\nconst setOptions = async () =>{\n{{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n{{- end }}\n}\n\n// 获取需要的字典 可能为空 按需保留\nsetOptions()\n\n\n// 多选数据\nconst multipleSelection = ref([])\n// 多选\nconst handleSelectionChange = (val) => {\n    multipleSelection.value = val\n}\n\n// 删除行\nconst deleteRow = (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n    }).then(() => {\n            delete{{.StructName}}Func(row)\n        })\n    }\n\n// 多选删除\nconst onDelete = async() => {\n  ElMessageBox.confirm('确定要删除吗?', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(async() => {\n      const {{.PrimaryField.FieldJson}}s = []\n      if (multipleSelection.value.length === 0) {\n        ElMessage({\n          type: 'warning',\n          message: '请选择要删除的数据'\n        })\n        return\n      }\n      multipleSelection.value &&\n        multipleSelection.value.map(item => {\n          {{.PrimaryField.FieldJson}}s.push(item.{{.PrimaryField.FieldJson}})\n        })\n      const res = await delete{{.StructName}}ByIds({ {{.PrimaryField.FieldJson}}s })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === {{.PrimaryField.FieldJson}}s.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n      })\n    }\n\n// 行为控制标记（弹窗内部需要增还是改）\nconst type = ref('')\n\n// 更新行\nconst update{{.StructName}}Func = async(row) => {\n    const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })\n    type.value = 'update'\n    if (res.code === 0) {\n        formData.value = res.data\n        dialogFormVisible.value = true\n    }\n}\n\n\n// 删除行\nconst delete{{.StructName}}Func = async (row) => {\n    const res = await delete{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })\n    if (res.code === 0) {\n        ElMessage({\n                type: 'success',\n                message: '删除成功'\n            })\n            if (tableData.value.length === 1 && page.value > 1) {\n            page.value--\n        }\n        getTableData()\n    }\n}\n\n// 弹窗控制标记\nconst dialogFormVisible = ref(false)\n\n// 打开弹窗\nconst openDialog = ({{- if .IsTree -}}row{{- end -}}) => {\n    type.value = 'create'\n    {{- if .IsTree }}\n    formData.value.parentID = row ? row.{{.PrimaryField.FieldJson}} : undefined\n    {{- end }}\n    dialogFormVisible.value = true\n}\n\n// 关闭弹窗\nconst closeDialog = () => {\n    dialogFormVisible.value = false\n    formData.value = {\n    {{- range .Fields}}\n      {{- if .Form}}\n        {{ GenerateDefaultFormValue . }}\n      {{- end }}\n    {{- end }}\n        }\n}\n// 弹窗确定\nconst enterDialog = async () => {\n     btnLoading.value = true\n     elFormRef.value?.validate( async (valid) => {\n             if (!valid) return btnLoading.value = false\n              let res\n              switch (type.value) {\n                case 'create':\n                  res = await create{{.StructName}}(formData.value)\n                  break\n                case 'update':\n                  res = await update{{.StructName}}(formData.value)\n                  break\n                default:\n                  res = await create{{.StructName}}(formData.value)\n                  break\n              }\n              btnLoading.value = false\n              if (res.code === 0) {\n                ElMessage({\n                  type: 'success',\n                  message: '创建/更改成功'\n                })\n                closeDialog()\n                getTableData()\n              }\n      })\n}\n\nconst detailForm = ref({})\n\n// 查看详情控制标记\nconst detailShow = ref(false)\n\n\n// 打开详情弹窗\nconst openDetailShow = () => {\n  detailShow.value = true\n}\n\n\n// 打开详情\nconst getDetails = async (row) => {\n  // 打开弹窗\n  const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })\n  if (res.code === 0) {\n    detailForm.value = res.data\n    openDetailShow()\n  }\n}\n\n\n// 关闭详情弹窗\nconst closeDetailShow = () => {\n  detailShow.value = false\n  detailForm.value = {}\n}\n\n\n</script>\n\n<style>\n{{if .HasFile }}\n.file-list{\n  display: flex;\n  flex-wrap: wrap;\n  gap: 4px;\n}\n\n.fileBtn{\n  margin-bottom: 10px;\n}\n\n.fileBtn:last-child{\n  margin-bottom: 0;\n}\n{{end}}\n</style>\n{{- else}}\n<template>\n<div>form</div>\n</template>\n<script setup>\ndefineOptions({\n  name: '{{.StructName}}'\n})\n</script>\n<style>\n</style>\n{{- end }}\n\n{{- end }}\n"
  },
  {
    "path": "server/resource/plugin/server/api/api.go.tpl",
    "content": "package api\n\nimport (\n{{if not .OnlyTemplate}}\n\t\"{{.Module}}/global\"\n    \"{{.Module}}/model/common/response\"\n    \"{{.Module}}/plugin/{{.Package}}/model\"\n    {{- if not .IsTree}}\n    \"{{.Module}}/plugin/{{.Package}}/model/request\"\n    {{- end }}\n    \"github.com/gin-gonic/gin\"\n    \"go.uber.org/zap\"\n    {{- if .AutoCreateResource}}\n    \"{{.Module}}/utils\"\n    {{- end }}\n{{- else }}\n    \"{{.Module}}/model/common/response\"\n    \"github.com/gin-gonic/gin\"\n{{- end }}\n)\n\nvar {{.StructName}} = new({{.Abbreviation}})\n\ntype {{.Abbreviation}} struct {}\n{{if not .OnlyTemplate}}\n// Create{{.StructName}} 创建{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 创建{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"创建{{.Description}}\"\n// @Success 200 {object} response.Response{msg=string} \"创建成功\"\n// @Router /{{.Abbreviation}}/create{{.StructName}} [post]\nfunc (a *{{.Abbreviation}}) Create{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tvar info model.{{.StructName}}\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\t{{- if .AutoCreateResource }}\n    info.CreatedBy = utils.GetUserID(c)\n\t{{- end }}\n\terr = service{{ .StructName }}.Create{{.StructName}}(ctx,&info)\n\tif err != nil {\n        global.GVA_LOG.Error(\"创建失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"创建失败:\" + err.Error(), c)\n\t\treturn\n\t}\n    response.OkWithMessage(\"创建成功\", c)\n}\n\n// Delete{{.StructName}} 删除{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"删除{{.Description}}\"\n// @Success 200 {object} response.Response{msg=string} \"删除成功\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]\nfunc (a *{{.Abbreviation}}) Delete{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\t{{.PrimaryField.FieldJson}} := c.Query(\"{{.PrimaryField.FieldJson}}\")\n{{- if .AutoCreateResource }}\n    userID := utils.GetUserID(c)\n{{- end }}\n\terr := service{{ .StructName }}.Delete{{.StructName}}(ctx,{{.PrimaryField.FieldJson}} {{- if .AutoCreateResource -}},userID{{- end -}})\n\tif err != nil {\n        global.GVA_LOG.Error(\"删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"删除失败:\" + err.Error(), c)\n\t\treturn\n\t}\n    response.OkWithMessage(\"删除成功\", c)\n}\n\n// Delete{{.StructName}}ByIds 批量删除{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 批量删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{msg=string} \"批量删除成功\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}}ByIds [delete]\nfunc (a *{{.Abbreviation}}) Delete{{.StructName}}ByIds(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\t{{.PrimaryField.FieldJson}}s := c.QueryArray(\"{{.PrimaryField.FieldJson}}s[]\")\n{{- if .AutoCreateResource }}\n    userID := utils.GetUserID(c)\n{{- end }}\n\terr := service{{ .StructName }}.Delete{{.StructName}}ByIds(ctx,{{.PrimaryField.FieldJson}}s{{- if .AutoCreateResource }},userID{{- end }})\n\tif err != nil {\n        global.GVA_LOG.Error(\"批量删除失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"批量删除失败:\" + err.Error(), c)\n\t\treturn\n\t}\n    response.OkWithMessage(\"批量删除成功\", c)\n}\n\n// Update{{.StructName}} 更新{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 更新{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"更新{{.Description}}\"\n// @Success 200 {object} response.Response{msg=string} \"更新成功\"\n// @Router /{{.Abbreviation}}/update{{.StructName}} [put]\nfunc (a *{{.Abbreviation}}) Update{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tvar info model.{{.StructName}}\n\terr := c.ShouldBindJSON(&info)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n{{- if .AutoCreateResource }}\n    info.UpdatedBy = utils.GetUserID(c)\n{{- end }}\n\terr = service{{ .StructName }}.Update{{.StructName}}(ctx,info)\n    if err != nil {\n        global.GVA_LOG.Error(\"更新失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"更新失败:\" + err.Error(), c)\n\t\treturn\n\t}\n    response.OkWithMessage(\"更新成功\", c)\n}\n\n// Find{{.StructName}} 用id查询{{.Description}}\n// @Tags {{.StructName}}\n// @Summary 用id查询{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param {{.PrimaryField.FieldJson}} query {{.PrimaryField.FieldType}} true \"用id查询{{.Description}}\"\n// @Success 200 {object} response.Response{data=model.{{.StructName}},msg=string} \"查询成功\"\n// @Router /{{.Abbreviation}}/find{{.StructName}} [get]\nfunc (a *{{.Abbreviation}}) Find{{.StructName}}(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\t{{.PrimaryField.FieldJson}} := c.Query(\"{{.PrimaryField.FieldJson}}\")\n\tre{{.Abbreviation}}, err := service{{ .StructName }}.Get{{.StructName}}(ctx,{{.PrimaryField.FieldJson}})\n\tif err != nil {\n        global.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n\t\tresponse.FailWithMessage(\"查询失败:\" + err.Error(), c)\n\t\treturn\n\t}\n    response.OkWithData(re{{.Abbreviation}}, c)\n}\n\n{{- if .IsTree }}\n// Get{{.StructName}}List 分页获取{{.Description}}列表\n// @Tags {{.StructName}}\n// @Summary 分页获取{{.Description}}列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]\nfunc (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tlist, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx)\n\tif err != nil {\n\t    global.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n        response.FailWithMessage(\"获取失败:\" + err.Error(), c)\n        return\n    }\n    response.OkWithDetailed(list, \"获取成功\", c)\n}\n{{- else }}\n// Get{{.StructName}}List 分页获取{{.Description}}列表\n// @Tags {{.StructName}}\n// @Summary 分页获取{{.Description}}列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query request.{{.StructName}}Search true \"分页获取{{.Description}}列表\"\n// @Success 200 {object} response.Response{data=response.PageResult,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]\nfunc (a *{{.Abbreviation}}) Get{{.StructName}}List(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n\tvar pageInfo request.{{.StructName}}Search\n\terr := c.ShouldBindQuery(&pageInfo)\n\tif err != nil {\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t\treturn\n\t}\n\tlist, total, err := service{{ .StructName }}.Get{{.StructName}}InfoList(ctx,pageInfo)\n\tif err != nil {\n\t    global.GVA_LOG.Error(\"获取失败!\", zap.Error(err))\n        response.FailWithMessage(\"获取失败:\" + err.Error(), c)\n        return\n    }\n    response.OkWithDetailed(response.PageResult{\n        List:     list,\n        Total:    total,\n        Page:     pageInfo.Page,\n        PageSize: pageInfo.PageSize,\n    }, \"获取成功\", c)\n}\n{{- end }}\n\n{{- if .HasDataSource }}\n// Get{{.StructName}}DataSource 获取{{.StructName}}的数据源\n// @Tags {{.StructName}}\n// @Summary 获取{{.StructName}}的数据源\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"查询成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}DataSource [get]\nfunc (a *{{.Abbreviation}}) Get{{.StructName}}DataSource(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n    // 此接口为获取数据源定义的数据\n   dataSource, err := service{{ .StructName }}.Get{{.StructName}}DataSource(ctx)\n   if err != nil {\n\t\tglobal.GVA_LOG.Error(\"查询失败!\", zap.Error(err))\n        response.FailWithMessage(\"查询失败:\" + err.Error(), c)\n\t\treturn\n   }\n    response.OkWithData(dataSource, c)\n}\n{{- end }}\n{{- end }}\n// Get{{.StructName}}Public 不需要鉴权的{{.Description}}接口\n// @Tags {{.StructName}}\n// @Summary 不需要鉴权的{{.Description}}接口\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]\nfunc (a *{{.Abbreviation}}) Get{{.StructName}}Public(c *gin.Context) {\n    // 创建业务用Context\n    ctx := c.Request.Context()\n\n    // 此接口不需要鉴权 示例为返回了一个固定的消息接口，一般本接口用于C端服务，需要自己实现业务逻辑\n    service{{ .StructName }}.Get{{.StructName}}Public(ctx)\n    response.OkWithDetailed(gin.H{\"info\": \"不需要鉴权的{{.Description}}接口信息\"}, \"获取成功\", c)\n}\n"
  },
  {
    "path": "server/resource/plugin/server/api/enter.go.tpl",
    "content": "package api\n\nvar Api = new(api)\n\ntype api struct {\n}\n"
  },
  {
    "path": "server/resource/plugin/server/config/config.go.tpl",
    "content": "package config\n\ntype Config struct {\n}\n"
  },
  {
    "path": "server/resource/plugin/server/gen/gen.go.tpl",
    "content": "package main\n\nimport (\n\t\"gorm.io/gen\"\n\t\"path/filepath\"\n)\n\n//go:generate go mod tidy\n//go:generate go mod download\n//go:generate go run gen.go\nfunc main() {\n\tg := gen.NewGenerator(gen.Config{\n\t\tOutPath: filepath.Join(\"..\", \"..\", \"..\", \"{{ .Package }}\", \"blender\", \"model\", \"dao\"),\n\t\tMode:    gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,\n\t})\n\tg.ApplyBasic()\n\tg.Execute()\n}\n"
  },
  {
    "path": "server/resource/plugin/server/initialize/api.go.tpl",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tmodel \"{{.Module}}/model/system\"\n\t\"{{.Module}}/plugin/plugin-tool/utils\"\n)\n\nfunc Api(ctx context.Context) {\n\tentities := []model.SysApi{}\n\tutils.RegisterApis(entities...)\n}\n"
  },
  {
    "path": "server/resource/plugin/server/initialize/dictionary.go.tpl",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tmodel \"{{.Module}}/model/system\"\n\t\"{{.Module}}/plugin/plugin-tool/utils\"\n)\n\nfunc Dictionary(ctx context.Context) {\n\tentities := []model.SysDictionary{}\n\tutils.RegisterDictionaries(entities...)\n}\n"
  },
  {
    "path": "server/resource/plugin/server/initialize/gorm.go.tpl",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"{{.Module}}/global\"\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/zap\"\n)\n\nfunc Gorm(ctx context.Context) {\n\terr := global.GVA_DB.WithContext(ctx).AutoMigrate()\n\tif err != nil {\n\t\terr = errors.Wrap(err, \"注册表失败!\")\n\t\tzap.L().Error(fmt.Sprintf(\"%+v\", err))\n\t}\n}\n"
  },
  {
    "path": "server/resource/plugin/server/initialize/menu.go.tpl",
    "content": "package initialize\n\nimport (\n\t\"context\"\n\tmodel \"{{.Module}}/model/system\"\n\t\"{{.Module}}/plugin/plugin-tool/utils\"\n)\n\nfunc Menu(ctx context.Context) {\n\tentities := []model.SysBaseMenu{}\n\tutils.RegisterMenus(entities...)\n}\n"
  },
  {
    "path": "server/resource/plugin/server/initialize/router.go.tpl",
    "content": "package initialize\n\nimport (\n\t\"{{.Module}}/global\"\n\t\"{{.Module}}/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc Router(engine *gin.Engine) {\n\tpublic := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group(\"\")\n\tpublic.Use()\n\tprivate := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group(\"\")\n\tprivate.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())\n}\n"
  },
  {
    "path": "server/resource/plugin/server/initialize/viper.go.tpl",
    "content": "package initialize\n\nimport (\n\t\"fmt\"\n\t\"{{.Module}}/global\"\n\t\"{{.Module}}/plugin/{{ .Package }}/plugin\"\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/zap\"\n)\n\nfunc Viper() {\n\terr := global.GVA_VP.UnmarshalKey(\"{{ .Package }}\", &plugin.Config)\n\tif err != nil {\n\t\terr = errors.Wrap(err, \"初始化配置文件失败!\")\n\t\tzap.L().Error(fmt.Sprintf(\"%+v\", err))\n\t}\n}\n"
  },
  {
    "path": "server/resource/plugin/server/model/model.go.tpl",
    "content": "{{- if .IsAdd}}\n// 在结构体中新增如下字段\n{{- range .Fields}}\n  {{ GenerateField . }}\n{{- end }}\n\n{{ else }}\npackage model\n\n{{- if not .OnlyTemplate}}\nimport (\n\t{{- if .GvaModel }}\n\t\"{{.Module}}/global\"\n\t{{- end }}\n\t{{- if or .HasTimer }}\n\t\"time\"\n\t{{- end }}\n\t{{- if .NeedJSON }}\n\t\"gorm.io/datatypes\"\n\t{{- end }}\n)\n{{- end }}\n\n// {{.StructName}} {{.Description}} 结构体\ntype {{.StructName}} struct {\n{{- if not .OnlyTemplate}}\n{{- if .GvaModel }}\n    global.GVA_MODEL\n{{- end }}\n{{- range .Fields}}\n  {{ GenerateField . }}\n{{- end }}\n    {{- if .AutoCreateResource }}\n    CreatedBy  uint   `gorm:\"column:created_by;comment:创建者\"`\n    UpdatedBy  uint   `gorm:\"column:updated_by;comment:更新者\"`\n    DeletedBy  uint   `gorm:\"column:deleted_by;comment:删除者\"`\n    {{- end }}\n    {{- if .IsTree }}\n    Children   []*{{.StructName}} `json:\"children\" gorm:\"-\"`     //子节点\n    ParentID   int             `json:\"parentID\" gorm:\"column:parent_id;comment:父节点\"`\n    {{- end }}\n    {{- end }}\n}\n\n{{ if .TableName }}\n// TableName {{.Description}} {{.StructName}}自定义表名 {{.TableName}}\nfunc ({{.StructName}}) TableName() string {\n    return \"{{.TableName}}\"\n}\n{{ end }}\n\n\n{{if .IsTree }}\n// GetChildren 实现TreeNode接口\nfunc (s *{{.StructName}}) GetChildren() []*{{.StructName}} {\n    return s.Children\n}\n\n// SetChildren 实现TreeNode接口\nfunc (s *{{.StructName}}) SetChildren(children *{{.StructName}}) {\n\ts.Children = append(s.Children, children)\n}\n\n// GetID 实现TreeNode接口\nfunc (s *{{.StructName}}) GetID() int {\n    return int({{if not .GvaModel}}*{{- end }}s.{{.PrimaryField.FieldName}})\n}\n\n// GetParentID 实现TreeNode接口\nfunc (s *{{.StructName}}) GetParentID() int {\n    return s.ParentID\n}\n{{ end }}\n\n\n{{ end }}\n"
  },
  {
    "path": "server/resource/plugin/server/model/request/request.go.tpl",
    "content": "{{- if .IsAdd}}\n// 在结构体中新增如下字段\n{{- range .Fields}}\n    {{- if ne .FieldSearchType \"\"}}\n         {{ GenerateSearchField . }}\n    {{- end}}\n{{- end }}\n{{- if .NeedSort}}\nSort  string `json:\"sort\" form:\"sort\"`\nOrder string `json:\"order\" form:\"order\"`\n{{- end}}\n{{- else }}\npackage request\n{{- if not .OnlyTemplate}}\nimport (\n\t\"{{.Module}}/model/common/request\"\n\t{{ if or .HasSearchTimer .GvaModel }}\"time\"{{ end }}\n)\n{{- end}}\ntype {{.StructName}}Search struct{\n{{- if not .OnlyTemplate}}\n\n{{- if .GvaModel }}\n    CreatedAtRange []time.Time `json:\"createdAtRange\" form:\"createdAtRange[]\"`\n{{- end }}\n{{- range .Fields}}\n    {{- if ne .FieldSearchType \"\"}}\n       {{ GenerateSearchField . }}\n    {{- end}}\n{{- end }}\n    request.PageInfo\n    {{- if .NeedSort}}\n    Sort  string `json:\"sort\" form:\"sort\"`\n    Order string `json:\"order\" form:\"order\"`\n    {{- end}}\n{{- end }}\n}\n{{- end }}\n"
  },
  {
    "path": "server/resource/plugin/server/plugin/plugin.go.tpl",
    "content": "package plugin\n\nimport \"{{.Module}}/plugin/{{ .Package }}/config\"\n\nvar Config config.Config\n"
  },
  {
    "path": "server/resource/plugin/server/plugin.go.tpl",
    "content": "package {{ .Package }}\n\nimport (\n\t\"context\"\n\t\"{{.Module}}/plugin/{{ .Package }}/initialize\"\n\tinterfaces \"{{.Module}}/utils/plugin/v2\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nvar _ interfaces.Plugin = (*plugin)(nil)\n\nvar Plugin = new(plugin)\n\ntype plugin struct{}\n\nfunc init() {\n\tinterfaces.Register(Plugin)\n}\n\n\n// 如果需要配置文件，请到config.Config中填充配置结构，且到下方发放中填入其在config.yaml中的key并添加如下方法\n// initialize.Viper()\n// 安装插件时候自动注册的api数据请到下方法.Api方法中实现并添加如下方法\n// initialize.Api(ctx)\n// 安装插件时候自动注册的api数据请到下方法.Menu方法中实现并添加如下方法\n// initialize.Menu(ctx)\n// 安装插件时候自动注册的api数据请到下方法.Dictionary方法中实现并添加如下方法\n// initialize.Dictionary(ctx)\nfunc (p *plugin) Register(group *gin.Engine) {\n\tctx := context.Background() \n\tinitialize.Gorm(ctx)\n\tinitialize.Router(group)\n}\n"
  },
  {
    "path": "server/resource/plugin/server/router/enter.go.tpl",
    "content": "package router\n\nvar Router = new(router)\n\ntype router struct {\n}\n"
  },
  {
    "path": "server/resource/plugin/server/router/router.go.tpl",
    "content": "package router\n\nimport (\n\t{{if .OnlyTemplate }} // {{end}}\"{{.Module}}/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\nvar {{.StructName}} = new({{.Abbreviation}})\n\ntype {{.Abbreviation}} struct {}\n\n// Init 初始化 {{.Description}} 路由信息\nfunc (r *{{.Abbreviation}}) Init(public *gin.RouterGroup, private *gin.RouterGroup) {\n{{- if not .OnlyTemplate }}\n\t{\n\t    group := private.Group(\"{{.Abbreviation}}\").Use(middleware.OperationRecord())\n\t\tgroup.POST(\"create{{.StructName}}\", api{{.StructName}}.Create{{.StructName}})   // 新建{{.Description}}\n\t\tgroup.DELETE(\"delete{{.StructName}}\", api{{.StructName}}.Delete{{.StructName}}) // 删除{{.Description}}\n\t\tgroup.DELETE(\"delete{{.StructName}}ByIds\", api{{.StructName}}.Delete{{.StructName}}ByIds) // 批量删除{{.Description}}\n\t\tgroup.PUT(\"update{{.StructName}}\", api{{.StructName}}.Update{{.StructName}})    // 更新{{.Description}}\n\t}\n\t{\n\t    group := private.Group(\"{{.Abbreviation}}\")\n\t\tgroup.GET(\"find{{.StructName}}\", api{{.StructName}}.Find{{.StructName}})        // 根据ID获取{{.Description}}\n\t\tgroup.GET(\"get{{.StructName}}List\", api{{.StructName}}.Get{{.StructName}}List)  // 获取{{.Description}}列表\n\t}\n\t{\n\t    group := public.Group(\"{{.Abbreviation}}\")\n    \t{{- if .HasDataSource}}\n\t    group.GET(\"get{{.StructName}}DataSource\", api{{.StructName}}.Get{{.StructName}}DataSource)  // 获取{{.Description}}数据源\n\t    {{- end}}\n\t    group.GET(\"get{{.StructName}}Public\", api{{.StructName}}.Get{{.StructName}}Public)  // {{.Description}}开放接口\n\t}\n{{- else}}\n     // {\n\t //   group := private.Group(\"{{.Abbreviation}}\").Use(middleware.OperationRecord())\n\t // }\n\t // {\n     //   group := private.Group(\"{{.Abbreviation}}\")\n     // }\n    {\n\t    group := public.Group(\"{{.Abbreviation}}\")\n\t    group.GET(\"get{{.StructName}}Public\", api{{.StructName}}.Get{{.StructName}}Public)  // {{.Description}}开放接口\n    }\n{{- end}}\n}\n"
  },
  {
    "path": "server/resource/plugin/server/service/enter.go.tpl",
    "content": "package service\n\nvar Service = new(service)\n\ntype service struct {\n}\n\n"
  },
  {
    "path": "server/resource/plugin/server/service/service.go.tpl",
    "content": "{{- $db := \"\" }}\n{{- if eq .BusinessDB \"\" }}\n {{- $db = \"global.GVA_DB\" }}\n{{- else}}\n {{- $db =  printf \"global.MustGetGlobalDBByDBName(\\\"%s\\\")\" .BusinessDB   }}\n{{- end}}\n\n{{- if .IsAdd}}\n\n// Get{{.StructName}}InfoList 新增搜索语句\n\n    {{ GenerateSearchConditions .Fields }}\n\n// Get{{.StructName}}InfoList 新增排序语句 请自行在搜索语句中添加orderMap内容\n       {{- range .Fields}}\n            {{- if .Sort}}\norderMap[\"{{.ColumnName}}\"] = true\n         \t{{- end}}\n       {{- end}}\n\n\n{{- if .HasDataSource }}\n//  Get{{.StructName}}DataSource()方法新增关联语句\n\t{{range $key, $value := .DataSourceMap}}\n{{$key}} := make([]map[string]any, 0)\n{{$db}}.Table(\"{{$value.Table}}\"){{- if $value.HasDeletedAt}}.Where(\"deleted_at IS NULL\"){{ end }}.Select(\"{{$value.Label}} as label,{{$value.Value}} as value\").Scan(&{{$key}})\nres[\"{{$key}}\"] = {{$key}}\n\t{{- end }}\n{{- end }}\n{{- else}}\npackage service\n\nimport (\n{{- if not .OnlyTemplate }}\n\t\"context\"\n\t\"{{.Module}}/global\"\n\t\"{{.Module}}/plugin/{{.Package}}/model\"\n\t{{- if not .IsTree }}\n    \"{{.Module}}/plugin/{{.Package}}/model/request\"\n    {{- else }}\n    \"errors\"\n    {{- end }}\n    {{- if .AutoCreateResource }}\n    \"gorm.io/gorm\"\n    {{- end}}\n{{- if .IsTree }}\n    \"{{.Module}}/utils\"\n{{- end }}\n{{- end }}\n)\n\nvar {{.StructName}} = new({{.Abbreviation}})\n\ntype {{.Abbreviation}} struct {}\n\n{{- $db := \"\" }}\n{{- if eq .BusinessDB \"\" }}\n {{- $db = \"global.GVA_DB\" }}\n{{- else}}\n {{- $db =  printf \"global.MustGetGlobalDBByDBName(\\\"%s\\\")\" .BusinessDB   }}\n{{- end}}\n{{- if not .OnlyTemplate }}\n// Create{{.StructName}} 创建{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Create{{.StructName}}(ctx context.Context, {{.Abbreviation}} *model.{{.StructName}}) (err error) {\n\terr = {{$db}}.Create({{.Abbreviation}}).Error\n\treturn err\n}\n\n// Delete{{.StructName}} 删除{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Delete{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string{{- if .AutoCreateResource -}},userID uint{{- end -}}) (err error) {\n\n\t{{- if .IsTree }}\n       var count int64\n       err = {{$db}}.Find(&model.{{.StructName}}{},\"parent_id = ?\",{{.PrimaryField.FieldJson}}).Count(&count).Error\n       if count > 0 {\n          return errors.New(\"此节点存在子节点不允许删除\")\n       }\n       if err != nil {\n          return err\n       }\n    {{- end }}\n\n\t{{- if .AutoCreateResource }}\n\terr = {{$db}}.Transaction(func(tx *gorm.DB) error {\n\t    if err := tx.Model(&model.{{.StructName}}{}).Where(\"{{.PrimaryField.ColumnName}} = ?\", {{.PrimaryField.FieldJson}}).Update(\"deleted_by\", userID).Error; err != nil {\n              return err\n        }\n        if err = tx.Delete(&model.{{.StructName}}{},\"{{.PrimaryField.ColumnName}} = ?\",{{.PrimaryField.FieldJson}}).Error; err != nil {\n              return err\n        }\n        return nil\n\t})\n    {{- else }}\n\terr = {{$db}}.Delete(&model.{{.StructName}}{},\"{{.PrimaryField.ColumnName}} = ?\",{{.PrimaryField.FieldJson}}).Error\n\t{{- end }}\n\treturn err\n}\n\n// Delete{{.StructName}}ByIds 批量删除{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Delete{{.StructName}}ByIds(ctx context.Context, {{.PrimaryField.FieldJson}}s []string {{- if .AutoCreateResource }},deleted_by uint{{- end}}) (err error) {\n\t{{- if .AutoCreateResource }}\n\terr = {{$db}}.Transaction(func(tx *gorm.DB) error {\n\t    if err := tx.Model(&model.{{.StructName}}{}).Where(\"{{.PrimaryField.ColumnName}} in ?\", {{.PrimaryField.FieldJson}}s).Update(\"deleted_by\", deleted_by).Error; err != nil {\n            return err\n        }\n        if err := tx.Where(\"{{.PrimaryField.ColumnName}} in ?\", {{.PrimaryField.FieldJson}}s).Delete(&model.{{.StructName}}{}).Error; err != nil {\n            return err\n        }\n        return nil\n    })\n    {{- else}}\n\terr = {{$db}}.Delete(&[]model.{{.StructName}}{},\"{{.PrimaryField.ColumnName}} in ?\",{{.PrimaryField.FieldJson}}s).Error\n    {{- end}}\n\treturn err\n}\n\n// Update{{.StructName}} 更新{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Update{{.StructName}}(ctx context.Context, {{.Abbreviation}} model.{{.StructName}}) (err error) {\n\terr = {{$db}}.Model(&model.{{.StructName}}{}).Where(\"{{.PrimaryField.ColumnName}} = ?\",{{.Abbreviation}}.{{.PrimaryField.FieldName}}).Updates(&{{.Abbreviation}}).Error\n\treturn err\n}\n\n// Get{{.StructName}} 根据{{.PrimaryField.FieldJson}}获取{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Get{{.StructName}}(ctx context.Context, {{.PrimaryField.FieldJson}} string) ({{.Abbreviation}} model.{{.StructName}}, err error) {\n\terr = {{$db}}.Where(\"{{.PrimaryField.ColumnName}} = ?\", {{.PrimaryField.FieldJson}}).First(&{{.Abbreviation}}).Error\n\treturn\n}\n\n\n{{- if .IsTree }}\n// Get{{.StructName}}InfoList 分页获取{{.Description}}记录,Tree模式下不添加分页和搜索\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context) (list []*model.{{.StructName}},err error) {\n    // 创建db\n\tdb := {{$db}}.Model(&model.{{.StructName}}{})\n    var {{.Abbreviation}}s []*model.{{.StructName}}\n\n\terr = db.Find(&{{.Abbreviation}}s).Error\n\n\treturn utils.BuildTree({{.Abbreviation}}s), err\n}\n{{- else }}\n// Get{{.StructName}}InfoList 分页获取{{.Description}}记录\n// Author [yourname](https://github.com/yourname)\nfunc (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info request.{{.StructName}}Search) (list []model.{{.StructName}}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n    // 创建db\n\tdb := {{$db}}.Model(&model.{{.StructName}}{})\n    var {{.Abbreviation}}s []model.{{.StructName}}\n    // 如果有条件搜索 下方会自动创建搜索语句\n{{- if .GvaModel }}\n    if len(info.CreatedAtRange) == 2 {\n     db = db.Where(\"created_at BETWEEN ? AND ?\", info.CreatedAtRange[0], info.CreatedAtRange[1])\n    }\n{{- end }}\n  {{ GenerateSearchConditions .Fields }}\n\terr = db.Count(&total).Error\n\tif err!=nil {\n    \treturn\n    }\n    {{- if .NeedSort}}\n        var OrderStr string\n        orderMap := make(map[string]bool)\n      {{- if .GvaModel }}\n        orderMap[\"id\"] = true\n        orderMap[\"created_at\"] = true\n      {{- end }}\n       {{- range .Fields}}\n        {{- if .Sort}}\n        orderMap[\"{{.ColumnName}}\"] = true\n        {{- end}}\n       {{- end}}\n       if orderMap[info.Sort] {\n          OrderStr = info.Sort\n          if info.Order == \"descending\" {\n             OrderStr = OrderStr + \" desc\"\n          }\n          db = db.Order(OrderStr)\n       }\n    {{- end}}\n\n\tif limit != 0 {\n       db = db.Limit(limit).Offset(offset)\n    }\n\terr = db.Find(&{{.Abbreviation}}s).Error\n\treturn  {{.Abbreviation}}s, total, err\n}\n{{- end }}\n{{- if .HasDataSource }}\nfunc (s *{{.Abbreviation}})Get{{.StructName}}DataSource(ctx context.Context) (res map[string][]map[string]any, err error) {\n\tres = make(map[string][]map[string]any)\n\t{{range $key, $value := .DataSourceMap}}\n\t   {{$key}} := make([]map[string]any, 0)\n\t   {{$db}}.Table(\"{{$value.Table}}\"){{- if $value.HasDeletedAt}}.Where(\"deleted_at IS NULL\"){{ end }}.Select(\"{{$value.Label}} as label,{{$value.Value}} as value\").Scan(&{{$key}})\n\t   res[\"{{$key}}\"] = {{$key}}\n\t{{- end }}\n\treturn\n}\n{{- end }}\n{{- end }}\n\nfunc (s *{{.Abbreviation}})Get{{.StructName}}Public(ctx context.Context) {\n\n}\n{{- end }}\n"
  },
  {
    "path": "server/resource/plugin/web/api/api.js.tpl",
    "content": "import service from '@/utils/request'\n{{- if not .OnlyTemplate}}\n// @Tags {{.StructName}}\n// @Summary 创建{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"创建{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /{{.Abbreviation}}/create{{.StructName}} [post]\nexport const create{{.StructName}} = (data) => {\n  return service({\n    url: '/{{.Abbreviation}}/create{{.StructName}}',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"删除{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]\nexport const delete{{.StructName}} = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/delete{{.StructName}}',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 批量删除{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /{{.Abbreviation}}/delete{{.StructName}} [delete]\nexport const delete{{.StructName}}ByIds = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/delete{{.StructName}}ByIds',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 更新{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.{{.StructName}} true \"更新{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /{{.Abbreviation}}/update{{.StructName}} [put]\nexport const update{{.StructName}} = (data) => {\n  return service({\n    url: '/{{.Abbreviation}}/update{{.StructName}}',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 用id查询{{.Description}}\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query model.{{.StructName}} true \"用id查询{{.Description}}\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /{{.Abbreviation}}/find{{.StructName}} [get]\nexport const find{{.StructName}} = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/find{{.StructName}}',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags {{.StructName}}\n// @Summary 分页获取{{.Description}}列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取{{.Description}}列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}List [get]\nexport const get{{.StructName}}List = (params) => {\n  return service({\n    url: '/{{.Abbreviation}}/get{{.StructName}}List',\n    method: 'get',\n    params\n  })\n}\n\n{{- if .HasDataSource}}\n// @Tags {{.StructName}}\n// @Summary 获取数据源\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /{{.Abbreviation}}/find{{.StructName}}DataSource [get]\nexport const get{{.StructName}}DataSource = () => {\n  return service({\n    url: '/{{.Abbreviation}}/get{{.StructName}}DataSource',\n    method: 'get',\n  })\n}\n{{- end}}\n{{- end}}\n// @Tags {{.StructName}}\n// @Summary 不需要鉴权的{{.Description}}接口\n// @Accept application/json\n// @Produce application/json\n// @Param data query request.{{.StructName}}Search true \"分页获取{{.Description}}列表\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /{{.Abbreviation}}/get{{.StructName}}Public [get]\nexport const get{{.StructName}}Public = () => {\n  return service({\n    url: '/{{.Abbreviation}}/get{{.StructName}}Public',\n    method: 'get',\n  })\n}\n"
  },
  {
    "path": "server/resource/plugin/web/form/form.vue.tpl",
    "content": "{{- if .IsAdd }}\n// 新增表单中增加如下代码\n{{- range .Fields}}\n          {{- if .Form}}\n<el-form-item label=\"{{.FieldDesc}}:\"  prop=\"{{.FieldJson}}\" >\n          {{- if .CheckDataSource}}\n    <el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model=\"formData.{{.FieldJson}}\" placeholder=\"请选择{{.FieldDesc}}\" style=\"width:100%\" :clearable=\"{{.Clearable}}\" >\n        <el-option v-for=\"(item,key) in dataSource.{{.FieldJson}}\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n    </el-select>\n          {{- else }}\n          {{- if eq .FieldType \"bool\" }}\n    <el-switch v-model=\"formData.{{.FieldJson}}\" active-color=\"#13ce66\" inactive-color=\"#ff4949\" active-text=\"是\" inactive-text=\"否\" clearable ></el-switch>\n          {{- end }}\n          {{- if eq .FieldType \"string\" }}\n          {{- if .DictType}}\n    <el-select {{if eq .FieldType \"array\"}}multiple {{end}}v-model=\"formData.{{ .FieldJson }}\" placeholder=\"请选择{{.FieldDesc}}\" style=\"width:100%\" :clearable=\"{{.Clearable}}\" >\n        <el-option v-for=\"(item,key) in {{ .DictType }}Options\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n    </el-select>\n          {{- else }}\n    <el-input v-model=\"formData.{{.FieldJson}}\" :clearable=\"{{.Clearable}}\"  placeholder=\"请输入{{.FieldDesc}}\" />\n          {{- end }}\n          {{- end }}\n          {{- if eq .FieldType \"richtext\" }}\n    <RichEdit v-model=\"formData.{{.FieldJson}}\"/>\n          {{- end }}\n          {{- if eq .FieldType \"json\" }}\n    // 此字段为json结构，可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取\n    {{\"{{\"}} formData.{{.FieldJson}} {{\"}}\"}}\n          {{- end }}\n           {{- if eq .FieldType \"array\" }}\n    <ArrayCtrl v-model=\"formData.{{ .FieldJson }}\" editable/>\n           {{- end }}\n          {{- if eq .FieldType \"int\" }}\n    <el-input v-model.number=\"formData.{{ .FieldJson }}\" :clearable=\"{{.Clearable}}\" placeholder=\"请输入{{.FieldDesc}}\" />\n          {{- end }}\n          {{- if eq .FieldType \"time.Time\" }}\n    <el-date-picker v-model=\"formData.{{ .FieldJson }}\" type=\"date\" style=\"width:100%\" placeholder=\"选择日期\" :clearable=\"{{.Clearable}}\"  />\n          {{- end }}\n          {{- if eq .FieldType \"float64\" }}\n    <el-input-number v-model=\"formData.{{ .FieldJson }}\"  style=\"width:100%\" :precision=\"2\" :clearable=\"{{.Clearable}}\"  />\n          {{- end }}\n          {{- if eq .FieldType \"enum\" }}\n    <el-select v-model=\"formData.{{ .FieldJson }}\" placeholder=\"请选择{{.FieldDesc}}\" style=\"width:100%\" :clearable=\"{{.Clearable}}\" >\n       <el-option v-for=\"item in [{{.DataTypeLong}}]\" :key=\"item\" :label=\"item\" :value=\"item\" />\n    </el-select>\n          {{- end }}\n          {{- if eq .FieldType \"picture\" }}\n    <SelectImage\n     v-model=\"formData.{{ .FieldJson }}\"\n     file-type=\"image\"\n    />\n          {{- end }}\n          {{- if eq .FieldType \"pictures\" }}\n    <SelectImage\n     multiple\n     v-model=\"formData.{{ .FieldJson }}\"\n     file-type=\"image\"\n     />\n          {{- end }}\n          {{- if eq .FieldType \"video\" }}\n    <SelectImage\n    v-model=\"formData.{{ .FieldJson }}\"\n    file-type=\"video\"\n    />\n           {{- end }}\n          {{- if eq .FieldType \"file\" }}\n    <SelectFile v-model=\"formData.{{ .FieldJson }}\" />\n          {{- end }}\n          {{- end }}\n</el-form-item>\n          {{- end }}\n          {{- end }}\n\n// 字典增加如下代码\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\n\n// init方法中增加如下调用\n\n{{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n{{- end }}\n\n// 基础formData结构增加如下字段\n{{- range .Fields}}\n          {{- if .Form}}\n            {{- if eq .FieldType \"bool\" }}\n{{.FieldJson}}: false,\n            {{- end }}\n            {{- if eq .FieldType \"string\" }}\n{{.FieldJson}}: '',\n            {{- end }}\n            {{- if eq .FieldType \"richtext\" }}\n{{.FieldJson}}: '',\n            {{- end }}\n            {{- if eq .FieldType \"int\" }}\n{{.FieldJson}}: {{- if or .DataSource}} undefined{{ else }} 0{{- end }},\n            {{- end }}\n            {{- if eq .FieldType \"time.Time\" }}\n{{.FieldJson}}: new Date(),\n            {{- end }}\n            {{- if eq .FieldType \"float64\" }}\n{{.FieldJson}}: 0,\n            {{- end }}\n            {{- if eq .FieldType \"picture\" }}\n{{.FieldJson}}: \"\",\n            {{- end }}\n            {{- if eq .FieldType \"video\" }}\n{{.FieldJson}}: \"\",\n            {{- end }}\n            {{- if eq .FieldType \"pictures\" }}\n{{.FieldJson}}: [],\n            {{- end }}\n            {{- if eq .FieldType \"file\" }}\n{{.FieldJson}}: [],\n            {{- end }}\n            {{- if eq .FieldType \"json\" }}\n{{.FieldJson}}: {},\n            {{- end }}\n            {{- if eq .FieldType \"array\" }}\n{{.FieldJson}}: [],\n            {{- end }}\n          {{- end }}\n        {{- end }}\n// 验证规则中增加如下字段\n\n{{- range .Fields }}\n        {{- if .Form }}\n            {{- if eq .Require true }}\n{{.FieldJson }} : [{\n    required: true,\n    message: '{{ .ErrorText }}',\n    trigger: ['input','blur'],\n},\n               {{- if eq .FieldType \"string\" }}\n{\n    whitespace: true,\n    message: '不能只输入空格',\n    trigger: ['input', 'blur'],\n}\n              {{- end }}\n],\n            {{- end }}\n        {{- end }}\n    {{- end }}\n\n{{- if .HasDataSource }}\n// 请引用\nget{{.StructName}}DataSource,\n\n//  获取数据源\nconst dataSource = ref([])\nconst getDataSourceFunc = async()=>{\n  const res = await get{{.StructName}}DataSource()\n  if (res.code === 0) {\n    dataSource.value = res.data\n  }\n}\ngetDataSourceFunc()\n{{- end }}\n{{- else }}\n{{- if not .OnlyTemplate }}\n<template>\n  <div>\n    <div class=\"gva-form-box\">\n      <el-form :model=\"formData\" ref=\"elFormRef\" label-position=\"right\" :rules=\"rule\" label-width=\"80px\">\n        {{- if .IsTree }}\n          <el-form-item label=\"父节点:\" prop=\"parentID\" >\n              <el-tree-select\n                  v-model=\"formData.parentID\"\n                  :data=\"[rootNode,...tableData]\"\n                  check-strictly\n                  :render-after-expand=\"false\"\n                  :props=\"defaultProps\"\n                  clearable\n                  style=\"width: 240px\"\n                  placeholder=\"根节点\"\n              />\n          </el-form-item>\n        {{- end }}\n      {{- range .Fields}}\n      {{- if .Form }}\n        <el-form-item label=\"{{.FieldDesc}}:\" prop=\"{{.FieldJson}}\">\n       {{- if .CheckDataSource}}\n        <el-select {{if eq .DataSource.Association 2}} multiple {{ end }} v-model=\"formData.{{.FieldJson}}\" placeholder=\"请选择{{.FieldDesc}}\" style=\"width:100%\" :clearable=\"{{.Clearable}}\" >\n          <el-option v-for=\"(item,key) in dataSource.{{.FieldJson}}\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n        </el-select>\n       {{- else }}\n      {{- if eq .FieldType \"bool\" }}\n          <el-switch v-model=\"formData.{{.FieldJson}}\" active-color=\"#13ce66\" inactive-color=\"#ff4949\" active-text=\"是\" inactive-text=\"否\" clearable ></el-switch>\n      {{- end }}\n      {{- if eq .FieldType \"string\" }}\n      {{- if .DictType}}\n           <el-select {{if eq .FieldType \"array\"}}multiple {{end}}v-model=\"formData.{{ .FieldJson }}\" placeholder=\"请选择{{.FieldDesc}}\" style=\"width:100%\" :clearable=\"{{.Clearable}}\" >\n              <el-option v-for=\"(item,key) in {{ .DictType }}Options\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n           </el-select>\n      {{- else }}\n          <el-input v-model=\"formData.{{.FieldJson}}\" :clearable=\"{{.Clearable}}\"  placeholder=\"请输入{{.FieldDesc}}\" />\n      {{- end }}\n      {{- end }}\n      {{- if eq .FieldType \"richtext\" }}\n          <RichEdit v-model=\"formData.{{.FieldJson}}\"/>\n      {{- end }}\n      {{- if eq .FieldType \"int\" }}\n          <el-input v-model.number=\"formData.{{ .FieldJson }}\" :clearable=\"{{.Clearable}}\" placeholder=\"请输入\" />\n      {{- end }}\n      {{- if eq .FieldType \"time.Time\" }}\n          <el-date-picker v-model=\"formData.{{ .FieldJson }}\" type=\"date\" placeholder=\"选择日期\" :clearable=\"{{.Clearable}}\"></el-date-picker>\n      {{- end }}\n      {{- if eq .FieldType \"float64\" }}\n          <el-input-number v-model=\"formData.{{ .FieldJson }}\" :precision=\"2\" :clearable=\"{{.Clearable}}\"></el-input-number>\n      {{- end }}\n      {{- if eq .FieldType \"enum\" }}\n        <el-select v-model=\"formData.{{ .FieldJson }}\" placeholder=\"请选择\" style=\"width:100%\" :clearable=\"{{.Clearable}}\">\n          <el-option v-for=\"item in [{{ .DataTypeLong }}]\" :key=\"item\" :label=\"item\" :value=\"item\" />\n        </el-select>\n      {{- end }}\n       {{- if eq .FieldType \"picture\" }}\n          <SelectImage v-model=\"formData.{{ .FieldJson }}\" file-type=\"image\"/>\n       {{- end }}\n       {{- if eq .FieldType \"video\" }}\n          <SelectImage v-model=\"formData.{{ .FieldJson }}\" file-type=\"video\"/>\n       {{- end }}\n       {{- if eq .FieldType \"pictures\" }}\n           <SelectImage v-model=\"formData.{{ .FieldJson }}\" multiple file-type=\"image\"/>\n       {{- end }}\n       {{- if eq .FieldType \"file\" }}\n          <SelectFile v-model=\"formData.{{ .FieldJson }}\" />\n       {{- end }}\n       {{- if eq .FieldType \"json\" }}\n          // 此字段为json结构，可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.{{.FieldJson}} 后端会按照json的类型进行存取\n          {{\"{{\"}} formData.{{.FieldJson}} {{\"}}\"}}\n       {{- end }}\n       {{- if eq .FieldType \"array\" }}\n          <ArrayCtrl v-model=\"formData.{{ .FieldJson }}\" editable/>\n       {{- end }}\n       {{- end }}\n       </el-form-item>\n      {{- end }}\n      {{- end }}\n        <el-form-item>\n          <el-button :loading=\"btnLoading\" type=\"primary\" @click=\"save\">保存</el-button>\n          <el-button type=\"primary\" @click=\"back\">返回</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport {\n  {{- if .HasDataSource }}\n    get{{.StructName}}DataSource,\n  {{- end }}\n  {{- if .IsTree }}\n    get{{.StructName}}List,\n  {{- end }}\n  create{{.StructName}},\n  update{{.StructName}},\n  find{{.StructName}}\n} from '@/plugin/{{.Package}}/api/{{.PackageName}}'\n\ndefineOptions({\n    name: '{{.StructName}}Form'\n})\n\n// 自动获取字典\nimport { getDictFunc } from '@/utils/format'\nimport { useRoute, useRouter } from \"vue-router\"\nimport { ElMessage } from 'element-plus'\nimport { ref, reactive } from 'vue'\n{{- if .HasPic }}\n// 图片选择组件\nimport SelectImage from '@/components/selectImage/selectImage.vue'\n{{- end }}\n\n{{- if .HasFile }}\n// 文件选择组件\nimport SelectFile from '@/components/selectFile/selectFile.vue'\n{{- end }}\n\n{{- if .HasRichText }}\n// 富文本组件\nimport RichEdit from '@/components/richtext/rich-edit.vue'\n{{- end }}\n\n{{- if .HasArray}}\n// 数组控制组件\nimport ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'\n{{- end }}\n\n\nconst route = useRoute()\nconst router = useRouter()\n\n{{- if .IsTree }}\nconst tableData = ref([])\n\nconst defaultProps = {\n  children: \"children\",\n  label: \"{{ .TreeJson }}\",\n  value: \"{{ .PrimaryField.FieldJson }}\"\n}\n\nconst rootNode = {\n  {{ .PrimaryField.FieldJson }}: 0,\n  {{ .TreeJson }}: '根节点',\n  children: []\n}\n\nconst getTableData = async() => {\n  const table = await get{{.StructName}}List()\n  if (table.code === 0) {\n    tableData.value = table.data || []\n  }\n}\n\ngetTableData()\n\n{{- end }}\n\n// 提交按钮loading\nconst btnLoading = ref(false)\n\nconst type = ref('')\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\nconst formData = ref({\n        {{- if .IsTree }}\n            parentID: undefined,\n        {{- end }}\n        {{- range .Fields}}\n          {{- if .Form }}\n            {{- if eq .FieldType \"bool\" }}\n            {{.FieldJson}}: false,\n            {{- end }}\n            {{- if eq .FieldType \"string\" }}\n            {{.FieldJson}}: '',\n            {{- end }}\n            {{- if eq .FieldType \"richtext\" }}\n            {{.FieldJson}}: '',\n            {{- end }}\n            {{- if eq .FieldType \"int\" }}\n            {{.FieldJson}}: {{- if or .DataSource }} undefined{{ else }} 0{{- end }},\n            {{- end }}\n            {{- if eq .FieldType \"time.Time\" }}\n            {{.FieldJson}}: new Date(),\n            {{- end }}\n            {{- if eq .FieldType \"float64\" }}\n            {{.FieldJson}}: 0,\n            {{- end }}\n            {{- if eq .FieldType \"picture\" }}\n            {{.FieldJson}}: \"\",\n            {{- end }}\n            {{- if eq .FieldType \"video\" }}\n            {{.FieldJson}}: \"\",\n            {{- end }}\n            {{- if eq .FieldType \"pictures\" }}\n            {{.FieldJson}}: [],\n            {{- end }}\n            {{- if eq .FieldType \"file\" }}\n            {{.FieldJson}}: [],\n            {{- end }}\n            {{- if eq .FieldType \"json\" }}\n            {{.FieldJson}}: {},\n            {{- end }}\n            {{- if eq .FieldType \"array\" }}\n            {{.FieldJson}}: [],\n            {{- end }}\n          {{- end }}\n        {{- end }}\n        })\n// 验证规则\nconst rule = reactive({\n    {{- range .Fields }}\n            {{- if eq .Require true }}\n               {{.FieldJson }} : [{\n                   required: true,\n                   message: '{{ .ErrorText }}',\n                   trigger: ['input','blur'],\n               }],\n            {{- end }}\n    {{- end }}\n})\n\nconst elFormRef = ref()\n\n{{- if .HasDataSource }}\n  const dataSource = ref([])\n  const getDataSourceFunc = async()=>{\n    const res = await get{{.StructName}}DataSource()\n    if (res.code === 0) {\n      dataSource.value = res.data\n    }\n  }\n  getDataSourceFunc()\n{{- end }}\n\n// 初始化方法\nconst init = async () => {\n // 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例\n    if (route.query.id) {\n      const res = await find{{.StructName}}({ ID: route.query.id })\n      if (res.code === 0) {\n        formData.value = res.data\n        type.value = 'update'\n      }\n    } else {\n      type.value = 'create'\n    }\n    {{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n    {{- end }}\n}\n\ninit()\n// 保存按钮\nconst save = async() => {\n      btnLoading.value = true\n      elFormRef.value?.validate( async (valid) => {\n         if (!valid) return btnLoading.value = false\n            let res\n           switch (type.value) {\n             case 'create':\n               res = await create{{.StructName}}(formData.value)\n               break\n             case 'update':\n               res = await update{{.StructName}}(formData.value)\n               break\n             default:\n               res = await create{{.StructName}}(formData.value)\n               break\n           }\n           btnLoading.value = false\n           if (res.code === 0) {\n             ElMessage({\n               type: 'success',\n               message: '创建/更改成功'\n             })\n           }\n       })\n}\n\n// 返回按钮\nconst back = () => {\n    router.go(-1)\n}\n\n</script>\n\n<style>\n</style>\n{{- else }}\n<template>\n<div>form</div>\n</template>\n<script setup>\n</script>\n<style>\n</style>\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "server/resource/plugin/web/view/view.vue.tpl",
    "content": "{{- $global := . }}\n{{- $templateID := printf \"%s_%s\" .Package .StructName }}\n{{- if .IsAdd }}\n// 请在搜索条件中增加如下代码\n{{- range .Fields}}\n    {{- if .FieldSearchType}}\n{{ GenerateSearchFormItem .}}\n    {{ end }}\n{{ end }}\n\n\n// 表格增加如下列代码\n\n{{- range .Fields}}\n    {{- if .Table}}\n       {{ GenerateTableColumn . }}\n    {{- end }}\n{{- end }}\n\n// 新增表单中增加如下代码\n{{- range .Fields}}\n   {{- if .Form}}\n     {{ GenerateFormItem . }}\n   {{- end }}\n{{- end }}\n\n// 查看抽屉中增加如下代码\n\n{{- range .Fields}}\n              {{- if .Desc }}\n    {{ GenerateDescriptionItem . }}\n              {{- end }}\n            {{- end }}\n\n// 字典增加如下代码\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\n\n// setOptions方法中增加如下调用\n\n{{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n{{- end }}\n\n// 基础formData结构（变量处和关闭表单处）增加如下字段\n{{- range .Fields}}\n          {{- if .Form}}\n            {{ GenerateDefaultFormValue . }}\n          {{- end }}\n        {{- end }}\n// 验证规则中增加如下字段\n\n{{- range .Fields }}\n        {{- if .Form }}\n            {{- if eq .Require true }}\n{{.FieldJson }} : [{\n    required: true,\n    message: '{{ .ErrorText }}',\n    trigger: ['input','blur'],\n},\n               {{- if eq .FieldType \"string\" }}\n{\n    whitespace: true,\n    message: '不能只输入空格',\n    trigger: ['input', 'blur'],\n}\n              {{- end }}\n],\n            {{- end }}\n        {{- end }}\n    {{- end }}\n\n\n\n{{- if .HasDataSource }}\n// 请引用\nget{{.StructName}}DataSource,\n\n//  获取数据源\nconst dataSource = ref({})\nconst getDataSourceFunc = async()=>{\n  const res = await get{{.StructName}}DataSource()\n  if (res.code === 0) {\n    dataSource.value = res.data || []\n  }\n}\ngetDataSourceFunc()\n{{- end }}\n\n{{- else }}\n\n{{- if not .OnlyTemplate}}\n<template>\n  <div>\n  {{- if not .IsTree }}\n    <div class=\"gva-search-box\">\n      <el-form ref=\"elSearchFormRef\" :inline=\"true\" :model=\"searchInfo\" class=\"demo-form-inline\" @keyup.enter=\"onSubmit\">\n      {{- if .GvaModel }}\n      <el-form-item label=\"创建日期\" prop=\"createdAtRange\">\n      <template #label>\n        <span>\n          创建日期\n          <el-tooltip content=\"搜索范围是开始日期（包含）至结束日期（不包含）\">\n            <el-icon><QuestionFilled /></el-icon>\n          </el-tooltip>\n        </span>\n      </template>\n         <el-date-picker\n                  v-model=\"searchInfo.createdAtRange\"\n                  class=\"!w-380px\"\n                  type=\"datetimerange\"\n                  range-separator=\"至\"\n                  start-placeholder=\"开始时间\"\n                  end-placeholder=\"结束时间\"\n                />\n       </el-form-item>\n      {{ end -}}\n           {{- range .Fields}} {{- if .FieldSearchType}} {{- if not .FieldSearchHide }}\n            {{ GenerateSearchFormItem .}}\n           {{ end }}{{ end }}{{ end }}\n        <template v-if=\"showAllQuery\">\n          <!-- 将需要控制显示状态的查询条件添加到此范围内 -->\n          {{- range .Fields}}  {{- if .FieldSearchType}} {{- if .FieldSearchHide }}\n          {{ GenerateSearchFormItem .}}\n          {{ end }}{{ end }}{{ end }}\n        </template>\n\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">查询</el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n          <el-button link type=\"primary\" icon=\"arrow-down\" @click=\"showAllQuery=true\" v-if=\"!showAllQuery\">展开</el-button>\n          <el-button link type=\"primary\" icon=\"arrow-up\" @click=\"showAllQuery=false\" v-else>收起</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  {{- end }}\n    <div class=\"gva-table-box\">\n        <div class=\"gva-btn-list\">\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.add\"{{ end }} type=\"primary\" icon=\"plus\" @click=\"openDialog()\">新增</el-button>\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.batchDelete\"{{ end }} icon=\"delete\" style=\"margin-left: 10px;\" :disabled=\"!multipleSelection.length\" @click=\"onDelete\">删除</el-button>\n            {{ if .HasExcel -}}\n            <ExportTemplate {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.exportTemplate\"{{ end }} template-id=\"{{$templateID}}\" />\n            <ExportExcel {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.exportExcel\"{{ end }} template-id=\"{{$templateID}}\" filterDeleted/>\n            <ImportExcel {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.importExcel\"{{ end }} template-id=\"{{$templateID}}\" @on-success=\"getTableData\" />\n            {{- end }}\n        </div>\n        <el-table\n        ref=\"multipleTable\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :data=\"tableData\"\n        row-key=\"{{.PrimaryField.FieldJson}}\"\n        @selection-change=\"handleSelectionChange\"\n        {{- if .NeedSort}}\n        @sort-change=\"sortChange\"\n        {{- end}}\n        >\n        <el-table-column type=\"selection\" width=\"55\" />\n        {{ if .GvaModel }}\n        <el-table-column sortable align=\"left\" label=\"日期\" prop=\"CreatedAt\" {{ if .IsTree -}} min-{{- end -}}width=\"180\">\n            <template #default=\"scope\">{{ \"{{ formatDate(scope.row.CreatedAt) }}\" }}</template>\n        </el-table-column>\n        {{ end }}\n        {{- range .Fields}}\n        {{- if .Table}}\n            {{ GenerateTableColumn . }}\n        {{- end }}\n        {{- end }}\n        <el-table-column align=\"left\" label=\"操作\" fixed=\"right\" min-width=\"240\">\n            <template #default=\"scope\">\n            {{- if .IsTree }}\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.add\"{{ end }} type=\"primary\" link class=\"table-button\" @click=\"openDialog(scope.row)\"><el-icon style=\"margin-right: 5px\"><InfoFilled /></el-icon>新增子节点</el-button>\n            {{- end }}\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.info\"{{ end }} type=\"primary\" link class=\"table-button\" @click=\"getDetails(scope.row)\"><el-icon style=\"margin-right: 5px\"><InfoFilled /></el-icon>查看</el-button>\n            <el-button {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.edit\"{{ end }} type=\"primary\" link icon=\"edit\" class=\"table-button\" @click=\"update{{.StructName}}Func(scope.row)\">编辑</el-button>\n            <el-button {{ if .IsTree }}v-if=\"!scope.row.children?.length\"{{ end }} {{ if $global.AutoCreateBtnAuth }}v-auth=\"btnAuth.delete\"{{ end }} type=\"primary\" link icon=\"delete\" @click=\"deleteRow(scope.row)\">删除</el-button>\n            </template>\n        </el-table-column>\n        </el-table>\n        <div class=\"gva-pagination\">\n            <el-pagination\n            layout=\"total, sizes, prev, pager, next, jumper\"\n            :current-page=\"page\"\n            :page-size=\"pageSize\"\n            :page-sizes=\"[10, 30, 50, 100]\"\n            :total=\"total\"\n            @current-change=\"handleCurrentChange\"\n            @size-change=\"handleSizeChange\"\n            />\n        </div>\n    </div>\n    <el-drawer destroy-on-close size=\"800\" v-model=\"dialogFormVisible\" :show-close=\"false\" :before-close=\"closeDialog\">\n       <template #header>\n              <div class=\"flex justify-between items-center\">\n                <span class=\"text-lg\">{{\"{{\"}}type==='create'?'新增':'编辑'{{\"}}\"}}</span>\n                <div>\n                  <el-button :loading=\"btnLoading\" type=\"primary\" @click=\"enterDialog\">确 定</el-button>\n                  <el-button @click=\"closeDialog\">取 消</el-button>\n                </div>\n              </div>\n            </template>\n\n          <el-form :model=\"formData\" label-position=\"top\" ref=\"elFormRef\" :rules=\"rule\" label-width=\"80px\">\n          {{- if .IsTree }}\n            <el-form-item label=\"父节点:\" prop=\"parentID\" >\n                <el-tree-select\n                    v-model=\"formData.parentID\"\n                    :data=\"[rootNode,...tableData]\"\n                    check-strictly\n                    :render-after-expand=\"false\"\n                    :props=\"defaultProps\"\n                    clearable\n                    style=\"width: 240px\"\n                    placeholder=\"根节点\"\n                />\n            </el-form-item>\n          {{- end }}\n        {{- range .Fields}}\n          {{- if .Form}}\n             {{ GenerateFormItem . }}\n          {{- end }}\n       {{- end }}\n          </el-form>\n    </el-drawer>\n\n    <el-drawer destroy-on-close size=\"800\" v-model=\"detailShow\" :show-close=\"true\" :before-close=\"closeDetailShow\" title=\"查看\">\n            <el-descriptions :column=\"1\" border>\n            {{- if .IsTree }}\n            <el-descriptions-item label=\"父节点\">\n                <el-tree-select\n                  v-model=\"detailForm.parentID\"\n                  :data=\"[rootNode,...tableData]\"\n                  check-strictly\n                  disabled\n                  :render-after-expand=\"false\"\n                  :props=\"defaultProps\"\n                  clearable\n                  style=\"width: 240px\"\n                  placeholder=\"根节点\"\n                />\n            </el-descriptions-item>\n            {{- end }}\n            {{- range .Fields}}\n              {{- if .Desc }}\n                 {{ GenerateDescriptionItem . }}\n              {{- end }}\n            {{- end }}\n            </el-descriptions>\n        </el-drawer>\n\n  </div>\n</template>\n\n<script setup>\nimport {\n  {{- if .HasDataSource }}\n    get{{.StructName}}DataSource,\n  {{- end }}\n  create{{.StructName}},\n  delete{{.StructName}},\n  delete{{.StructName}}ByIds,\n  update{{.StructName}},\n  find{{.StructName}},\n  get{{.StructName}}List\n} from '@/plugin/{{.Package}}/api/{{.PackageName}}'\n\n{{- if or .HasPic .HasFile}}\nimport { getUrl } from '@/utils/image'\n{{- end }}\n{{- if .HasPic }}\n// 图片选择组件\nimport SelectImage from '@/components/selectImage/selectImage.vue'\n{{- end }}\n\n{{- if .HasRichText }}\n// 富文本组件\nimport RichEdit from '@/components/richtext/rich-edit.vue'\nimport RichView from '@/components/richtext/rich-view.vue'\n{{- end }}\n\n{{- if .HasFile }}\n// 文件选择组件\nimport SelectFile from '@/components/selectFile/selectFile.vue'\n{{- end }}\n\n{{- if .HasArray}}\n// 数组控制组件\nimport ArrayCtrl from '@/components/arrayCtrl/arrayCtrl.vue'\n{{- end }}\n\n// 全量引入格式化工具 请按需保留\nimport { getDictFunc, formatDate, formatBoolean, filterDict ,filterDataSource, returnArrImg, onDownloadFile } from '@/utils/format'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport { ref, reactive } from 'vue'\n{{- if .AutoCreateBtnAuth }}\n// 引入按钮权限标识\nimport { useBtnAuth } from '@/utils/btnAuth'\n{{- end }}\n\n{{if .HasExcel -}}\n// 导出组件\nimport ExportExcel from '@/components/exportExcel/exportExcel.vue'\n// 导入组件\nimport ImportExcel from '@/components/exportExcel/importExcel.vue'\n// 导出模板组件\nimport ExportTemplate from '@/components/exportExcel/exportTemplate.vue'\n{{- end}}\n\n\ndefineOptions({\n    name: '{{.StructName}}'\n})\n\n{{- if .AutoCreateBtnAuth }}\n// 按钮权限实例化\n    const btnAuth = useBtnAuth()\n{{- end }}\n\n// 提交按钮loading\nconst btnLoading = ref(false)\n\n// 控制更多查询条件显示/隐藏状态\nconst showAllQuery = ref(false)\n\n// 自动化生成的字典（可能为空）以及字段\n    {{- range $index, $element := .DictTypes}}\nconst {{ $element }}Options = ref([])\n    {{- end }}\nconst formData = ref({\n        {{- if .IsTree }}\n            parentID:undefined,\n        {{- end }}\n        {{- range .Fields}}\n          {{- if .Form}}\n            {{ GenerateDefaultFormValue . }}\n          {{- end }}\n        {{- end }}\n        })\n\n{{- if .HasDataSource }}\n  const dataSource = ref([])\n  const getDataSourceFunc = async()=>{\n    const res = await get{{.StructName}}DataSource()\n    if (res.code === 0) {\n      dataSource.value = res.data\n    }\n  }\n  getDataSourceFunc()\n{{- end }}\n\n\n\n// 验证规则\nconst rule = reactive({\n    {{- range .Fields }}\n        {{- if .Form }}\n            {{- if eq .Require true }}\n               {{.FieldJson }} : [{\n                   required: true,\n                   message: '{{ .ErrorText }}',\n                   trigger: ['input','blur'],\n               },\n               {{- if eq .FieldType \"string\" }}\n               {\n                   whitespace: true,\n                   message: '不能只输入空格',\n                   trigger: ['input', 'blur'],\n              }\n              {{- end }}\n              ],\n            {{- end }}\n        {{- end }}\n    {{- end }}\n})\n\nconst elFormRef = ref()\nconst elSearchFormRef = ref()\n\n// =========== 表格控制部分 ===========\nconst page = ref(1)\nconst total = ref(0)\nconst pageSize = ref(10)\nconst tableData = ref([])\nconst searchInfo = ref({})\n\n{{- if .NeedSort}}\n// 排序\nconst sortChange = ({ prop, order }) => {\n  const sortMap = {\n    CreatedAt:\"created_at\",\n    ID:\"id\",\n    {{- range .Fields}}\n     {{- if .Table}}\n      {{- if and .Sort}}\n        {{- if not (eq .ColumnName \"\")}}\n            {{.FieldJson}}: '{{.ColumnName}}',\n        {{- end}}\n      {{- end}}\n     {{- end}}\n    {{- end}}\n  }\n\n  let sort = sortMap[prop]\n  if(!sort){\n   sort = prop.replace(/[A-Z]/g, match => `_${match.toLowerCase()}`)\n  }\n\n  searchInfo.value.sort = sort\n  searchInfo.value.order = order\n  getTableData()\n}\n{{- end}}\n\n{{- if not .IsTree }}\n// 重置\nconst onReset = () => {\n  searchInfo.value = {}\n  getTableData()\n}\n\n// 搜索\nconst onSubmit = () => {\n  elSearchFormRef.value?.validate(async(valid) => {\n    if (!valid) return\n    page.value = 1\n    {{- range .Fields}}{{- if eq .FieldType \"bool\" }}\n    if (searchInfo.value.{{.FieldJson}} === \"\"){\n        searchInfo.value.{{.FieldJson}}=null\n    }{{ end }}{{ end }}\n    getTableData()\n  })\n}\n\n// 分页\nconst handleSizeChange = (val) => {\n  pageSize.value = val\n  getTableData()\n}\n\n// 修改页面容量\nconst handleCurrentChange = (val) => {\n  page.value = val\n  getTableData()\n}\n\n// 查询\nconst getTableData = async() => {\n  const table = await get{{.StructName}}List({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })\n  if (table.code === 0) {\n    tableData.value = table.data.list\n    total.value = table.data.total\n    page.value = table.data.page\n    pageSize.value = table.data.pageSize\n  }\n}\n{{- else }}\n// 树选择器配置\nconst defaultProps = {\n  children: \"children\",\n  label: \"{{ .TreeJson }}\",\n  value: \"{{ .PrimaryField.FieldJson }}\"\n}\n\nconst rootNode = {\n  {{ .PrimaryField.FieldJson }}: 0,\n  {{ .TreeJson }}: '根节点',\n  children: []\n}\n\n// 查询\nconst getTableData = async() => {\n  const table = await get{{.StructName}}List()\n  if (table.code === 0) {\n    tableData.value = table.data || []\n  }\n}\n{{- end }}\n\ngetTableData()\n\n// ============== 表格控制部分结束 ===============\n\n// 获取需要的字典 可能为空 按需保留\nconst setOptions = async () =>{\n{{- range $index, $element := .DictTypes }}\n    {{ $element }}Options.value = await getDictFunc('{{$element}}')\n{{- end }}\n}\n\n// 获取需要的字典 可能为空 按需保留\nsetOptions()\n\n\n// 多选数据\nconst multipleSelection = ref([])\n// 多选\nconst handleSelectionChange = (val) => {\n    multipleSelection.value = val\n}\n\n// 删除行\nconst deleteRow = (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n    }).then(() => {\n            delete{{.StructName}}Func(row)\n        })\n    }\n\n// 多选删除\nconst onDelete = async() => {\n  ElMessageBox.confirm('确定要删除吗?', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(async() => {\n      const {{.PrimaryField.FieldJson}}s = []\n      if (multipleSelection.value.length === 0) {\n        ElMessage({\n          type: 'warning',\n          message: '请选择要删除的数据'\n        })\n        return\n      }\n      multipleSelection.value &&\n        multipleSelection.value.map(item => {\n          {{.PrimaryField.FieldJson}}s.push(item.{{.PrimaryField.FieldJson}})\n        })\n      const res = await delete{{.StructName}}ByIds({ {{.PrimaryField.FieldJson}}s })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === {{.PrimaryField.FieldJson}}s.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n      })\n    }\n\n// 行为控制标记（弹窗内部需要增还是改）\nconst type = ref('')\n\n// 更新行\nconst update{{.StructName}}Func = async(row) => {\n    const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })\n    type.value = 'update'\n    if (res.code === 0) {\n        formData.value = res.data\n        dialogFormVisible.value = true\n    }\n}\n\n\n// 删除行\nconst delete{{.StructName}}Func = async (row) => {\n    const res = await delete{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })\n    if (res.code === 0) {\n        ElMessage({\n                type: 'success',\n                message: '删除成功'\n            })\n            if (tableData.value.length === 1 && page.value > 1) {\n            page.value--\n        }\n        getTableData()\n    }\n}\n\n// 弹窗控制标记\nconst dialogFormVisible = ref(false)\n\n// 打开弹窗\nconst openDialog = ({{- if .IsTree -}}row{{- end -}}) => {\n    type.value = 'create'\n    {{- if .IsTree }}\n    formData.value.parentID = row ? row.{{.PrimaryField.FieldJson}} : undefined\n    {{- end }}\n    dialogFormVisible.value = true\n}\n\n// 关闭弹窗\nconst closeDialog = () => {\n    dialogFormVisible.value = false\n    formData.value = {\n    {{- range .Fields}}\n      {{- if .Form}}\n        {{ GenerateDefaultFormValue . }}\n      {{- end }}\n    {{- end }}\n        }\n}\n// 弹窗确定\nconst enterDialog = async () => {\n     btnLoading.value = true\n     elFormRef.value?.validate( async (valid) => {\n             if (!valid) return btnLoading.value = false\n              let res\n              switch (type.value) {\n                case 'create':\n                  res = await create{{.StructName}}(formData.value)\n                  break\n                case 'update':\n                  res = await update{{.StructName}}(formData.value)\n                  break\n                default:\n                  res = await create{{.StructName}}(formData.value)\n                  break\n              }\n              btnLoading.value = false\n              if (res.code === 0) {\n                ElMessage({\n                  type: 'success',\n                  message: '创建/更改成功'\n                })\n                closeDialog()\n                getTableData()\n              }\n      })\n}\n\nconst detailForm = ref({})\n\n// 查看详情控制标记\nconst detailShow = ref(false)\n\n\n// 打开详情弹窗\nconst openDetailShow = () => {\n  detailShow.value = true\n}\n\n\n// 打开详情\nconst getDetails = async (row) => {\n  // 打开弹窗\n  const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })\n  if (res.code === 0) {\n    detailForm.value = res.data\n    openDetailShow()\n  }\n}\n\n\n// 关闭详情弹窗\nconst closeDetailShow = () => {\n  detailShow.value = false\n  detailForm.value = {}\n}\n\n\n</script>\n\n<style>\n{{if .HasFile }}\n.file-list{\n  display: flex;\n  flex-wrap: wrap;\n  gap: 4px;\n}\n\n.fileBtn{\n  margin-bottom: 10px;\n}\n\n.fileBtn:last-child{\n  margin-bottom: 0;\n}\n{{end}}\n</style>\n{{- else}}\n<template>\n<div>form</div>\n</template>\n<script setup>\ndefineOptions({\n  name: '{{.StructName}}'\n})\n</script>\n<style>\n</style>\n{{- end }}\n\n{{- end }}\n"
  },
  {
    "path": "server/router/enter.go",
    "content": "package router\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/router/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/router/system\"\n)\n\nvar RouterGroupApp = new(RouterGroup)\n\ntype RouterGroup struct {\n\tSystem  system.RouterGroup\n\tExample example.RouterGroup\n}\n"
  },
  {
    "path": "server/router/example/enter.go",
    "content": "package example\n\nimport (\n\tapi \"github.com/flipped-aurora/gin-vue-admin/server/api/v1\"\n)\n\ntype RouterGroup struct {\n\tCustomerRouter\n\tFileUploadAndDownloadRouter\n\tAttachmentCategoryRouter\n}\n\nvar (\n\texaCustomerApi              = api.ApiGroupApp.ExampleApiGroup.CustomerApi\n\texaFileUploadAndDownloadApi = api.ApiGroupApp.ExampleApiGroup.FileUploadAndDownloadApi\n\tattachmentCategoryApi       = api.ApiGroupApp.ExampleApiGroup.AttachmentCategoryApi\n)\n"
  },
  {
    "path": "server/router/example/exa_attachment_category.go",
    "content": "package example\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype AttachmentCategoryRouter struct{}\n\nfunc (r *AttachmentCategoryRouter) InitAttachmentCategoryRouterRouter(Router *gin.RouterGroup) {\n\trouter := Router.Group(\"attachmentCategory\")\n\t{\n\t\trouter.GET(\"getCategoryList\", attachmentCategoryApi.GetCategoryList) // 分类列表\n\t\trouter.POST(\"addCategory\", attachmentCategoryApi.AddCategory)        // 添加/编辑分类\n\t\trouter.POST(\"deleteCategory\", attachmentCategoryApi.DeleteCategory)  // 删除分类\n\t}\n}\n"
  },
  {
    "path": "server/router/example/exa_customer.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype CustomerRouter struct{}\n\nfunc (e *CustomerRouter) InitCustomerRouter(Router *gin.RouterGroup) {\n\tcustomerRouter := Router.Group(\"customer\").Use(middleware.OperationRecord())\n\tcustomerRouterWithoutRecord := Router.Group(\"customer\")\n\t{\n\t\tcustomerRouter.POST(\"customer\", exaCustomerApi.CreateExaCustomer)   // 创建客户\n\t\tcustomerRouter.PUT(\"customer\", exaCustomerApi.UpdateExaCustomer)    // 更新客户\n\t\tcustomerRouter.DELETE(\"customer\", exaCustomerApi.DeleteExaCustomer) // 删除客户\n\t}\n\t{\n\t\tcustomerRouterWithoutRecord.GET(\"customer\", exaCustomerApi.GetExaCustomer)         // 获取单一客户信息\n\t\tcustomerRouterWithoutRecord.GET(\"customerList\", exaCustomerApi.GetExaCustomerList) // 获取客户列表\n\t}\n}\n"
  },
  {
    "path": "server/router/example/exa_file_upload_and_download.go",
    "content": "package example\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype FileUploadAndDownloadRouter struct{}\n\nfunc (e *FileUploadAndDownloadRouter) InitFileUploadAndDownloadRouter(Router *gin.RouterGroup) {\n\tfileUploadAndDownloadRouter := Router.Group(\"fileUploadAndDownload\")\n\t{\n\t\tfileUploadAndDownloadRouter.POST(\"upload\", exaFileUploadAndDownloadApi.UploadFile)                                 // 上传文件\n\t\tfileUploadAndDownloadRouter.POST(\"getFileList\", exaFileUploadAndDownloadApi.GetFileList)                           // 获取上传文件列表\n\t\tfileUploadAndDownloadRouter.POST(\"deleteFile\", exaFileUploadAndDownloadApi.DeleteFile)                             // 删除指定文件\n\t\tfileUploadAndDownloadRouter.POST(\"editFileName\", exaFileUploadAndDownloadApi.EditFileName)                         // 编辑文件名或者备注\n\t\tfileUploadAndDownloadRouter.POST(\"breakpointContinue\", exaFileUploadAndDownloadApi.BreakpointContinue)             // 断点续传\n\t\tfileUploadAndDownloadRouter.GET(\"findFile\", exaFileUploadAndDownloadApi.FindFile)                                  // 查询当前文件成功的切片\n\t\tfileUploadAndDownloadRouter.POST(\"breakpointContinueFinish\", exaFileUploadAndDownloadApi.BreakpointContinueFinish) // 切片传输完成\n\t\tfileUploadAndDownloadRouter.POST(\"removeChunk\", exaFileUploadAndDownloadApi.RemoveChunk)                           // 删除切片\n\t\tfileUploadAndDownloadRouter.POST(\"importURL\", exaFileUploadAndDownloadApi.ImportURL)                               // 导入URL\n\t}\n}\n"
  },
  {
    "path": "server/router/system/enter.go",
    "content": "package system\n\nimport api \"github.com/flipped-aurora/gin-vue-admin/server/api/v1\"\n\ntype RouterGroup struct {\n\tApiRouter\n\tJwtRouter\n\tSysRouter\n\tBaseRouter\n\tInitRouter\n\tMenuRouter\n\tUserRouter\n\tCasbinRouter\n\tAutoCodeRouter\n\tAuthorityRouter\n\tDictionaryRouter\n\tOperationRecordRouter\n\tDictionaryDetailRouter\n\tAuthorityBtnRouter\n\tSysExportTemplateRouter\n\tSysParamsRouter\n\tSysVersionRouter\n\tSysErrorRouter\n\tLoginLogRouter\n\tApiTokenRouter\n\tSkillsRouter\n}\n\nvar (\n\tdbApi               = api.ApiGroupApp.SystemApiGroup.DBApi\n\tjwtApi              = api.ApiGroupApp.SystemApiGroup.JwtApi\n\tbaseApi             = api.ApiGroupApp.SystemApiGroup.BaseApi\n\tcasbinApi           = api.ApiGroupApp.SystemApiGroup.CasbinApi\n\tsystemApi           = api.ApiGroupApp.SystemApiGroup.SystemApi\n\tsysParamsApi        = api.ApiGroupApp.SystemApiGroup.SysParamsApi\n\tautoCodeApi         = api.ApiGroupApp.SystemApiGroup.AutoCodeApi\n\tauthorityApi        = api.ApiGroupApp.SystemApiGroup.AuthorityApi\n\tapiRouterApi        = api.ApiGroupApp.SystemApiGroup.SystemApiApi\n\tdictionaryApi       = api.ApiGroupApp.SystemApiGroup.DictionaryApi\n\tauthorityBtnApi     = api.ApiGroupApp.SystemApiGroup.AuthorityBtnApi\n\tauthorityMenuApi    = api.ApiGroupApp.SystemApiGroup.AuthorityMenuApi\n\tautoCodePluginApi   = api.ApiGroupApp.SystemApiGroup.AutoCodePluginApi\n\tautocodeHistoryApi  = api.ApiGroupApp.SystemApiGroup.AutoCodeHistoryApi\n\toperationRecordApi  = api.ApiGroupApp.SystemApiGroup.OperationRecordApi\n\tautoCodePackageApi  = api.ApiGroupApp.SystemApiGroup.AutoCodePackageApi\n\tdictionaryDetailApi = api.ApiGroupApp.SystemApiGroup.DictionaryDetailApi\n\tautoCodeTemplateApi = api.ApiGroupApp.SystemApiGroup.AutoCodeTemplateApi\n\texportTemplateApi   = api.ApiGroupApp.SystemApiGroup.SysExportTemplateApi\n\tsysVersionApi       = api.ApiGroupApp.SystemApiGroup.SysVersionApi\n\tsysErrorApi         = api.ApiGroupApp.SystemApiGroup.SysErrorApi\n\tskillsApi           = api.ApiGroupApp.SystemApiGroup.SkillsApi\n)\n"
  },
  {
    "path": "server/router/system/sys_api.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype ApiRouter struct{}\n\nfunc (s *ApiRouter) InitApiRouter(Router *gin.RouterGroup, RouterPub *gin.RouterGroup) {\n\tapiRouter := Router.Group(\"api\").Use(middleware.OperationRecord())\n\tapiRouterWithoutRecord := Router.Group(\"api\")\n\n\tapiPublicRouterWithoutRecord := RouterPub.Group(\"api\")\n\t{\n\t\tapiRouter.GET(\"getApiGroups\", apiRouterApi.GetApiGroups)          // 获取路由组\n\t\tapiRouter.GET(\"syncApi\", apiRouterApi.SyncApi)                    // 同步Api\n\t\tapiRouter.POST(\"ignoreApi\", apiRouterApi.IgnoreApi)               // 忽略Api\n\t\tapiRouter.POST(\"enterSyncApi\", apiRouterApi.EnterSyncApi)         // 确认同步Api\n\t\tapiRouter.POST(\"createApi\", apiRouterApi.CreateApi)               // 创建Api\n\t\tapiRouter.POST(\"deleteApi\", apiRouterApi.DeleteApi)               // 删除Api\n\t\tapiRouter.POST(\"getApiById\", apiRouterApi.GetApiById)             // 获取单条Api消息\n\t\tapiRouter.POST(\"updateApi\", apiRouterApi.UpdateApi)               // 更新api\n\t\tapiRouter.DELETE(\"deleteApisByIds\", apiRouterApi.DeleteApisByIds) // 删除选中api\n\t\tapiRouter.POST(\"setApiRoles\", apiRouterApi.SetApiRoles)          // 全量覆盖API关联角色\n\t}\n\t{\n\t\tapiRouterWithoutRecord.POST(\"getAllApis\", apiRouterApi.GetAllApis) // 获取所有api\n\t\tapiRouterWithoutRecord.POST(\"getApiList\", apiRouterApi.GetApiList) // 获取Api列表\n\t\tapiRouterWithoutRecord.GET(\"getApiRoles\", apiRouterApi.GetApiRoles) // 获取API关联角色ID列表\n\t}\n\t{\n\t\tapiPublicRouterWithoutRecord.GET(\"freshCasbin\", apiRouterApi.FreshCasbin) // 刷新casbin权限\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_api_token.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/api/v1\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype ApiTokenRouter struct{}\n\nfunc (s *ApiTokenRouter) InitApiTokenRouter(Router *gin.RouterGroup) {\n\tapiTokenRouter := Router.Group(\"sysApiToken\").Use(middleware.OperationRecord())\n\tapiTokenApi := v1.ApiGroupApp.SystemApiGroup.ApiTokenApi\n\t{\n\t\tapiTokenRouter.POST(\"createApiToken\", apiTokenApi.CreateApiToken)   // 签发Token\n\t\tapiTokenRouter.POST(\"getApiTokenList\", apiTokenApi.GetApiTokenList) // 获取列表\n\t\tapiTokenRouter.POST(\"deleteApiToken\", apiTokenApi.DeleteApiToken)   // 作废Token\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_authority.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype AuthorityRouter struct{}\n\nfunc (s *AuthorityRouter) InitAuthorityRouter(Router *gin.RouterGroup) {\n\tauthorityRouter := Router.Group(\"authority\").Use(middleware.OperationRecord())\n\tauthorityRouterWithoutRecord := Router.Group(\"authority\")\n\t{\n\t\tauthorityRouter.POST(\"createAuthority\", authorityApi.CreateAuthority)   // 创建角色\n\t\tauthorityRouter.POST(\"deleteAuthority\", authorityApi.DeleteAuthority)   // 删除角色\n\t\tauthorityRouter.PUT(\"updateAuthority\", authorityApi.UpdateAuthority)    // 更新角色\n\t\tauthorityRouter.POST(\"copyAuthority\", authorityApi.CopyAuthority)       // 拷贝角色\n\t\tauthorityRouter.POST(\"setDataAuthority\", authorityApi.SetDataAuthority) // 设置角色资源权限\n\t\tauthorityRouter.POST(\"setRoleUsers\", authorityApi.SetRoleUsers)         // 全量覆盖角色关联用户\n\t}\n\t{\n\t\tauthorityRouterWithoutRecord.POST(\"getAuthorityList\", authorityApi.GetAuthorityList)     // 获取角色列表\n\t\tauthorityRouterWithoutRecord.GET(\"getUsersByAuthority\", authorityApi.GetUsersByAuthority) // 获取角色关联用户ID列表\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_authority_btn.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype AuthorityBtnRouter struct{}\n\nvar AuthorityBtnRouterApp = new(AuthorityBtnRouter)\n\nfunc (s *AuthorityBtnRouter) InitAuthorityBtnRouterRouter(Router *gin.RouterGroup) {\n\t// authorityRouter := Router.Group(\"authorityBtn\").Use(middleware.OperationRecord())\n\tauthorityRouterWithoutRecord := Router.Group(\"authorityBtn\")\n\t{\n\t\tauthorityRouterWithoutRecord.POST(\"getAuthorityBtn\", authorityBtnApi.GetAuthorityBtn)\n\t\tauthorityRouterWithoutRecord.POST(\"setAuthorityBtn\", authorityBtnApi.SetAuthorityBtn)\n\t\tauthorityRouterWithoutRecord.POST(\"canRemoveAuthorityBtn\", authorityBtnApi.CanRemoveAuthorityBtn)\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_auto_code.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype AutoCodeRouter struct{}\n\nfunc (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPublic *gin.RouterGroup) {\n\tautoCodeRouter := Router.Group(\"autoCode\")\n\tpublicAutoCodeRouter := RouterPublic.Group(\"autoCode\")\n\t{\n\t\tautoCodeRouter.GET(\"getDB\", autoCodeApi.GetDB)         // 获取数据库\n\t\tautoCodeRouter.GET(\"getTables\", autoCodeApi.GetTables) // 获取对应数据库的表\n\t\tautoCodeRouter.GET(\"getColumn\", autoCodeApi.GetColumn) // 获取指定表所有字段信息\n\t}\n\t{\n\t\tautoCodeRouter.POST(\"preview\", autoCodeTemplateApi.Preview)   // 获取自动创建代码预览\n\t\tautoCodeRouter.POST(\"createTemp\", autoCodeTemplateApi.Create) // 创建自动化代码\n\t\tautoCodeRouter.POST(\"addFunc\", autoCodeTemplateApi.AddFunc)   // 为代码插入方法\n\t}\n\t{\n\t\tautoCodeRouter.POST(\"mcp\", autoCodeTemplateApi.MCP)         // 自动创建Mcp Tool模板\n\t\tautoCodeRouter.POST(\"mcpList\", autoCodeTemplateApi.MCPList) // 获取MCP ToolList\n\t\tautoCodeRouter.POST(\"mcpTest\", autoCodeTemplateApi.MCPTest) // MCP 工具测试\n\t}\n\t{\n\t\tautoCodeRouter.POST(\"getPackage\", autoCodePackageApi.All)       // 获取package包\n\t\tautoCodeRouter.POST(\"delPackage\", autoCodePackageApi.Delete)    // 删除package包\n\t\tautoCodeRouter.POST(\"createPackage\", autoCodePackageApi.Create) // 创建package包\n\t}\n\t{\n\t\tautoCodeRouter.GET(\"getTemplates\", autoCodePackageApi.Templates) // 创建package包\n\t}\n\t{\n\t\tautoCodeRouter.POST(\"pubPlug\", autoCodePluginApi.Packaged)      // 打包插件\n\t\tautoCodeRouter.POST(\"installPlugin\", autoCodePluginApi.Install) // 自动安装插件\n\t\tautoCodeRouter.POST(\"removePlugin\", autoCodePluginApi.Remove)   // 自动删除插件\n\t\tautoCodeRouter.GET(\"getPluginList\", autoCodePluginApi.GetPluginList) // 获取插件列表\n\t}\n\t{\n\t\tpublicAutoCodeRouter.POST(\"llmAuto\", autoCodeApi.LLMAuto)\n\t\tpublicAutoCodeRouter.POST(\"initMenu\", autoCodePluginApi.InitMenu)             // 同步插件菜单\n\t\tpublicAutoCodeRouter.POST(\"initAPI\", autoCodePluginApi.InitAPI)               // 同步插件API\n\t\tpublicAutoCodeRouter.POST(\"initDictionary\", autoCodePluginApi.InitDictionary) // 同步插件字典\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_auto_code_history.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype AutoCodeHistoryRouter struct{}\n\nfunc (s *AutoCodeRouter) InitAutoCodeHistoryRouter(Router *gin.RouterGroup) {\n\tautoCodeHistoryRouter := Router.Group(\"autoCode\")\n\t{\n\t\tautoCodeHistoryRouter.POST(\"getMeta\", autocodeHistoryApi.First)         // 根据id获取meta信息\n\t\tautoCodeHistoryRouter.POST(\"rollback\", autocodeHistoryApi.RollBack)     // 回滚\n\t\tautoCodeHistoryRouter.POST(\"delSysHistory\", autocodeHistoryApi.Delete)  // 删除回滚记录\n\t\tautoCodeHistoryRouter.POST(\"getSysHistory\", autocodeHistoryApi.GetList) // 获取回滚记录分页\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_base.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype BaseRouter struct{}\n\nfunc (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) (R gin.IRoutes) {\n\tbaseRouter := Router.Group(\"base\")\n\t{\n\t\tbaseRouter.POST(\"login\", baseApi.Login)\n\t\tbaseRouter.POST(\"captcha\", baseApi.Captcha)\n\t}\n\treturn baseRouter\n}\n"
  },
  {
    "path": "server/router/system/sys_casbin.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype CasbinRouter struct{}\n\nfunc (s *CasbinRouter) InitCasbinRouter(Router *gin.RouterGroup) {\n\tcasbinRouter := Router.Group(\"casbin\").Use(middleware.OperationRecord())\n\tcasbinRouterWithoutRecord := Router.Group(\"casbin\")\n\t{\n\t\tcasbinRouter.POST(\"updateCasbin\", casbinApi.UpdateCasbin)\n\t}\n\t{\n\t\tcasbinRouterWithoutRecord.POST(\"getPolicyPathByAuthorityId\", casbinApi.GetPolicyPathByAuthorityId)\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_dictionary.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype DictionaryRouter struct{}\n\nfunc (s *DictionaryRouter) InitSysDictionaryRouter(Router *gin.RouterGroup) {\n\tsysDictionaryRouter := Router.Group(\"sysDictionary\").Use(middleware.OperationRecord())\n\tsysDictionaryRouterWithoutRecord := Router.Group(\"sysDictionary\")\n\t{\n\t\tsysDictionaryRouter.POST(\"createSysDictionary\", dictionaryApi.CreateSysDictionary)   // 新建SysDictionary\n\t\tsysDictionaryRouter.DELETE(\"deleteSysDictionary\", dictionaryApi.DeleteSysDictionary) // 删除SysDictionary\n\t\tsysDictionaryRouter.PUT(\"updateSysDictionary\", dictionaryApi.UpdateSysDictionary)    // 更新SysDictionary\n\t\tsysDictionaryRouter.POST(\"importSysDictionary\", dictionaryApi.ImportSysDictionary)   // 导入SysDictionary\n\t\tsysDictionaryRouter.GET(\"exportSysDictionary\", dictionaryApi.ExportSysDictionary)    // 导出SysDictionary\n\t}\n\t{\n\t\tsysDictionaryRouterWithoutRecord.GET(\"findSysDictionary\", dictionaryApi.FindSysDictionary)       // 根据ID获取SysDictionary\n\t\tsysDictionaryRouterWithoutRecord.GET(\"getSysDictionaryList\", dictionaryApi.GetSysDictionaryList) // 获取SysDictionary列表\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_dictionary_detail.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype DictionaryDetailRouter struct{}\n\nfunc (s *DictionaryDetailRouter) InitSysDictionaryDetailRouter(Router *gin.RouterGroup) {\n\tdictionaryDetailRouter := Router.Group(\"sysDictionaryDetail\").Use(middleware.OperationRecord())\n\tdictionaryDetailRouterWithoutRecord := Router.Group(\"sysDictionaryDetail\")\n\t{\n\t\tdictionaryDetailRouter.POST(\"createSysDictionaryDetail\", dictionaryDetailApi.CreateSysDictionaryDetail)   // 新建SysDictionaryDetail\n\t\tdictionaryDetailRouter.DELETE(\"deleteSysDictionaryDetail\", dictionaryDetailApi.DeleteSysDictionaryDetail) // 删除SysDictionaryDetail\n\t\tdictionaryDetailRouter.PUT(\"updateSysDictionaryDetail\", dictionaryDetailApi.UpdateSysDictionaryDetail)    // 更新SysDictionaryDetail\n\t}\n\t{\n\t\tdictionaryDetailRouterWithoutRecord.GET(\"findSysDictionaryDetail\", dictionaryDetailApi.FindSysDictionaryDetail)           // 根据ID获取SysDictionaryDetail\n\t\tdictionaryDetailRouterWithoutRecord.GET(\"getSysDictionaryDetailList\", dictionaryDetailApi.GetSysDictionaryDetailList)     // 获取SysDictionaryDetail列表\n\t\tdictionaryDetailRouterWithoutRecord.GET(\"getDictionaryTreeList\", dictionaryDetailApi.GetDictionaryTreeList)               // 获取字典详情树形结构\n\t\tdictionaryDetailRouterWithoutRecord.GET(\"getDictionaryTreeListByType\", dictionaryDetailApi.GetDictionaryTreeListByType)   // 根据字典类型获取字典详情树形结构\n\t\tdictionaryDetailRouterWithoutRecord.GET(\"getDictionaryDetailsByParent\", dictionaryDetailApi.GetDictionaryDetailsByParent) // 根据父级ID获取字典详情\n\t\tdictionaryDetailRouterWithoutRecord.GET(\"getDictionaryPath\", dictionaryDetailApi.GetDictionaryPath)                       // 获取字典详情的完整路径\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_error.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype SysErrorRouter struct{}\n\n// InitSysErrorRouter 初始化 错误日志 路由信息\nfunc (s *SysErrorRouter) InitSysErrorRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {\n    sysErrorRouter := Router.Group(\"sysError\").Use(middleware.OperationRecord())\n    sysErrorRouterWithoutRecord := Router.Group(\"sysError\")\n    sysErrorRouterWithoutAuth := PublicRouter.Group(\"sysError\")\n    {\n        sysErrorRouter.DELETE(\"deleteSysError\", sysErrorApi.DeleteSysError)           // 删除错误日志\n        sysErrorRouter.DELETE(\"deleteSysErrorByIds\", sysErrorApi.DeleteSysErrorByIds) // 批量删除错误日志\n        sysErrorRouter.PUT(\"updateSysError\", sysErrorApi.UpdateSysError)              // 更新错误日志\n        sysErrorRouter.GET(\"getSysErrorSolution\", sysErrorApi.GetSysErrorSolution)    // 触发错误日志处理\n    }\n    {\n        sysErrorRouterWithoutRecord.GET(\"findSysError\", sysErrorApi.FindSysError)       // 根据ID获取错误日志\n        sysErrorRouterWithoutRecord.GET(\"getSysErrorList\", sysErrorApi.GetSysErrorList) // 获取错误日志列表\n    }\n    {\n        sysErrorRouterWithoutAuth.POST(\"createSysError\", sysErrorApi.CreateSysError) // 新建错误日志\n    }\n}\n"
  },
  {
    "path": "server/router/system/sys_export_template.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype SysExportTemplateRouter struct {\n}\n\n// InitSysExportTemplateRouter 初始化 导出模板 路由信息\nfunc (s *SysExportTemplateRouter) InitSysExportTemplateRouter(Router *gin.RouterGroup, pubRouter *gin.RouterGroup) {\n\tsysExportTemplateRouter := Router.Group(\"sysExportTemplate\").Use(middleware.OperationRecord())\n\tsysExportTemplateRouterWithoutRecord := Router.Group(\"sysExportTemplate\")\n\tsysExportTemplateRouterWithoutAuth := pubRouter.Group(\"sysExportTemplate\")\n\n\t{\n\t\tsysExportTemplateRouter.POST(\"createSysExportTemplate\", exportTemplateApi.CreateSysExportTemplate)             // 新建导出模板\n\t\tsysExportTemplateRouter.DELETE(\"deleteSysExportTemplate\", exportTemplateApi.DeleteSysExportTemplate)           // 删除导出模板\n\t\tsysExportTemplateRouter.DELETE(\"deleteSysExportTemplateByIds\", exportTemplateApi.DeleteSysExportTemplateByIds) // 批量删除导出模板\n\t\tsysExportTemplateRouter.PUT(\"updateSysExportTemplate\", exportTemplateApi.UpdateSysExportTemplate)              // 更新导出模板\n\t\tsysExportTemplateRouter.POST(\"importExcel\", exportTemplateApi.ImportExcel)                                     // 导入excel模板数据\n\t}\n\t{\n\t\tsysExportTemplateRouterWithoutRecord.GET(\"findSysExportTemplate\", exportTemplateApi.FindSysExportTemplate)       // 根据ID获取导出模板\n\t\tsysExportTemplateRouterWithoutRecord.GET(\"getSysExportTemplateList\", exportTemplateApi.GetSysExportTemplateList) // 获取导出模板列表\n\t\tsysExportTemplateRouterWithoutRecord.GET(\"exportExcel\", exportTemplateApi.ExportExcel)                           // 获取导出token\n\t\tsysExportTemplateRouterWithoutRecord.GET(\"exportTemplate\", exportTemplateApi.ExportTemplate)                     // 导出表格模板\n        sysExportTemplateRouterWithoutRecord.GET(\"previewSQL\", exportTemplateApi.PreviewSQL)                         // 预览SQL\n\t}\n\t{\n\t\tsysExportTemplateRouterWithoutAuth.GET(\"exportExcelByToken\", exportTemplateApi.ExportExcelByToken)       // 通过token导出表格\n\t\tsysExportTemplateRouterWithoutAuth.GET(\"exportTemplateByToken\", exportTemplateApi.ExportTemplateByToken) // 通过token导出模板\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_initdb.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype InitRouter struct{}\n\nfunc (s *InitRouter) InitInitRouter(Router *gin.RouterGroup) {\n\tinitRouter := Router.Group(\"init\")\n\t{\n\t\tinitRouter.POST(\"initdb\", dbApi.InitDB)   // 初始化数据库\n\t\tinitRouter.POST(\"checkdb\", dbApi.CheckDB) // 检测是否需要初始化数据库\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_jwt.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype JwtRouter struct{}\n\nfunc (s *JwtRouter) InitJwtRouter(Router *gin.RouterGroup) {\n\tjwtRouter := Router.Group(\"jwt\")\n\t{\n\t\tjwtRouter.POST(\"jsonInBlacklist\", jwtApi.JsonInBlacklist) // jwt加入黑名单\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_login_log.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/api/v1\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype LoginLogRouter struct{}\n\nfunc (s *LoginLogRouter) InitLoginLogRouter(Router *gin.RouterGroup) {\n\tloginLogRouter := Router.Group(\"sysLoginLog\").Use(middleware.OperationRecord())\n\tloginLogRouterWithoutRecord := Router.Group(\"sysLoginLog\")\n\tsysLoginLogApi := v1.ApiGroupApp.SystemApiGroup.LoginLogApi\n\t{\n\t\tloginLogRouter.DELETE(\"deleteLoginLog\", sysLoginLogApi.DeleteLoginLog)           // 删除登录日志\n\t\tloginLogRouter.DELETE(\"deleteLoginLogByIds\", sysLoginLogApi.DeleteLoginLogByIds) // 批量删除登录日志\n\t}\n\t{\n\t\tloginLogRouterWithoutRecord.GET(\"findLoginLog\", sysLoginLogApi.FindLoginLog)       // 根据ID获取登录日志(详情)\n\t\tloginLogRouterWithoutRecord.GET(\"getLoginLogList\", sysLoginLogApi.GetLoginLogList) // 获取登录日志列表\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_menu.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype MenuRouter struct{}\n\nfunc (s *MenuRouter) InitMenuRouter(Router *gin.RouterGroup) (R gin.IRoutes) {\n\tmenuRouter := Router.Group(\"menu\").Use(middleware.OperationRecord())\n\tmenuRouterWithoutRecord := Router.Group(\"menu\")\n\t{\n\t\tmenuRouter.POST(\"addBaseMenu\", authorityMenuApi.AddBaseMenu)           // 新增菜单\n\t\tmenuRouter.POST(\"addMenuAuthority\", authorityMenuApi.AddMenuAuthority) //\t增加menu和角色关联关系\n\t\tmenuRouter.POST(\"deleteBaseMenu\", authorityMenuApi.DeleteBaseMenu)     // 删除菜单\n\t\tmenuRouter.POST(\"updateBaseMenu\", authorityMenuApi.UpdateBaseMenu)     // 更新菜单\n\t\tmenuRouter.POST(\"setMenuRoles\", authorityMenuApi.SetMenuRoles)         // 全量覆盖菜单关联角色\n\t}\n\t{\n\t\tmenuRouterWithoutRecord.POST(\"getMenu\", authorityMenuApi.GetMenu)                   // 获取菜单树\n\t\tmenuRouterWithoutRecord.POST(\"getMenuList\", authorityMenuApi.GetMenuList)           // 分页获取基础menu列表\n\t\tmenuRouterWithoutRecord.POST(\"getBaseMenuTree\", authorityMenuApi.GetBaseMenuTree)   // 获取用户动态路由\n\t\tmenuRouterWithoutRecord.POST(\"getMenuAuthority\", authorityMenuApi.GetMenuAuthority) // 获取指定角色menu\n\t\tmenuRouterWithoutRecord.POST(\"getBaseMenuById\", authorityMenuApi.GetBaseMenuById)   // 根据id获取菜单\n\t\tmenuRouterWithoutRecord.GET(\"getMenuRoles\", authorityMenuApi.GetMenuRoles)          // 获取菜单关联角色ID列表\n\t}\n\treturn menuRouter\n}\n"
  },
  {
    "path": "server/router/system/sys_operation_record.go",
    "content": "package system\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype OperationRecordRouter struct{}\n\nfunc (s *OperationRecordRouter) InitSysOperationRecordRouter(Router *gin.RouterGroup) {\n\toperationRecordRouter := Router.Group(\"sysOperationRecord\")\n\t{\n\t\toperationRecordRouter.DELETE(\"deleteSysOperationRecord\", operationRecordApi.DeleteSysOperationRecord)           // 删除SysOperationRecord\n\t\toperationRecordRouter.DELETE(\"deleteSysOperationRecordByIds\", operationRecordApi.DeleteSysOperationRecordByIds) // 批量删除SysOperationRecord\n\t\toperationRecordRouter.GET(\"findSysOperationRecord\", operationRecordApi.FindSysOperationRecord)                  // 根据ID获取SysOperationRecord\n\t\toperationRecordRouter.GET(\"getSysOperationRecordList\", operationRecordApi.GetSysOperationRecordList)            // 获取SysOperationRecord列表\n\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_params.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype SysParamsRouter struct{}\n\n// InitSysParamsRouter 初始化 参数 路由信息\nfunc (s *SysParamsRouter) InitSysParamsRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {\n\tsysParamsRouter := Router.Group(\"sysParams\").Use(middleware.OperationRecord())\n\tsysParamsRouterWithoutRecord := Router.Group(\"sysParams\")\n\t{\n\t\tsysParamsRouter.POST(\"createSysParams\", sysParamsApi.CreateSysParams)             // 新建参数\n\t\tsysParamsRouter.DELETE(\"deleteSysParams\", sysParamsApi.DeleteSysParams)           // 删除参数\n\t\tsysParamsRouter.DELETE(\"deleteSysParamsByIds\", sysParamsApi.DeleteSysParamsByIds) // 批量删除参数\n\t\tsysParamsRouter.PUT(\"updateSysParams\", sysParamsApi.UpdateSysParams)              // 更新参数\n\t}\n\t{\n\t\tsysParamsRouterWithoutRecord.GET(\"findSysParams\", sysParamsApi.FindSysParams)       // 根据ID获取参数\n\t\tsysParamsRouterWithoutRecord.GET(\"getSysParamsList\", sysParamsApi.GetSysParamsList) // 获取参数列表\n\t\tsysParamsRouterWithoutRecord.GET(\"getSysParam\", sysParamsApi.GetSysParam)           // 根据Key获取参数\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_skills.go",
    "content": "package system\n\nimport \"github.com/gin-gonic/gin\"\n\ntype SkillsRouter struct{}\n\nfunc (s *SkillsRouter) InitSkillsRouter(Router *gin.RouterGroup, pubRouter *gin.RouterGroup) {\n\tskillsRouter := Router.Group(\"skills\")\n\tskillsRouterPub := pubRouter.Group(\"skills\")\n\t{\n\t\tskillsRouter.GET(\"getTools\", skillsApi.GetTools)\n\t\tskillsRouter.POST(\"getSkillList\", skillsApi.GetSkillList)\n\t\tskillsRouter.POST(\"getSkillDetail\", skillsApi.GetSkillDetail)\n\t\tskillsRouter.POST(\"saveSkill\", skillsApi.SaveSkill)\n\t\tskillsRouter.POST(\"deleteSkill\", skillsApi.DeleteSkill)\n\t\tskillsRouter.POST(\"createScript\", skillsApi.CreateScript)\n\t\tskillsRouter.POST(\"getScript\", skillsApi.GetScript)\n\t\tskillsRouter.POST(\"saveScript\", skillsApi.SaveScript)\n\t\tskillsRouter.POST(\"createResource\", skillsApi.CreateResource)\n\t\tskillsRouter.POST(\"getResource\", skillsApi.GetResource)\n\t\tskillsRouter.POST(\"saveResource\", skillsApi.SaveResource)\n\t\tskillsRouter.POST(\"createReference\", skillsApi.CreateReference)\n\t\tskillsRouter.POST(\"getReference\", skillsApi.GetReference)\n\t\tskillsRouter.POST(\"saveReference\", skillsApi.SaveReference)\n\t\tskillsRouter.POST(\"createTemplate\", skillsApi.CreateTemplate)\n\t\tskillsRouter.POST(\"getTemplate\", skillsApi.GetTemplate)\n\t\tskillsRouter.POST(\"saveTemplate\", skillsApi.SaveTemplate)\n\t\tskillsRouter.POST(\"getGlobalConstraint\", skillsApi.GetGlobalConstraint)\n\t\tskillsRouter.POST(\"saveGlobalConstraint\", skillsApi.SaveGlobalConstraint)\n\t\tskillsRouter.POST(\"packageSkill\", skillsApi.PackageSkill)\n\t}\n\t{\n\t\tskillsRouterPub.POST(\"downloadOnlineSkill\", skillsApi.DownloadOnlineSkill)\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_system.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype SysRouter struct{}\n\nfunc (s *SysRouter) InitSystemRouter(Router *gin.RouterGroup) {\n\tsysRouter := Router.Group(\"system\").Use(middleware.OperationRecord())\n\tsysRouterWithoutRecord := Router.Group(\"system\")\n\n\t{\n\t\tsysRouter.POST(\"setSystemConfig\", systemApi.SetSystemConfig) // 设置配置文件内容\n\t\tsysRouter.POST(\"reloadSystem\", systemApi.ReloadSystem)       // 重启服务\n\t}\n\t{\n\t\tsysRouterWithoutRecord.POST(\"getSystemConfig\", systemApi.GetSystemConfig) // 获取配置文件内容\n\t\tsysRouterWithoutRecord.POST(\"getServerInfo\", systemApi.GetServerInfo)     // 获取服务器信息\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_user.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype UserRouter struct{}\n\nfunc (s *UserRouter) InitUserRouter(Router *gin.RouterGroup) {\n\tuserRouter := Router.Group(\"user\").Use(middleware.OperationRecord())\n\tuserRouterWithoutRecord := Router.Group(\"user\")\n\t{\n\t\tuserRouter.POST(\"admin_register\", baseApi.Register)               // 管理员注册账号\n\t\tuserRouter.POST(\"changePassword\", baseApi.ChangePassword)         // 用户修改密码\n\t\tuserRouter.POST(\"setUserAuthority\", baseApi.SetUserAuthority)     // 设置用户权限\n\t\tuserRouter.DELETE(\"deleteUser\", baseApi.DeleteUser)               // 删除用户\n\t\tuserRouter.PUT(\"setUserInfo\", baseApi.SetUserInfo)                // 设置用户信息\n\t\tuserRouter.PUT(\"setSelfInfo\", baseApi.SetSelfInfo)                // 设置自身信息\n\t\tuserRouter.POST(\"setUserAuthorities\", baseApi.SetUserAuthorities) // 设置用户权限组\n\t\tuserRouter.POST(\"resetPassword\", baseApi.ResetPassword)           // 重置用户密码\n\t\tuserRouter.PUT(\"setSelfSetting\", baseApi.SetSelfSetting)          // 用户界面配置\n\t}\n\t{\n\t\tuserRouterWithoutRecord.POST(\"getUserList\", baseApi.GetUserList) // 分页获取用户列表\n\t\tuserRouterWithoutRecord.GET(\"getUserInfo\", baseApi.GetUserInfo)  // 获取自身信息\n\t}\n}\n"
  },
  {
    "path": "server/router/system/sys_version.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/middleware\"\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype SysVersionRouter struct{}\n\n// InitSysVersionRouter 初始化 版本管理 路由信息\nfunc (s *SysVersionRouter) InitSysVersionRouter(Router *gin.RouterGroup) {\n\tsysVersionRouter := Router.Group(\"sysVersion\").Use(middleware.OperationRecord())\n\tsysVersionRouterWithoutRecord := Router.Group(\"sysVersion\")\n\t{\n\t\tsysVersionRouter.DELETE(\"deleteSysVersion\", sysVersionApi.DeleteSysVersion)           // 删除版本管理\n\t\tsysVersionRouter.DELETE(\"deleteSysVersionByIds\", sysVersionApi.DeleteSysVersionByIds) // 批量删除版本管理\n\t\tsysVersionRouter.POST(\"exportVersion\", sysVersionApi.ExportVersion)                   // 导出版本数据\n\t\tsysVersionRouter.POST(\"importVersion\", sysVersionApi.ImportVersion)                   // 导入版本数据\n\t}\n\t{\n\t\tsysVersionRouterWithoutRecord.GET(\"findSysVersion\", sysVersionApi.FindSysVersion)           // 根据ID获取版本管理\n\t\tsysVersionRouterWithoutRecord.GET(\"getSysVersionList\", sysVersionApi.GetSysVersionList)     // 获取版本管理列表\n\t\tsysVersionRouterWithoutRecord.GET(\"downloadVersionJson\", sysVersionApi.DownloadVersionJson) // 下载版本JSON数据\n\t}\n}\n"
  },
  {
    "path": "server/service/enter.go",
    "content": "package service\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n)\n\nvar ServiceGroupApp = new(ServiceGroup)\n\ntype ServiceGroup struct {\n\tSystemServiceGroup  system.ServiceGroup\n\tExampleServiceGroup example.ServiceGroup\n}\n"
  },
  {
    "path": "server/service/example/enter.go",
    "content": "package example\n\ntype ServiceGroup struct {\n\tCustomerService\n\tFileUploadAndDownloadService\n\tAttachmentCategoryService\n}\n"
  },
  {
    "path": "server/service/example/exa_attachment_category.go",
    "content": "package example\n\nimport (\n\t\"errors\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"gorm.io/gorm\"\n)\n\ntype AttachmentCategoryService struct{}\n\n// AddCategory 创建/更新的分类\nfunc (a *AttachmentCategoryService) AddCategory(req *example.ExaAttachmentCategory) (err error) {\n\t// 检查是否已存在相同名称的分类\n\tif (!errors.Is(global.GVA_DB.Take(&example.ExaAttachmentCategory{}, \"name = ? and pid = ?\", req.Name, req.Pid).Error, gorm.ErrRecordNotFound)) {\n\t\treturn errors.New(\"分类名称已存在\")\n\t}\n\tif req.ID > 0 {\n\t\tif err = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where(\"id = ?\", req.ID).Updates(&example.ExaAttachmentCategory{\n\t\t\tName: req.Name,\n\t\t\tPid:  req.Pid,\n\t\t}).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err = global.GVA_DB.Create(&example.ExaAttachmentCategory{\n\t\t\tName: req.Name,\n\t\t\tPid:  req.Pid,\n\t\t}).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteCategory 删除分类\nfunc (a *AttachmentCategoryService) DeleteCategory(id *int) error {\n\tvar childCount int64\n\tglobal.GVA_DB.Model(&example.ExaAttachmentCategory{}).Where(\"pid = ?\", id).Count(&childCount)\n\tif childCount > 0 {\n\t\treturn errors.New(\"请先删除子级\")\n\t}\n\treturn global.GVA_DB.Where(\"id = ?\", id).Unscoped().Delete(&example.ExaAttachmentCategory{}).Error\n}\n\n// GetCategoryList 分类列表\nfunc (a *AttachmentCategoryService) GetCategoryList() (res []*example.ExaAttachmentCategory, err error) {\n\tvar fileLists []example.ExaAttachmentCategory\n\terr = global.GVA_DB.Model(&example.ExaAttachmentCategory{}).Find(&fileLists).Error\n\tif err != nil {\n\t\treturn res, err\n\t}\n\treturn a.getChildrenList(fileLists, 0), nil\n}\n\n// getChildrenList 子类\nfunc (a *AttachmentCategoryService) getChildrenList(categories []example.ExaAttachmentCategory, parentID uint) []*example.ExaAttachmentCategory {\n\tvar tree []*example.ExaAttachmentCategory\n\tfor _, category := range categories {\n\t\tif category.Pid == parentID {\n\t\t\tcategory.Children = a.getChildrenList(categories, category.ID)\n\t\t\ttree = append(tree, &category)\n\t\t}\n\t}\n\treturn tree\n}\n"
  },
  {
    "path": "server/service/example/exa_breakpoint_continue.go",
    "content": "package example\n\nimport (\n\t\"errors\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"gorm.io/gorm\"\n)\n\ntype FileUploadAndDownloadService struct{}\n\nvar FileUploadAndDownloadServiceApp = new(FileUploadAndDownloadService)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: FindOrCreateFile\n//@description: 上传文件时检测当前文件属性，如果没有文件则创建，有则返回文件的当前切片\n//@param: fileMd5 string, fileName string, chunkTotal int\n//@return: file model.ExaFile, err error\n\nfunc (e *FileUploadAndDownloadService) FindOrCreateFile(fileMd5 string, fileName string, chunkTotal int) (file example.ExaFile, err error) {\n\tvar cfile example.ExaFile\n\tcfile.FileMd5 = fileMd5\n\tcfile.FileName = fileName\n\tcfile.ChunkTotal = chunkTotal\n\n\tif errors.Is(global.GVA_DB.Where(\"file_md5 = ? AND file_name = ? AND is_finish = ?\", fileMd5, fileName, true).First(&file).Error, gorm.ErrRecordNotFound) {\n\t\terr = global.GVA_DB.Where(\"file_md5 = ? AND file_name = ?\", fileMd5, fileName).Preload(\"ExaFileChunk\").FirstOrCreate(&file, cfile).Error\n\t\treturn file, err\n\t}\n\tcfile.IsFinish = true\n\tcfile.FilePath = file.FilePath\n\terr = global.GVA_DB.Create(&cfile).Error\n\treturn cfile, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateFileChunk\n//@description: 创建文件切片记录\n//@param: id uint, fileChunkPath string, fileChunkNumber int\n//@return: error\n\nfunc (e *FileUploadAndDownloadService) CreateFileChunk(id uint, fileChunkPath string, fileChunkNumber int) error {\n\tvar chunk example.ExaFileChunk\n\tchunk.FileChunkPath = fileChunkPath\n\tchunk.ExaFileID = id\n\tchunk.FileChunkNumber = fileChunkNumber\n\terr := global.GVA_DB.Create(&chunk).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteFileChunk\n//@description: 删除文件切片记录\n//@param: fileMd5 string, fileName string, filePath string\n//@return: error\n\nfunc (e *FileUploadAndDownloadService) DeleteFileChunk(fileMd5 string, filePath string) error {\n\tvar chunks []example.ExaFileChunk\n\tvar file example.ExaFile\n\terr := global.GVA_DB.Where(\"file_md5 = ?\", fileMd5).First(&file).\n\t\tUpdates(map[string]interface{}{\n\t\t\t\"IsFinish\":  true,\n\t\t\t\"file_path\": filePath,\n\t\t}).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = global.GVA_DB.Where(\"exa_file_id = ?\", file.ID).Delete(&chunks).Unscoped().Error\n\treturn err\n}\n"
  },
  {
    "path": "server/service/example/exa_customer.go",
    "content": "package example\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemService \"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n)\n\ntype CustomerService struct{}\n\nvar CustomerServiceApp = new(CustomerService)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateExaCustomer\n//@description: 创建客户\n//@param: e model.ExaCustomer\n//@return: err error\n\nfunc (exa *CustomerService) CreateExaCustomer(e example.ExaCustomer) (err error) {\n\terr = global.GVA_DB.Create(&e).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteFileChunk\n//@description: 删除客户\n//@param: e model.ExaCustomer\n//@return: err error\n\nfunc (exa *CustomerService) DeleteExaCustomer(e example.ExaCustomer) (err error) {\n\terr = global.GVA_DB.Delete(&e).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateExaCustomer\n//@description: 更新客户\n//@param: e *model.ExaCustomer\n//@return: err error\n\nfunc (exa *CustomerService) UpdateExaCustomer(e *example.ExaCustomer) (err error) {\n\terr = global.GVA_DB.Save(e).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetExaCustomer\n//@description: 获取客户信息\n//@param: id uint\n//@return: customer model.ExaCustomer, err error\n\nfunc (exa *CustomerService) GetExaCustomer(id uint) (customer example.ExaCustomer, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", id).First(&customer).Error\n\treturn\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetCustomerInfoList\n//@description: 分页获取客户列表\n//@param: sysUserAuthorityID string, info request.PageInfo\n//@return: list interface{}, total int64, err error\n\nfunc (exa *CustomerService) GetCustomerInfoList(sysUserAuthorityID uint, info request.PageInfo) (list interface{}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\tdb := global.GVA_DB.Model(&example.ExaCustomer{})\n\tvar a system.SysAuthority\n\ta.AuthorityId = sysUserAuthorityID\n\tauth, err := systemService.AuthorityServiceApp.GetAuthorityInfo(a)\n\tif err != nil {\n\t\treturn\n\t}\n\tvar dataId []uint\n\tfor _, v := range auth.DataAuthorityId {\n\t\tdataId = append(dataId, v.AuthorityId)\n\t}\n\tvar CustomerList []example.ExaCustomer\n\terr = db.Where(\"sys_user_authority_id in ?\", dataId).Count(&total).Error\n\tif err != nil {\n\t\treturn CustomerList, total, err\n\t} else {\n\t\terr = db.Limit(limit).Offset(offset).Preload(\"SysUser\").Where(\"sys_user_authority_id in ?\", dataId).Find(&CustomerList).Error\n\t}\n\treturn CustomerList, total, err\n}\n"
  },
  {
    "path": "server/service/example/exa_file_upload_download.go",
    "content": "package example\n\nimport (\n\t\"errors\"\n\t\"mime/multipart\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/upload\"\n\t\"gorm.io/gorm\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Upload\n//@description: 创建文件上传记录\n//@param: file model.ExaFileUploadAndDownload\n//@return: error\n\nfunc (e *FileUploadAndDownloadService) Upload(file example.ExaFileUploadAndDownload) error {\n\treturn global.GVA_DB.Create(&file).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: FindFile\n//@description: 查询文件记录\n//@param: id uint\n//@return: model.ExaFileUploadAndDownload, error\n\nfunc (e *FileUploadAndDownloadService) FindFile(id uint) (example.ExaFileUploadAndDownload, error) {\n\tvar file example.ExaFileUploadAndDownload\n\terr := global.GVA_DB.Where(\"id = ?\", id).First(&file).Error\n\treturn file, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteFile\n//@description: 删除文件记录\n//@param: file model.ExaFileUploadAndDownload\n//@return: err error\n\nfunc (e *FileUploadAndDownloadService) DeleteFile(file example.ExaFileUploadAndDownload) (err error) {\n\tvar fileFromDb example.ExaFileUploadAndDownload\n\tfileFromDb, err = e.FindFile(file.ID)\n\tif err != nil {\n\t\treturn\n\t}\n\toss := upload.NewOss()\n\tif err = oss.DeleteFile(fileFromDb.Key); err != nil {\n\t\treturn errors.New(\"文件删除失败\")\n\t}\n\terr = global.GVA_DB.Where(\"id = ?\", file.ID).Unscoped().Delete(&file).Error\n\treturn err\n}\n\n// EditFileName 编辑文件名或者备注\nfunc (e *FileUploadAndDownloadService) EditFileName(file example.ExaFileUploadAndDownload) (err error) {\n\tvar fileFromDb example.ExaFileUploadAndDownload\n\treturn global.GVA_DB.Where(\"id = ?\", file.ID).First(&fileFromDb).Update(\"name\", file.Name).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetFileRecordInfoList\n//@description: 分页获取数据\n//@param: info request.ExaAttachmentCategorySearch\n//@return: list interface{}, total int64, err error\n\nfunc (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.ExaAttachmentCategorySearch) (list []example.ExaFileUploadAndDownload, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\tdb := global.GVA_DB.Model(&example.ExaFileUploadAndDownload{})\n\n\tif len(info.Keyword) > 0 {\n\t\tdb = db.Where(\"name LIKE ?\", \"%\"+info.Keyword+\"%\")\n\t}\n\n\tif info.ClassId > 0 {\n\t\tdb = db.Where(\"class_id = ?\", info.ClassId)\n\t}\n\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\terr = db.Limit(limit).Offset(offset).Order(\"id desc\").Find(&list).Error\n\treturn list, total, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UploadFile\n//@description: 根据配置文件判断是文件上传到本地或者七牛云\n//@param: header *multipart.FileHeader, noSave string\n//@return: file model.ExaFileUploadAndDownload, err error\n\nfunc (e *FileUploadAndDownloadService) UploadFile(header *multipart.FileHeader, noSave string, classId int) (file example.ExaFileUploadAndDownload, err error) {\n\toss := upload.NewOss()\n\tfilePath, key, uploadErr := oss.UploadFile(header)\n\tif uploadErr != nil {\n\t\treturn file, uploadErr\n\t}\n\ts := strings.Split(header.Filename, \".\")\n\tf := example.ExaFileUploadAndDownload{\n\t\tUrl:     filePath,\n\t\tName:    header.Filename,\n\t\tClassId: classId,\n\t\tTag:     s[len(s)-1],\n\t\tKey:     key,\n\t}\n\tif noSave == \"0\" {\n\t\t// 检查是否已存在相同key的记录\n\t\tvar existingFile example.ExaFileUploadAndDownload\n\t\terr = global.GVA_DB.Where(&example.ExaFileUploadAndDownload{Key: key}).First(&existingFile).Error\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\treturn f, e.Upload(f)\n\t\t}\n\t\treturn f, err\n\t}\n\treturn f, nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: ImportURL\n//@description: 导入URL\n//@param: file model.ExaFileUploadAndDownload\n//@return: error\n\nfunc (e *FileUploadAndDownloadService) ImportURL(file *[]example.ExaFileUploadAndDownload) error {\n\treturn global.GVA_DB.Create(&file).Error\n}\n"
  },
  {
    "path": "server/service/system/auto_code_history.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/ast\"\n\t\"github.com/pkg/errors\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\trequest \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"go.uber.org/zap\"\n)\n\nvar AutocodeHistory = new(autoCodeHistory)\n\ntype autoCodeHistory struct{}\n\n// Create 创建代码生成器历史记录\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [songzhibin97](https://github.com/songzhibin97)\nfunc (s *autoCodeHistory) Create(ctx context.Context, info request.SysAutoHistoryCreate) error {\n\tcreate := info.Create()\n\terr := global.GVA_DB.WithContext(ctx).Create(&create).Error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"创建失败!\")\n\t}\n\treturn nil\n}\n\n// First 根据id获取代码生成器历史的数据\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [songzhibin97](https://github.com/songzhibin97)\nfunc (s *autoCodeHistory) First(ctx context.Context, info common.GetById) (string, error) {\n\tvar meta string\n\terr := global.GVA_DB.WithContext(ctx).Model(model.SysAutoCodeHistory{}).Where(\"id = ?\", info.ID).Pluck(\"request\", &meta).Error\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"获取失败!\")\n\t}\n\treturn meta, nil\n}\n\n// Repeat 检测重复\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [songzhibin97](https://github.com/songzhibin97)\nfunc (s *autoCodeHistory) Repeat(businessDB, structName, abbreviation, Package string) bool {\n\tvar count int64\n\tglobal.GVA_DB.Model(&model.SysAutoCodeHistory{}).Where(\"business_db = ? and (struct_name = ? OR abbreviation = ?) and package = ? and flag = ?\", businessDB, structName, abbreviation, Package, 0).Count(&count).Debug()\n\treturn count > 0\n}\n\n// RollBack 回滚\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [songzhibin97](https://github.com/songzhibin97)\nfunc (s *autoCodeHistory) RollBack(ctx context.Context, info request.SysAutoHistoryRollBack) error {\n\tvar history model.SysAutoCodeHistory\n\terr := global.GVA_DB.Where(\"id = ?\", info.ID).First(&history).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tif history.ExportTemplateID != 0 {\n\t\terr = global.GVA_DB.Delete(&model.SysExportTemplate{}, \"id = ?\", history.ExportTemplateID).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif info.DeleteApi {\n\t\tids := info.ApiIds(history)\n\t\terr = ApiServiceApp.DeleteApisByIds(ids)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"ClearTag DeleteApiByIds:\", zap.Error(err))\n\t\t}\n\t} // 清除API表\n\tif info.DeleteMenu {\n\t\terr = BaseMenuServiceApp.DeleteBaseMenu(int(history.MenuID))\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"删除菜单失败!\")\n\t\t}\n\t} // 清除菜单表\n\tif info.DeleteTable {\n\t\terr = s.DropTable(history.BusinessDB, history.Table)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"删除表失败!\")\n\t\t}\n\t} // 删除表\n\ttemplates := make(map[string]string, len(history.Templates))\n\tfor key, template := range history.Templates {\n\t\t{\n\t\t\tserver := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)\n\t\t\tkeys := strings.Split(key, \"/\")\n\t\t\tkey = filepath.Join(keys...)\n\t\t\tkey = strings.TrimPrefix(key, server)\n\t\t} // key\n\t\t{\n\t\t\tweb := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot())\n\t\t\tserver := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)\n\t\t\tslices := strings.Split(template, \"/\")\n\t\t\ttemplate = filepath.Join(slices...)\n\t\t\text := path.Ext(template)\n\t\t\tswitch ext {\n\t\t\tcase \".js\", \".vue\":\n\t\t\t\ttemplate = filepath.Join(web, template)\n\t\t\tcase \".go\":\n\t\t\t\ttemplate = filepath.Join(server, template)\n\t\t\t}\n\t\t} // value\n\t\ttemplates[key] = template\n\t}\n\thistory.Templates = templates\n\tfor key, value := range history.Injections {\n\t\tvar injection ast.Ast\n\t\tswitch key {\n\t\tcase ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter:\n\n\t\tcase ast.TypePackageApiModuleEnter, ast.TypePackageRouterModuleEnter, ast.TypePackageServiceModuleEnter:\n\t\t\tvar entity ast.PackageModuleEnter\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\tcase ast.TypePackageInitializeGorm:\n\t\t\tvar entity ast.PackageInitializeGorm\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\tcase ast.TypePackageInitializeRouter:\n\t\t\tvar entity ast.PackageInitializeRouter\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\tcase ast.TypePluginGen:\n\t\t\tvar entity ast.PluginGen\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\tcase ast.TypePluginApiEnter, ast.TypePluginRouterEnter, ast.TypePluginServiceEnter:\n\t\t\tvar entity ast.PluginEnter\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\tcase ast.TypePluginInitializeGorm:\n\t\t\tvar entity ast.PluginInitializeGorm\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\tcase ast.TypePluginInitializeRouter:\n\t\t\tvar entity ast.PluginInitializeRouter\n\t\t\t_ = json.Unmarshal([]byte(value), &entity)\n\t\t\tinjection = &entity\n\t\t}\n\t\tif injection == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfile, _ := injection.Parse(\"\", nil)\n\t\tif file != nil {\n\t\t\t_ = injection.Rollback(file)\n\t\t\terr = injection.Format(\"\", nil, file)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfmt.Printf(\"[filepath:%s]回滚注入代码成功!\\n\", key)\n\t\t}\n\t} // 清除注入代码\n\tremoveBasePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, \"rm_file\", strconv.FormatInt(int64(time.Now().Nanosecond()), 10))\n\tfor _, value := range history.Templates {\n\t\tif !filepath.IsAbs(value) {\n\t\t\tcontinue\n\t\t}\n\t\tremovePath := filepath.Join(removeBasePath, strings.TrimPrefix(value, global.GVA_CONFIG.AutoCode.Root))\n\t\terr = utils.FileMove(value, removePath)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"[src:%s][dst:%s]文件移动失败!\", value, removePath)\n\t\t}\n\t} // 移动文件\n\terr = global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{}).Where(\"id = ?\", info.ID).Update(\"flag\", 1).Error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"更新失败!\")\n\t}\n\treturn nil\n}\n\n// Delete 删除历史数据\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [songzhibin97](https://github.com/songzhibin97)\nfunc (s *autoCodeHistory) Delete(ctx context.Context, info common.GetById) error {\n\terr := global.GVA_DB.WithContext(ctx).Where(\"id = ?\", info.Uint()).Delete(&model.SysAutoCodeHistory{}).Error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"删除失败!\")\n\t}\n\treturn nil\n}\n\n// GetList 获取系统历史数据\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [songzhibin97](https://github.com/songzhibin97)\nfunc (s *autoCodeHistory) GetList(ctx context.Context, info common.PageInfo) (list []model.SysAutoCodeHistory, total int64, err error) {\n\tvar entities []model.SysAutoCodeHistory\n\tdb := global.GVA_DB.WithContext(ctx).Model(&model.SysAutoCodeHistory{})\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn nil, total, err\n\t}\n\terr = db.Scopes(info.Paginate()).Order(\"updated_at desc\").Find(&entities).Error\n\treturn entities, total, err\n}\n\n// DropTable 获取指定数据库和指定数据表的所有字段名,类型值等\n// @author: [piexlmax](https://github.com/piexlmax)\nfunc (s *autoCodeHistory) DropTable(BusinessDb, tableName string) error {\n\tif BusinessDb != \"\" {\n\t\treturn global.MustGetGlobalDBByDBName(BusinessDb).Exec(\"DROP TABLE \" + tableName).Error\n\t} else {\n\t\treturn global.GVA_DB.Exec(\"DROP TABLE \" + tableName).Error\n\t}\n}\n"
  },
  {
    "path": "server/service/system/auto_code_llm.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\tcommonResp \"github.com/flipped-aurora/gin-vue-admin/server/model/common/response\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/request\"\n\t\"github.com/goccy/go-json\"\n\t\"io\"\n\t\"strings\"\n)\n\n// LLMAuto 调用大模型服务，返回生成结果数据\n// 入参为通用 JSONMap，需包含 mode（例如 ai/butler/eye/painter 等）以及业务 prompt/payload\nfunc (s *AutoCodeService) LLMAuto(ctx context.Context, llm common.JSONMap) (interface{}, error) {\n\tif global.GVA_CONFIG.AutoCode.AiPath == \"\" {\n\t\treturn nil, errors.New(\"请先前往插件市场个人中心获取AiPath并填入config.yaml中\")\n\t}\n\n\t// 构建调用路径：{AiPath} 中的 {FUNC} 由 mode 替换\n\tmode := fmt.Sprintf(\"%v\", llm[\"mode\"]) // 统一转字符串，避免 nil 造成路径异常\n\tpath := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, \"{FUNC}\", mode)\n\n\tres, err := request.HttpRequest(\n\t\tpath,\n\t\t\"POST\",\n\t\tnil,\n\t\tnil,\n\t\tllm,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"大模型生成失败: %w\", err)\n\t}\n\tdefer res.Body.Close()\n\n\tvar resStruct commonResp.Response\n\tb, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"读取大模型响应失败: %w\", err)\n\t}\n\tif err = json.Unmarshal(b, &resStruct); err != nil {\n\t\treturn nil, fmt.Errorf(\"解析大模型响应失败: %w\", err)\n\t}\n\tif resStruct.Code == 7 { // 业务约定：7 表示模型生成失败\n\t\treturn nil, fmt.Errorf(\"大模型生成失败: %s\", resStruct.Msg)\n\t}\n\treturn resStruct.Data, nil\n}\n"
  },
  {
    "path": "server/service/system/auto_code_mcp.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/autocode\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"text/template\"\n)\n\nfunc (s *autoCodeTemplate) CreateMcp(ctx context.Context, info request.AutoMcpTool) (toolFilePath string, err error) {\n\tmcpTemplatePath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"resource\", \"mcp\", \"tools.tpl\")\n\tmcpToolPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"mcp\")\n\n\tvar files *template.Template\n\n\ttemplateName := filepath.Base(mcpTemplatePath)\n\n\tfiles, err = template.New(templateName).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(mcpTemplatePath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfileName := utils.HumpToUnderscore(info.Name)\n\n\ttoolFilePath = filepath.Join(mcpToolPath, fileName+\".go\")\n\n\tf, err := os.Create(toolFilePath)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer f.Close()\n\n\t// 执行模板，将内容写入文件\n\terr = files.Execute(f, info)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn\n\n}\n"
  },
  {
    "path": "server/service/system/auto_code_package.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"go/token\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tcommon \"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/ast\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/autocode\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nvar AutoCodePackage = new(autoCodePackage)\n\ntype autoCodePackage struct{}\n\n// Create 创建包信息\n// @author: [piexlmax](https://github.com/piexlmax)\n// @author: [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodePackage) Create(ctx context.Context, info *request.SysAutoCodePackageCreate) error {\n\tswitch {\n\tcase info.Template == \"\":\n\t\treturn errors.New(\"模板不能为空!\")\n\tcase info.Template == \"page\":\n\t\treturn errors.New(\"page为表单生成器!\")\n\tcase info.PackageName == \"\":\n\t\treturn errors.New(\"PackageName不能为空!\")\n\tcase token.IsKeyword(info.PackageName):\n\t\treturn errors.Errorf(\"%s为go的关键字!\", info.PackageName)\n\tcase info.Template == \"package\":\n\t\tif info.PackageName == \"system\" || info.PackageName == \"example\" {\n\t\t\treturn errors.New(\"不能使用已保留的package name\")\n\t\t}\n\tdefault:\n\t\tbreak\n\t}\n\tif !errors.Is(global.GVA_DB.Where(\"package_name = ? and template = ?\", info.PackageName, info.Template).First(&model.SysAutoCodePackage{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"存在相同PackageName\")\n\t}\n\tcreate := info.Create()\n\treturn global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {\n\t\terr := tx.Create(&create).Error\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"创建失败!\")\n\t\t}\n\t\tcode := info.AutoCode()\n\t\t_, asts, creates, err := s.templates(ctx, create, code, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor key, value := range creates { // key 为 模版绝对路径\n\t\t\tvar files *template.Template\n\t\t\tfiles, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"[filepath:%s]读取模版文件失败!\", key)\n\t\t\t}\n\t\t\terr = os.MkdirAll(filepath.Dir(value), os.ModePerm)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"[filepath:%s]创建文件夹失败!\", value)\n\t\t\t}\n\t\t\tvar file *os.File\n\t\t\tfile, err = os.Create(value)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"[filepath:%s]创建文件夹失败!\", value)\n\t\t\t}\n\t\t\terr = files.Execute(file, code)\n\t\t\t_ = file.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"[filepath:%s]生成失败!\", value)\n\t\t\t}\n\t\t\tfmt.Printf(\"[template:%s][filepath:%s]生成成功!\\n\", key, value)\n\t\t}\n\t\tfor key, value := range asts {\n\t\t\tkeys := strings.Split(key, \"=>\")\n\t\t\tif len(keys) == 2 {\n\t\t\t\tswitch keys[1] {\n\t\t\t\tcase ast.TypePluginInitializeV2, ast.TypePackageApiEnter, ast.TypePackageRouterEnter, ast.TypePackageServiceEnter:\n\t\t\t\t\tfile, _ := value.Parse(\"\", nil)\n\t\t\t\t\tif file != nil {\n\t\t\t\t\t\terr = value.Injection(file)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\terr = value.Format(\"\", nil, file)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfmt.Printf(\"[type:%s]注入成功!\\n\", key)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// Delete 删除包记录\n// @author: [piexlmax](https://github.com/piexlmax)\n// @author: [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodePackage) Delete(ctx context.Context, info common.GetById) error {\n\terr := global.GVA_DB.WithContext(ctx).Delete(&model.SysAutoCodePackage{}, info.Uint()).Error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"删除失败!\")\n\t}\n\treturn nil\n}\n\n// DeleteByNames\n// @author: [piexlmax](https://github.com/piexlmax)\n// @author: [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodePackage) DeleteByNames(ctx context.Context, names []string) error {\n\tif len(names) == 0 {\n\t\treturn nil\n\t}\n\terr := global.GVA_DB.WithContext(ctx).Where(\"package_name IN ?\", names).Delete(&model.SysAutoCodePackage{}).Error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"删除失败!\")\n\t}\n\treturn nil\n}\n\n// All 获取所有包\n// @author: [piexlmax](https://github.com/piexlmax)\n// @author: [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodePackage) All(ctx context.Context) (entities []model.SysAutoCodePackage, err error) {\n\tserver := make([]model.SysAutoCodePackage, 0)\n\tplugin := make([]model.SysAutoCodePackage, 0)\n\tserverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\")\n\tpluginPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\")\n\tserverDir, err := os.ReadDir(serverPath)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"读取service文件夹失败!\")\n\t}\n\tpluginDir, err := os.ReadDir(pluginPath)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"读取plugin文件夹失败!\")\n\t}\n\tfor i := 0; i < len(serverDir); i++ {\n\t\tif serverDir[i].IsDir() {\n\t\t\tserverPackage := model.SysAutoCodePackage{\n\t\t\t\tPackageName: serverDir[i].Name(),\n\t\t\t\tTemplate:    \"package\",\n\t\t\t\tLabel:       serverDir[i].Name() + \"包\",\n\t\t\t\tDesc:        \"系统自动读取\" + serverDir[i].Name() + \"包\",\n\t\t\t\tModule:      global.GVA_CONFIG.AutoCode.Module,\n\t\t\t}\n\t\t\tserver = append(server, serverPackage)\n\t\t}\n\t}\n\tfor i := 0; i < len(pluginDir); i++ {\n\t\tif pluginDir[i].IsDir() {\n\t\t\tdirNameMap := map[string]bool{\n\t\t\t\t\"api\":        true,\n\t\t\t\t\"config\":     true,\n\t\t\t\t\"initialize\": true,\n\t\t\t\t\"plugin\":     true,\n\t\t\t\t\"router\":     true,\n\t\t\t\t\"service\":    true,\n\t\t\t}\n\t\t\tdir, e := os.ReadDir(filepath.Join(pluginPath, pluginDir[i].Name()))\n\t\t\tif e != nil {\n\t\t\t\treturn nil, errors.Wrap(err, \"读取plugin文件夹失败!\")\n\t\t\t}\n\t\t\t//dir目录需要包含所有的dirNameMap\n\t\t\tfor k := 0; k < len(dir); k++ {\n\t\t\t\tif dir[k].IsDir() {\n\t\t\t\t\tif ok := dirNameMap[dir[k].Name()]; ok {\n\t\t\t\t\t\tdelete(dirNameMap, dir[k].Name())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar desc string\n\t\t\tif len(dirNameMap) == 0 {\n\t\t\t\t// 完全符合标准结构\n\t\t\t\tdesc = \"系统自动读取\" + pluginDir[i].Name() + \"插件，使用前请确认是否为v2版本插件\"\n\t\t\t} else {\n\t\t\t\t// 缺少某些结构，生成警告描述\n\t\t\t\tvar missingDirs []string\n\t\t\t\tfor dirName := range dirNameMap {\n\t\t\t\t\tmissingDirs = append(missingDirs, dirName)\n\t\t\t\t}\n\t\t\t\tdesc = fmt.Sprintf(\"系统自动读取，但是缺少 %s 结构，不建议自动化和mcp使用\", strings.Join(missingDirs, \"、\"))\n\t\t\t}\n\n\t\t\tpluginPackage := model.SysAutoCodePackage{\n\t\t\t\tPackageName: pluginDir[i].Name(),\n\t\t\t\tTemplate:    \"plugin\",\n\t\t\t\tLabel:       pluginDir[i].Name() + \"插件\",\n\t\t\t\tDesc:        desc,\n\t\t\t\tModule:      global.GVA_CONFIG.AutoCode.Module,\n\t\t\t}\n\t\t\tplugin = append(plugin, pluginPackage)\n\t\t}\n\t}\n\n\terr = global.GVA_DB.WithContext(ctx).Find(&entities).Error\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"获取所有包失败!\")\n\t}\n\tentitiesMap := make(map[string]model.SysAutoCodePackage)\n\tfor i := 0; i < len(entities); i++ {\n\t\tentitiesMap[entities[i].PackageName] = entities[i]\n\t}\n\tcreateEntity := []model.SysAutoCodePackage{}\n\tfor i := 0; i < len(server); i++ {\n\t\tif _, ok := entitiesMap[server[i].PackageName]; !ok {\n\t\t\tif server[i].Template == \"package\" {\n\t\t\t\tcreateEntity = append(createEntity, server[i])\n\t\t\t}\n\t\t}\n\t}\n\tfor i := 0; i < len(plugin); i++ {\n\t\tif _, ok := entitiesMap[plugin[i].PackageName]; !ok {\n\t\t\tif plugin[i].Template == \"plugin\" {\n\t\t\t\tcreateEntity = append(createEntity, plugin[i])\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(createEntity) > 0 {\n\t\terr = global.GVA_DB.WithContext(ctx).Create(&createEntity).Error\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"同步失败!\")\n\t\t}\n\t\tentities = append(entities, createEntity...)\n\t}\n\n\t// 处理数据库存在但实体文件不存在的情况 - 删除数据库中对应的数据\n\texistingPackageNames := make(map[string]bool)\n\t// 收集所有存在的包名\n\tfor i := 0; i < len(server); i++ {\n\t\texistingPackageNames[server[i].PackageName] = true\n\t}\n\tfor i := 0; i < len(plugin); i++ {\n\t\texistingPackageNames[plugin[i].PackageName] = true\n\t}\n\n\t// 找出需要删除的数据库记录\n\tdeleteEntityIDs := []uint{}\n\tfor i := 0; i < len(entities); i++ {\n\t\tif !existingPackageNames[entities[i].PackageName] {\n\t\t\tdeleteEntityIDs = append(deleteEntityIDs, entities[i].ID)\n\t\t}\n\t}\n\n\t// 删除数据库中不存在文件的记录\n\tif len(deleteEntityIDs) > 0 {\n\t\terr = global.GVA_DB.WithContext(ctx).Delete(&model.SysAutoCodePackage{}, deleteEntityIDs).Error\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"删除不存在的包记录失败!\")\n\t\t}\n\t\t// 从返回结果中移除已删除的记录\n\t\tfilteredEntities := []model.SysAutoCodePackage{}\n\t\tfor i := 0; i < len(entities); i++ {\n\t\t\tif existingPackageNames[entities[i].PackageName] {\n\t\t\t\tfilteredEntities = append(filteredEntities, entities[i])\n\t\t\t}\n\t\t}\n\t\tentities = filteredEntities\n\t}\n\n\treturn entities, nil\n}\n\n// Templates 获取所有模版文件夹\n// @author: [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodePackage) Templates(ctx context.Context) ([]string, error) {\n\ttemplates := make([]string, 0)\n\tentries, err := os.ReadDir(\"resource\")\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"读取模版文件夹失败!\")\n\t}\n\tfor i := 0; i < len(entries); i++ {\n\t\tif entries[i].IsDir() {\n\t\t\tif entries[i].Name() == \"page\" {\n\t\t\t\tcontinue\n\t\t\t} // page 为表单生成器\n\t\t\tif entries[i].Name() == \"function\" {\n\t\t\t\tcontinue\n\t\t\t} // function 为函数生成器\n\t\t\tif entries[i].Name() == \"preview\" {\n\t\t\t\tcontinue\n\t\t\t} // preview 为预览代码生成器的代码\n\t\t\tif entries[i].Name() == \"mcp\" {\n\t\t\t\tcontinue\n\t\t\t} // preview 为mcp生成器的代码\n\t\t\ttemplates = append(templates, entries[i].Name())\n\t\t}\n\t}\n\treturn templates, nil\n}\n\nfunc (s *autoCodePackage) templates(ctx context.Context, entity model.SysAutoCodePackage, info request.AutoCode, isPackage bool) (code map[string]string, asts map[string]ast.Ast, creates map[string]string, err error) {\n\tcode = make(map[string]string)\n\tasts = make(map[string]ast.Ast)\n\tcreates = make(map[string]string)\n\ttemplateDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"resource\", entity.Template)\n\ttemplateDirs, err := os.ReadDir(templateDir)\n\tif err != nil {\n\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", templateDir)\n\t}\n\tfor i := 0; i < len(templateDirs); i++ {\n\t\tsecond := filepath.Join(templateDir, templateDirs[i].Name())\n\t\tswitch templateDirs[i].Name() {\n\t\tcase \"server\":\n\t\t\tif !info.GenerateServer && !isPackage {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvar secondDirs []os.DirEntry\n\t\t\tsecondDirs, err = os.ReadDir(second)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", second)\n\t\t\t}\n\t\t\tfor j := 0; j < len(secondDirs); j++ {\n\t\t\t\tif secondDirs[j].Name() == \".DS_Store\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthree := filepath.Join(second, secondDirs[j].Name())\n\t\t\t\tif !secondDirs[j].IsDir() {\n\t\t\t\t\text := filepath.Ext(secondDirs[j].Name())\n\t\t\t\t\tif ext != \".tpl\" {\n\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版后缀!\", three)\n\t\t\t\t\t}\n\t\t\t\t\tname := strings.TrimSuffix(secondDirs[j].Name(), ext)\n\t\t\t\t\tif name == \"main.go\" || name == \"plugin.go\" {\n\t\t\t\t\t\tpluginInitialize := &ast.PluginInitializeV2{\n\t\t\t\t\t\t\tType:        ast.TypePluginInitializeV2,\n\t\t\t\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, name),\n\t\t\t\t\t\t\tPluginPath:  filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"register.go\"),\n\t\t\t\t\t\t\tImportPath:  fmt.Sprintf(`\"%s/plugin/%s\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\tPackageName: entity.PackageName,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tasts[pluginInitialize.PluginPath+\"=>\"+pluginInitialize.Type.String()] = pluginInitialize\n\t\t\t\t\t\tcreates[three] = pluginInitialize.Path\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", three)\n\t\t\t\t}\n\t\t\t\tswitch secondDirs[j].Name() {\n\t\t\t\tcase \"api\", \"router\", \"service\":\n\t\t\t\t\tvar threeDirs []os.DirEntry\n\t\t\t\t\tthreeDirs, err = os.ReadDir(three)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", three)\n\t\t\t\t\t}\n\t\t\t\t\tfor k := 0; k < len(threeDirs); k++ {\n\t\t\t\t\t\tif threeDirs[k].Name() == \".DS_Store\" {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfour := filepath.Join(three, threeDirs[k].Name())\n\t\t\t\t\t\tif threeDirs[k].IsDir() {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件夹!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\text := filepath.Ext(four)\n\t\t\t\t\t\tif ext != \".tpl\" {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版后缀!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tapi := strings.Index(threeDirs[k].Name(), \"api\")\n\t\t\t\t\t\thasEnter := strings.Index(threeDirs[k].Name(), \"enter\")\n\t\t\t\t\t\trouter := strings.Index(threeDirs[k].Name(), \"router\")\n\t\t\t\t\t\tservice := strings.Index(threeDirs[k].Name(), \"service\")\n\t\t\t\t\t\tif router == -1 && api == -1 && service == -1 && hasEnter == -1 {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif entity.Template == \"package\" {\n\t\t\t\t\t\t\tcreate := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+\".go\")\n\t\t\t\t\t\t\tif api != -1 {\n\t\t\t\t\t\t\t\tcreate = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), \"v1\", entity.PackageName, info.HumpPackageName+\".go\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif hasEnter != -1 {\n\t\t\t\t\t\t\t\tisApi := strings.Index(secondDirs[j].Name(), \"api\")\n\t\t\t\t\t\t\t\tisRouter := strings.Index(secondDirs[j].Name(), \"router\")\n\t\t\t\t\t\t\t\tisService := strings.Index(secondDirs[j].Name(), \"service\")\n\t\t\t\t\t\t\t\tif isApi != -1 {\n\t\t\t\t\t\t\t\t\tpackageApiEnter := &ast.PackageEnter{\n\t\t\t\t\t\t\t\t\t\tType:              ast.TypePackageApiEnter,\n\t\t\t\t\t\t\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), \"v1\", \"enter.go\"),\n\t\t\t\t\t\t\t\t\t\tImportPath:        fmt.Sprintf(`\"%s/%s/%s/%s\"`, global.GVA_CONFIG.AutoCode.Module, \"api\", \"v1\", entity.PackageName),\n\t\t\t\t\t\t\t\t\t\tStructName:        utils.FirstUpper(entity.PackageName) + \"ApiGroup\",\n\t\t\t\t\t\t\t\t\t\tPackageName:       entity.PackageName,\n\t\t\t\t\t\t\t\t\t\tPackageStructName: \"ApiGroup\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tasts[packageApiEnter.Path+\"=>\"+packageApiEnter.Type.String()] = packageApiEnter\n\t\t\t\t\t\t\t\t\tpackageApiModuleEnter := &ast.PackageModuleEnter{\n\t\t\t\t\t\t\t\t\t\tType:        ast.TypePackageApiModuleEnter,\n\t\t\t\t\t\t\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), \"v1\", entity.PackageName, \"enter.go\"),\n\t\t\t\t\t\t\t\t\t\tImportPath:  fmt.Sprintf(`\"%s/service\"`, global.GVA_CONFIG.AutoCode.Module),\n\t\t\t\t\t\t\t\t\t\tStructName:  info.StructName + \"Api\",\n\t\t\t\t\t\t\t\t\t\tAppName:     \"ServiceGroupApp\",\n\t\t\t\t\t\t\t\t\t\tGroupName:   utils.FirstUpper(entity.PackageName) + \"ServiceGroup\",\n\t\t\t\t\t\t\t\t\t\tModuleName:  info.Abbreviation + \"Service\",\n\t\t\t\t\t\t\t\t\t\tPackageName: \"service\",\n\t\t\t\t\t\t\t\t\t\tServiceName: info.StructName + \"Service\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tasts[packageApiModuleEnter.Path+\"=>\"+packageApiModuleEnter.Type.String()] = packageApiModuleEnter\n\t\t\t\t\t\t\t\t\tcreates[four] = packageApiModuleEnter.Path\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif isRouter != -1 {\n\t\t\t\t\t\t\t\t\tpackageRouterEnter := &ast.PackageEnter{\n\t\t\t\t\t\t\t\t\t\tType:              ast.TypePackageRouterEnter,\n\t\t\t\t\t\t\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), \"enter.go\"),\n\t\t\t\t\t\t\t\t\t\tImportPath:        fmt.Sprintf(`\"%s/%s/%s\"`, global.GVA_CONFIG.AutoCode.Module, secondDirs[j].Name(), entity.PackageName),\n\t\t\t\t\t\t\t\t\t\tStructName:        utils.FirstUpper(entity.PackageName),\n\t\t\t\t\t\t\t\t\t\tPackageName:       entity.PackageName,\n\t\t\t\t\t\t\t\t\t\tPackageStructName: \"RouterGroup\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tasts[packageRouterEnter.Path+\"=>\"+packageRouterEnter.Type.String()] = packageRouterEnter\n\t\t\t\t\t\t\t\t\tpackageRouterModuleEnter := &ast.PackageModuleEnter{\n\t\t\t\t\t\t\t\t\t\tType:        ast.TypePackageRouterModuleEnter,\n\t\t\t\t\t\t\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, \"enter.go\"),\n\t\t\t\t\t\t\t\t\t\tImportPath:  fmt.Sprintf(`api \"%s/api/v1\"`, global.GVA_CONFIG.AutoCode.Module),\n\t\t\t\t\t\t\t\t\t\tStructName:  info.StructName + \"Router\",\n\t\t\t\t\t\t\t\t\t\tAppName:     \"ApiGroupApp\",\n\t\t\t\t\t\t\t\t\t\tGroupName:   utils.FirstUpper(entity.PackageName) + \"ApiGroup\",\n\t\t\t\t\t\t\t\t\t\tModuleName:  info.Abbreviation + \"Api\",\n\t\t\t\t\t\t\t\t\t\tPackageName: \"api\",\n\t\t\t\t\t\t\t\t\t\tServiceName: info.StructName + \"Api\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcreates[four] = packageRouterModuleEnter.Path\n\t\t\t\t\t\t\t\t\tasts[packageRouterModuleEnter.Path+\"=>\"+packageRouterModuleEnter.Type.String()] = packageRouterModuleEnter\n\t\t\t\t\t\t\t\t\tpackageInitializeRouter := &ast.PackageInitializeRouter{\n\t\t\t\t\t\t\t\t\t\tType:                 ast.TypePackageInitializeRouter,\n\t\t\t\t\t\t\t\t\t\tPath:                 filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"router_biz.go\"),\n\t\t\t\t\t\t\t\t\t\tImportPath:           fmt.Sprintf(`\"%s/router\"`, global.GVA_CONFIG.AutoCode.Module),\n\t\t\t\t\t\t\t\t\t\tAppName:              \"RouterGroupApp\",\n\t\t\t\t\t\t\t\t\t\tGroupName:            utils.FirstUpper(entity.PackageName),\n\t\t\t\t\t\t\t\t\t\tModuleName:           entity.PackageName + \"Router\",\n\t\t\t\t\t\t\t\t\t\tPackageName:          \"router\",\n\t\t\t\t\t\t\t\t\t\tFunctionName:         \"Init\" + info.StructName + \"Router\",\n\t\t\t\t\t\t\t\t\t\tLeftRouterGroupName:  \"privateGroup\",\n\t\t\t\t\t\t\t\t\t\tRightRouterGroupName: \"publicGroup\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tasts[packageInitializeRouter.Path+\"=>\"+packageInitializeRouter.Type.String()] = packageInitializeRouter\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif isService != -1 {\n\t\t\t\t\t\t\t\t\tpath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))\n\t\t\t\t\t\t\t\t\timportPath := fmt.Sprintf(`\"%s/service/%s\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName)\n\t\t\t\t\t\t\t\t\tpackageServiceEnter := &ast.PackageEnter{\n\t\t\t\t\t\t\t\t\t\tType:              ast.TypePackageServiceEnter,\n\t\t\t\t\t\t\t\t\t\tPath:              path,\n\t\t\t\t\t\t\t\t\t\tImportPath:        importPath,\n\t\t\t\t\t\t\t\t\t\tStructName:        utils.FirstUpper(entity.PackageName) + \"ServiceGroup\",\n\t\t\t\t\t\t\t\t\t\tPackageName:       entity.PackageName,\n\t\t\t\t\t\t\t\t\t\tPackageStructName: \"ServiceGroup\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tasts[packageServiceEnter.Path+\"=>\"+packageServiceEnter.Type.String()] = packageServiceEnter\n\t\t\t\t\t\t\t\t\tpackageServiceModuleEnter := &ast.PackageModuleEnter{\n\t\t\t\t\t\t\t\t\t\tType:       ast.TypePackageServiceModuleEnter,\n\t\t\t\t\t\t\t\t\t\tPath:       filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, \"enter.go\"),\n\t\t\t\t\t\t\t\t\t\tStructName: info.StructName + \"Service\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tasts[packageServiceModuleEnter.Path+\"=>\"+packageServiceModuleEnter.Type.String()] = packageServiceModuleEnter\n\t\t\t\t\t\t\t\t\tcreates[four] = packageServiceModuleEnter.Path\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcode[four] = create\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif hasEnter != -1 {\n\t\t\t\t\t\t\tisApi := strings.Index(secondDirs[j].Name(), \"api\")\n\t\t\t\t\t\t\tisRouter := strings.Index(secondDirs[j].Name(), \"router\")\n\t\t\t\t\t\t\tisService := strings.Index(secondDirs[j].Name(), \"service\")\n\t\t\t\t\t\t\tif isRouter != -1 {\n\t\t\t\t\t\t\t\tpluginRouterEnter := &ast.PluginEnter{\n\t\t\t\t\t\t\t\t\tType:            ast.TypePluginRouterEnter,\n\t\t\t\t\t\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),\n\t\t\t\t\t\t\t\t\tImportPath:      fmt.Sprintf(`\"%s/plugin/%s/api\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\t\t\tStructName:      info.StructName,\n\t\t\t\t\t\t\t\t\tStructCamelName: info.Abbreviation,\n\t\t\t\t\t\t\t\t\tModuleName:      \"api\" + info.StructName,\n\t\t\t\t\t\t\t\t\tGroupName:       \"Api\",\n\t\t\t\t\t\t\t\t\tPackageName:     \"api\",\n\t\t\t\t\t\t\t\t\tServiceName:     info.StructName,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tasts[pluginRouterEnter.Path+\"=>\"+pluginRouterEnter.Type.String()] = pluginRouterEnter\n\t\t\t\t\t\t\t\tcreates[four] = pluginRouterEnter.Path\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif isApi != -1 {\n\t\t\t\t\t\t\t\tpluginApiEnter := &ast.PluginEnter{\n\t\t\t\t\t\t\t\t\tType:            ast.TypePluginApiEnter,\n\t\t\t\t\t\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),\n\t\t\t\t\t\t\t\t\tImportPath:      fmt.Sprintf(`\"%s/plugin/%s/service\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\t\t\tStructName:      info.StructName,\n\t\t\t\t\t\t\t\t\tStructCamelName: info.Abbreviation,\n\t\t\t\t\t\t\t\t\tModuleName:      \"service\" + info.StructName,\n\t\t\t\t\t\t\t\t\tGroupName:       \"Service\",\n\t\t\t\t\t\t\t\t\tPackageName:     \"service\",\n\t\t\t\t\t\t\t\t\tServiceName:     info.StructName,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tasts[pluginApiEnter.Path+\"=>\"+pluginApiEnter.Type.String()] = pluginApiEnter\n\t\t\t\t\t\t\t\tcreates[four] = pluginApiEnter.Path\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif isService != -1 {\n\t\t\t\t\t\t\t\tpluginServiceEnter := &ast.PluginEnter{\n\t\t\t\t\t\t\t\t\tType:            ast.TypePluginServiceEnter,\n\t\t\t\t\t\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),\n\t\t\t\t\t\t\t\t\tStructName:      info.StructName,\n\t\t\t\t\t\t\t\t\tStructCamelName: info.Abbreviation,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tasts[pluginServiceEnter.Path+\"=>\"+pluginServiceEnter.Type.String()] = pluginServiceEnter\n\t\t\t\t\t\t\t\tcreates[four] = pluginServiceEnter.Path\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t} // enter.go\n\t\t\t\t\t\tcreate := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+\".go\")\n\t\t\t\t\t\tcode[four] = create\n\t\t\t\t\t}\n\t\t\t\tcase \"gen\", \"config\", \"initialize\", \"plugin\", \"response\":\n\t\t\t\t\tif entity.Template == \"package\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t} // package模板不需要生成gen, config, initialize\n\t\t\t\t\tvar threeDirs []os.DirEntry\n\t\t\t\t\tthreeDirs, err = os.ReadDir(three)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", three)\n\t\t\t\t\t}\n\t\t\t\t\tfor k := 0; k < len(threeDirs); k++ {\n\t\t\t\t\t\tif threeDirs[k].Name() == \".DS_Store\" {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfour := filepath.Join(three, threeDirs[k].Name())\n\t\t\t\t\t\tif threeDirs[k].IsDir() {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件夹!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\text := filepath.Ext(four)\n\t\t\t\t\t\tif ext != \".tpl\" {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版后缀!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgen := strings.Index(threeDirs[k].Name(), \"gen\")\n\t\t\t\t\t\tapi := strings.Index(threeDirs[k].Name(), \"api\")\n\t\t\t\t\t\tmenu := strings.Index(threeDirs[k].Name(), \"menu\")\n\t\t\t\t\t\tviper := strings.Index(threeDirs[k].Name(), \"viper\")\n\t\t\t\t\t\tplugin := strings.Index(threeDirs[k].Name(), \"plugin\")\n\t\t\t\t\t\tconfig := strings.Index(threeDirs[k].Name(), \"config\")\n\t\t\t\t\t\trouter := strings.Index(threeDirs[k].Name(), \"router\")\n\t\t\t\t\t\thasGorm := strings.Index(threeDirs[k].Name(), \"gorm\")\n\t\t\t\t\t\tresponse := strings.Index(threeDirs[k].Name(), \"response\")\n\t\t\t\t\t\tdictionary := strings.Index(threeDirs[k].Name(), \"dictionary\")\n\t\t\t\t\t\tif gen != -1 && api != -1 && menu != -1 && viper != -1 && plugin != -1 && config != -1 && router != -1 && hasGorm != -1 && response != -1 && dictionary != -1 {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif api != -1 || menu != -1 || viper != -1 || response != -1 || plugin != -1 || config != -1 || dictionary != -1 {\n\t\t\t\t\t\t\tcreates[four] = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif gen != -1 {\n\t\t\t\t\t\t\tpluginGen := &ast.PluginGen{\n\t\t\t\t\t\t\t\tType:        ast.TypePluginGen,\n\t\t\t\t\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),\n\t\t\t\t\t\t\t\tImportPath:  fmt.Sprintf(`\"%s/plugin/%s/model\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\t\tStructName:  info.StructName,\n\t\t\t\t\t\t\t\tPackageName: \"model\",\n\t\t\t\t\t\t\t\tIsNew:       true,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tasts[pluginGen.Path+\"=>\"+pluginGen.Type.String()] = pluginGen\n\t\t\t\t\t\t\tcreates[four] = pluginGen.Path\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif hasGorm != -1 {\n\t\t\t\t\t\t\tpluginInitializeGorm := &ast.PluginInitializeGorm{\n\t\t\t\t\t\t\t\tType:        ast.TypePluginInitializeGorm,\n\t\t\t\t\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),\n\t\t\t\t\t\t\t\tImportPath:  fmt.Sprintf(`\"%s/plugin/%s/model\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\t\tStructName:  info.StructName,\n\t\t\t\t\t\t\t\tPackageName: \"model\",\n\t\t\t\t\t\t\t\tIsNew:       true,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tasts[pluginInitializeGorm.Path+\"=>\"+pluginInitializeGorm.Type.String()] = pluginInitializeGorm\n\t\t\t\t\t\t\tcreates[four] = pluginInitializeGorm.Path\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif router != -1 {\n\t\t\t\t\t\t\tpluginInitializeRouter := &ast.PluginInitializeRouter{\n\t\t\t\t\t\t\t\tType:                 ast.TypePluginInitializeRouter,\n\t\t\t\t\t\t\t\tPath:                 filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), strings.TrimSuffix(threeDirs[k].Name(), ext)),\n\t\t\t\t\t\t\t\tImportPath:           fmt.Sprintf(`\"%s/plugin/%s/router\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\t\tAppName:              \"Router\",\n\t\t\t\t\t\t\t\tGroupName:            info.StructName,\n\t\t\t\t\t\t\t\tPackageName:          \"router\",\n\t\t\t\t\t\t\t\tFunctionName:         \"Init\",\n\t\t\t\t\t\t\t\tLeftRouterGroupName:  \"public\",\n\t\t\t\t\t\t\t\tRightRouterGroupName: \"private\",\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tasts[pluginInitializeRouter.Path+\"=>\"+pluginInitializeRouter.Type.String()] = pluginInitializeRouter\n\t\t\t\t\t\t\tcreates[four] = pluginInitializeRouter.Path\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase \"model\":\n\t\t\t\t\tvar threeDirs []os.DirEntry\n\t\t\t\t\tthreeDirs, err = os.ReadDir(three)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", three)\n\t\t\t\t\t}\n\t\t\t\t\tfor k := 0; k < len(threeDirs); k++ {\n\t\t\t\t\t\tif threeDirs[k].Name() == \".DS_Store\" {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfour := filepath.Join(three, threeDirs[k].Name())\n\t\t\t\t\t\tif threeDirs[k].IsDir() {\n\t\t\t\t\t\t\tvar fourDirs []os.DirEntry\n\t\t\t\t\t\t\tfourDirs, err = os.ReadDir(four)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", four)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor l := 0; l < len(fourDirs); l++ {\n\t\t\t\t\t\t\t\tif fourDirs[l].Name() == \".DS_Store\" {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfive := filepath.Join(four, fourDirs[l].Name())\n\t\t\t\t\t\t\t\tif fourDirs[l].IsDir() {\n\t\t\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件夹!\", five)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\text := filepath.Ext(five)\n\t\t\t\t\t\t\t\tif ext != \".tpl\" {\n\t\t\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版后缀!\", five)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\thasRequest := strings.Index(fourDirs[l].Name(), \"request\")\n\t\t\t\t\t\t\t\tif hasRequest == -1 {\n\t\t\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", five)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcreate := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), threeDirs[k].Name(), info.HumpPackageName+\".go\")\n\t\t\t\t\t\t\t\tif entity.Template == \"package\" {\n\t\t\t\t\t\t\t\t\tcreate = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, threeDirs[k].Name(), info.HumpPackageName+\".go\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcode[five] = create\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\text := filepath.Ext(threeDirs[k].Name())\n\t\t\t\t\t\tif ext != \".tpl\" {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版后缀!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\thasModel := strings.Index(threeDirs[k].Name(), \"model\")\n\t\t\t\t\t\tif hasModel == -1 {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcreate := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", entity.PackageName, secondDirs[j].Name(), info.HumpPackageName+\".go\")\n\t\t\t\t\t\tif entity.Template == \"package\" {\n\t\t\t\t\t\t\tpackageInitializeGorm := &ast.PackageInitializeGorm{\n\t\t\t\t\t\t\t\tType:        ast.TypePackageInitializeGorm,\n\t\t\t\t\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\t\t\t\t\tImportPath:  fmt.Sprintf(`\"%s/model/%s\"`, global.GVA_CONFIG.AutoCode.Module, entity.PackageName),\n\t\t\t\t\t\t\t\tBusiness:    info.BusinessDB,\n\t\t\t\t\t\t\t\tStructName:  info.StructName,\n\t\t\t\t\t\t\t\tPackageName: entity.PackageName,\n\t\t\t\t\t\t\t\tIsNew:       true,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcode[four] = packageInitializeGorm.Path\n\t\t\t\t\t\t\tasts[packageInitializeGorm.Path+\"=>\"+packageInitializeGorm.Type.String()] = packageInitializeGorm\n\t\t\t\t\t\t\tcreate = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, secondDirs[j].Name(), entity.PackageName, info.HumpPackageName+\".go\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcode[four] = create\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件夹!\", three)\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"web\":\n\t\t\tif !info.GenerateWeb && !isPackage {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvar secondDirs []os.DirEntry\n\t\t\tsecondDirs, err = os.ReadDir(second)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", second)\n\t\t\t}\n\t\t\tfor j := 0; j < len(secondDirs); j++ {\n\t\t\t\tif secondDirs[j].Name() == \".DS_Store\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthree := filepath.Join(second, secondDirs[j].Name())\n\t\t\t\tif !secondDirs[j].IsDir() {\n\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", three)\n\t\t\t\t}\n\t\t\t\tswitch secondDirs[j].Name() {\n\t\t\t\tcase \"api\", \"form\", \"view\", \"table\":\n\t\t\t\t\tvar threeDirs []os.DirEntry\n\t\t\t\t\tthreeDirs, err = os.ReadDir(three)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"读取模版文件夹[%s]失败!\", three)\n\t\t\t\t\t}\n\t\t\t\t\tfor k := 0; k < len(threeDirs); k++ {\n\t\t\t\t\t\tif threeDirs[k].Name() == \".DS_Store\" {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfour := filepath.Join(three, threeDirs[k].Name())\n\t\t\t\t\t\tif threeDirs[k].IsDir() {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件夹!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\text := filepath.Ext(four)\n\t\t\t\t\t\tif ext != \".tpl\" {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版后缀!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tapi := strings.Index(threeDirs[k].Name(), \"api\")\n\t\t\t\t\t\tform := strings.Index(threeDirs[k].Name(), \"form\")\n\t\t\t\t\t\tview := strings.Index(threeDirs[k].Name(), \"view\")\n\t\t\t\t\t\ttable := strings.Index(threeDirs[k].Name(), \"table\")\n\t\t\t\t\t\tif api == -1 && form == -1 && view == -1 && table == -1 {\n\t\t\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", four)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif entity.Template == \"package\" {\n\t\t\t\t\t\t\tif view != -1 || table != -1 {\n\t\t\t\t\t\t\t\tformPath := filepath.Join(three, \"form.vue\"+ext)\n\t\t\t\t\t\t\t\tvalue, ok := code[formPath]\n\t\t\t\t\t\t\t\tif ok {\n\t\t\t\t\t\t\t\t\tvalue = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.PackageName+\"Form\"+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))\n\t\t\t\t\t\t\t\t\tcode[formPath] = value\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcreate := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName, info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))\n\t\t\t\t\t\t\tif api != -1 {\n\t\t\t\t\t\t\t\tcreate = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), secondDirs[j].Name(), entity.PackageName, info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcode[four] = create\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcreate := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.WebRoot(), \"plugin\", entity.PackageName, secondDirs[j].Name(), info.PackageName+filepath.Ext(strings.TrimSuffix(threeDirs[k].Name(), ext)))\n\t\t\t\t\t\tcode[four] = create\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件夹!\", three)\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"readme.txt.tpl\", \"readme.txt.template\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\tif templateDirs[i].Name() == \".DS_Store\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, nil, nil, errors.Errorf(\"[filpath:%s]非法模版文件!\", second)\n\t\t}\n\t}\n\treturn code, asts, creates, nil\n}\n"
  },
  {
    "path": "server/service/system/auto_code_package_test.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\nfunc Test_autoCodePackage_Create(t *testing.T) {\n\ttype args struct {\n\t\tctx  context.Context\n\t\tinfo *request.SysAutoCodePackageCreate\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 package\",\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tinfo: &request.SysAutoCodePackageCreate{\n\t\t\t\t\tTemplate:    \"package\",\n\t\t\t\t\tPackageName: \"gva\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 plugin\",\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tinfo: &request.SysAutoCodePackageCreate{\n\t\t\t\t\tTemplate:    \"plugin\",\n\t\t\t\t\tPackageName: \"gva\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &autoCodePackage{}\n\t\t\tif err := a.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Create() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_autoCodePackage_templates(t *testing.T) {\n\ttype args struct {\n\t\tctx       context.Context\n\t\tentity    model.SysAutoCodePackage\n\t\tinfo      request.AutoCode\n\t\tisPackage bool\n\t}\n\ttests := []struct {\n\t\tname      string\n\t\targs      args\n\t\twantCode  map[string]string\n\t\twantEnter map[string]map[string]string\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\tname: \"测试1\",\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tentity: model.SysAutoCodePackage{\n\t\t\t\t\tDesc:        \"描述\",\n\t\t\t\t\tLabel:       \"展示名\",\n\t\t\t\t\tTemplate:    \"plugin\",\n\t\t\t\t\tPackageName: \"preview\",\n\t\t\t\t},\n\t\t\t\tinfo: request.AutoCode{\n\t\t\t\t\tAbbreviation:    \"user\",\n\t\t\t\t\tHumpPackageName: \"user\",\n\t\t\t\t},\n\t\t\t\tisPackage: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := &autoCodePackage{}\n\t\t\tgotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info, tt.args.isPackage)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"templates() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor key, value := range gotCode {\n\t\t\t\tt.Logf(\"\\n\")\n\t\t\t\tt.Logf(key)\n\t\t\t\tt.Logf(value)\n\t\t\t\tt.Logf(\"\\n\")\n\t\t\t}\n\t\t\tt.Log(gotCreates)\n\t\t\tif !reflect.DeepEqual(gotEnter, tt.wantEnter) {\n\t\t\t\tt.Errorf(\"templates() gotEnter = %v, want %v\", gotEnter, tt.wantEnter)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/service/system/auto_code_plugin.go",
    "content": "package system\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\tgoast \"go/ast\"\n\t\"go/parser\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tpluginUtils \"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\tast \"github.com/flipped-aurora/gin-vue-admin/server/utils/ast\"\n\t\"github.com/mholt/archives\"\n\tcp \"github.com/otiai10/copy\"\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/zap\"\n)\n\nvar AutoCodePlugin = new(autoCodePlugin)\n\ntype autoCodePlugin struct{}\n\n// Install 插件安装\nfunc (s *autoCodePlugin) Install(file *multipart.FileHeader) (web, server int, err error) {\n\tconst GVAPLUGPINATH = \"./gva-plug-temp/\"\n\tdefer os.RemoveAll(GVAPLUGPINATH)\n\t_, err = os.Stat(GVAPLUGPINATH)\n\tif os.IsNotExist(err) {\n\t\tos.Mkdir(GVAPLUGPINATH, os.ModePerm)\n\t}\n\n\tsrc, err := file.Open()\n\tif err != nil {\n\t\treturn -1, -1, err\n\t}\n\tdefer src.Close()\n\n\t// 在临时目录创建目标文件\n\t// 使用完整路径拼接的好处：明确文件位置，避免路径混乱\n\tout, err := os.Create(GVAPLUGPINATH + file.Filename)\n\tif err != nil {\n\t\treturn -1, -1, err\n\t}\n\n\t// 将上传的文件内容复制到临时文件\n\t// 使用io.Copy的好处：高效处理大文件，自动管理缓冲区，避免内存溢出\n\t_, err = io.Copy(out, src)\n\tif err != nil {\n\t\tout.Close()\n\t\treturn -1, -1, err\n\t}\n\n\t// 立即关闭文件，确保数据写入磁盘并释放文件句柄\n\t// 必须在解压前关闭，否则在Windows系统上会导致文件被占用无法解压\n\terr = out.Close()\n\tif err != nil {\n\t\treturn -1, -1, err\n\t}\n\n\tpaths, err := utils.Unzip(GVAPLUGPINATH+file.Filename, GVAPLUGPINATH)\n\tpaths = filterFile(paths)\n\tvar webIndex = -1\n\tvar serverIndex = -1\n\twebPlugin := \"\"\n\tserverPlugin := \"\"\n\tserverPackage := \"\"\n\tserverRootName := \"\"\n\n\tfor i := range paths {\n\t\tpaths[i] = filepath.ToSlash(paths[i])\n\t\tpathArr := strings.Split(paths[i], \"/\")\n\t\tln := len(pathArr)\n\n\t\tif ln < 4 {\n\t\t\tcontinue\n\t\t}\n\t\tif pathArr[2]+\"/\"+pathArr[3] == `server/plugin` {\n\t\t\tif len(serverPlugin) == 0 {\n\t\t\t\tserverPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])\n\t\t\t}\n\t\t\tif serverRootName == \"\" && ln > 1 && pathArr[1] != \"\" {\n\t\t\t\tserverRootName = pathArr[1]\n\t\t\t}\n\t\t\tif ln > 4 && serverPackage == \"\" && pathArr[4] != \"\" {\n\t\t\t\tserverPackage = pathArr[4]\n\t\t\t}\n\t\t}\n\t\tif pathArr[2]+\"/\"+pathArr[3] == `web/plugin` && len(webPlugin) == 0 {\n\t\t\twebPlugin = filepath.Join(pathArr[0], pathArr[1], pathArr[2], pathArr[3])\n\t\t}\n\t}\n\tif len(serverPlugin) == 0 && len(webPlugin) == 0 {\n\t\tzap.L().Error(\"非标准插件，请按照文档自动迁移使用\")\n\t\treturn webIndex, serverIndex, errors.New(\"非标准插件，请按照文档自动迁移使用\")\n\t}\n\n\tif len(serverPlugin) != 0 {\n\t\tif serverPackage == \"\" {\n\t\t\tserverPackage = serverRootName\n\t\t}\n\t\terr = installation(serverPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Server)\n\t\tif err != nil {\n\t\t\treturn webIndex, serverIndex, err\n\t\t}\n\t\terr = ensurePluginRegisterImport(serverPackage)\n\t\tif err != nil {\n\t\t\treturn webIndex, serverIndex, err\n\t\t}\n\t}\n\n\tif len(webPlugin) != 0 {\n\t\terr = installation(webPlugin, global.GVA_CONFIG.AutoCode.Server, global.GVA_CONFIG.AutoCode.Web)\n\t\tif err != nil {\n\t\t\treturn webIndex, serverIndex, err\n\t\t}\n\t}\n\n\treturn 1, 1, err\n}\n\nfunc installation(path string, formPath string, toPath string) error {\n\tarr := strings.Split(filepath.ToSlash(path), \"/\")\n\tln := len(arr)\n\tif ln < 3 {\n\t\treturn errors.New(\"arr\")\n\t}\n\tname := arr[ln-3]\n\n\tvar form = filepath.Join(global.GVA_CONFIG.AutoCode.Root, formPath, path)\n\tvar to = filepath.Join(global.GVA_CONFIG.AutoCode.Root, toPath, \"plugin\")\n\t_, err := os.Stat(to + name)\n\tif err == nil {\n\t\tzap.L().Error(\"autoPath 已存在同名插件，请自行手动安装\", zap.String(\"to\", to))\n\t\treturn errors.New(toPath + \"已存在同名插件，请自行手动安装\")\n\t}\n\treturn cp.Copy(form, to, cp.Options{Skip: skipMacSpecialDocument})\n}\n\nfunc ensurePluginRegisterImport(packageName string) error {\n\tmodule := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Module)\n\tif module == \"\" {\n\t\treturn errors.New(\"autocode module is empty\")\n\t}\n\tif packageName == \"\" {\n\t\treturn errors.New(\"plugin package is empty\")\n\t}\n\n\tregisterPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"register.go\")\n\tsrc, err := os.ReadFile(registerPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, registerPath, src, parser.ParseComments)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\timportPath := fmt.Sprintf(\"%s/plugin/%s\", module, packageName)\n\tif ast.CheckImport(astFile, importPath) {\n\t\treturn nil\n\t}\n\n\timportSpec := &goast.ImportSpec{\n\t\tName: goast.NewIdent(\"_\"),\n\t\tPath: &goast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"%q\", importPath)},\n\t}\n\tvar importDecl *goast.GenDecl\n\tfor _, decl := range astFile.Decls {\n\t\tgenDecl, ok := decl.(*goast.GenDecl)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif genDecl.Tok == token.IMPORT {\n\t\t\timportDecl = genDecl\n\t\t\tbreak\n\t\t}\n\t}\n\tif importDecl == nil {\n\t\tastFile.Decls = append([]goast.Decl{\n\t\t\t&goast.GenDecl{\n\t\t\t\tTok:   token.IMPORT,\n\t\t\t\tSpecs: []goast.Spec{importSpec},\n\t\t\t},\n\t\t}, astFile.Decls...)\n\t} else {\n\t\timportDecl.Specs = append(importDecl.Specs, importSpec)\n\t}\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\n\treturn os.WriteFile(registerPath, bf.Bytes(), 0666)\n}\n\nfunc filterFile(paths []string) []string {\n\tnp := make([]string, 0, len(paths))\n\tfor _, path := range paths {\n\t\tif ok, _ := skipMacSpecialDocument(nil, path, \"\"); ok {\n\t\t\tcontinue\n\t\t}\n\t\tnp = append(np, path)\n\t}\n\treturn np\n}\n\nfunc skipMacSpecialDocument(_ os.FileInfo, src, _ string) (bool, error) {\n\tif strings.Contains(src, \".DS_Store\") || strings.Contains(src, \"__MACOSX\") {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\nfunc (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) {\n\tif plugName == \"\" {\n\t\treturn \"\", errors.New(\"插件名称不能为空\")\n\t}\n\n\t// 防止路径穿越\n\tplugName = filepath.Clean(plugName)\n\n\twebPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, \"plugin\", plugName)\n\tserverPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", plugName)\n\t// 创建一个新的zip文件\n\n\t// 判断目录是否存在\n\t_, err = os.Stat(webPath)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"web路径不存在\")\n\t}\n\t_, err = os.Stat(serverPath)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"server路径不存在\")\n\t}\n\n\tfileName := plugName + \".zip\"\n\t// 创建一个新的zip文件\n\tfiles, err := archives.FilesFromDisk(context.Background(), nil, map[string]string{\n\t\twebPath:    plugName + \"/web/plugin/\" + plugName,\n\t\tserverPath: plugName + \"/server/plugin/\" + plugName,\n\t})\n\n\t// create the output file we'll write to\n\tout, err := os.Create(fileName)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer out.Close()\n\n\t// we can use the CompressedArchive type to gzip a tarball\n\t// (compression is not required; you could use Tar directly)\n\tformat := archives.CompressedArchive{\n\t\t//Compression: archives.Gz{},\n\t\tArchival: archives.Zip{},\n\t}\n\n\t// create the archive\n\terr = format.Archive(context.Background(), out, files)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, fileName), nil\n}\n\nfunc (s *autoCodePlugin) InitMenu(menuInfo request.InitMenu) (err error) {\n\tmenuPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", menuInfo.PlugName, \"initialize\", \"menu.go\")\n\tsrc, err := os.ReadFile(menuPath)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tarrayAst := ast.FindArray(astFile, \"model\", \"SysBaseMenu\")\n\tvar menus []system.SysBaseMenu\n\n\tparentMenu := []system.SysBaseMenu{\n\t\t{\n\t\t\tParentId:  0,\n\t\t\tPath:      menuInfo.PlugName + \"Menu\",\n\t\t\tName:      menuInfo.PlugName + \"Menu\",\n\t\t\tHidden:    false,\n\t\t\tComponent: \"view/routerHolder.vue\",\n\t\t\tSort:      0,\n\t\t\tMeta: system.Meta{\n\t\t\t\tTitle: menuInfo.ParentMenu,\n\t\t\t\tIcon:  \"school\",\n\t\t\t},\n\t\t},\n\t}\n\n\t// 查询菜单及其关联的参数和按钮\n\terr = global.GVA_DB.Preload(\"Parameters\").Preload(\"MenuBtn\").Find(&menus, \"id in (?)\", menuInfo.Menus).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tmenus = append(parentMenu, menus...)\n\tmenuExpr := ast.CreateMenuStructAst(menus)\n\tarrayAst.Elts = *menuExpr\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\n\tos.WriteFile(menuPath, bf.Bytes(), 0666)\n\treturn nil\n}\n\nfunc (s *autoCodePlugin) InitAPI(apiInfo request.InitApi) (err error) {\n\tapiPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", apiInfo.PlugName, \"initialize\", \"api.go\")\n\tsrc, err := os.ReadFile(apiPath)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tarrayAst := ast.FindArray(astFile, \"model\", \"SysApi\")\n\tvar apis []system.SysApi\n\terr = global.GVA_DB.Find(&apis, \"id in (?)\", apiInfo.APIs).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tapisExpr := ast.CreateApiStructAst(apis)\n\tarrayAst.Elts = *apisExpr\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\n\tos.WriteFile(apiPath, bf.Bytes(), 0666)\n\treturn nil\n}\n\nfunc (s *autoCodePlugin) InitDictionary(dictInfo request.InitDictionary) (err error) {\n\tdictPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", dictInfo.PlugName, \"initialize\", \"dictionary.go\")\n\tsrc, err := os.ReadFile(dictPath)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tarrayAst := ast.FindArray(astFile, \"model\", \"SysDictionary\")\n\tvar dictionaries []system.SysDictionary\n\terr = global.GVA_DB.Preload(\"SysDictionaryDetails\").Find(&dictionaries, \"id in (?)\", dictInfo.Dictionaries).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tdictExpr := ast.CreateDictionaryStructAst(dictionaries)\n\tarrayAst.Elts = *dictExpr\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\n\tos.WriteFile(dictPath, bf.Bytes(), 0666)\n\treturn nil\n}\n\nfunc (s *autoCodePlugin) Remove(pluginName string, pluginType string) (err error) {\n\t// 1. 删除前端代码\n\tif pluginType == \"web\" || pluginType == \"full\" {\n\t\twebDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, \"plugin\", pluginName)\n\t\terr = os.RemoveAll(webDir)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"删除前端插件目录失败\")\n\t\t}\n\t}\n\n\t// 2. 删除后端代码\n\tif pluginType == \"server\" || pluginType == \"full\" {\n\t\tserverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", pluginName)\n\t\terr = os.RemoveAll(serverDir)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"删除后端插件目录失败\")\n\t\t}\n\n\t\t// 移除注册\n\t\tremovePluginRegisterImport(pluginName)\n\t}\n\n\t// 通过utils 获取 api 菜单 字典\n\tapis, menus, dicts := pluginUtils.GetPluginData(pluginName)\n\n\t// 3. 删除菜单 (递归删除)\n\tif len(menus) > 0 {\n\t\tfor _, menu := range menus {\n\t\t\tvar dbMenu system.SysBaseMenu\n\t\t\tif err := global.GVA_DB.Where(\"name = ?\", menu.Name).First(&dbMenu).Error; err == nil {\n\t\t\t\t// 获取该菜单及其所有子菜单的ID\n\t\t\t\tvar menuIds []int\n\t\t\t\tGetMenuIds(dbMenu, &menuIds)\n\t\t\t\t// 逆序删除，先删除子菜单\n\t\t\t\tfor i := len(menuIds) - 1; i >= 0; i-- {\n\t\t\t\t\terr := BaseMenuServiceApp.DeleteBaseMenu(menuIds[i])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tzap.L().Error(\"删除菜单失败\", zap.Int(\"id\", menuIds[i]), zap.Error(err))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4. 删除API\n\tif len(apis) > 0 {\n\t\tfor _, api := range apis {\n\t\t\tvar dbApi system.SysApi\n\t\t\tif err := global.GVA_DB.Where(\"path = ? AND method = ?\", api.Path, api.Method).First(&dbApi).Error; err == nil {\n\t\t\t\terr := ApiServiceApp.DeleteApi(dbApi)\n\t\t\t\tif err != nil {\n\t\t\t\t\tzap.L().Error(\"删除API失败\", zap.String(\"path\", api.Path), zap.Error(err))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 5. 删除字典\n\tif len(dicts) > 0 {\n\t\tfor _, dict := range dicts {\n\t\t\tvar dbDict system.SysDictionary\n\t\t\tif err := global.GVA_DB.Where(\"type = ?\", dict.Type).First(&dbDict).Error; err == nil {\n\t\t\t\terr := DictionaryServiceApp.DeleteSysDictionary(dbDict)\n\t\t\t\tif err != nil {\n\t\t\t\t\tzap.L().Error(\"删除字典失败\", zap.String(\"type\", dict.Type), zap.Error(err))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc GetMenuIds(menu system.SysBaseMenu, ids *[]int) {\n\t*ids = append(*ids, int(menu.ID))\n\tvar children []system.SysBaseMenu\n\tglobal.GVA_DB.Where(\"parent_id = ?\", menu.ID).Find(&children)\n\tfor _, child := range children {\n        // 先递归收集子菜单\n\t\tGetMenuIds(child, ids)\n\t}\n}\n\nfunc removePluginRegisterImport(packageName string) error {\n\tmodule := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Module)\n\tif module == \"\" {\n\t\treturn errors.New(\"autocode module is empty\")\n\t}\n\tif packageName == \"\" {\n\t\treturn errors.New(\"plugin package is empty\")\n\t}\n\n\tregisterPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"register.go\")\n\tsrc, err := os.ReadFile(registerPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, registerPath, src, parser.ParseComments)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\timportPath := fmt.Sprintf(\"%s/plugin/%s\", module, packageName)\n    importLit := fmt.Sprintf(\"%q\", importPath)\n\n    // 移除 import\n\tvar newDecls []goast.Decl\n\tfor _, decl := range astFile.Decls {\n\t\tgenDecl, ok := decl.(*goast.GenDecl)\n\t\tif !ok {\n\t\t\tnewDecls = append(newDecls, decl)\n\t\t\tcontinue\n\t\t}\n\t\tif genDecl.Tok == token.IMPORT {\n\t\t\tvar newSpecs []goast.Spec\n\t\t\tfor _, spec := range genDecl.Specs {\n\t\t\t\timportSpec, ok := spec.(*goast.ImportSpec)\n\t\t\t\tif !ok {\n\t\t\t\t\tnewSpecs = append(newSpecs, spec)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif importSpec.Path.Value != importLit {\n\t\t\t\t\tnewSpecs = append(newSpecs, spec)\n\t\t\t\t}\n\t\t\t}\n            // 如果还有其他import，保留该 decl\n\t\t\tif len(newSpecs) > 0 {\n\t\t\t\tgenDecl.Specs = newSpecs\n\t\t\t\tnewDecls = append(newDecls, genDecl)\n\t\t\t}\n\t\t} else {\n\t\t\tnewDecls = append(newDecls, decl)\n\t\t}\n\t}\n\tastFile.Decls = newDecls\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\n\treturn os.WriteFile(registerPath, bf.Bytes(), 0666)\n}\n"
  },
  {
    "path": "server/service/system/auto_code_template.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils/autocode\"\n\t\"go/ast\"\n\t\"go/format\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\tmodel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tutilsAst \"github.com/flipped-aurora/gin-vue-admin/server/utils/ast\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nvar AutoCodeTemplate = new(autoCodeTemplate)\n\ntype autoCodeTemplate struct{}\n\nfunc (s *autoCodeTemplate) checkPackage(Pkg string, template string) (err error) {\n\tswitch template {\n\tcase \"package\":\n\t\tapiEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", Pkg, \"enter.go\")\n\t\t_, err = os.Stat(apiEnter)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"package结构异常,缺少api/v1/%s/enter.go\", Pkg)\n\t\t}\n\t\tserviceEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", Pkg, \"enter.go\")\n\t\t_, err = os.Stat(serviceEnter)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"package结构异常,缺少service/%s/enter.go\", Pkg)\n\t\t}\n\t\trouterEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", Pkg, \"enter.go\")\n\t\t_, err = os.Stat(routerEnter)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"package结构异常,缺少router/%s/enter.go\", Pkg)\n\t\t}\n\tcase \"plugin\":\n\t\tpluginEnter := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", Pkg, \"plugin.go\")\n\t\t_, err = os.Stat(pluginEnter)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"plugin结构异常,缺少plugin/%s/plugin.go\", Pkg)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Create 创建生成自动化代码\nfunc (s *autoCodeTemplate) Create(ctx context.Context, info request.AutoCode) error {\n\thistory := info.History()\n\tvar autoPkg model.SysAutoCodePackage\n\terr := global.GVA_DB.WithContext(ctx).Where(\"package_name = ?\", info.Package).First(&autoPkg).Error\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"查询包失败!\")\n\t}\n\terr = s.checkPackage(info.Package, autoPkg.Template)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// 增加判断: 重复创建struct 或者重复的简称\n\tif AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) {\n\t\treturn errors.New(\"已经创建过此数据结构,请勿重复创建!\")\n\t}\n\n\tgenerate, templates, injections, err := s.generate(ctx, info, autoPkg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor key, builder := range generate {\n\t\terr = os.MkdirAll(filepath.Dir(key), os.ModePerm)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"[filepath:%s]创建文件夹失败!\", key)\n\t\t}\n\t\terr = os.WriteFile(key, []byte(builder.String()), 0666)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"[filepath:%s]写入文件失败!\", key)\n\t\t}\n\t}\n\n\t// 自动创建api\n\tif info.AutoCreateApiToSql && !info.OnlyTemplate {\n\t\tapis := info.Apis()\n\t\terr := global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {\n\t\t\tfor _, v := range apis {\n\t\t\t\tvar api model.SysApi\n\t\t\t\tvar id uint\n\t\t\t\terr := tx.Where(\"path = ? AND method = ?\", v.Path, v.Method).First(&api).Error\n\t\t\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\t\t\tif err = tx.Create(&v).Error; err != nil { // 遇到错误时回滚事务\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tid = v.ID\n\t\t\t\t} else {\n\t\t\t\t\tid = api.ID\n\t\t\t\t}\n\t\t\t\thistory.ApiIDs = append(history.ApiIDs, id)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// 自动创建menu\n\tif info.AutoCreateMenuToSql {\n\t\tvar entity model.SysBaseMenu\n\t\tvar id uint\n\t\terr := global.GVA_DB.WithContext(ctx).First(&entity, \"name = ?\", info.Abbreviation).Error\n\t\tif err == nil {\n\t\t\tid = entity.ID\n\t\t} else {\n\t\t\tentity = info.Menu(autoPkg.Template)\n\t\t\tif info.AutoCreateBtnAuth && !info.OnlyTemplate {\n\t\t\t\tentity.MenuBtn = []model.SysBaseMenuBtn{\n\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"add\", Desc: \"新增\"},\n\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"batchDelete\", Desc: \"批量删除\"},\n\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"delete\", Desc: \"删除\"},\n\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"edit\", Desc: \"编辑\"},\n\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"info\", Desc: \"详情\"},\n\t\t\t\t}\n\t\t\t\tif info.HasExcel {\n\t\t\t\t\texcelBtn := []model.SysBaseMenuBtn{\n\t\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"exportTemplate\", Desc: \"导出模板\"},\n\t\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"exportExcel\", Desc: \"导出Excel\"},\n\t\t\t\t\t\t{SysBaseMenuID: entity.ID, Name: \"importExcel\", Desc: \"导入Excel\"},\n\t\t\t\t\t}\n\t\t\t\t\tentity.MenuBtn = append(entity.MenuBtn, excelBtn...)\n\t\t\t\t}\n\t\t\t}\n\t\t\terr = global.GVA_DB.WithContext(ctx).Create(&entity).Error\n\t\t\tid = entity.ID\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"创建菜单失败!\")\n\t\t\t}\n\t\t}\n\t\thistory.MenuID = id\n\t}\n\n\tif info.HasExcel {\n\t\tdbName := info.BusinessDB\n\t\tname := info.Package + \"_\" + info.StructName\n\t\ttableName := info.TableName\n\t\tfieldsMap := make(map[string]string, len(info.Fields))\n\t\tfor _, field := range info.Fields {\n\t\t\tif field.Excel {\n\t\t\t\tfieldsMap[field.ColumnName] = field.FieldDesc\n\t\t\t}\n\t\t}\n\t\ttemplateInfo, _ := json.Marshal(fieldsMap)\n\t\tsysExportTemplate := model.SysExportTemplate{\n\t\t\tDBName:       dbName,\n\t\t\tName:         name,\n\t\t\tTableName:    tableName,\n\t\t\tTemplateID:   name,\n\t\t\tTemplateInfo: string(templateInfo),\n\t\t}\n\t\terr = SysExportTemplateServiceApp.CreateSysExportTemplate(&sysExportTemplate)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\thistory.ExportTemplateID = sysExportTemplate.ID\n\t}\n\n\t// 创建历史记录\n\thistory.Templates = templates\n\thistory.Injections = make(map[string]string, len(injections))\n\tfor key, value := range injections {\n\t\tbytes, _ := json.Marshal(value)\n\t\thistory.Injections[key] = string(bytes)\n\t}\n\terr = AutocodeHistory.Create(ctx, history)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Preview 预览自动化代码\nfunc (s *autoCodeTemplate) Preview(ctx context.Context, info request.AutoCode) (map[string]string, error) {\n\tvar entity model.SysAutoCodePackage\n\terr := global.GVA_DB.WithContext(ctx).Where(\"package_name = ?\", info.Package).First(&entity).Error\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"查询包失败!\")\n\t}\n\t// 增加判断: 重复创建struct 或者重复的简称\n\tif AutocodeHistory.Repeat(info.BusinessDB, info.StructName, info.Abbreviation, info.Package) && !info.IsAdd {\n\t\treturn nil, errors.New(\"已经创建过此数据结构或重复简称,请勿重复创建!\")\n\t}\n\n\tpreview := make(map[string]string)\n\tcodes, _, _, err := s.generate(ctx, info, entity)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor key, writer := range codes {\n\t\tif len(key) > len(global.GVA_CONFIG.AutoCode.Root) {\n\t\t\tkey, _ = filepath.Rel(global.GVA_CONFIG.AutoCode.Root, key)\n\t\t}\n\t\t// 获取key的后缀 取消.\n\t\tsuffix := filepath.Ext(key)[1:]\n\t\tvar builder strings.Builder\n\t\tbuilder.WriteString(\"```\" + suffix + \"\\n\\n\")\n\t\tbuilder.WriteString(writer.String())\n\t\tbuilder.WriteString(\"\\n\\n```\")\n\t\tpreview[key] = builder.String()\n\t}\n\treturn preview, nil\n}\n\nfunc (s *autoCodeTemplate) generate(ctx context.Context, info request.AutoCode, entity model.SysAutoCodePackage) (map[string]strings.Builder, map[string]string, map[string]utilsAst.Ast, error) {\n\ttemplates, asts, _, err := AutoCodePackage.templates(ctx, entity, info, false)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\tcode := make(map[string]strings.Builder)\n\tfor key, create := range templates {\n\t\tvar files *template.Template\n\t\tfiles, err = template.New(filepath.Base(key)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(key)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"[filpath:%s]读取模版文件失败!\", key)\n\t\t}\n\t\tvar builder strings.Builder\n\t\terr = files.Execute(&builder, info)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, errors.Wrapf(err, \"[filpath:%s]生成文件失败!\", create)\n\t\t}\n\t\tcode[create] = builder\n\t} // 生成文件\n\tinjections := make(map[string]utilsAst.Ast, len(asts))\n\tfor key, value := range asts {\n\t\tkeys := strings.Split(key, \"=>\")\n\t\tif len(keys) == 2 {\n\t\t\tif keys[1] == utilsAst.TypePluginInitializeV2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif info.OnlyTemplate {\n\t\t\t\tif keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !info.AutoMigrate {\n\t\t\t\tif keys[1] == utilsAst.TypePackageInitializeGorm || keys[1] == utilsAst.TypePluginInitializeGorm {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar builder strings.Builder\n\t\t\tparse, _ := value.Parse(\"\", &builder)\n\t\t\tif parse != nil {\n\t\t\t\t_ = value.Injection(parse)\n\t\t\t\terr = value.Format(\"\", &builder, parse)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, nil, nil, err\n\t\t\t\t}\n\t\t\t\tcode[keys[0]] = builder\n\t\t\t\tinjections[keys[1]] = value\n\t\t\t\tfmt.Println(keys[0], \"注入成功!\")\n\t\t\t}\n\t\t}\n\t}\n\t// 注入代码\n\treturn code, templates, injections, nil\n}\n\nfunc (s *autoCodeTemplate) AddFunc(info request.AutoFunc) error {\n\tautoPkg := model.SysAutoCodePackage{}\n\terr := global.GVA_DB.First(&autoPkg, \"package_name = ?\", info.Package).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tif autoPkg.Template != \"package\" {\n\t\tinfo.IsPlugin = true\n\t}\n\terr = s.addTemplateToFile(\"api.go\", info)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = s.addTemplateToFile(\"server.go\", info)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = s.addTemplateToFile(\"api.js\", info)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.addTemplateToAst(\"router\", info)\n}\n\nfunc (s *autoCodeTemplate) GetApiAndServer(info request.AutoFunc) (map[string]string, error) {\n\tautoPkg := model.SysAutoCodePackage{}\n\terr := global.GVA_DB.First(&autoPkg, \"package_name = ?\", info.Package).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif autoPkg.Template != \"package\" {\n\t\tinfo.IsPlugin = true\n\t}\n\n\tapiStr, err := s.getTemplateStr(\"api.go\", info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserverStr, err := s.getTemplateStr(\"server.go\", info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tjsStr, err := s.getTemplateStr(\"api.js\", info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\"api\": apiStr, \"server\": serverStr, \"js\": jsStr}, nil\n\n}\n\nfunc (s *autoCodeTemplate) getTemplateStr(t string, info request.AutoFunc) (string, error) {\n\ttempPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"resource\", \"function\", t+\".tpl\")\n\tfiles, err := template.New(filepath.Base(tempPath)).Funcs(autocode.GetTemplateFuncMap()).ParseFiles(tempPath)\n\tif err != nil {\n\t\treturn \"\", errors.Wrapf(err, \"[filepath:%s]读取模版文件失败!\", tempPath)\n\t}\n\tvar builder strings.Builder\n\terr = files.Execute(&builder, info)\n\tif err != nil {\n\t\tfmt.Println(err.Error())\n\t\treturn \"\", errors.Wrapf(err, \"[filpath:%s]生成文件失败!\", tempPath)\n\t}\n\treturn builder.String(), nil\n}\n\nfunc (s *autoCodeTemplate) addTemplateToAst(t string, info request.AutoFunc) error {\n\ttPath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", info.Package, info.HumpPackageName+\".go\")\n\tfuncName := fmt.Sprintf(\"Init%sRouter\", info.StructName)\n\n\trouterStr := \"RouterWithoutAuth\"\n\tif info.IsAuth {\n\t\trouterStr = \"Router\"\n\t}\n\n\tstmtStr := fmt.Sprintf(\"%s%s.%s(\\\"%s\\\", %sApi.%s)\", info.Abbreviation, routerStr, info.Method, info.Router, info.Abbreviation, info.FuncName)\n\tif info.IsPlugin {\n\t\ttPath = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", info.Package, \"router\", info.HumpPackageName+\".go\")\n\t\tstmtStr = fmt.Sprintf(\"group.%s(\\\"%s\\\", api%s.%s)\", info.Method, info.Router, info.StructName, info.FuncName)\n\t\tfuncName = \"Init\"\n\t}\n\n\tsrc, err := os.ReadFile(tPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfuncDecl := utilsAst.FindFunction(astFile, funcName)\n\tstmtNode := utilsAst.CreateStmt(stmtStr)\n\n\tif info.IsAuth {\n\t\tfor i := 0; i < len(funcDecl.Body.List); i++ {\n\t\t\tst := funcDecl.Body.List[i]\n\t\t\t// 使用类型断言来检查stmt是否是一个块语句\n\t\t\tif blockStmt, ok := st.(*ast.BlockStmt); ok {\n\t\t\t\t// 如果是，插入代码 跳出\n\t\t\t\tblockStmt.List = append(blockStmt.List, stmtNode)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor i := len(funcDecl.Body.List) - 1; i >= 0; i-- {\n\t\t\tst := funcDecl.Body.List[i]\n\t\t\t// 使用类型断言来检查stmt是否是一个块语句\n\t\t\tif blockStmt, ok := st.(*ast.BlockStmt); ok {\n\t\t\t\t// 如果是，插入代码 跳出\n\t\t\t\tblockStmt.List = append(blockStmt.List, stmtNode)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// 创建一个新的文件\n\tf, err := os.Create(tPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tif err := format.Node(f, fileSet, astFile); err != nil {\n\t\treturn err\n\t}\n\treturn err\n}\n\nfunc (s *autoCodeTemplate) addTemplateToFile(t string, info request.AutoFunc) error {\n\tgetTemplateStr, err := s.getTemplateStr(t, info)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar target string\n\n\tswitch t {\n\tcase \"api.go\":\n\t\tif info.IsAi && info.ApiFunc != \"\" {\n\t\t\tgetTemplateStr = info.ApiFunc\n\t\t}\n\t\ttarget = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", info.Package, info.HumpPackageName+\".go\")\n\tcase \"server.go\":\n\t\tif info.IsAi && info.ServerFunc != \"\" {\n\t\t\tgetTemplateStr = info.ServerFunc\n\t\t}\n\t\ttarget = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", info.Package, info.HumpPackageName+\".go\")\n\tcase \"api.js\":\n\t\tif info.IsAi && info.JsFunc != \"\" {\n\t\t\tgetTemplateStr = info.JsFunc\n\t\t}\n\t\ttarget = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, \"api\", info.Package, info.PackageName+\".js\")\n\t}\n\tif info.IsPlugin {\n\t\tswitch t {\n\t\tcase \"api.go\":\n\t\t\ttarget = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", info.Package, \"api\", info.HumpPackageName+\".go\")\n\t\tcase \"server.go\":\n\t\t\ttarget = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", info.Package, \"service\", info.HumpPackageName+\".go\")\n\t\tcase \"api.js\":\n\t\t\ttarget = filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, \"plugin\", info.Package, \"api\", info.PackageName+\".js\")\n\t\t}\n\t}\n\n\t// 打开文件，如果不存在则返回错误\n\tfile, err := os.OpenFile(target, os.O_WRONLY|os.O_APPEND, 0644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\t// 写入内容\n\t_, err = fmt.Fprintln(file, getTemplateStr)\n\tif err != nil {\n\t\tfmt.Printf(\"写入文件失败: %s\\n\", err.Error())\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/service/system/auto_code_template_test.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc Test_autoCodeTemplate_Create(t *testing.T) {\n\ttype args struct {\n\t\tctx  context.Context\n\t\tinfo request.AutoCode\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t// TODO: Add test cases.\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := &autoCodeTemplate{}\n\t\t\tif err := s.Create(tt.args.ctx, tt.args.info); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Create() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_autoCodeTemplate_Preview(t *testing.T) {\n\ttype args struct {\n\t\tctx  context.Context\n\t\tinfo request.AutoCode\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    map[string]string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 package\",\n\t\t\targs: args{\n\t\t\t\tctx:  context.Background(),\n\t\t\t\tinfo: request.AutoCode{},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 plugin\",\n\t\t\targs: args{\n\t\t\t\tctx:  context.Background(),\n\t\t\t\tinfo: request.AutoCode{},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttestJson := `{\"structName\":\"SysUser\",\"tableName\":\"sys_users\",\"packageName\":\"sysUsers\",\"package\":\"gva\",\"abbreviation\":\"sysUsers\",\"description\":\"sysUsers表\",\"businessDB\":\"\",\"autoCreateApiToSql\":true,\"autoCreateMenuToSql\":true,\"autoMigrate\":true,\"gvaModel\":true,\"autoCreateResource\":false,\"fields\":[{\"fieldName\":\"Uuid\",\"fieldDesc\":\"用户UUID\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"uuid\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"uuid\",\"comment\":\"用户UUID\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"Username\",\"fieldDesc\":\"用户登录名\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"username\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"username\",\"comment\":\"用户登录名\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"Password\",\"fieldDesc\":\"用户登录密码\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"password\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"password\",\"comment\":\"用户登录密码\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"NickName\",\"fieldDesc\":\"用户昵称\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"nickName\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"nick_name\",\"comment\":\"用户昵称\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"SideMode\",\"fieldDesc\":\"用户侧边主题\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"sideMode\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"side_mode\",\"comment\":\"用户侧边主题\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"HeaderImg\",\"fieldDesc\":\"用户头像\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"headerImg\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"header_img\",\"comment\":\"用户头像\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"BaseColor\",\"fieldDesc\":\"基础颜色\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"baseColor\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"base_color\",\"comment\":\"基础颜色\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"AuthorityId\",\"fieldDesc\":\"用户角色ID\",\"fieldType\":\"int\",\"dataType\":\"bigint\",\"fieldJson\":\"authorityId\",\"primaryKey\":false,\"dataTypeLong\":\"20\",\"columnName\":\"authority_id\",\"comment\":\"用户角色ID\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"Phone\",\"fieldDesc\":\"用户手机号\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"phone\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"phone\",\"comment\":\"用户手机号\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"Email\",\"fieldDesc\":\"用户邮箱\",\"fieldType\":\"string\",\"dataType\":\"varchar\",\"fieldJson\":\"email\",\"primaryKey\":false,\"dataTypeLong\":\"191\",\"columnName\":\"email\",\"comment\":\"用户邮箱\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}},{\"fieldName\":\"Enable\",\"fieldDesc\":\"用户是否被冻结 1正常 2冻结\",\"fieldType\":\"int\",\"dataType\":\"bigint\",\"fieldJson\":\"enable\",\"primaryKey\":false,\"dataTypeLong\":\"19\",\"columnName\":\"enable\",\"comment\":\"用户是否被冻结 1正常 2冻结\",\"require\":false,\"errorText\":\"\",\"clearable\":true,\"fieldSearchType\":\"\",\"fieldIndexType\":\"\",\"dictType\":\"\",\"front\":true,\"dataSource\":{\"association\":1,\"table\":\"\",\"label\":\"\",\"value\":\"\"}}],\"humpPackageName\":\"sys_users\"}`\n\t\t\terr := json.Unmarshal([]byte(testJson), &tt.args.info)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = tt.args.info.Pretreatment()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgot, err := AutoCodeTemplate.Preview(tt.args.ctx, tt.args.info)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Preview() error = %+v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Preview() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/service/system/enter.go",
    "content": "package system\n\ntype ServiceGroup struct {\n\tJwtService\n\tApiService\n\tMenuService\n\tUserService\n\tCasbinService\n\tInitDBService\n\tAutoCodeService\n\tBaseMenuService\n\tAuthorityService\n\tDictionaryService\n\tSystemConfigService\n\tOperationRecordService\n\tDictionaryDetailService\n\tAuthorityBtnService\n\tSysExportTemplateService\n\tSysParamsService\n\tSysVersionService\n\tSkillsService\n\tAutoCodePlugin   autoCodePlugin\n\tAutoCodePackage  autoCodePackage\n\tAutoCodeHistory  autoCodeHistory\n\tAutoCodeTemplate autoCodeTemplate\n\tSysErrorService\n\tLoginLogService\n\tApiTokenService\n}\n"
  },
  {
    "path": "server/service/system/jwt_black_list.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n)\n\ntype JwtService struct{}\n\nvar JwtServiceApp = new(JwtService)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: JsonInBlacklist\n//@description: 拉黑jwt\n//@param: jwtList model.JwtBlacklist\n//@return: err error\n\nfunc (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err error) {\n\terr = global.GVA_DB.Create(&jwtList).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tglobal.BlackCache.SetDefault(jwtList.Jwt, struct{}{})\n\treturn\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetRedisJWT\n//@description: 从redis取jwt\n//@param: userName string\n//@return: redisJWT string, err error\n\nfunc (jwtService *JwtService) GetRedisJWT(userName string) (redisJWT string, err error) {\n\tredisJWT, err = global.GVA_REDIS.Get(context.Background(), userName).Result()\n\treturn redisJWT, err\n}\n\nfunc LoadAll() {\n\tvar data []string\n\terr := global.GVA_DB.Model(&system.JwtBlacklist{}).Select(\"jwt\").Find(&data).Error\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"加载数据库jwt黑名单失败!\", zap.Error(err))\n\t\treturn\n\t}\n\tfor i := 0; i < len(data); i++ {\n\t\tglobal.BlackCache.SetDefault(data[i], struct{}{})\n\t} // jwt黑名单 加入 BlackCache 中\n}\n"
  },
  {
    "path": "server/service/system/sys_api.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemRes \"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"gorm.io/gorm\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateApi\n//@description: 新增基础api\n//@param: api model.SysApi\n//@return: err error\n\ntype ApiService struct{}\n\nvar ApiServiceApp = new(ApiService)\n\nfunc (apiService *ApiService) CreateApi(api system.SysApi) (err error) {\n\tif !errors.Is(global.GVA_DB.Where(\"path = ? AND method = ?\", api.Path, api.Method).First(&system.SysApi{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"存在相同api\")\n\t}\n\treturn global.GVA_DB.Create(&api).Error\n}\n\nfunc (apiService *ApiService) GetApiGroups() (groups []string, groupApiMap map[string]string, err error) {\n\tvar apis []system.SysApi\n\terr = global.GVA_DB.Find(&apis).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tgroupApiMap = make(map[string]string, 0)\n\tfor i := range apis {\n\t\tpathArr := strings.Split(apis[i].Path, \"/\")\n\t\tnewGroup := true\n\t\tfor i2 := range groups {\n\t\t\tif groups[i2] == apis[i].ApiGroup {\n\t\t\t\tnewGroup = false\n\t\t\t}\n\t\t}\n\t\tif newGroup {\n\t\t\tgroups = append(groups, apis[i].ApiGroup)\n\t\t}\n\t\tgroupApiMap[pathArr[1]] = apis[i].ApiGroup\n\t}\n\treturn\n}\n\nfunc (apiService *ApiService) SyncApi() (newApis, deleteApis, ignoreApis []system.SysApi, err error) {\n\tnewApis = make([]system.SysApi, 0)\n\tdeleteApis = make([]system.SysApi, 0)\n\tignoreApis = make([]system.SysApi, 0)\n\tvar apis []system.SysApi\n\terr = global.GVA_DB.Find(&apis).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tvar ignores []system.SysIgnoreApi\n\terr = global.GVA_DB.Find(&ignores).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor i := range ignores {\n\t\tignoreApis = append(ignoreApis, system.SysApi{\n\t\t\tPath:        ignores[i].Path,\n\t\t\tDescription: \"\",\n\t\t\tApiGroup:    \"\",\n\t\t\tMethod:      ignores[i].Method,\n\t\t})\n\t}\n\n\tvar cacheApis []system.SysApi\n\tfor i := range global.GVA_ROUTERS {\n\t\tignoresFlag := false\n\t\tfor j := range ignores {\n\t\t\tif ignores[j].Path == global.GVA_ROUTERS[i].Path && ignores[j].Method == global.GVA_ROUTERS[i].Method {\n\t\t\t\tignoresFlag = true\n\t\t\t}\n\t\t}\n\t\tif !ignoresFlag {\n\t\t\tcacheApis = append(cacheApis, system.SysApi{\n\t\t\t\tPath:   global.GVA_ROUTERS[i].Path,\n\t\t\t\tMethod: global.GVA_ROUTERS[i].Method,\n\t\t\t})\n\t\t}\n\t}\n\n\t//对比数据库中的api和内存中的api，如果数据库中的api不存在于内存中，则把api放入删除数组，如果内存中的api不存在于数据库中，则把api放入新增数组\n\tfor i := range cacheApis {\n\t\tvar flag bool\n\t\t// 如果存在于内存不存在于api数组中\n\t\tfor j := range apis {\n\t\t\tif cacheApis[i].Path == apis[j].Path && cacheApis[i].Method == apis[j].Method {\n\t\t\t\tflag = true\n\t\t\t}\n\t\t}\n\t\tif !flag {\n\t\t\tnewApis = append(newApis, system.SysApi{\n\t\t\t\tPath:        cacheApis[i].Path,\n\t\t\t\tDescription: \"\",\n\t\t\t\tApiGroup:    \"\",\n\t\t\t\tMethod:      cacheApis[i].Method,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor i := range apis {\n\t\tvar flag bool\n\t\t// 如果存在于api数组不存在于内存\n\t\tfor j := range cacheApis {\n\t\t\tif cacheApis[j].Path == apis[i].Path && cacheApis[j].Method == apis[i].Method {\n\t\t\t\tflag = true\n\t\t\t}\n\t\t}\n\t\tif !flag {\n\t\t\tdeleteApis = append(deleteApis, apis[i])\n\t\t}\n\t}\n\treturn\n}\n\nfunc (apiService *ApiService) IgnoreApi(ignoreApi system.SysIgnoreApi) (err error) {\n\tif ignoreApi.Flag {\n\t\treturn global.GVA_DB.Create(&ignoreApi).Error\n\t}\n\treturn global.GVA_DB.Unscoped().Delete(&ignoreApi, \"path = ? AND method = ?\", ignoreApi.Path, ignoreApi.Method).Error\n}\n\nfunc (apiService *ApiService) EnterSyncApi(syncApis systemRes.SysSyncApis) (err error) {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tvar txErr error\n\t\tif len(syncApis.NewApis) > 0 {\n\t\t\ttxErr = tx.Create(&syncApis.NewApis).Error\n\t\t\tif txErr != nil {\n\t\t\t\treturn txErr\n\t\t\t}\n\t\t}\n\t\tfor i := range syncApis.DeleteApis {\n\t\t\tCasbinServiceApp.ClearCasbin(1, syncApis.DeleteApis[i].Path, syncApis.DeleteApis[i].Method)\n\t\t\ttxErr = tx.Delete(&system.SysApi{}, \"path = ? AND method = ?\", syncApis.DeleteApis[i].Path, syncApis.DeleteApis[i].Method).Error\n\t\t\tif txErr != nil {\n\t\t\t\treturn txErr\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteApi\n//@description: 删除基础api\n//@param: api model.SysApi\n//@return: err error\n\nfunc (apiService *ApiService) DeleteApi(api system.SysApi) (err error) {\n\tvar entity system.SysApi\n\terr = global.GVA_DB.First(&entity, \"id = ?\", api.ID).Error // 根据id查询api记录\n\tif errors.Is(err, gorm.ErrRecordNotFound) {                // api记录不存在\n\t\treturn err\n\t}\n\terr = global.GVA_DB.Delete(&entity).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tCasbinServiceApp.ClearCasbin(1, entity.Path, entity.Method)\n\treturn nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetAPIInfoList\n//@description: 分页获取数据,\n//@param: api model.SysApi, info request.PageInfo, order string, desc bool\n//@return: list interface{}, total int64, err error\n\nfunc (apiService *ApiService) GetAPIInfoList(api system.SysApi, info request.PageInfo, order string, desc bool) (list interface{}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\tdb := global.GVA_DB.Model(&system.SysApi{})\n\tvar apiList []system.SysApi\n\n\tif api.Path != \"\" {\n\t\tdb = db.Where(\"path LIKE ?\", \"%\"+api.Path+\"%\")\n\t}\n\n\tif api.Description != \"\" {\n\t\tdb = db.Where(\"description LIKE ?\", \"%\"+api.Description+\"%\")\n\t}\n\n\tif api.Method != \"\" {\n\t\tdb = db.Where(\"method = ?\", api.Method)\n\t}\n\n\tif api.ApiGroup != \"\" {\n\t\tdb = db.Where(\"api_group = ?\", api.ApiGroup)\n\t}\n\n\terr = db.Count(&total).Error\n\n\tif err != nil {\n\t\treturn apiList, total, err\n\t}\n\n\tdb = db.Limit(limit).Offset(offset)\n\tOrderStr := \"id desc\"\n\tif order != \"\" {\n\t\torderMap := make(map[string]bool, 5)\n\t\torderMap[\"id\"] = true\n\t\torderMap[\"path\"] = true\n\t\torderMap[\"api_group\"] = true\n\t\torderMap[\"description\"] = true\n\t\torderMap[\"method\"] = true\n\t\tif !orderMap[order] {\n\t\t\terr = fmt.Errorf(\"非法的排序字段: %v\", order)\n\t\t\treturn apiList, total, err\n\t\t}\n\t\tOrderStr = order\n\t\tif desc {\n\t\t\tOrderStr = order + \" desc\"\n\t\t}\n\t}\n\terr = db.Order(OrderStr).Find(&apiList).Error\n\treturn apiList, total, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetAllApis\n//@description: 获取所有的api\n//@return:  apis []model.SysApi, err error\n\nfunc (apiService *ApiService) GetAllApis(authorityID uint) (apis []system.SysApi, err error) {\n\tparentAuthorityID, err := AuthorityServiceApp.GetParentAuthorityID(authorityID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = global.GVA_DB.Order(\"id desc\").Find(&apis).Error\n\tif parentAuthorityID == 0 || !global.GVA_CONFIG.System.UseStrictAuth {\n\t\treturn\n\t}\n\tpaths := CasbinServiceApp.GetPolicyPathByAuthorityId(authorityID)\n\t// 挑选 apis里面的path和method也在paths里面的api\n\tvar authApis []system.SysApi\n\tfor i := range apis {\n\t\tfor j := range paths {\n\t\t\tif paths[j].Path == apis[i].Path && paths[j].Method == apis[i].Method {\n\t\t\t\tauthApis = append(authApis, apis[i])\n\t\t\t}\n\t\t}\n\t}\n\treturn authApis, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetApiById\n//@description: 根据id获取api\n//@param: id float64\n//@return: api model.SysApi, err error\n\nfunc (apiService *ApiService) GetApiById(id int) (api system.SysApi, err error) {\n\terr = global.GVA_DB.First(&api, \"id = ?\", id).Error\n\treturn\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateApi\n//@description: 根据id更新api\n//@param: api model.SysApi\n//@return: err error\n\nfunc (apiService *ApiService) UpdateApi(api system.SysApi) (err error) {\n\tvar oldA system.SysApi\n\terr = global.GVA_DB.First(&oldA, \"id = ?\", api.ID).Error\n\tif oldA.Path != api.Path || oldA.Method != api.Method {\n\t\tvar duplicateApi system.SysApi\n\t\tif ferr := global.GVA_DB.First(&duplicateApi, \"path = ? AND method = ?\", api.Path, api.Method).Error; ferr != nil {\n\t\t\tif !errors.Is(ferr, gorm.ErrRecordNotFound) {\n\t\t\t\treturn ferr\n\t\t\t}\n\t\t} else {\n\t\t\tif duplicateApi.ID != api.ID {\n\t\t\t\treturn errors.New(\"存在相同api路径\")\n\t\t\t}\n\t\t}\n\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = CasbinServiceApp.UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn global.GVA_DB.Save(&api).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteApisByIds\n//@description: 删除选中API\n//@param: apis []model.SysApi\n//@return: err error\n\nfunc (apiService *ApiService) DeleteApisByIds(ids request.IdsReq) (err error) {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tvar apis []system.SysApi\n\t\terr = tx.Find(&apis, \"id in ?\", ids.Ids).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = tx.Delete(&[]system.SysApi{}, \"id in ?\", ids.Ids).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, sysApi := range apis {\n\t\t\tCasbinServiceApp.ClearCasbin(1, sysApi.Path, sysApi.Method)\n\t\t}\n\t\treturn err\n\t})\n}\n"
  },
  {
    "path": "server/service/system/sys_api_token.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsysReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/golang-jwt/jwt/v5\"\n\t\"time\"\n)\n\ntype ApiTokenService struct{}\n\nfunc (apiVersion *ApiTokenService) CreateApiToken(apiToken system.SysApiToken, days int) (string, error) {\n\tvar user system.SysUser\n\tif err := global.GVA_DB.Where(\"id = ?\", apiToken.UserID).First(&user).Error; err != nil {\n\t\treturn \"\", errors.New(\"用户不存在\")\n\t}\n\n\thasAuth := false\n\tfor _, auth := range user.Authorities {\n\t\tif auth.AuthorityId == apiToken.AuthorityID {\n\t\t\thasAuth = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasAuth && user.AuthorityId != apiToken.AuthorityID {\n\t\treturn \"\", errors.New(\"用户不具备该角色权限\")\n\t}\n\n\tj := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一不同的部分是过期时间\n\n\texpireTime := time.Duration(days) * 24 * time.Hour\n\tif days == -1 {\n\t\texpireTime = 100 * 365 * 24 * time.Hour\n\t}\n\n\tbf, _ := utils.ParseDuration(global.GVA_CONFIG.JWT.BufferTime)\n\n\tclaims := sysReq.CustomClaims{\n\t\tBaseClaims: sysReq.BaseClaims{\n\t\t\tUUID:        user.UUID,\n\t\t\tID:          user.ID,\n\t\t\tUsername:    user.Username,\n\t\t\tNickName:    user.NickName,\n\t\t\tAuthorityId: apiToken.AuthorityID,\n\t\t},\n\t\tBufferTime: int64(bf / time.Second), // 缓冲时间\n\t\tRegisteredClaims: jwt.RegisteredClaims{\n\t\t\tAudience:  jwt.ClaimStrings{\"GVA\"},\n\t\t\tNotBefore: jwt.NewNumericDate(time.Now().Add(-1000)),\n\t\t\tExpiresAt: jwt.NewNumericDate(time.Now().Add(expireTime)),\n\t\t\tIssuer:    global.GVA_CONFIG.JWT.Issuer,\n\t\t},\n\t}\n\n\ttoken, err := j.CreateToken(claims)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tapiToken.Token = token\n\tapiToken.Status = true\n\tapiToken.ExpiresAt = time.Now().Add(expireTime)\n\terr = global.GVA_DB.Create(&apiToken).Error\n\treturn token, err\n}\n\nfunc (apiVersion *ApiTokenService) GetApiTokenList(info sysReq.SysApiTokenSearch) (list []system.SysApiToken, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\tdb := global.GVA_DB.Model(&system.SysApiToken{})\n\n\tdb = db.Preload(\"User\")\n\n\tif info.UserID != 0 {\n\t\tdb = db.Where(\"user_id = ?\", info.UserID)\n\t}\n\tif info.Status != nil {\n\t\tdb = db.Where(\"status = ?\", *info.Status)\n\t}\n\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\terr = db.Limit(limit).Offset(offset).Order(\"created_at desc\").Find(&list).Error\n\treturn list, total, err\n}\n\nfunc (apiVersion *ApiTokenService) DeleteApiToken(id uint) error {\n\tvar apiToken system.SysApiToken\n\terr := global.GVA_DB.First(&apiToken, id).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tjwtService := JwtService{}\n\terr = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: apiToken.Token})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn global.GVA_DB.Model(&apiToken).Update(\"status\", false).Error\n}\n"
  },
  {
    "path": "server/service/system/sys_authority.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"gorm.io/gorm\"\n)\n\nvar ErrRoleExistence = errors.New(\"存在相同角色id\")\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateAuthority\n//@description: 创建一个角色\n//@param: auth model.SysAuthority\n//@return: authority system.SysAuthority, err error\n\ntype AuthorityService struct{}\n\nvar AuthorityServiceApp = new(AuthorityService)\n\nfunc (authorityService *AuthorityService) CreateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) {\n\n\tif err = global.GVA_DB.Where(\"authority_id = ?\", auth.AuthorityId).First(&system.SysAuthority{}).Error; !errors.Is(err, gorm.ErrRecordNotFound) {\n\t\treturn auth, ErrRoleExistence\n\t}\n\n\te := global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\n\t\tif err = tx.Create(&auth).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tauth.SysBaseMenus = systemReq.DefaultMenu()\n\t\tif err = tx.Model(&auth).Association(\"SysBaseMenus\").Replace(&auth.SysBaseMenus); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcasbinInfos := systemReq.DefaultCasbin()\n\t\tauthorityId := strconv.Itoa(int(auth.AuthorityId))\n\t\trules := [][]string{}\n\t\tfor _, v := range casbinInfos {\n\t\t\trules = append(rules, []string{authorityId, v.Path, v.Method})\n\t\t}\n\t\treturn CasbinServiceApp.AddPolicies(tx, rules)\n\t})\n\n\treturn auth, e\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CopyAuthority\n//@description: 复制一个角色\n//@param: copyInfo response.SysAuthorityCopyResponse\n//@return: authority system.SysAuthority, err error\n\nfunc (authorityService *AuthorityService) CopyAuthority(adminAuthorityID uint, copyInfo response.SysAuthorityCopyResponse) (authority system.SysAuthority, err error) {\n\tvar authorityBox system.SysAuthority\n\tif !errors.Is(global.GVA_DB.Where(\"authority_id = ?\", copyInfo.Authority.AuthorityId).First(&authorityBox).Error, gorm.ErrRecordNotFound) {\n\t\treturn authority, ErrRoleExistence\n\t}\n\tcopyInfo.Authority.Children = []system.SysAuthority{}\n\tmenus, err := MenuServiceApp.GetMenuAuthority(&request.GetAuthorityId{AuthorityId: copyInfo.OldAuthorityId})\n\tif err != nil {\n\t\treturn\n\t}\n\tvar baseMenu []system.SysBaseMenu\n\tfor _, v := range menus {\n\t\tintNum := v.MenuId\n\t\tv.SysBaseMenu.ID = uint(intNum)\n\t\tbaseMenu = append(baseMenu, v.SysBaseMenu)\n\t}\n\tcopyInfo.Authority.SysBaseMenus = baseMenu\n\terr = global.GVA_DB.Create(&copyInfo.Authority).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar btns []system.SysAuthorityBtn\n\n\terr = global.GVA_DB.Find(&btns, \"authority_id = ?\", copyInfo.OldAuthorityId).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tif len(btns) > 0 {\n\t\tfor i := range btns {\n\t\t\tbtns[i].AuthorityId = copyInfo.Authority.AuthorityId\n\t\t}\n\t\terr = global.GVA_DB.Create(&btns).Error\n\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tpaths := CasbinServiceApp.GetPolicyPathByAuthorityId(copyInfo.OldAuthorityId)\n\terr = CasbinServiceApp.UpdateCasbin(adminAuthorityID, copyInfo.Authority.AuthorityId, paths)\n\tif err != nil {\n\t\t_ = authorityService.DeleteAuthority(&copyInfo.Authority)\n\t}\n\treturn copyInfo.Authority, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateAuthority\n//@description: 更改一个角色\n//@param: auth model.SysAuthority\n//@return: authority system.SysAuthority, err error\n\nfunc (authorityService *AuthorityService) UpdateAuthority(auth system.SysAuthority) (authority system.SysAuthority, err error) {\n\tvar oldAuthority system.SysAuthority\n\terr = global.GVA_DB.Where(\"authority_id = ?\", auth.AuthorityId).First(&oldAuthority).Error\n\tif err != nil {\n\t\tglobal.GVA_LOG.Debug(err.Error())\n\t\treturn system.SysAuthority{}, errors.New(\"查询角色数据失败\")\n\t}\n\terr = global.GVA_DB.Model(&oldAuthority).Updates(&auth).Error\n\treturn auth, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteAuthority\n//@description: 删除角色\n//@param: auth *model.SysAuthority\n//@return: err error\n\nfunc (authorityService *AuthorityService) DeleteAuthority(auth *system.SysAuthority) error {\n\tif errors.Is(global.GVA_DB.Debug().Preload(\"Users\").First(&auth).Error, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"该角色不存在\")\n\t}\n\tif len(auth.Users) != 0 {\n\t\treturn errors.New(\"此角色有用户正在使用禁止删除\")\n\t}\n\tif !errors.Is(global.GVA_DB.Where(\"authority_id = ?\", auth.AuthorityId).First(&system.SysUser{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"此角色有用户正在使用禁止删除\")\n\t}\n\tif !errors.Is(global.GVA_DB.Where(\"parent_id = ?\", auth.AuthorityId).First(&system.SysAuthority{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"此角色存在子角色不允许删除\")\n\t}\n\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tvar err error\n\t\tif err = tx.Preload(\"SysBaseMenus\").Preload(\"DataAuthorityId\").Where(\"authority_id = ?\", auth.AuthorityId).First(auth).Unscoped().Delete(auth).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(auth.SysBaseMenus) > 0 {\n\t\t\tif err = tx.Model(auth).Association(\"SysBaseMenus\").Delete(auth.SysBaseMenus); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// err = db.Association(\"SysBaseMenus\").Delete(&auth)\n\t\t}\n\t\tif len(auth.DataAuthorityId) > 0 {\n\t\t\tif err = tx.Model(auth).Association(\"DataAuthorityId\").Delete(auth.DataAuthorityId); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif err = tx.Delete(&system.SysUserAuthority{}, \"sys_authority_authority_id = ?\", auth.AuthorityId).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = tx.Where(\"authority_id = ?\", auth.AuthorityId).Delete(&[]system.SysAuthorityBtn{}).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tauthorityId := strconv.Itoa(int(auth.AuthorityId))\n\n\t\tif err = CasbinServiceApp.RemoveFilteredPolicy(tx, authorityId); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetAuthorityInfoList\n//@description: 分页获取数据\n//@param: info request.PageInfo\n//@return: list interface{}, total int64, err error\n\nfunc (authorityService *AuthorityService) GetAuthorityInfoList(authorityID uint) (list []system.SysAuthority, err error) {\n\tvar authority system.SysAuthority\n\terr = global.GVA_DB.Where(\"authority_id = ?\", authorityID).First(&authority).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar authorities []system.SysAuthority\n\tdb := global.GVA_DB.Model(&system.SysAuthority{})\n\tif global.GVA_CONFIG.System.UseStrictAuth {\n\t\t// 当开启了严格树形结构后\n\t\tif *authority.ParentId == 0 {\n\t\t\t// 只有顶级角色可以修改自己的权限和以下权限\n\t\t\terr = db.Preload(\"DataAuthorityId\").Where(\"authority_id = ?\", authorityID).Find(&authorities).Error\n\t\t} else {\n\t\t\t// 非顶级角色只能修改以下权限\n\t\t\terr = db.Debug().Preload(\"DataAuthorityId\").Where(\"parent_id = ?\", authorityID).Find(&authorities).Error\n\t\t}\n\t} else {\n\t\terr = db.Preload(\"DataAuthorityId\").Where(\"parent_id = ?\", \"0\").Find(&authorities).Error\n\t}\n\n\tfor k := range authorities {\n\t\terr = authorityService.findChildrenAuthority(&authorities[k])\n\t}\n\treturn authorities, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetAuthorityInfoList\n//@description: 分页获取数据\n//@param: info request.PageInfo\n//@return: list interface{}, total int64, err error\n\nfunc (authorityService *AuthorityService) GetStructAuthorityList(authorityID uint) (list []uint, err error) {\n\tvar auth system.SysAuthority\n\t_ = global.GVA_DB.First(&auth, \"authority_id = ?\", authorityID).Error\n\tvar authorities []system.SysAuthority\n\terr = global.GVA_DB.Preload(\"DataAuthorityId\").Where(\"parent_id = ?\", authorityID).Find(&authorities).Error\n\tif len(authorities) > 0 {\n\t\tfor k := range authorities {\n\t\t\tlist = append(list, authorities[k].AuthorityId)\n\t\t\tchildrenList, err := authorityService.GetStructAuthorityList(authorities[k].AuthorityId)\n\t\t\tif err == nil {\n\t\t\t\tlist = append(list, childrenList...)\n\t\t\t}\n\t\t}\n\t}\n\tif *auth.ParentId == 0 {\n\t\tlist = append(list, authorityID)\n\t}\n\treturn list, err\n}\n\nfunc (authorityService *AuthorityService) CheckAuthorityIDAuth(authorityID, targetID uint) (err error) {\n\tif !global.GVA_CONFIG.System.UseStrictAuth {\n\t\treturn nil\n\t}\n\tauthIDS, err := authorityService.GetStructAuthorityList(authorityID)\n\tif err != nil {\n\t\treturn err\n\t}\n\thasAuth := false\n\tfor _, v := range authIDS {\n\t\tif v == targetID {\n\t\t\thasAuth = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasAuth {\n\t\treturn errors.New(\"您提交的角色ID不合法\")\n\t}\n\treturn nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetAuthorityInfo\n//@description: 获取所有角色信息\n//@param: auth model.SysAuthority\n//@return: sa system.SysAuthority, err error\n\nfunc (authorityService *AuthorityService) GetAuthorityInfo(auth system.SysAuthority) (sa system.SysAuthority, err error) {\n\terr = global.GVA_DB.Preload(\"DataAuthorityId\").Where(\"authority_id = ?\", auth.AuthorityId).First(&sa).Error\n\treturn sa, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetDataAuthority\n//@description: 设置角色资源权限\n//@param: auth model.SysAuthority\n//@return: error\n\nfunc (authorityService *AuthorityService) SetDataAuthority(adminAuthorityID uint, auth system.SysAuthority) error {\n\tvar checkIDs []uint\n\tcheckIDs = append(checkIDs, auth.AuthorityId)\n\tfor i := range auth.DataAuthorityId {\n\t\tcheckIDs = append(checkIDs, auth.DataAuthorityId[i].AuthorityId)\n\t}\n\n\tfor i := range checkIDs {\n\t\terr := authorityService.CheckAuthorityIDAuth(adminAuthorityID, checkIDs[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tvar s system.SysAuthority\n\tglobal.GVA_DB.Preload(\"DataAuthorityId\").First(&s, \"authority_id = ?\", auth.AuthorityId)\n\terr := global.GVA_DB.Model(&s).Association(\"DataAuthorityId\").Replace(&auth.DataAuthorityId)\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetMenuAuthority\n//@description: 菜单与角色绑定\n//@param: auth *model.SysAuthority\n//@return: error\n\nfunc (authorityService *AuthorityService) SetMenuAuthority(auth *system.SysAuthority) error {\n\tvar s system.SysAuthority\n\tglobal.GVA_DB.Preload(\"SysBaseMenus\").First(&s, \"authority_id = ?\", auth.AuthorityId)\n\terr := global.GVA_DB.Model(&s).Association(\"SysBaseMenus\").Replace(&auth.SysBaseMenus)\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: findChildrenAuthority\n//@description: 查询子角色\n//@param: authority *model.SysAuthority\n//@return: err error\n\nfunc (authorityService *AuthorityService) findChildrenAuthority(authority *system.SysAuthority) (err error) {\n\terr = global.GVA_DB.Preload(\"DataAuthorityId\").Where(\"parent_id = ?\", authority.AuthorityId).Find(&authority.Children).Error\n\tif len(authority.Children) > 0 {\n\t\tfor k := range authority.Children {\n\t\t\terr = authorityService.findChildrenAuthority(&authority.Children[k])\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint) (parentID uint, err error) {\n\tvar authority system.SysAuthority\n\terr = global.GVA_DB.Where(\"authority_id = ?\", authorityID).First(&authority).Error\n\tif err != nil {\n\t\treturn\n\t}\n\treturn *authority.ParentId, nil\n}\n\n// GetUserIdsByAuthorityId 获取拥有指定角色的所有用户ID\nfunc (authorityService *AuthorityService) GetUserIdsByAuthorityId(authorityId uint) (userIds []uint, err error) {\n\tvar records []system.SysUserAuthority\n\terr = global.GVA_DB.Where(\"sys_authority_authority_id = ?\", authorityId).Find(&records).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, r := range records {\n\t\tuserIds = append(userIds, r.SysUserId)\n\t}\n\treturn userIds, nil\n}\n\n// SetRoleUsers 全量覆盖某角色关联的用户列表\n// 入参：角色ID + 目标用户ID列表，保存时将该角色的关联关系完全替换为传入列表\nfunc (authorityService *AuthorityService) SetRoleUsers(authorityId uint, userIds []uint) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\t// 1. 查出当前拥有该角色的所有用户ID\n\t\tvar existingRecords []system.SysUserAuthority\n\t\tif err := tx.Where(\"sys_authority_authority_id = ?\", authorityId).Find(&existingRecords).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcurrentSet := make(map[uint]struct{})\n\t\tfor _, r := range existingRecords {\n\t\t\tcurrentSet[r.SysUserId] = struct{}{}\n\t\t}\n\n\t\ttargetSet := make(map[uint]struct{})\n\t\tfor _, id := range userIds {\n\t\t\ttargetSet[id] = struct{}{}\n\t\t}\n\n\t\t// 2. 删除该角色所有已有的用户关联\n\t\tif err := tx.Delete(&system.SysUserAuthority{}, \"sys_authority_authority_id = ?\", authorityId).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 3. 对被移除的用户：若该角色是其主角色，则将主角色切换为其剩余的其他角色\n\t\tfor userId := range currentSet {\n\t\t\tif _, ok := targetSet[userId]; ok {\n\t\t\t\tcontinue // 仍在目标列表中，不处理\n\t\t\t}\n\t\t\tvar user system.SysUser\n\t\t\tif err := tx.First(&user, \"id = ?\", userId).Error; err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif user.AuthorityId == authorityId {\n\t\t\t\t// 从剩余关联（已删除当前角色后）中找另一个角色作为主角色\n\t\t\t\tvar another system.SysUserAuthority\n\t\t\t\tif err := tx.Where(\"sys_user_id = ?\", userId).First(&another).Error; err != nil {\n\t\t\t\t\t// 没有其他角色，主角色保持不变，不做处理\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif err := tx.Model(&system.SysUser{}).Where(\"id = ?\", userId).\n\t\t\t\t\tUpdate(\"authority_id\", another.SysAuthorityAuthorityId).Error; err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 4. 批量插入新的关联记录\n\t\tif len(userIds) > 0 {\n\t\t\tnewRecords := make([]system.SysUserAuthority, 0, len(userIds))\n\t\t\tfor _, userId := range userIds {\n\t\t\t\tnewRecords = append(newRecords, system.SysUserAuthority{\n\t\t\t\t\tSysUserId:               userId,\n\t\t\t\t\tSysAuthorityAuthorityId: authorityId,\n\t\t\t\t})\n\t\t\t}\n\t\t\tif err := tx.Create(&newRecords).Error; err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "server/service/system/sys_authority_btn.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"gorm.io/gorm\"\n)\n\ntype AuthorityBtnService struct{}\n\nvar AuthorityBtnServiceApp = new(AuthorityBtnService)\n\nfunc (a *AuthorityBtnService) GetAuthorityBtn(req request.SysAuthorityBtnReq) (res response.SysAuthorityBtnRes, err error) {\n\tvar authorityBtn []system.SysAuthorityBtn\n\terr = global.GVA_DB.Find(&authorityBtn, \"authority_id = ? and sys_menu_id = ?\", req.AuthorityId, req.MenuID).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tvar selected []uint\n\tfor _, v := range authorityBtn {\n\t\tselected = append(selected, v.SysBaseMenuBtnID)\n\t}\n\tres.Selected = selected\n\treturn res, err\n}\n\nfunc (a *AuthorityBtnService) SetAuthorityBtn(req request.SysAuthorityBtnReq) (err error) {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tvar authorityBtn []system.SysAuthorityBtn\n\t\terr = tx.Delete(&[]system.SysAuthorityBtn{}, \"authority_id = ? and sys_menu_id = ?\", req.AuthorityId, req.MenuID).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, v := range req.Selected {\n\t\t\tauthorityBtn = append(authorityBtn, system.SysAuthorityBtn{\n\t\t\t\tAuthorityId:      req.AuthorityId,\n\t\t\t\tSysMenuID:        req.MenuID,\n\t\t\t\tSysBaseMenuBtnID: v,\n\t\t\t})\n\t\t}\n\t\tif len(authorityBtn) > 0 {\n\t\t\terr = tx.Create(&authorityBtn).Error\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn err\n\t})\n}\n\nfunc (a *AuthorityBtnService) CanRemoveAuthorityBtn(ID string) (err error) {\n\tfErr := global.GVA_DB.First(&system.SysAuthorityBtn{}, \"sys_base_menu_btn_id = ?\", ID).Error\n\tif errors.Is(fErr, gorm.ErrRecordNotFound) {\n\t\treturn nil\n\t}\n\treturn errors.New(\"此按钮正在被使用无法删除\")\n}\n"
  },
  {
    "path": "server/service/system/sys_auto_code_interface.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n)\n\ntype AutoCodeService struct{}\n\ntype Database interface {\n\tGetDB(businessDB string) (data []response.Db, err error)\n\tGetTables(businessDB string, dbName string) (data []response.Table, err error)\n\tGetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error)\n}\n\nfunc (autoCodeService *AutoCodeService) Database(businessDB string) Database {\n\n\tif businessDB == \"\" {\n\t\tswitch global.GVA_CONFIG.System.DbType {\n\t\tcase \"mysql\":\n\t\t\treturn AutoCodeMysql\n\t\tcase \"pgsql\":\n\t\t\treturn AutoCodePgsql\n\t\tcase \"mssql\":\n\t\t\treturn AutoCodeMssql\n\t\tcase \"oracle\":\n\t\t\treturn AutoCodeOracle\n\t\tcase \"sqlite\":\n\t\t\treturn AutoCodeSqlite\n\t\tdefault:\n\t\t\treturn AutoCodeMysql\n\t\t}\n\t} else {\n\t\tfor _, info := range global.GVA_CONFIG.DBList {\n\t\t\tif info.AliasName == businessDB {\n\t\t\t\tswitch info.Type {\n\t\t\t\tcase \"mysql\":\n\t\t\t\t\treturn AutoCodeMysql\n\t\t\t\tcase \"mssql\":\n\t\t\t\t\treturn AutoCodeMssql\n\t\t\t\tcase \"pgsql\":\n\t\t\t\t\treturn AutoCodePgsql\n\t\t\t\tcase \"oracle\":\n\t\t\t\t\treturn AutoCodeOracle\n\t\t\t\tcase \"sqlite\":\n\t\t\t\t\treturn AutoCodeSqlite\n\t\t\t\tdefault:\n\t\t\t\t\treturn AutoCodeMysql\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn AutoCodeMysql\n\t}\n\n}\n"
  },
  {
    "path": "server/service/system/sys_auto_code_mssql.go",
    "content": "package system\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n)\n\nvar AutoCodeMssql = new(autoCodeMssql)\n\ntype autoCodeMssql struct{}\n\n// GetDB 获取数据库的所有数据库名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeMssql) GetDB(businessDB string) (data []response.Db, err error) {\n\tvar entities []response.Db\n\tsql := \"select name AS 'database' from sys.databases;\"\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error\n\t}\n\treturn entities, err\n}\n\n// GetTables 获取数据库的所有表名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeMssql) GetTables(businessDB string, dbName string) (data []response.Table, err error) {\n\tvar entities []response.Table\n\n\tsql := fmt.Sprintf(`select name as 'table_name' from %s.DBO.sysobjects where xtype='U'`, dbName)\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error\n\t}\n\n\treturn entities, err\n}\n\n// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeMssql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {\n\tvar entities []response.Column\n\tsql := fmt.Sprintf(`\nSELECT\n    sc.name AS column_name,\n    st.name AS data_type,\n    sc.max_length AS data_type_long,\n    CASE\n        WHEN pk.object_id IS NOT NULL THEN 1\n        ELSE 0\n    END AS primary_key,\n    sc.column_id\nFROM\n    %s.sys.columns sc\nJOIN\n    sys.types st ON sc.user_type_id=st.user_type_id\nLEFT JOIN\n    %s.sys.objects so ON so.name='%s' AND so.type='U'\nLEFT JOIN\n    %s.sys.indexes si ON si.object_id = so.object_id AND si.is_primary_key = 1\nLEFT JOIN\n    %s.sys.index_columns sic ON sic.object_id = si.object_id AND sic.index_id = si.index_id AND sic.column_id = sc.column_id\nLEFT JOIN\n    %s.sys.key_constraints pk ON pk.object_id = si.object_id\nWHERE\n    st.is_user_defined=0 AND sc.object_id = so.object_id\nORDER BY\n    sc.column_id\n`, dbName, dbName, tableName, dbName, dbName, dbName)\n\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error\n\t}\n\n\treturn entities, err\n}\n"
  },
  {
    "path": "server/service/system/sys_auto_code_mysql.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n)\n\nvar AutoCodeMysql = new(autoCodeMysql)\n\ntype autoCodeMysql struct{}\n\n// GetDB 获取数据库的所有数据库名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeMysql) GetDB(businessDB string) (data []response.Db, err error) {\n\tvar entities []response.Db\n\tsql := \"SELECT SCHEMA_NAME AS `database` FROM INFORMATION_SCHEMA.SCHEMATA;\"\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error\n\t}\n\treturn entities, err\n}\n\n// GetTables 获取数据库的所有表名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeMysql) GetTables(businessDB string, dbName string) (data []response.Table, err error) {\n\tvar entities []response.Table\n\tsql := `select table_name as table_name from information_schema.tables where table_schema = ?`\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql, dbName).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql, dbName).Scan(&entities).Error\n\t}\n\n\treturn entities, err\n}\n\n// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeMysql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {\n\tvar entities []response.Column\n\tsql := `\n\tSELECT \n    c.COLUMN_NAME column_name,\n    c.DATA_TYPE data_type,\n    CASE c.DATA_TYPE\n        WHEN 'longtext' THEN c.CHARACTER_MAXIMUM_LENGTH\n        WHEN 'varchar' THEN c.CHARACTER_MAXIMUM_LENGTH\n        WHEN 'double' THEN CONCAT_WS(',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE)\n        WHEN 'decimal' THEN CONCAT_WS(',', c.NUMERIC_PRECISION, c.NUMERIC_SCALE)\n        WHEN 'int' THEN c.NUMERIC_PRECISION\n        WHEN 'bigint' THEN c.NUMERIC_PRECISION\n        ELSE '' \n    END AS data_type_long,\n    c.COLUMN_COMMENT column_comment,\n    CASE WHEN kcu.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS primary_key,\n    c.ORDINAL_POSITION\nFROM \n    INFORMATION_SCHEMA.COLUMNS c\nLEFT JOIN \n    INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu \nON \n    c.TABLE_SCHEMA = kcu.TABLE_SCHEMA \n    AND c.TABLE_NAME = kcu.TABLE_NAME \n    AND c.COLUMN_NAME = kcu.COLUMN_NAME \n    AND kcu.CONSTRAINT_NAME = 'PRIMARY'\nWHERE \n    c.TABLE_NAME = ? \n    AND c.TABLE_SCHEMA = ?\nORDER BY \n    c.ORDINAL_POSITION;`\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql, tableName, dbName).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error\n\t}\n\n\treturn entities, err\n}\n"
  },
  {
    "path": "server/service/system/sys_auto_code_oracle.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n)\n\nvar AutoCodeOracle = new(autoCodeOracle)\n\ntype autoCodeOracle struct{}\n\n// GetDB 获取数据库的所有数据库名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeOracle) GetDB(businessDB string) (data []response.Db, err error) {\n\tvar entities []response.Db\n\tsql := `SELECT lower(username) AS \"database\" FROM all_users`\n\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error\n\treturn entities, err\n}\n\n// GetTables 获取数据库的所有表名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeOracle) GetTables(businessDB string, dbName string) (data []response.Table, err error) {\n\tvar entities []response.Table\n\tsql := `select lower(table_name) as \"table_name\" from all_tables where lower(owner) = ?`\n\n\terr = global.GVA_DBList[businessDB].Raw(sql, dbName).Scan(&entities).Error\n\treturn entities, err\n}\n\n// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (s *autoCodeOracle) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {\n\tvar entities []response.Column\n\tsql := `\n\tSELECT\n    lower(a.COLUMN_NAME) as \"column_name\",\n    (CASE WHEN a.DATA_TYPE = 'NUMBER' AND a.DATA_SCALE=0 THEN 'int' else lower(a.DATA_TYPE) end)  as \"data_type\",\n    (CASE WHEN a.DATA_TYPE = 'NUMBER' THEN a.DATA_PRECISION else a.DATA_LENGTH end) as \"data_type_long\",\n    b.COMMENTS as \"column_comment\",\n    (CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END) as \"primary_key\",\n    a.COLUMN_ID\nFROM\n    all_tab_columns a\nJOIN\n    all_col_comments b ON a.OWNER = b.OWNER AND a.TABLE_NAME = b.TABLE_NAME AND a.COLUMN_NAME = b.COLUMN_NAME\nLEFT JOIN\n    (\n        SELECT\n            acc.OWNER,\n            acc.TABLE_NAME,\n            acc.COLUMN_NAME\n        FROM\n            all_cons_columns acc\n        JOIN\n            all_constraints ac ON acc.OWNER = ac.OWNER AND acc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME\n        WHERE\n            ac.CONSTRAINT_TYPE = 'P'\n    ) pk ON a.OWNER = pk.OWNER AND a.TABLE_NAME = pk.TABLE_NAME AND a.COLUMN_NAME = pk.COLUMN_NAME\nWHERE\n    lower(a.table_name) = ?\n    AND lower(a.OWNER) = ?\nORDER BY\n    a.COLUMN_ID\n`\n\n\terr = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error\n\treturn entities, err\n}\n"
  },
  {
    "path": "server/service/system/sys_auto_code_pgsql.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n)\n\nvar AutoCodePgsql = new(autoCodePgsql)\n\ntype autoCodePgsql struct{}\n\n// GetDB 获取数据库的所有数据库名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (a *autoCodePgsql) GetDB(businessDB string) (data []response.Db, err error) {\n\tvar entities []response.Db\n\tsql := `SELECT datname as database FROM pg_database WHERE datistemplate = false`\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Scan(&entities).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&entities).Error\n\t}\n\n\treturn entities, err\n}\n\n// GetTables 获取数据库的所有表名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (a *autoCodePgsql) GetTables(businessDB string, dbName string) (data []response.Table, err error) {\n\tvar entities []response.Table\n\tsql := `select table_name as table_name from information_schema.tables where table_catalog = ? and table_schema = ?`\n\n\tdb := global.GVA_DB\n\tif businessDB != \"\" {\n\t\tdb = global.GVA_DBList[businessDB]\n\t}\n\n\terr = db.Raw(sql, dbName, \"public\").Scan(&entities).Error\n\treturn entities, err\n}\n\n// GetColumn 获取指定数据库和指定数据表的所有字段名,类型值等\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (a *autoCodePgsql) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {\n\t// todo 数据获取不全, 待完善sql\n\tsql := `\nSELECT\n    psc.COLUMN_NAME AS COLUMN_NAME,\n    psc.udt_name AS data_type,\n    CASE\n        psc.udt_name\n        WHEN 'text' THEN\n            concat_ws ( '', '', psc.CHARACTER_MAXIMUM_LENGTH )\n        WHEN 'varchar' THEN\n            concat_ws ( '', '', psc.CHARACTER_MAXIMUM_LENGTH )\n        WHEN 'smallint' THEN\n            concat_ws ( ',', psc.NUMERIC_PRECISION, psc.NUMERIC_SCALE )\n        WHEN 'decimal' THEN\n            concat_ws ( ',', psc.NUMERIC_PRECISION, psc.NUMERIC_SCALE )\n        WHEN 'integer' THEN\n            concat_ws ( '', '', psc.NUMERIC_PRECISION )\n        WHEN 'int4' THEN\n            concat_ws ( '', '', psc.NUMERIC_PRECISION )\n        WHEN 'int8' THEN\n            concat_ws ( '', '', psc.NUMERIC_PRECISION )\n        WHEN 'bigint' THEN\n            concat_ws ( '', '', psc.NUMERIC_PRECISION )\n        WHEN 'timestamp' THEN\n            concat_ws ( '', '', psc.datetime_precision )\n        ELSE ''\n        END AS data_type_long,\n    (\n        SELECT\n            pd.description\n        FROM\n            pg_description pd\n        WHERE\n            (pd.objoid,pd.objsubid) in (\n                SELECT pa.attrelid,pa.attnum\n                FROM\n                    pg_attribute pa\n                WHERE pa.attrelid = ( SELECT oid FROM pg_class pc WHERE\n                    pc.relname = psc.table_name\n                )\n                  and attname = psc.column_name\n            )\n    ) AS column_comment,\n    (\n        SELECT\n            COUNT(*)\n        FROM\n            pg_constraint\n        WHERE\n            contype = 'p'\n          AND conrelid = (\n            SELECT\n                oid\n            FROM\n                pg_class\n            WHERE\n                relname = psc.table_name\n        )\n          AND conkey::int[] @> ARRAY[(\n            SELECT\n                attnum::integer\n            FROM\n                pg_attribute\n            WHERE\n                attrelid = conrelid\n              AND attname = psc.column_name\n        )]\n    ) > 0 AS primary_key,\n    psc.ordinal_position\nFROM\n    INFORMATION_SCHEMA.COLUMNS psc\nWHERE\n  table_catalog = ?\n  AND table_schema = 'public' \n  AND TABLE_NAME = ?\nORDER BY\n    psc.ordinal_position;\n`\n\tvar entities []response.Column\n\t//sql = strings.ReplaceAll(sql, \"@table_catalog\", dbName)\n\t//sql = strings.ReplaceAll(sql, \"@table_name\", tableName)\n\tdb := global.GVA_DB\n\tif businessDB != \"\" {\n\t\tdb = global.GVA_DBList[businessDB]\n\t}\n\n\terr = db.Raw(sql, dbName, tableName).Scan(&entities).Error\n\treturn entities, err\n}\n"
  },
  {
    "path": "server/service/system/sys_auto_code_sqlite.go",
    "content": "package system\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/response\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nvar AutoCodeSqlite = new(autoCodeSqlite)\n\ntype autoCodeSqlite struct{}\n\n// GetDB 获取数据库的所有数据库名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (a *autoCodeSqlite) GetDB(businessDB string) (data []response.Db, err error) {\n\tvar entities []response.Db\n\tsql := \"PRAGMA database_list;\"\n\tvar databaseList []struct {\n\t\tFile string `gorm:\"column:file\"`\n\t}\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Find(&databaseList).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Find(&databaseList).Error\n\t}\n\tfor _, database := range databaseList {\n\t\tif database.File != \"\" {\n\t\t\tfileName := filepath.Base(database.File)\n\t\t\tfileExt := filepath.Ext(fileName)\n\t\t\tfileNameWithoutExt := strings.TrimSuffix(fileName, fileExt)\n\n\t\t\tentities = append(entities, response.Db{fileNameWithoutExt})\n\t\t}\n\t}\n\t// entities = append(entities, response.Db{global.GVA_CONFIG.Sqlite.Dbname})\n\treturn entities, err\n}\n\n// GetTables 获取数据库的所有表名\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (a *autoCodeSqlite) GetTables(businessDB string, dbName string) (data []response.Table, err error) {\n\tvar entities []response.Table\n\tsql := `SELECT name FROM sqlite_master WHERE type='table'`\n\ttabelNames := []string{}\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Find(&tabelNames).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Find(&tabelNames).Error\n\t}\n\tfor _, tabelName := range tabelNames {\n\t\tentities = append(entities, response.Table{tabelName})\n\t}\n\treturn entities, err\n}\n\n// GetColumn 获取指定数据表的所有字段名,类型值等\n// Author [piexlmax](https://github.com/piexlmax)\n// Author [SliverHorn](https://github.com/SliverHorn)\nfunc (a *autoCodeSqlite) GetColumn(businessDB string, tableName string, dbName string) (data []response.Column, err error) {\n\tvar entities []response.Column\n\tsql := fmt.Sprintf(\"PRAGMA table_info(%s);\", tableName)\n\tvar columnInfos []struct {\n\t\tName string `gorm:\"column:name\"`\n\t\tType string `gorm:\"column:type\"`\n\t\tPk   int    `gorm:\"column:pk\"`\n\t}\n\tif businessDB == \"\" {\n\t\terr = global.GVA_DB.Raw(sql).Scan(&columnInfos).Error\n\t} else {\n\t\terr = global.GVA_DBList[businessDB].Raw(sql).Scan(&columnInfos).Error\n\t}\n\tfor _, columnInfo := range columnInfos {\n\t\tentities = append(entities, response.Column{\n\t\t\tColumnName: columnInfo.Name,\n\t\t\tDataType:   columnInfo.Type,\n\t\t\tPrimaryKey: columnInfo.Pk == 1,\n\t\t})\n\t}\n\treturn entities, err\n}\n"
  },
  {
    "path": "server/service/system/sys_base_menu.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"gorm.io/gorm\"\n)\n\ntype BaseMenuService struct{}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteBaseMenu\n//@description: 删除基础路由\n//@param: id float64\n//@return: err error\n\nvar BaseMenuServiceApp = new(BaseMenuService)\n\nfunc (baseMenuService *BaseMenuService) DeleteBaseMenu(id int) (err error) {\n\terr = global.GVA_DB.First(&system.SysBaseMenu{}, \"parent_id = ?\", id).Error\n\tif err == nil {\n\t\treturn errors.New(\"此菜单存在子菜单不可删除\")\n\t}\n\tvar menu system.SysBaseMenu\n\terr = global.GVA_DB.First(&menu, id).Error\n\tif err != nil {\n\t\treturn errors.New(\"记录不存在\")\n\t}\n\terr = global.GVA_DB.First(&system.SysAuthority{}, \"default_router = ?\", menu.Name).Error\n\tif err == nil {\n\t\treturn errors.New(\"此菜单有角色正在作为首页，不可删除\")\n\t}\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\n\t\terr = tx.Delete(&system.SysBaseMenu{}, \"id = ?\", id).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = tx.Delete(&system.SysBaseMenuParameter{}, \"sys_base_menu_id = ?\", id).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = tx.Delete(&system.SysBaseMenuBtn{}, \"sys_base_menu_id = ?\", id).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = tx.Delete(&system.SysAuthorityBtn{}, \"sys_menu_id = ?\", id).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = tx.Delete(&system.SysAuthorityMenu{}, \"sys_base_menu_id = ?\", id).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateBaseMenu\n//@description: 更新路由\n//@param: menu model.SysBaseMenu\n//@return: err error\n\nfunc (baseMenuService *BaseMenuService) UpdateBaseMenu(menu system.SysBaseMenu) (err error) {\n\tvar oldMenu system.SysBaseMenu\n\tupDateMap := make(map[string]interface{})\n\tupDateMap[\"keep_alive\"] = menu.KeepAlive\n\tupDateMap[\"transition_type\"] = menu.TransitionType\n\tupDateMap[\"close_tab\"] = menu.CloseTab\n\tupDateMap[\"default_menu\"] = menu.DefaultMenu\n\tupDateMap[\"parent_id\"] = menu.ParentId\n\tupDateMap[\"path\"] = menu.Path\n\tupDateMap[\"name\"] = menu.Name\n\tupDateMap[\"hidden\"] = menu.Hidden\n\tupDateMap[\"component\"] = menu.Component\n\tupDateMap[\"title\"] = menu.Title\n\tupDateMap[\"active_name\"] = menu.ActiveName\n\tupDateMap[\"icon\"] = menu.Icon\n\tupDateMap[\"sort\"] = menu.Sort\n\n\terr = global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\ttx.Where(\"id = ?\", menu.ID).Find(&oldMenu)\n\t\tif oldMenu.Name != menu.Name {\n\t\t\tif !errors.Is(tx.Where(\"id <> ? AND name = ?\", menu.ID, menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {\n\t\t\t\tglobal.GVA_LOG.Debug(\"存在相同name修改失败\")\n\t\t\t\treturn errors.New(\"存在相同name修改失败\")\n\t\t\t}\n\t\t}\n\t\ttxErr := tx.Unscoped().Delete(&system.SysBaseMenuParameter{}, \"sys_base_menu_id = ?\", menu.ID).Error\n\t\tif txErr != nil {\n\t\t\tglobal.GVA_LOG.Debug(txErr.Error())\n\t\t\treturn txErr\n\t\t}\n\t\ttxErr = tx.Unscoped().Delete(&system.SysBaseMenuBtn{}, \"sys_base_menu_id = ?\", menu.ID).Error\n\t\tif txErr != nil {\n\t\t\tglobal.GVA_LOG.Debug(txErr.Error())\n\t\t\treturn txErr\n\t\t}\n\t\tif len(menu.Parameters) > 0 {\n\t\t\tfor k := range menu.Parameters {\n\t\t\t\tmenu.Parameters[k].SysBaseMenuID = menu.ID\n\t\t\t}\n\t\t\ttxErr = tx.Create(&menu.Parameters).Error\n\t\t\tif txErr != nil {\n\t\t\t\tglobal.GVA_LOG.Debug(txErr.Error())\n\t\t\t\treturn txErr\n\t\t\t}\n\t\t}\n\n\t\tif len(menu.MenuBtn) > 0 {\n\t\t\tfor k := range menu.MenuBtn {\n\t\t\t\tmenu.MenuBtn[k].SysBaseMenuID = menu.ID\n\t\t\t}\n\t\t\ttxErr = tx.Create(&menu.MenuBtn).Error\n\t\t\tif txErr != nil {\n\t\t\t\tglobal.GVA_LOG.Debug(txErr.Error())\n\t\t\t\treturn txErr\n\t\t\t}\n\t\t}\n\n\t\ttxErr = tx.Model(&oldMenu).Updates(upDateMap).Error\n\t\tif txErr != nil {\n\t\t\tglobal.GVA_LOG.Debug(txErr.Error())\n\t\t\treturn txErr\n\t\t}\n\t\treturn nil\n\t})\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetBaseMenuById\n//@description: 返回当前选中menu\n//@param: id float64\n//@return: menu system.SysBaseMenu, err error\n\nfunc (baseMenuService *BaseMenuService) GetBaseMenuById(id int) (menu system.SysBaseMenu, err error) {\n\terr = global.GVA_DB.Preload(\"MenuBtn\").Preload(\"Parameters\").Where(\"id = ?\", id).First(&menu).Error\n\treturn\n}\n"
  },
  {
    "path": "server/service/system/sys_casbin.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\n\t\"gorm.io/gorm\"\n\n\tgormadapter \"github.com/casbin/gorm-adapter/v3\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t_ \"github.com/go-sql-driver/mysql\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateCasbin\n//@description: 更新casbin权限\n//@param: authorityId string, casbinInfos []request.CasbinInfo\n//@return: error\n\ntype CasbinService struct{}\n\nvar CasbinServiceApp = new(CasbinService)\n\nfunc (casbinService *CasbinService) UpdateCasbin(adminAuthorityID, AuthorityID uint, casbinInfos []request.CasbinInfo) error {\n\n\terr := AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, AuthorityID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif global.GVA_CONFIG.System.UseStrictAuth {\n\t\tapis, e := ApiServiceApp.GetAllApis(adminAuthorityID)\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\n\t\tfor i := range casbinInfos {\n\t\t\thasApi := false\n\t\t\tfor j := range apis {\n\t\t\t\tif apis[j].Path == casbinInfos[i].Path && apis[j].Method == casbinInfos[i].Method {\n\t\t\t\t\thasApi = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !hasApi {\n\t\t\t\treturn errors.New(\"存在api不在权限列表中\")\n\t\t\t}\n\t\t}\n\t}\n\n\tauthorityId := strconv.Itoa(int(AuthorityID))\n\tcasbinService.ClearCasbin(0, authorityId)\n\trules := [][]string{}\n\t//做权限去重处理\n\tdeduplicateMap := make(map[string]bool)\n\tfor _, v := range casbinInfos {\n\t\tkey := authorityId + v.Path + v.Method\n\t\tif _, ok := deduplicateMap[key]; !ok {\n\t\t\tdeduplicateMap[key] = true\n\t\t\trules = append(rules, []string{authorityId, v.Path, v.Method})\n\t\t}\n\t}\n\tif len(rules) == 0 {\n\t\treturn nil\n\t} // 设置空权限无需调用 AddPolicies 方法\n\te := utils.GetCasbin()\n\tsuccess, _ := e.AddPolicies(rules)\n\tif !success {\n\t\treturn errors.New(\"存在相同api,添加失败,请联系管理员\")\n\t}\n\treturn nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateCasbinApi\n//@description: API更新随动\n//@param: oldPath string, newPath string, oldMethod string, newMethod string\n//@return: error\n\nfunc (casbinService *CasbinService) UpdateCasbinApi(oldPath string, newPath string, oldMethod string, newMethod string) error {\n\terr := global.GVA_DB.Model(&gormadapter.CasbinRule{}).Where(\"v1 = ? AND v2 = ?\", oldPath, oldMethod).Updates(map[string]interface{}{\n\t\t\"v1\": newPath,\n\t\t\"v2\": newMethod,\n\t}).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\te := utils.GetCasbin()\n\treturn e.LoadPolicy()\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetPolicyPathByAuthorityId\n//@description: 获取权限列表\n//@param: authorityId string\n//@return: pathMaps []request.CasbinInfo\n\nfunc (casbinService *CasbinService) GetPolicyPathByAuthorityId(AuthorityID uint) (pathMaps []request.CasbinInfo) {\n\te := utils.GetCasbin()\n\tauthorityId := strconv.Itoa(int(AuthorityID))\n\tlist, _ := e.GetFilteredPolicy(0, authorityId)\n\tfor _, v := range list {\n\t\tpathMaps = append(pathMaps, request.CasbinInfo{\n\t\t\tPath:   v[1],\n\t\t\tMethod: v[2],\n\t\t})\n\t}\n\treturn pathMaps\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: ClearCasbin\n//@description: 清除匹配的权限\n//@param: v int, p ...string\n//@return: bool\n\nfunc (casbinService *CasbinService) ClearCasbin(v int, p ...string) bool {\n\te := utils.GetCasbin()\n\tsuccess, _ := e.RemoveFilteredPolicy(v, p...)\n\treturn success\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: RemoveFilteredPolicy\n//@description: 使用数据库方法清理筛选的politicy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效\n//@param: db *gorm.DB, authorityId string\n//@return: error\n\nfunc (casbinService *CasbinService) RemoveFilteredPolicy(db *gorm.DB, authorityId string) error {\n\treturn db.Delete(&gormadapter.CasbinRule{}, \"v0 = ?\", authorityId).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SyncPolicy\n//@description: 同步目前数据库的policy 此方法需要调用FreshCasbin方法才可以在系统中即刻生效\n//@param: db *gorm.DB, authorityId string, rules [][]string\n//@return: error\n\nfunc (casbinService *CasbinService) SyncPolicy(db *gorm.DB, authorityId string, rules [][]string) error {\n\terr := casbinService.RemoveFilteredPolicy(db, authorityId)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn casbinService.AddPolicies(db, rules)\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: AddPolicies\n//@description: 添加匹配的权限\n//@param: v int, p ...string\n//@return: bool\n\nfunc (casbinService *CasbinService) AddPolicies(db *gorm.DB, rules [][]string) error {\n\tvar casbinRules []gormadapter.CasbinRule\n\tfor i := range rules {\n\t\tcasbinRules = append(casbinRules, gormadapter.CasbinRule{\n\t\t\tPtype: \"p\",\n\t\t\tV0:    rules[i][0],\n\t\t\tV1:    rules[i][1],\n\t\t\tV2:    rules[i][2],\n\t\t})\n\t}\n\treturn db.Create(&casbinRules).Error\n}\n\nfunc (casbinService *CasbinService) FreshCasbin() (err error) {\n\te := utils.GetCasbin()\n\terr = e.LoadPolicy()\n\treturn err\n}\n\n// GetAuthoritiesByApi 获取拥有指定API权限的所有角色ID\nfunc (casbinService *CasbinService) GetAuthoritiesByApi(path, method string) (authorityIds []uint, err error) {\n\tvar rules []gormadapter.CasbinRule\n\terr = global.GVA_DB.Where(\"ptype = 'p' AND v1 = ? AND v2 = ?\", path, method).Find(&rules).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, r := range rules {\n\t\tid, e := strconv.Atoi(r.V0)\n\t\tif e == nil {\n\t\t\tauthorityIds = append(authorityIds, uint(id))\n\t\t}\n\t}\n\treturn authorityIds, nil\n}\n\n// SetApiAuthorities 全量覆盖某API关联的角色列表\nfunc (casbinService *CasbinService) SetApiAuthorities(path, method string, authorityIds []uint) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\t// 1. 删除该API所有已有的角色关联\n\t\tif err := tx.Where(\"ptype = 'p' AND v1 = ? AND v2 = ?\", path, method).Delete(&gormadapter.CasbinRule{}).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// 2. 批量插入新的关联记录\n\t\tif len(authorityIds) > 0 {\n\t\t\tnewRules := make([]gormadapter.CasbinRule, 0, len(authorityIds))\n\t\t\tfor _, authorityId := range authorityIds {\n\t\t\t\tnewRules = append(newRules, gormadapter.CasbinRule{\n\t\t\t\t\tPtype: \"p\",\n\t\t\t\t\tV0:    strconv.Itoa(int(authorityId)),\n\t\t\t\t\tV1:    path,\n\t\t\t\t\tV2:    method,\n\t\t\t\t})\n\t\t\t}\n\t\t\tif err := tx.Create(&newRules).Error; err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "server/service/system/sys_dictionary.go",
    "content": "package system\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"gorm.io/gorm\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateSysDictionary\n//@description: 创建字典数据\n//@param: sysDictionary model.SysDictionary\n//@return: err error\n\ntype DictionaryService struct{}\n\nvar DictionaryServiceApp = new(DictionaryService)\n\nfunc (dictionaryService *DictionaryService) CreateSysDictionary(sysDictionary system.SysDictionary) (err error) {\n\tif (!errors.Is(global.GVA_DB.First(&system.SysDictionary{}, \"type = ?\", sysDictionary.Type).Error, gorm.ErrRecordNotFound)) {\n\t\treturn errors.New(\"存在相同的type，不允许创建\")\n\t}\n\terr = global.GVA_DB.Create(&sysDictionary).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteSysDictionary\n//@description: 删除字典数据\n//@param: sysDictionary model.SysDictionary\n//@return: err error\n\nfunc (dictionaryService *DictionaryService) DeleteSysDictionary(sysDictionary system.SysDictionary) (err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", sysDictionary.ID).Preload(\"SysDictionaryDetails\").First(&sysDictionary).Error\n\tif err != nil && errors.Is(err, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"请不要搞事\")\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = global.GVA_DB.Delete(&sysDictionary).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif sysDictionary.SysDictionaryDetails != nil {\n\t\treturn global.GVA_DB.Where(\"sys_dictionary_id=?\", sysDictionary.ID).Delete(sysDictionary.SysDictionaryDetails).Error\n\t}\n\treturn\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateSysDictionary\n//@description: 更新字典数据\n//@param: sysDictionary *model.SysDictionary\n//@return: err error\n\nfunc (dictionaryService *DictionaryService) UpdateSysDictionary(sysDictionary *system.SysDictionary) (err error) {\n\tvar dict system.SysDictionary\n\tsysDictionaryMap := map[string]interface{}{\n\t\t\"Name\":     sysDictionary.Name,\n\t\t\"Type\":     sysDictionary.Type,\n\t\t\"Status\":   sysDictionary.Status,\n\t\t\"Desc\":     sysDictionary.Desc,\n\t\t\"ParentID\": sysDictionary.ParentID,\n\t}\n\terr = global.GVA_DB.Where(\"id = ?\", sysDictionary.ID).First(&dict).Error\n\tif err != nil {\n\t\tglobal.GVA_LOG.Debug(err.Error())\n\t\treturn errors.New(\"查询字典数据失败\")\n\t}\n\tif dict.Type != sysDictionary.Type {\n\t\tif !errors.Is(global.GVA_DB.First(&system.SysDictionary{}, \"type = ?\", sysDictionary.Type).Error, gorm.ErrRecordNotFound) {\n\t\t\treturn errors.New(\"存在相同的type，不允许创建\")\n\t\t}\n\t}\n\n\t// 检查是否会形成循环引用\n\tif sysDictionary.ParentID != nil && *sysDictionary.ParentID != 0 {\n\t\tif err := dictionaryService.checkCircularReference(sysDictionary.ID, *sysDictionary.ParentID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = global.GVA_DB.Model(&dict).Updates(sysDictionaryMap).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetSysDictionary\n//@description: 根据id或者type获取字典单条数据\n//@param: Type string, Id uint\n//@return: err error, sysDictionary model.SysDictionary\n\nfunc (dictionaryService *DictionaryService) GetSysDictionary(Type string, Id uint, status *bool) (sysDictionary system.SysDictionary, err error) {\n\tvar flag = false\n\tif status == nil {\n\t\tflag = true\n\t} else {\n\t\tflag = *status\n\t}\n\terr = global.GVA_DB.Where(\"(type = ? OR id = ?) and status = ?\", Type, Id, flag).Preload(\"SysDictionaryDetails\", func(db *gorm.DB) *gorm.DB {\n\t\treturn db.Where(\"status = ? and deleted_at is null\", true).Order(\"sort\")\n\t}).First(&sysDictionary).Error\n\treturn\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: GetSysDictionaryInfoList\n//@description: 分页获取字典列表\n//@param: info request.SysDictionarySearch\n//@return: err error, list interface{}, total int64\n\nfunc (dictionaryService *DictionaryService) GetSysDictionaryInfoList(c *gin.Context, req request.SysDictionarySearch) (list interface{}, err error) {\n\tvar sysDictionarys []system.SysDictionary\n\tquery := global.GVA_DB.WithContext(c)\n\tif req.Name != \"\" {\n\t\tquery = query.Where(\"name LIKE ? OR type LIKE ?\", \"%\"+req.Name+\"%\", \"%\"+req.Name+\"%\")\n\t}\n\t// 预加载子字典\n\tquery = query.Preload(\"Children\")\n\terr = query.Find(&sysDictionarys).Error\n\treturn sysDictionarys, err\n}\n\n// checkCircularReference 检查是否会形成循环引用\nfunc (dictionaryService *DictionaryService) checkCircularReference(currentID uint, parentID uint) error {\n\tif currentID == parentID {\n\t\treturn errors.New(\"不能将字典设置为自己的父级\")\n\t}\n\n\t// 递归检查父级链条\n\tvar parent system.SysDictionary\n\terr := global.GVA_DB.Where(\"id = ?\", parentID).First(&parent).Error\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\treturn nil // 父级不存在，允许设置\n\t\t}\n\t\treturn err\n\t}\n\n\t// 如果父级还有父级，继续检查\n\tif parent.ParentID != nil && *parent.ParentID != 0 {\n\t\treturn dictionaryService.checkCircularReference(currentID, *parent.ParentID)\n\t}\n\n\treturn nil\n}\n\n//@author: [pixelMax]\n//@function: ExportSysDictionary\n//@description: 导出字典JSON（包含字典详情）\n//@param: id uint\n//@return: exportData map[string]interface{}, err error\n\nfunc (dictionaryService *DictionaryService) ExportSysDictionary(id uint) (exportData map[string]interface{}, err error) {\n\tvar dictionary system.SysDictionary\n\t// 查询字典及其所有详情\n\terr = global.GVA_DB.Where(\"id = ?\", id).Preload(\"SysDictionaryDetails\", func(db *gorm.DB) *gorm.DB {\n\t\treturn db.Order(\"sort\")\n\t}).First(&dictionary).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 清空字典详情中的ID、创建时间、更新时间等字段\n\tvar cleanDetails []map[string]interface{}\n\tfor _, detail := range dictionary.SysDictionaryDetails {\n\t\tcleanDetail := map[string]interface{}{\n\t\t\t\"label\":  detail.Label,\n\t\t\t\"value\":  detail.Value,\n\t\t\t\"extend\": detail.Extend,\n\t\t\t\"status\": detail.Status,\n\t\t\t\"sort\":   detail.Sort,\n\t\t\t\"level\":  detail.Level,\n\t\t\t\"path\":   detail.Path,\n\t\t}\n\t\tcleanDetails = append(cleanDetails, cleanDetail)\n\t}\n\n\t// 构造导出数据\n\texportData = map[string]interface{}{\n\t\t\"name\":                 dictionary.Name,\n\t\t\"type\":                 dictionary.Type,\n\t\t\"status\":               dictionary.Status,\n\t\t\"desc\":                 dictionary.Desc,\n\t\t\"sysDictionaryDetails\": cleanDetails,\n\t}\n\n\treturn exportData, nil\n}\n\n//@author: [pixelMax]\n//@function: ImportSysDictionary\n//@description: 导入字典JSON（包含字典详情）\n//@param: jsonStr string\n//@return: err error\n\nfunc (dictionaryService *DictionaryService) ImportSysDictionary(jsonStr string) error {\n\t// 直接解析到 SysDictionary 结构体\n\tvar importData system.SysDictionary\n\tif err := json.Unmarshal([]byte(jsonStr), &importData); err != nil {\n\t\treturn errors.New(\"JSON 格式错误: \" + err.Error())\n\t}\n\n\t// 验证必填字段\n\tif importData.Name == \"\" {\n\t\treturn errors.New(\"字典名称不能为空\")\n\t}\n\tif importData.Type == \"\" {\n\t\treturn errors.New(\"字典类型不能为空\")\n\t}\n\n\t// 检查字典类型是否已存在\n\tif !errors.Is(global.GVA_DB.First(&system.SysDictionary{}, \"type = ?\", importData.Type).Error, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"存在相同的type，不允许导入\")\n\t}\n\n\t// 创建字典（清空导入数据的ID和时间戳）\n\tdictionary := system.SysDictionary{\n\t\tName:   importData.Name,\n\t\tType:   importData.Type,\n\t\tStatus: importData.Status,\n\t\tDesc:   importData.Desc,\n\t}\n\n\t// 开启事务\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\t// 创建字典\n\t\tif err := tx.Create(&dictionary).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 处理字典详情\n\t\tif len(importData.SysDictionaryDetails) > 0 {\n\t\t\t// 创建一个映射来跟踪旧ID到新ID的对应关系\n\t\t\tidMap := make(map[uint]uint)\n\n\t\t\t// 第一遍：创建所有详情记录\n\t\t\tfor _, detail := range importData.SysDictionaryDetails {\n\t\t\t\t// 验证必填字段\n\t\t\t\tif detail.Label == \"\" || detail.Value == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// 记录旧ID\n\t\t\t\toldID := detail.ID\n\n\t\t\t\t// 创建新的详情记录（ID会被GORM自动设置）\n\t\t\t\tdetailRecord := system.SysDictionaryDetail{\n\t\t\t\t\tLabel:           detail.Label,\n\t\t\t\t\tValue:           detail.Value,\n\t\t\t\t\tExtend:          detail.Extend,\n\t\t\t\t\tStatus:          detail.Status,\n\t\t\t\t\tSort:            detail.Sort,\n\t\t\t\t\tLevel:           detail.Level,\n\t\t\t\t\tPath:            detail.Path,\n\t\t\t\t\tSysDictionaryID: int(dictionary.ID),\n\t\t\t\t}\n\n\t\t\t\t// 创建详情记录\n\t\t\t\tif err := tx.Create(&detailRecord).Error; err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// 记录旧ID到新ID的映射\n\t\t\t\tif oldID > 0 {\n\t\t\t\t\tidMap[oldID] = detailRecord.ID\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 第二遍：更新parent_id关系\n\t\t\tfor _, detail := range importData.SysDictionaryDetails {\n\t\t\t\tif detail.ParentID != nil && *detail.ParentID > 0 && detail.ID > 0 {\n\t\t\t\t\tif newID, exists := idMap[detail.ID]; exists {\n\t\t\t\t\t\tif newParentID, parentExists := idMap[*detail.ParentID]; parentExists {\n\t\t\t\t\t\t\tif err := tx.Model(&system.SysDictionaryDetail{}).\n\t\t\t\t\t\t\t\tWhere(\"id = ?\", newID).\n\t\t\t\t\t\t\t\tUpdate(\"parent_id\", newParentID).Error; err != nil {\n\t\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "server/service/system/sys_dictionary_detail.go",
    "content": "package system\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateSysDictionaryDetail\n//@description: 创建字典详情数据\n//@param: sysDictionaryDetail model.SysDictionaryDetail\n//@return: err error\n\ntype DictionaryDetailService struct{}\n\nvar DictionaryDetailServiceApp = new(DictionaryDetailService)\n\nfunc (dictionaryDetailService *DictionaryDetailService) CreateSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) {\n\t// 计算层级和路径\n\tif sysDictionaryDetail.ParentID != nil {\n\t\tvar parent system.SysDictionaryDetail\n\t\terr = global.GVA_DB.First(&parent, *sysDictionaryDetail.ParentID).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsysDictionaryDetail.Level = parent.Level + 1\n\t\tif parent.Path == \"\" {\n\t\t\tsysDictionaryDetail.Path = strconv.Itoa(int(parent.ID))\n\t\t} else {\n\t\t\tsysDictionaryDetail.Path = parent.Path + \",\" + strconv.Itoa(int(parent.ID))\n\t\t}\n\t} else {\n\t\tsysDictionaryDetail.Level = 0\n\t\tsysDictionaryDetail.Path = \"\"\n\t}\n\n\terr = global.GVA_DB.Create(&sysDictionaryDetail).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteSysDictionaryDetail\n//@description: 删除字典详情数据\n//@param: sysDictionaryDetail model.SysDictionaryDetail\n//@return: err error\n\nfunc (dictionaryDetailService *DictionaryDetailService) DeleteSysDictionaryDetail(sysDictionaryDetail system.SysDictionaryDetail) (err error) {\n\t// 检查是否有子项\n\tvar count int64\n\terr = global.GVA_DB.Model(&system.SysDictionaryDetail{}).Where(\"parent_id = ?\", sysDictionaryDetail.ID).Count(&count).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tif count > 0 {\n\t\treturn fmt.Errorf(\"该字典详情下还有子项，无法删除\")\n\t}\n\n\terr = global.GVA_DB.Delete(&sysDictionaryDetail).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: UpdateSysDictionaryDetail\n//@description: 更新字典详情数据\n//@param: sysDictionaryDetail *model.SysDictionaryDetail\n//@return: err error\n\nfunc (dictionaryDetailService *DictionaryDetailService) UpdateSysDictionaryDetail(sysDictionaryDetail *system.SysDictionaryDetail) (err error) {\n\t// 如果更新了父级ID，需要重新计算层级和路径\n\tif sysDictionaryDetail.ParentID != nil {\n\t\tvar parent system.SysDictionaryDetail\n\t\terr = global.GVA_DB.First(&parent, *sysDictionaryDetail.ParentID).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 检查循环引用\n\t\tif dictionaryDetailService.checkCircularReference(sysDictionaryDetail.ID, *sysDictionaryDetail.ParentID) {\n\t\t\treturn fmt.Errorf(\"不能将字典详情设置为自己或其子项的父级\")\n\t\t}\n\n\t\tsysDictionaryDetail.Level = parent.Level + 1\n\t\tif parent.Path == \"\" {\n\t\t\tsysDictionaryDetail.Path = strconv.Itoa(int(parent.ID))\n\t\t} else {\n\t\t\tsysDictionaryDetail.Path = parent.Path + \",\" + strconv.Itoa(int(parent.ID))\n\t\t}\n\t} else {\n\t\tsysDictionaryDetail.Level = 0\n\t\tsysDictionaryDetail.Path = \"\"\n\t}\n\n\terr = global.GVA_DB.Save(sysDictionaryDetail).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 更新所有子项的层级和路径\n\treturn dictionaryDetailService.updateChildrenLevelAndPath(sysDictionaryDetail.ID)\n}\n\n// checkCircularReference 检查循环引用\nfunc (dictionaryDetailService *DictionaryDetailService) checkCircularReference(id, parentID uint) bool {\n\tif id == parentID {\n\t\treturn true\n\t}\n\n\tvar parent system.SysDictionaryDetail\n\terr := global.GVA_DB.First(&parent, parentID).Error\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tif parent.ParentID == nil {\n\t\treturn false\n\t}\n\n\treturn dictionaryDetailService.checkCircularReference(id, *parent.ParentID)\n}\n\n// updateChildrenLevelAndPath 更新子项的层级和路径\nfunc (dictionaryDetailService *DictionaryDetailService) updateChildrenLevelAndPath(parentID uint) error {\n\tvar children []system.SysDictionaryDetail\n\terr := global.GVA_DB.Where(\"parent_id = ?\", parentID).Find(&children).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar parent system.SysDictionaryDetail\n\terr = global.GVA_DB.First(&parent, parentID).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, child := range children {\n\t\tchild.Level = parent.Level + 1\n\t\tif parent.Path == \"\" {\n\t\t\tchild.Path = strconv.Itoa(int(parent.ID))\n\t\t} else {\n\t\t\tchild.Path = parent.Path + \",\" + strconv.Itoa(int(parent.ID))\n\t\t}\n\n\t\terr = global.GVA_DB.Save(&child).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 递归更新子项的子项\n\t\terr = dictionaryDetailService.updateChildrenLevelAndPath(child.ID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetSysDictionaryDetail\n//@description: 根据id获取字典详情单条数据\n//@param: id uint\n//@return: sysDictionaryDetail system.SysDictionaryDetail, err error\n\nfunc (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetail(id uint) (sysDictionaryDetail system.SysDictionaryDetail, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", id).First(&sysDictionaryDetail).Error\n\treturn\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetSysDictionaryDetailInfoList\n//@description: 分页获取字典详情列表\n//@param: info request.SysDictionaryDetailSearch\n//@return: list interface{}, total int64, err error\n\nfunc (dictionaryDetailService *DictionaryDetailService) GetSysDictionaryDetailInfoList(info request.SysDictionaryDetailSearch) (list interface{}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysDictionaryDetail{})\n\tvar sysDictionaryDetails []system.SysDictionaryDetail\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif info.Label != \"\" {\n\t\tdb = db.Where(\"label LIKE ?\", \"%\"+info.Label+\"%\")\n\t}\n\tif info.Value != \"\" {\n\t\tdb = db.Where(\"value = ?\", info.Value)\n\t}\n\tif info.Status != nil {\n\t\tdb = db.Where(\"status = ?\", info.Status)\n\t}\n\tif info.SysDictionaryID != 0 {\n\t\tdb = db.Where(\"sys_dictionary_id = ?\", info.SysDictionaryID)\n\t}\n\tif info.ParentID != nil {\n\t\tdb = db.Where(\"parent_id = ?\", *info.ParentID)\n\t}\n\tif info.Level != nil {\n\t\tdb = db.Where(\"level = ?\", *info.Level)\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\terr = db.Limit(limit).Offset(offset).Order(\"sort\").Order(\"id\").Find(&sysDictionaryDetails).Error\n\treturn sysDictionaryDetails, total, err\n}\n\n// 按照字典id获取字典全部内容的方法\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryList(dictionaryID uint) (list []system.SysDictionaryDetail, err error) {\n\tvar sysDictionaryDetails []system.SysDictionaryDetail\n\terr = global.GVA_DB.Find(&sysDictionaryDetails, \"sys_dictionary_id = ?\", dictionaryID).Error\n\treturn sysDictionaryDetails, err\n}\n\n// GetDictionaryTreeList 获取字典树形结构列表\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryTreeList(dictionaryID uint) (list []system.SysDictionaryDetail, err error) {\n\tvar sysDictionaryDetails []system.SysDictionaryDetail\n\t// 只获取顶级项目（parent_id为空）\n\terr = global.GVA_DB.Where(\"sys_dictionary_id = ? AND parent_id IS NULL\", dictionaryID).Order(\"sort\").Find(&sysDictionaryDetails).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 递归加载子项并设置disabled属性\n\tfor i := range sysDictionaryDetails {\n\t\t// 设置disabled属性：当status为false时，disabled为true\n\t\tif sysDictionaryDetails[i].Status != nil {\n\t\t\tsysDictionaryDetails[i].Disabled = !*sysDictionaryDetails[i].Status\n\t\t} else {\n\t\t\tsysDictionaryDetails[i].Disabled = false // 默认不禁用\n\t\t}\n\t\t\n\t\terr = dictionaryDetailService.loadChildren(&sysDictionaryDetails[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn sysDictionaryDetails, nil\n}\n\n// loadChildren 递归加载子项\nfunc (dictionaryDetailService *DictionaryDetailService) loadChildren(detail *system.SysDictionaryDetail) error {\n\tvar children []system.SysDictionaryDetail\n\terr := global.GVA_DB.Where(\"parent_id = ?\", detail.ID).Order(\"sort\").Find(&children).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor i := range children {\n\t\t// 设置disabled属性：当status为false时，disabled为true\n\t\tif children[i].Status != nil {\n\t\t\tchildren[i].Disabled = !*children[i].Status\n\t\t} else {\n\t\t\tchildren[i].Disabled = false // 默认不禁用\n\t\t}\n\t\t\n\t\terr = dictionaryDetailService.loadChildren(&children[i])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdetail.Children = children\n\treturn nil\n}\n\n// GetDictionaryDetailsByParent 根据父级ID获取字典详情\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryDetailsByParent(req request.GetDictionaryDetailsByParentRequest) (list []system.SysDictionaryDetail, err error) {\n\tdb := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Where(\"sys_dictionary_id = ?\", req.SysDictionaryID)\n\n\tif req.ParentID != nil {\n\t\tdb = db.Where(\"parent_id = ?\", *req.ParentID)\n\t} else {\n\t\tdb = db.Where(\"parent_id IS NULL\")\n\t}\n\n\terr = db.Order(\"sort\").Find(&list).Error\n\tif err != nil {\n\t\treturn list, err\n\t}\n\n\t// 设置disabled属性\n\tfor i := range list {\n\t\tif list[i].Status != nil {\n\t\t\tlist[i].Disabled = !*list[i].Status\n\t\t} else {\n\t\t\tlist[i].Disabled = false // 默认不禁用\n\t\t}\n\t}\n\n\t// 如果需要包含子级数据，使用递归方式加载所有层级的子项\n\tif req.IncludeChildren {\n\t\tfor i := range list {\n\t\t\terr = dictionaryDetailService.loadChildren(&list[i])\n\t\t\tif err != nil {\n\t\t\t\treturn list, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn list, err\n}\n\n// 按照字典type获取字典全部内容的方法\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryListByType(t string) (list []system.SysDictionaryDetail, err error) {\n\tvar sysDictionaryDetails []system.SysDictionaryDetail\n\tdb := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Joins(\"JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id\")\n\terr = db.Find(&sysDictionaryDetails, \"type = ?\", t).Error\n\treturn sysDictionaryDetails, err\n}\n\n// GetDictionaryTreeListByType 根据字典类型获取树形结构\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryTreeListByType(t string) (list []system.SysDictionaryDetail, err error) {\n\tvar sysDictionaryDetails []system.SysDictionaryDetail\n\tdb := global.GVA_DB.Model(&system.SysDictionaryDetail{}).\n\t\tJoins(\"JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id\").\n\t\tWhere(\"sys_dictionaries.type = ? AND sys_dictionary_details.parent_id IS NULL\", t).\n\t\tOrder(\"sys_dictionary_details.sort\")\n\n\terr = db.Find(&sysDictionaryDetails).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 递归加载子项并设置disabled属性\n\tfor i := range sysDictionaryDetails {\n\t\t// 设置disabled属性：当status为false时，disabled为true\n\t\tif sysDictionaryDetails[i].Status != nil {\n\t\t\tsysDictionaryDetails[i].Disabled = !*sysDictionaryDetails[i].Status\n\t\t} else {\n\t\t\tsysDictionaryDetails[i].Disabled = false // 默认不禁用\n\t\t}\n\t\t\n\t\terr = dictionaryDetailService.loadChildren(&sysDictionaryDetails[i])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn sysDictionaryDetails, nil\n}\n\n// 按照字典id+字典内容value获取单条字典内容\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryInfoByValue(dictionaryID uint, value string) (detail system.SysDictionaryDetail, err error) {\n\tvar sysDictionaryDetail system.SysDictionaryDetail\n\terr = global.GVA_DB.First(&sysDictionaryDetail, \"sys_dictionary_id = ? and value = ?\", dictionaryID, value).Error\n\treturn sysDictionaryDetail, err\n}\n\n// 按照字典type+字典内容value获取单条字典内容\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryInfoByTypeValue(t string, value string) (detail system.SysDictionaryDetail, err error) {\n\tvar sysDictionaryDetails system.SysDictionaryDetail\n\tdb := global.GVA_DB.Model(&system.SysDictionaryDetail{}).Joins(\"JOIN sys_dictionaries ON sys_dictionaries.id = sys_dictionary_details.sys_dictionary_id\")\n\terr = db.First(&sysDictionaryDetails, \"sys_dictionaries.type = ? and sys_dictionary_details.value = ?\", t, value).Error\n\treturn sysDictionaryDetails, err\n}\n\n// GetDictionaryPath 获取字典详情的完整路径\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryPath(id uint) (path []system.SysDictionaryDetail, err error) {\n\tvar detail system.SysDictionaryDetail\n\terr = global.GVA_DB.First(&detail, id).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpath = append(path, detail)\n\n\tif detail.ParentID != nil {\n\t\tparentPath, err := dictionaryDetailService.GetDictionaryPath(*detail.ParentID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpath = append(parentPath, path...)\n\t}\n\n\treturn path, nil\n}\n\n// GetDictionaryPathByValue 根据值获取字典详情的完整路径\nfunc (dictionaryDetailService *DictionaryDetailService) GetDictionaryPathByValue(dictionaryID uint, value string) (path []system.SysDictionaryDetail, err error) {\n\tdetail, err := dictionaryDetailService.GetDictionaryInfoByValue(dictionaryID, value)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dictionaryDetailService.GetDictionaryPath(detail.ID)\n}\n"
  },
  {
    "path": "server/service/system/sys_error.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\ntype SysErrorService struct{}\n\n// CreateSysError 创建错误日志记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) CreateSysError(ctx context.Context, sysError *system.SysError) (err error) {\n\tif global.GVA_DB == nil {\n\t\treturn nil\n\t}\n\terr = global.GVA_DB.Create(sysError).Error\n\treturn err\n}\n\n// DeleteSysError 删除错误日志记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) DeleteSysError(ctx context.Context, ID string) (err error) {\n\terr = global.GVA_DB.Delete(&system.SysError{}, \"id = ?\", ID).Error\n\treturn err\n}\n\n// DeleteSysErrorByIds 批量删除错误日志记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) DeleteSysErrorByIds(ctx context.Context, IDs []string) (err error) {\n\terr = global.GVA_DB.Delete(&[]system.SysError{}, \"id in ?\", IDs).Error\n\treturn err\n}\n\n// UpdateSysError 更新错误日志记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) UpdateSysError(ctx context.Context, sysError system.SysError) (err error) {\n\terr = global.GVA_DB.Model(&system.SysError{}).Where(\"id = ?\", sysError.ID).Updates(&sysError).Error\n\treturn err\n}\n\n// GetSysError 根据ID获取错误日志记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) GetSysError(ctx context.Context, ID string) (sysError system.SysError, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", ID).First(&sysError).Error\n\treturn\n}\n\n// GetSysErrorInfoList 分页获取错误日志记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) GetSysErrorInfoList(ctx context.Context, info systemReq.SysErrorSearch) (list []system.SysError, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysError{}).Order(\"created_at desc\")\n\tvar sysErrors []system.SysError\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif len(info.CreatedAtRange) == 2 {\n\t\tdb = db.Where(\"created_at BETWEEN ? AND ?\", info.CreatedAtRange[0], info.CreatedAtRange[1])\n\t}\n\n\tif info.Form != nil && *info.Form != \"\" {\n\t\tdb = db.Where(\"form = ?\", *info.Form)\n\t}\n\tif info.Info != nil && *info.Info != \"\" {\n\t\tdb = db.Where(\"info LIKE ?\", \"%\"+*info.Info+\"%\")\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif limit != 0 {\n\t\tdb = db.Limit(limit).Offset(offset)\n\t}\n\n\terr = db.Find(&sysErrors).Error\n\treturn sysErrors, total, err\n}\n\n// GetSysErrorSolution 异步处理错误\n// Author [yourname](https://github.com/yourname)\nfunc (sysErrorService *SysErrorService) GetSysErrorSolution(ctx context.Context, ID string) (err error) {\n\t// 立即更新为处理中\n\terr = global.GVA_DB.WithContext(ctx).Model(&system.SysError{}).Where(\"id = ?\", ID).Update(\"status\", \"处理中\").Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 异步协程在一分钟后更新为处理完成\n\tgo func(id string) {\n\t\t// 查询当前错误信息用于生成方案\n\t\tvar se system.SysError\n\t\t_ = global.GVA_DB.Model(&system.SysError{}).Where(\"id = ?\", id).First(&se).Error\n\n\t\t// 构造 LLM 请求参数，使用管家模式(butler)根据错误信息生成解决方案\n\t\tvar form, info string\n\t\tif se.Form != nil {\n\t\t\tform = *se.Form\n\t\t}\n\t\tif se.Info != nil {\n\t\t\tinfo = *se.Info\n\t\t}\n\n\t\tllmReq := common.JSONMap{\n\t\t\t\"mode\": \"solution\",\n\t\t\t\"info\": info,\n\t\t\t\"form\": form,\n\t\t}\n\n\t\t// 调用服务层 LLMAuto，忽略错误但尽量写入方案\n\t\tvar solution string\n\t\tif data, err := (&AutoCodeService{}).LLMAuto(context.Background(), llmReq); err == nil {\n\t\t\tsolution = fmt.Sprintf(\"%v\", data.(map[string]interface{})[\"text\"])\n\t\t\t_ = global.GVA_DB.Model(&system.SysError{}).Where(\"id = ?\", id).Updates(map[string]interface{}{\"status\": \"处理完成\", \"solution\": solution}).Error\n\t\t} else {\n\t\t\t// 即使生成失败也标记为完成，避免任务卡住\n\t\t\t_ = global.GVA_DB.Model(&system.SysError{}).Where(\"id = ?\", id).Update(\"status\", \"处理失败\").Error\n\t\t}\n\t}(ID)\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/service/system/sys_export_template.go",
    "content": "package system\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/xuri/excelize/v2\"\n\t\"gorm.io/gorm\"\n)\n\ntype SysExportTemplateService struct {\n}\n\nvar SysExportTemplateServiceApp = new(SysExportTemplateService)\n\n// CreateSysExportTemplate 创建导出模板记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) CreateSysExportTemplate(sysExportTemplate *system.SysExportTemplate) (err error) {\n\terr = global.GVA_DB.Create(sysExportTemplate).Error\n\treturn err\n}\n\n// DeleteSysExportTemplate 删除导出模板记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {\n\terr = global.GVA_DB.Delete(&sysExportTemplate).Error\n\treturn err\n}\n\n// DeleteSysExportTemplateByIds 批量删除导出模板记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplateByIds(ids request.IdsReq) (err error) {\n\terr = global.GVA_DB.Delete(&[]system.SysExportTemplate{}, \"id in ?\", ids.Ids).Error\n\treturn err\n}\n\n// UpdateSysExportTemplate 更新导出模板记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) UpdateSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tconditions := sysExportTemplate.Conditions\n\t\te := tx.Delete(&[]system.Condition{}, \"template_id = ?\", sysExportTemplate.TemplateID).Error\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\tsysExportTemplate.Conditions = nil\n\n\t\tjoins := sysExportTemplate.JoinTemplate\n\t\te = tx.Delete(&[]system.JoinTemplate{}, \"template_id = ?\", sysExportTemplate.TemplateID).Error\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\tsysExportTemplate.JoinTemplate = nil\n\n\t\te = tx.Updates(&sysExportTemplate).Error\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\tif len(conditions) > 0 {\n\t\t\tfor i := range conditions {\n\t\t\t\tconditions[i].ID = 0\n\t\t\t}\n\t\t\te = tx.Create(&conditions).Error\n\t\t}\n\t\tif len(joins) > 0 {\n\t\t\tfor i := range joins {\n\t\t\t\tjoins[i].ID = 0\n\t\t\t}\n\t\t\te = tx.Create(&joins).Error\n\t\t}\n\t\treturn e\n\t})\n}\n\n// GetSysExportTemplate 根据id获取导出模板记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplate(id uint) (sysExportTemplate system.SysExportTemplate, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", id).Preload(\"JoinTemplate\").Preload(\"Conditions\").First(&sysExportTemplate).Error\n\treturn\n}\n\n// GetSysExportTemplateInfoList 分页获取导出模板记录\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateInfoList(info systemReq.SysExportTemplateSearch) (list []system.SysExportTemplate, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysExportTemplate{})\n\tvar sysExportTemplates []system.SysExportTemplate\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif info.StartCreatedAt != nil && info.EndCreatedAt != nil {\n\t\tdb = db.Where(\"created_at BETWEEN ? AND ?\", info.StartCreatedAt, info.EndCreatedAt)\n\t}\n\tif info.Name != \"\" {\n\t\tdb = db.Where(\"name LIKE ?\", \"%\"+info.Name+\"%\")\n\t}\n\tif info.TableName != \"\" {\n\t\tdb = db.Where(\"table_name = ?\", info.TableName)\n\t}\n\tif info.TemplateID != \"\" {\n\t\tdb = db.Where(\"template_id = ?\", info.TemplateID)\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif limit != 0 {\n\t\tdb = db.Limit(limit).Offset(offset)\n\t}\n\n\terr = db.Find(&sysExportTemplates).Error\n\treturn sysExportTemplates, total, err\n}\n\n// ExportExcel 导出Excel\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) {\n\tvar params = values.Get(\"params\")\n\tparamsValues, err := url.ParseQuery(params)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"解析 params 参数失败: %v\", err)\n\t}\n\tvar template system.SysExportTemplate\n\terr = global.GVA_DB.Preload(\"Conditions\").Preload(\"JoinTemplate\").First(&template, \"template_id = ?\", templateID).Error\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tf := excelize.NewFile()\n\tdefer func() {\n\t\tif err := f.Close(); err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}()\n\t// Create a new sheet.\n\tindex, err := f.NewSheet(\"Sheet1\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tvar templateInfoMap = make(map[string]string)\n\tcolumns, err := utils.GetJSONKeys(template.TemplateInfo)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\terr = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tvar tableTitle []string\n\tvar selectKeyFmt []string\n\tfor _, key := range columns {\n\t\tselectKeyFmt = append(selectKeyFmt, key)\n\t\ttableTitle = append(tableTitle, templateInfoMap[key])\n\t}\n\n\tselects := strings.Join(selectKeyFmt, \", \")\n\tvar tableMap []map[string]interface{}\n\tdb := global.GVA_DB\n\tif template.DBName != \"\" {\n\t\tdb = global.MustGetGlobalDBByDBName(template.DBName)\n\t}\n\n\t// 如果有自定义SQL，则优先使用自定义SQL\n\tif template.SQL != \"\" {\n\t\t// 将 url.Values 转换为 map[string]interface{} 以支持 GORM 的命名参数\n\t\tsqlParams := make(map[string]interface{})\n\t\tfor k, v := range paramsValues {\n\t\t\tif len(v) > 0 {\n\t\t\t\tsqlParams[k] = v[0]\n\t\t\t}\n\t\t}\n\n\t\t// 执行原生 SQL，支持 @key 命名参数\n\t\terr = db.Raw(template.SQL, sqlParams).Scan(&tableMap).Error\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t} else {\n\t\tif len(template.JoinTemplate) > 0 {\n\t\t\tfor _, join := range template.JoinTemplate {\n\t\t\t\tdb = db.Joins(join.JOINS + \" \" + join.Table + \" ON \" + join.ON)\n\t\t\t}\n\t\t}\n\n\t\tdb = db.Select(selects).Table(template.TableName)\n\n\t\tfilterDeleted := false\n\n\t\tfilterParam := paramsValues.Get(\"filterDeleted\")\n\t\tif filterParam == \"true\" {\n\t\t\tfilterDeleted = true\n\t\t}\n\n\t\tif filterDeleted {\n\t\t\t// 自动过滤主表的软删除\n\t\t\tdb = db.Where(fmt.Sprintf(\"%s.deleted_at IS NULL\", template.TableName))\n\n\t\t\t// 过滤关联表的软删除(如果有)\n\t\t\tif len(template.JoinTemplate) > 0 {\n\t\t\t\tfor _, join := range template.JoinTemplate {\n\t\t\t\t\t// 检查关联表是否有deleted_at字段\n\t\t\t\t\thasDeletedAt := sysExportTemplateService.hasDeletedAtColumn(join.Table)\n\t\t\t\t\tif hasDeletedAt {\n\t\t\t\t\t\tdb = db.Where(fmt.Sprintf(\"%s.deleted_at IS NULL\", join.Table))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(template.Conditions) > 0 {\n\t\t\tfor _, condition := range template.Conditions {\n\t\t\t\tsql := fmt.Sprintf(\"%s %s ?\", condition.Column, condition.Operator)\n\t\t\t\tvalue := paramsValues.Get(condition.From)\n\n\t\t\t\tif condition.Operator == \"IN\" || condition.Operator == \"NOT IN\" {\n\t\t\t\t\tsql = fmt.Sprintf(\"%s %s (?)\", condition.Column, condition.Operator)\n\t\t\t\t}\n\n\t\t\t\tif condition.Operator == \"BETWEEN\" {\n\t\t\t\t\tsql = fmt.Sprintf(\"%s BETWEEN ? AND ?\", condition.Column)\n\t\t\t\t\tstartValue := paramsValues.Get(\"start\" + condition.From)\n\t\t\t\t\tendValue := paramsValues.Get(\"end\" + condition.From)\n\t\t\t\t\tif startValue != \"\" && endValue != \"\" {\n\t\t\t\t\t\tdb = db.Where(sql, startValue, endValue)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif value != \"\" {\n\t\t\t\t\tif condition.Operator == \"LIKE\" {\n\t\t\t\t\t\tvalue = \"%\" + value + \"%\"\n\t\t\t\t\t}\n\t\t\t\t\tdb = db.Where(sql, value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// 通过参数传入limit\n\t\tlimit := paramsValues.Get(\"limit\")\n\t\tif limit != \"\" {\n\t\t\tl, e := strconv.Atoi(limit)\n\t\t\tif e == nil {\n\t\t\t\tdb = db.Limit(l)\n\t\t\t}\n\t\t}\n\t\t// 模板的默认limit\n\t\tif limit == \"\" && template.Limit != nil && *template.Limit != 0 {\n\t\t\tdb = db.Limit(*template.Limit)\n\t\t}\n\n\t\t// 通过参数传入offset\n\t\toffset := paramsValues.Get(\"offset\")\n\t\tif offset != \"\" {\n\t\t\to, e := strconv.Atoi(offset)\n\t\t\tif e == nil {\n\t\t\t\tdb = db.Offset(o)\n\t\t\t}\n\t\t}\n\n\t\t// 获取当前表的所有字段\n\t\ttable := template.TableName\n\t\torderColumns, err := db.Migrator().ColumnTypes(table)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\n\t\t// 创建一个 map 来存储字段名\n\t\tfields := make(map[string]bool)\n\n\t\tfor _, column := range orderColumns {\n\t\t\tfields[column.Name()] = true\n\t\t}\n\n\t\t// 通过参数传入order\n\t\torder := paramsValues.Get(\"order\")\n\n\t\tif order == \"\" && template.Order != \"\" {\n\t\t\t// 如果没有order入参，这里会使用模板的默认排序\n\t\t\torder = template.Order\n\t\t}\n\n\t\tif order != \"\" {\n\t\t\tcheckOrderArr := strings.Split(order, \" \")\n\t\t\torderStr := \"\"\n\t\t\t// 检查请求的排序字段是否在字段列表中\n\t\t\tif _, ok := fields[checkOrderArr[0]]; !ok {\n\t\t\t\treturn nil, \"\", fmt.Errorf(\"order by %s is not in the fields\", order)\n\t\t\t}\n\t\t\torderStr = checkOrderArr[0]\n\t\t\tif len(checkOrderArr) > 1 {\n\t\t\t\tif checkOrderArr[1] != \"asc\" && checkOrderArr[1] != \"desc\" {\n\t\t\t\t\treturn nil, \"\", fmt.Errorf(\"order by %s is not secure\", order)\n\t\t\t\t}\n\t\t\t\torderStr = orderStr + \" \" + checkOrderArr[1]\n\t\t\t}\n\t\t\tdb = db.Order(orderStr)\n\t\t}\n\n\t\terr = db.Debug().Find(&tableMap).Error\n\t\tif err != nil {\n\t\t\treturn nil, \"\", err\n\t\t}\n\t}\n\n\tvar rows [][]string\n\trows = append(rows, tableTitle)\n\tfor _, exTable := range tableMap {\n\t\tvar row []string\n\t\tfor _, column := range columns {\n\t\t\tcolumn = strings.ReplaceAll(column, \"\\\"\", \"\")\n\t\t\tcolumn = strings.ReplaceAll(column, \"`\", \"\")\n\t\t\tif len(template.JoinTemplate) > 0 {\n\t\t\t\tcolumnAs := strings.Split(column, \" as \")\n\t\t\t\tif len(columnAs) > 1 {\n\t\t\t\t\tcolumn = strings.TrimSpace(strings.Split(column, \" as \")[1])\n\t\t\t\t} else {\n\t\t\t\t\tcolumnArr := strings.Split(column, \".\")\n\t\t\t\t\tif len(columnArr) > 1 {\n\t\t\t\t\t\tcolumn = strings.Split(column, \".\")[1]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 需要对时间类型特殊处理\n\t\t\tif t, ok := exTable[column].(time.Time); ok {\n\t\t\t\trow = append(row, t.Format(\"2006-01-02 15:04:05\"))\n\t\t\t} else {\n\t\t\t\trow = append(row, fmt.Sprintf(\"%v\", exTable[column]))\n\t\t\t}\n\t\t}\n\t\trows = append(rows, row)\n\t}\n\tfor i, row := range rows {\n\t\tfor j, colCell := range row {\n\t\t\tcell := fmt.Sprintf(\"%s%d\", getColumnName(j+1), i+1)\n\n\t\t\tvar sErr error\n\t\t\tif v, err := strconv.ParseFloat(colCell, 64); err == nil {\n\t\t\t\tsErr = f.SetCellValue(\"Sheet1\", cell, v)\n\t\t\t} else if v, err := strconv.ParseInt(colCell, 10, 64); err == nil {\n\t\t\t\tsErr = f.SetCellValue(\"Sheet1\", cell, v)\n\t\t\t} else {\n\t\t\t\tsErr = f.SetCellValue(\"Sheet1\", cell, colCell)\n\t\t\t}\n\n\t\t\tif sErr != nil {\n\t\t\t\treturn nil, \"\", sErr\n\t\t\t}\n\t\t}\n\t}\n\tf.SetActiveSheet(index)\n\tfile, err = f.WriteToBuffer()\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\treturn file, template.Name, nil\n}\n\n// PreviewSQL 预览最终生成的 SQL（不执行查询，仅返回 SQL 字符串）\n// Author [piexlmax](https://github.com/piexlmax) & [trae-ai]\nfunc (sysExportTemplateService *SysExportTemplateService) PreviewSQL(templateID string, values url.Values) (sqlPreview string, err error) {\n\t// 解析 params（与导出逻辑保持一致）\n\tvar params = values.Get(\"params\")\n\tparamsValues, _ := url.ParseQuery(params)\n\n\t// 加载模板\n\tvar template system.SysExportTemplate\n\terr = global.GVA_DB.Preload(\"Conditions\").Preload(\"JoinTemplate\").First(&template, \"template_id = ?\", templateID).Error\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// 解析模板列\n\tvar templateInfoMap = make(map[string]string)\n\tcolumns, err := utils.GetJSONKeys(template.TemplateInfo)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\terr = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvar selectKeyFmt []string\n\tfor _, key := range columns {\n\t\tselectKeyFmt = append(selectKeyFmt, key)\n\t}\n\tselects := strings.Join(selectKeyFmt, \", \")\n\n\t// 生成 FROM 与 JOIN 片段\n\tvar sb strings.Builder\n\tsb.WriteString(\"SELECT \")\n\tsb.WriteString(selects)\n\tsb.WriteString(\" FROM \")\n\tsb.WriteString(template.TableName)\n\n\tif len(template.JoinTemplate) > 0 {\n\t\tfor _, join := range template.JoinTemplate {\n\t\t\tsb.WriteString(\" \")\n\t\t\tsb.WriteString(join.JOINS)\n\t\t\tsb.WriteString(\" \")\n\t\t\tsb.WriteString(join.Table)\n\t\t\tsb.WriteString(\" ON \")\n\t\t\tsb.WriteString(join.ON)\n\t\t}\n\t}\n\n\t// WHERE 条件\n\tvar wheres []string\n\n\t// 软删除过滤\n\tfilterDeleted := false\n\tif paramsValues != nil {\n\t\tfilterParam := paramsValues.Get(\"filterDeleted\")\n\t\tif filterParam == \"true\" {\n\t\t\tfilterDeleted = true\n\t\t}\n\t}\n\tif filterDeleted {\n\t\twheres = append(wheres, fmt.Sprintf(\"%s.deleted_at IS NULL\", template.TableName))\n\t\tif len(template.JoinTemplate) > 0 {\n\t\t\tfor _, join := range template.JoinTemplate {\n\t\t\t\tif sysExportTemplateService.hasDeletedAtColumn(join.Table) {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s.deleted_at IS NULL\", join.Table))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 模板条件（保留与 ExportExcel 同步的解析规则）\n\tif len(template.Conditions) > 0 {\n\t\tfor _, condition := range template.Conditions {\n\t\t\top := strings.ToUpper(strings.TrimSpace(condition.Operator))\n\t\t\tcol := strings.TrimSpace(condition.Column)\n\n\t\t\t// 预览优先展示传入值，没有则展示占位符\n\t\t\tval := \"\"\n\t\t\tif paramsValues != nil {\n\t\t\t\tval = paramsValues.Get(condition.From)\n\t\t\t}\n\n\t\t\tswitch op {\n\t\t\tcase \"BETWEEN\":\n\t\t\t\tstartValue := \"\"\n\t\t\t\tendValue := \"\"\n\t\t\t\tif paramsValues != nil {\n\t\t\t\t\tstartValue = paramsValues.Get(\"start\" + condition.From)\n\t\t\t\t\tendValue = paramsValues.Get(\"end\" + condition.From)\n\t\t\t\t}\n\t\t\t\tif startValue != \"\" && endValue != \"\" {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s BETWEEN '%s' AND '%s'\", col, startValue, endValue))\n\t\t\t\t} else {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s BETWEEN {start%s} AND {end%s}\", col, condition.From, condition.From))\n\t\t\t\t}\n\t\t\tcase \"IN\", \"NOT IN\":\n\t\t\t\tif val != \"\" {\n\t\t\t\t\t// 逗号分隔值做简单展示\n\t\t\t\t\tparts := strings.Split(val, \",\")\n\t\t\t\t\tfor i := range parts {\n\t\t\t\t\t\tparts[i] = strings.TrimSpace(parts[i])\n\t\t\t\t\t}\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s %s ('%s')\", col, op, strings.Join(parts, \"','\")))\n\t\t\t\t} else {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s %s ({%s})\", col, op, condition.From))\n\t\t\t\t}\n\t\t\tcase \"LIKE\":\n\t\t\t\tif val != \"\" {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s LIKE '%%%s%%'\", col, val))\n\t\t\t\t} else {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s LIKE {%%%s%%}\", col, condition.From))\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif val != \"\" {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s %s '%s'\", col, op, val))\n\t\t\t\t} else {\n\t\t\t\t\twheres = append(wheres, fmt.Sprintf(\"%s %s {%s}\", col, op, condition.From))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(wheres) > 0 {\n\t\tsb.WriteString(\" WHERE \")\n\t\tsb.WriteString(strings.Join(wheres, \" AND \"))\n\t}\n\n\t// 排序\n\torder := \"\"\n\tif paramsValues != nil {\n\t\torder = paramsValues.Get(\"order\")\n\t}\n\tif order == \"\" && template.Order != \"\" {\n\t\torder = template.Order\n\t}\n\tif order != \"\" {\n\t\tsb.WriteString(\" ORDER BY \")\n\t\tsb.WriteString(order)\n\t}\n\n\t// limit/offset（如果传入或默认值为0，则不生成）\n\tlimitStr := \"\"\n\toffsetStr := \"\"\n\tif paramsValues != nil {\n\t\tlimitStr = paramsValues.Get(\"limit\")\n\t\toffsetStr = paramsValues.Get(\"offset\")\n\t}\n\n\t// 处理模板默认limit（仅当非0时）\n\tif limitStr == \"\" && template.Limit != nil && *template.Limit != 0 {\n\t\tlimitStr = strconv.Itoa(*template.Limit)\n\t}\n\n\t// 解析为数值，用于判断是否生成\n\tlimitInt := 0\n\toffsetInt := 0\n\tif limitStr != \"\" {\n\t\tif v, e := strconv.Atoi(limitStr); e == nil {\n\t\t\tlimitInt = v\n\t\t}\n\t}\n\tif offsetStr != \"\" {\n\t\tif v, e := strconv.Atoi(offsetStr); e == nil {\n\t\t\toffsetInt = v\n\t\t}\n\t}\n\n\tif limitInt > 0 {\n\t\tsb.WriteString(\" LIMIT \")\n\t\tsb.WriteString(strconv.Itoa(limitInt))\n\t\tif offsetInt > 0 {\n\t\t\tsb.WriteString(\" OFFSET \")\n\t\t\tsb.WriteString(strconv.Itoa(offsetInt))\n\t\t}\n\t} else {\n\t\t// 当limit未设置或为0时，仅当offset>0才生成OFFSET\n\t\tif offsetInt > 0 {\n\t\t\tsb.WriteString(\" OFFSET \")\n\t\t\tsb.WriteString(strconv.Itoa(offsetInt))\n\t\t}\n\t}\n\n\treturn sb.String(), nil\n}\n\n// ExportTemplate 导出Excel模板\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templateID string) (file *bytes.Buffer, name string, err error) {\n\tvar template system.SysExportTemplate\n\terr = global.GVA_DB.First(&template, \"template_id = ?\", templateID).Error\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tf := excelize.NewFile()\n\tdefer func() {\n\t\tif err := f.Close(); err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}()\n\t// Create a new sheet.\n\tindex, err := f.NewSheet(\"Sheet1\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\tvar templateInfoMap = make(map[string]string)\n\n\tcolumns, err := utils.GetJSONKeys(template.TemplateInfo)\n\n\terr = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tvar tableTitle []string\n\tfor _, key := range columns {\n\t\ttableTitle = append(tableTitle, templateInfoMap[key])\n\t}\n\n\tfor i := range tableTitle {\n\t\tfErr := f.SetCellValue(\"Sheet1\", fmt.Sprintf(\"%s%d\", getColumnName(i+1), 1), tableTitle[i])\n\t\tif fErr != nil {\n\t\t\treturn nil, \"\", fErr\n\t\t}\n\t}\n\tf.SetActiveSheet(index)\n\tfile, err = f.WriteToBuffer()\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\treturn file, template.Name, nil\n}\n\n// 辅助函数：检查表是否有deleted_at列\nfunc (s *SysExportTemplateService) hasDeletedAtColumn(tableName string) bool {\n\tvar count int64\n\tglobal.GVA_DB.Raw(\"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = 'deleted_at'\", tableName).Count(&count)\n\treturn count > 0\n}\n\n// ImportExcel 导入Excel\n// Author [piexlmax](https://github.com/piexlmax)\nfunc (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID string, file *multipart.FileHeader) (err error) {\n\tvar template system.SysExportTemplate\n\terr = global.GVA_DB.First(&template, \"template_id = ?\", templateID).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsrc, err := file.Open()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer src.Close()\n\n\tf, err := excelize.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trows, err := f.GetRows(\"Sheet1\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(rows) < 2 {\n\t\treturn errors.New(\"Excel data is not enough.\\nIt should contain title row and data\")\n\t}\n\n\tvar templateInfoMap = make(map[string]string)\n\terr = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdb := global.GVA_DB\n\tif template.DBName != \"\" {\n\t\tdb = global.MustGetGlobalDBByDBName(template.DBName)\n\t}\n\n\titems, err := sysExportTemplateService.parseExcelToMap(rows, templateInfoMap)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn db.Transaction(func(tx *gorm.DB) error {\n\t\tif template.ImportSQL != \"\" {\n\t\t\treturn sysExportTemplateService.importBySQL(tx, template.ImportSQL, items)\n\t\t}\n\t\treturn sysExportTemplateService.importByGORM(tx, template.TableName, items)\n\t})\n}\n\nfunc (sysExportTemplateService *SysExportTemplateService) parseExcelToMap(rows [][]string, templateInfoMap map[string]string) ([]map[string]interface{}, error) {\n\tvar titleKeyMap = make(map[string]string)\n\tfor key, title := range templateInfoMap {\n\t\ttitleKeyMap[title] = key\n\t}\n\n\texcelTitle := rows[0]\n\tfor i, str := range excelTitle {\n\t\texcelTitle[i] = strings.TrimSpace(str)\n\t}\n\tvalues := rows[1:]\n\titems := make([]map[string]interface{}, 0, len(values))\n\tfor _, row := range values {\n\t\tvar item = make(map[string]interface{})\n\t\tfor ii, value := range row {\n\t\t\tif ii >= len(excelTitle) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, ok := titleKeyMap[excelTitle[ii]]; !ok {\n\t\t\t\tcontinue // excel中多余的标题，在模板信息中没有对应的字段，因此key为空，必须跳过\n\t\t\t}\n\t\t\tkey := titleKeyMap[excelTitle[ii]]\n\t\t\titem[key] = value\n\t\t}\n\t\titems = append(items, item)\n\t}\n\treturn items, nil\n}\n\nfunc (sysExportTemplateService *SysExportTemplateService) importBySQL(tx *gorm.DB, sql string, items []map[string]interface{}) error {\n\tfor _, item := range items {\n\t\tif err := tx.Exec(sql, item).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (sysExportTemplateService *SysExportTemplateService) importByGORM(tx *gorm.DB, tableName string, items []map[string]interface{}) error {\n\tneedCreated := tx.Migrator().HasColumn(tableName, \"created_at\")\n\tneedUpdated := tx.Migrator().HasColumn(tableName, \"updated_at\")\n\n\tfor _, item := range items {\n\t\tif item[\"created_at\"] == nil && needCreated {\n\t\t\titem[\"created_at\"] = time.Now()\n\t\t}\n\t\tif item[\"updated_at\"] == nil && needUpdated {\n\t\t\titem[\"updated_at\"] = time.Now()\n\t\t}\n\t}\n\treturn tx.Table(tableName).CreateInBatches(&items, 1000).Error\n}\n\nfunc getColumnName(n int) string {\n\tcolumnName := \"\"\n\tfor n > 0 {\n\t\tn--\n\t\tcolumnName = string(rune('A'+n%26)) + columnName\n\t\tn /= 26\n\t}\n\treturn columnName\n}\n"
  },
  {
    "path": "server/service/system/sys_initdb.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"gorm.io/gorm\"\n\t\"sort\"\n)\n\nconst (\n\tMysql           = \"mysql\"\n\tPgsql           = \"pgsql\"\n\tSqlite          = \"sqlite\"\n\tMssql           = \"mssql\"\n\tInitSuccess     = \"\\n[%v] --> 初始数据成功!\\n\"\n\tInitDataExist   = \"\\n[%v] --> %v 的初始数据已存在!\\n\"\n\tInitDataFailed  = \"\\n[%v] --> %v 初始数据失败! \\nerr: %+v\\n\"\n\tInitDataSuccess = \"\\n[%v] --> %v 初始数据成功!\\n\"\n)\n\nconst (\n\tInitOrderSystem   = 10\n\tInitOrderInternal = 1000\n\tInitOrderExternal = 100000\n)\n\nvar (\n\tErrMissingDBContext        = errors.New(\"missing db in context\")\n\tErrMissingDependentContext = errors.New(\"missing dependent value in context\")\n\tErrDBTypeMismatch          = errors.New(\"db type mismatch\")\n)\n\n// SubInitializer 提供 source/*/init() 使用的接口，每个 initializer 完成一个初始化过程\ntype SubInitializer interface {\n\tInitializerName() string // 不一定代表单独一个表，所以改成了更宽泛的语义\n\tMigrateTable(ctx context.Context) (next context.Context, err error)\n\tInitializeData(ctx context.Context) (next context.Context, err error)\n\tTableCreated(ctx context.Context) bool\n\tDataInserted(ctx context.Context) bool\n}\n\n// TypedDBInitHandler 执行传入的 initializer\ntype TypedDBInitHandler interface {\n\tEnsureDB(ctx context.Context, conf *request.InitDB) (context.Context, error) // 建库，失败属于 fatal error，因此让它 panic\n\tWriteConfig(ctx context.Context) error                                       // 回写配置\n\tInitTables(ctx context.Context, inits initSlice) error                       // 建表 handler\n\tInitData(ctx context.Context, inits initSlice) error                         // 建数据 handler\n}\n\n// orderedInitializer 组合一个顺序字段，以供排序\ntype orderedInitializer struct {\n\torder int\n\tSubInitializer\n}\n\n// initSlice 供 initializer 排序依赖时使用\ntype initSlice []*orderedInitializer\n\nvar (\n\tinitializers initSlice\n\tcache        map[string]*orderedInitializer\n)\n\n// RegisterInit 注册要执行的初始化过程，会在 InitDB() 时调用\nfunc RegisterInit(order int, i SubInitializer) {\n\tif initializers == nil {\n\t\tinitializers = initSlice{}\n\t}\n\tif cache == nil {\n\t\tcache = map[string]*orderedInitializer{}\n\t}\n\tname := i.InitializerName()\n\tif _, existed := cache[name]; existed {\n\t\tpanic(fmt.Sprintf(\"Name conflict on %s\", name))\n\t}\n\tni := orderedInitializer{order, i}\n\tinitializers = append(initializers, &ni)\n\tcache[name] = &ni\n}\n\n/* ---- * service * ---- */\n\ntype InitDBService struct{}\n\n// InitDB 创建数据库并初始化 总入口\nfunc (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) {\n\tctx := context.TODO()\n\tctx = context.WithValue(ctx, \"adminPassword\", conf.AdminPassword)\n\tif len(initializers) == 0 {\n\t\treturn errors.New(\"无可用初始化过程，请检查初始化是否已执行完成\")\n\t}\n\tsort.Sort(&initializers) // 保证有依赖的 initializer 排在后面执行\n\t// Note: 若 initializer 只有单一依赖，可以写为 B=A+1, C=A+1; 由于 BC 之间没有依赖关系，所以谁先谁后并不影响初始化\n\t// 若存在多个依赖，可以写为 C=A+B, D=A+B+C, E=A+1;\n\t// C必然>A|B，因此在AB之后执行，D必然>A|B|C，因此在ABC后执行，而E只依赖A，顺序与CD无关，因此E与CD哪个先执行并不影响\n\tvar initHandler TypedDBInitHandler\n\tswitch conf.DBType {\n\tcase \"mysql\":\n\t\tinitHandler = NewMysqlInitHandler()\n\t\tctx = context.WithValue(ctx, \"dbtype\", \"mysql\")\n\tcase \"pgsql\":\n\t\tinitHandler = NewPgsqlInitHandler()\n\t\tctx = context.WithValue(ctx, \"dbtype\", \"pgsql\")\n\tcase \"sqlite\":\n\t\tinitHandler = NewSqliteInitHandler()\n\t\tctx = context.WithValue(ctx, \"dbtype\", \"sqlite\")\n\tcase \"mssql\":\n\t\tinitHandler = NewMssqlInitHandler()\n\t\tctx = context.WithValue(ctx, \"dbtype\", \"mssql\")\n\tdefault:\n\t\tinitHandler = NewMysqlInitHandler()\n\t\tctx = context.WithValue(ctx, \"dbtype\", \"mysql\")\n\t}\n\tctx, err = initHandler.EnsureDB(ctx, &conf)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdb := ctx.Value(\"db\").(*gorm.DB)\n\tglobal.GVA_DB = db\n\n\tif err = initHandler.InitTables(ctx, initializers); err != nil {\n\t\treturn err\n\t}\n\tif err = initHandler.InitData(ctx, initializers); err != nil {\n\t\treturn err\n\t}\n\n\tif err = initHandler.WriteConfig(ctx); err != nil {\n\t\treturn err\n\t}\n\tinitializers = initSlice{}\n\tcache = map[string]*orderedInitializer{}\n\treturn nil\n}\n\n// createDatabase 创建数据库（ EnsureDB() 中调用 ）\nfunc createDatabase(dsn string, driver string, createSql string) error {\n\tdb, err := sql.Open(driver, dsn)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func(db *sql.DB) {\n\t\terr = db.Close()\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}(db)\n\tif err = db.Ping(); err != nil {\n\t\treturn err\n\t}\n\t_, err = db.Exec(createSql)\n\treturn err\n}\n\n// createTables 创建表（默认 dbInitHandler.initTables 行为）\nfunc createTables(ctx context.Context, inits initSlice) error {\n\tnext, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tfor _, init := range inits {\n\t\tif init.TableCreated(next) {\n\t\t\tcontinue\n\t\t}\n\t\tif n, err := init.MigrateTable(next); err != nil {\n\t\t\treturn err\n\t\t} else {\n\t\t\tnext = n\n\t\t}\n\t}\n\treturn nil\n}\n\n/* -- sortable interface -- */\n\nfunc (a initSlice) Len() int {\n\treturn len(a)\n}\n\nfunc (a initSlice) Less(i, j int) bool {\n\treturn a[i].order < a[j].order\n}\n\nfunc (a initSlice) Swap(i, j int) {\n\ta[i], a[j] = a[j], a[i]\n}\n"
  },
  {
    "path": "server/service/system/sys_initdb_mssql.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/google/uuid\"\n\t\"github.com/gookit/color\"\n\t\"gorm.io/driver/sqlserver\"\n\t\"gorm.io/gorm\"\n\t\"path/filepath\"\n)\n\ntype MssqlInitHandler struct{}\n\nfunc NewMssqlInitHandler() *MssqlInitHandler {\n\treturn &MssqlInitHandler{}\n}\n\n// WriteConfig mssql回写配置\nfunc (h MssqlInitHandler) WriteConfig(ctx context.Context) error {\n\tc, ok := ctx.Value(\"config\").(config.Mssql)\n\tif !ok {\n\t\treturn errors.New(\"mssql config invalid\")\n\t}\n\tglobal.GVA_CONFIG.System.DbType = \"mssql\"\n\tglobal.GVA_CONFIG.Mssql = c\n\tglobal.GVA_CONFIG.JWT.SigningKey = uuid.New().String()\n\tcs := utils.StructToMap(global.GVA_CONFIG)\n\tfor k, v := range cs {\n\t\tglobal.GVA_VP.Set(k, v)\n\t}\n\tglobal.GVA_ACTIVE_DBNAME = &c.Dbname\n\treturn global.GVA_VP.WriteConfig()\n}\n\n// EnsureDB 创建数据库并初始化 mssql\nfunc (h MssqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {\n\tif s, ok := ctx.Value(\"dbtype\").(string); !ok || s != \"mssql\" {\n\t\treturn ctx, ErrDBTypeMismatch\n\t}\n\n\tc := conf.ToMssqlConfig()\n\tnext = context.WithValue(ctx, \"config\", c)\n\tif c.Dbname == \"\" {\n\t\treturn ctx, nil\n\t} // 如果没有数据库名, 则跳出初始化数据\n\n\tdsn := conf.MssqlEmptyDsn()\n\n\tmssqlConfig := sqlserver.Config{\n\t\tDSN:               dsn, // DSN data source name\n\t\tDefaultStringSize: 191, // string 类型字段的默认长度\n\t}\n\n\tvar db *gorm.DB\n\n\tif db, err = gorm.Open(sqlserver.New(mssqlConfig), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {\n\t\treturn nil, err\n\t}\n\n\tglobal.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs(\"..\")\n\tnext = context.WithValue(next, \"db\", db)\n\treturn next, err\n}\n\nfunc (h MssqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {\n\treturn createTables(ctx, inits)\n}\n\nfunc (h MssqlInitHandler) InitData(ctx context.Context, inits initSlice) error {\n\tnext, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tfor _, init := range inits {\n\t\tif init.DataInserted(next) {\n\t\t\tcolor.Info.Printf(InitDataExist, Mssql, init.InitializerName())\n\t\t\tcontinue\n\t\t}\n\t\tif n, err := init.InitializeData(next); err != nil {\n\t\t\tcolor.Info.Printf(InitDataFailed, Mssql, init.InitializerName(), err)\n\t\t\treturn err\n\t\t} else {\n\t\t\tnext = n\n\t\t\tcolor.Info.Printf(InitDataSuccess, Mssql, init.InitializerName())\n\t\t}\n\t}\n\tcolor.Info.Printf(InitSuccess, Mssql)\n\treturn nil\n}\n"
  },
  {
    "path": "server/service/system/sys_initdb_mysql.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/gookit/color\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/google/uuid\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n)\n\ntype MysqlInitHandler struct{}\n\nfunc NewMysqlInitHandler() *MysqlInitHandler {\n\treturn &MysqlInitHandler{}\n}\n\n// WriteConfig mysql回写配置\nfunc (h MysqlInitHandler) WriteConfig(ctx context.Context) error {\n\tc, ok := ctx.Value(\"config\").(config.Mysql)\n\tif !ok {\n\t\treturn errors.New(\"mysql config invalid\")\n\t}\n\tglobal.GVA_CONFIG.System.DbType = \"mysql\"\n\tglobal.GVA_CONFIG.Mysql = c\n\tglobal.GVA_CONFIG.JWT.SigningKey = uuid.New().String()\n\tcs := utils.StructToMap(global.GVA_CONFIG)\n\tfor k, v := range cs {\n\t\tglobal.GVA_VP.Set(k, v)\n\t}\n\tglobal.GVA_ACTIVE_DBNAME = &c.Dbname\n\treturn global.GVA_VP.WriteConfig()\n}\n\n// EnsureDB 创建数据库并初始化 mysql\nfunc (h MysqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {\n\tif s, ok := ctx.Value(\"dbtype\").(string); !ok || s != \"mysql\" {\n\t\treturn ctx, ErrDBTypeMismatch\n\t}\n\n\tc := conf.ToMysqlConfig()\n\tnext = context.WithValue(ctx, \"config\", c)\n\tif c.Dbname == \"\" {\n\t\treturn ctx, nil\n\t} // 如果没有数据库名, 则跳出初始化数据\n\n\tdsn := conf.MysqlEmptyDsn()\n\tcreateSql := fmt.Sprintf(\"CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;\", c.Dbname)\n\tif err = createDatabase(dsn, \"mysql\", createSql); err != nil {\n\t\treturn nil, err\n\t} // 创建数据库\n\n\tvar db *gorm.DB\n\tif db, err = gorm.Open(mysql.New(mysql.Config{\n\t\tDSN:                       c.Dsn(), // DSN data source name\n\t\tDefaultStringSize:         191,     // string 类型字段的默认长度\n\t\tSkipInitializeWithVersion: true,    // 根据版本自动配置\n\t}), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {\n\t\treturn ctx, err\n\t}\n\tglobal.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs(\"..\")\n\tnext = context.WithValue(next, \"db\", db)\n\treturn next, err\n}\n\nfunc (h MysqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {\n\treturn createTables(ctx, inits)\n}\n\nfunc (h MysqlInitHandler) InitData(ctx context.Context, inits initSlice) error {\n\tnext, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tfor _, init := range inits {\n\t\tif init.DataInserted(next) {\n\t\t\tcolor.Info.Printf(InitDataExist, Mysql, init.InitializerName())\n\t\t\tcontinue\n\t\t}\n\t\tif n, err := init.InitializeData(next); err != nil {\n\t\t\tcolor.Info.Printf(InitDataFailed, Mysql, init.InitializerName(), err)\n\t\t\treturn err\n\t\t} else {\n\t\t\tnext = n\n\t\t\tcolor.Info.Printf(InitDataSuccess, Mysql, init.InitializerName())\n\t\t}\n\t}\n\tcolor.Info.Printf(InitSuccess, Mysql)\n\treturn nil\n}\n"
  },
  {
    "path": "server/service/system/sys_initdb_pgsql.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/gookit/color\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/google/uuid\"\n\t\"gorm.io/driver/postgres\"\n\t\"gorm.io/gorm\"\n)\n\ntype PgsqlInitHandler struct{}\n\nfunc NewPgsqlInitHandler() *PgsqlInitHandler {\n\treturn &PgsqlInitHandler{}\n}\n\n// WriteConfig pgsql 回写配置\nfunc (h PgsqlInitHandler) WriteConfig(ctx context.Context) error {\n\tc, ok := ctx.Value(\"config\").(config.Pgsql)\n\tif !ok {\n\t\treturn errors.New(\"postgresql config invalid\")\n\t}\n\tglobal.GVA_CONFIG.System.DbType = \"pgsql\"\n\tglobal.GVA_CONFIG.Pgsql = c\n\tglobal.GVA_CONFIG.JWT.SigningKey = uuid.New().String()\n\tcs := utils.StructToMap(global.GVA_CONFIG)\n\tfor k, v := range cs {\n\t\tglobal.GVA_VP.Set(k, v)\n\t}\n\tglobal.GVA_ACTIVE_DBNAME = &c.Dbname\n\treturn global.GVA_VP.WriteConfig()\n}\n\n// EnsureDB 创建数据库并初始化 pg\nfunc (h PgsqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {\n\tif s, ok := ctx.Value(\"dbtype\").(string); !ok || s != \"pgsql\" {\n\t\treturn ctx, ErrDBTypeMismatch\n\t}\n\n\tc := conf.ToPgsqlConfig()\n\tnext = context.WithValue(ctx, \"config\", c)\n\tif c.Dbname == \"\" {\n\t\treturn ctx, nil\n\t} // 如果没有数据库名, 则跳出初始化数据\n\n\tdsn := conf.PgsqlEmptyDsn()\n\tvar createSql string\n\tif conf.Template != \"\" {\n\t\tcreateSql = fmt.Sprintf(\"CREATE DATABASE %s WITH TEMPLATE %s;\", c.Dbname, conf.Template)\n\t} else {\n\t\tcreateSql = fmt.Sprintf(\"CREATE DATABASE %s;\", c.Dbname)\n\t}\n\tif err = createDatabase(dsn, \"pgx\", createSql); err != nil {\n\t\treturn nil, err\n\t} // 创建数据库\n\n\tvar db *gorm.DB\n\tif db, err = gorm.Open(postgres.New(postgres.Config{\n\t\tDSN:                  c.Dsn(), // DSN data source name\n\t\tPreferSimpleProtocol: false,\n\t}), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {\n\t\treturn ctx, err\n\t}\n\tglobal.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs(\"..\")\n\tnext = context.WithValue(next, \"db\", db)\n\treturn next, err\n}\n\nfunc (h PgsqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {\n\treturn createTables(ctx, inits)\n}\n\nfunc (h PgsqlInitHandler) InitData(ctx context.Context, inits initSlice) error {\n\tnext, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tfor i := 0; i < len(inits); i++ {\n\t\tif inits[i].DataInserted(next) {\n\t\t\tcolor.Info.Printf(InitDataExist, Pgsql, inits[i].InitializerName())\n\t\t\tcontinue\n\t\t}\n\t\tif n, err := inits[i].InitializeData(next); err != nil {\n\t\t\tcolor.Info.Printf(InitDataFailed, Pgsql, inits[i].InitializerName(), err)\n\t\t\treturn err\n\t\t} else {\n\t\t\tnext = n\n\t\t\tcolor.Info.Printf(InitDataSuccess, Pgsql, inits[i].InitializerName())\n\t\t}\n\t}\n\tcolor.Info.Printf(InitSuccess, Pgsql)\n\treturn nil\n}\n"
  },
  {
    "path": "server/service/system/sys_initdb_sqlite.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"github.com/glebarez/sqlite\"\n\t\"github.com/google/uuid\"\n\t\"github.com/gookit/color\"\n\t\"gorm.io/gorm\"\n\t\"path/filepath\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n)\n\ntype SqliteInitHandler struct{}\n\nfunc NewSqliteInitHandler() *SqliteInitHandler {\n\treturn &SqliteInitHandler{}\n}\n\n// WriteConfig mysql回写配置\nfunc (h SqliteInitHandler) WriteConfig(ctx context.Context) error {\n\tc, ok := ctx.Value(\"config\").(config.Sqlite)\n\tif !ok {\n\t\treturn errors.New(\"sqlite config invalid\")\n\t}\n\tglobal.GVA_CONFIG.System.DbType = \"sqlite\"\n\tglobal.GVA_CONFIG.Sqlite = c\n\tglobal.GVA_CONFIG.JWT.SigningKey = uuid.New().String()\n\tcs := utils.StructToMap(global.GVA_CONFIG)\n\tfor k, v := range cs {\n\t\tglobal.GVA_VP.Set(k, v)\n\t}\n\tglobal.GVA_ACTIVE_DBNAME = &c.Dbname\n\treturn global.GVA_VP.WriteConfig()\n}\n\n// EnsureDB 创建数据库并初始化 sqlite\nfunc (h SqliteInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {\n\tif s, ok := ctx.Value(\"dbtype\").(string); !ok || s != \"sqlite\" {\n\t\treturn ctx, ErrDBTypeMismatch\n\t}\n\n\tc := conf.ToSqliteConfig()\n\tnext = context.WithValue(ctx, \"config\", c)\n\tif c.Dbname == \"\" {\n\t\treturn ctx, nil\n\t} // 如果没有数据库名, 则跳出初始化数据\n\n\tdsn := conf.SqliteEmptyDsn()\n\n\tvar db *gorm.DB\n\tif db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{\n\t\tDisableForeignKeyConstraintWhenMigrating: true,\n\t}); err != nil {\n\t\treturn ctx, err\n\t}\n\tglobal.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs(\"..\")\n\tnext = context.WithValue(next, \"db\", db)\n\treturn next, err\n}\n\nfunc (h SqliteInitHandler) InitTables(ctx context.Context, inits initSlice) error {\n\treturn createTables(ctx, inits)\n}\n\nfunc (h SqliteInitHandler) InitData(ctx context.Context, inits initSlice) error {\n\tnext, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tfor _, init := range inits {\n\t\tif init.DataInserted(next) {\n\t\t\tcolor.Info.Printf(InitDataExist, Sqlite, init.InitializerName())\n\t\t\tcontinue\n\t\t}\n\t\tif n, err := init.InitializeData(next); err != nil {\n\t\t\tcolor.Info.Printf(InitDataFailed, Sqlite, init.InitializerName(), err)\n\t\t\treturn err\n\t\t} else {\n\t\t\tnext = n\n\t\t\tcolor.Info.Printf(InitDataSuccess, Sqlite, init.InitializerName())\n\t\t}\n\t}\n\tcolor.Info.Printf(InitSuccess, Sqlite)\n\treturn nil\n}\n"
  },
  {
    "path": "server/service/system/sys_login_log.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\ntype LoginLogService struct{}\n\nvar LoginLogServiceApp = new(LoginLogService)\n\nfunc (loginLogService *LoginLogService) CreateLoginLog(loginLog system.SysLoginLog) (err error) {\n\terr = global.GVA_DB.Create(&loginLog).Error\n\treturn err\n}\n\nfunc (loginLogService *LoginLogService) DeleteLoginLogByIds(ids request.IdsReq) (err error) {\n\terr = global.GVA_DB.Delete(&[]system.SysLoginLog{}, \"id in (?)\", ids.Ids).Error\n\treturn err\n}\n\nfunc (loginLogService *LoginLogService) DeleteLoginLog(loginLog system.SysLoginLog) (err error) {\n\terr = global.GVA_DB.Delete(&loginLog).Error\n\treturn err\n}\n\nfunc (loginLogService *LoginLogService) GetLoginLog(id uint) (loginLog system.SysLoginLog, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", id).First(&loginLog).Error\n\treturn\n}\n\nfunc (loginLogService *LoginLogService) GetLoginLogInfoList(info systemReq.SysLoginLogSearch) (list interface{}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysLoginLog{})\n\tvar loginLogs []system.SysLoginLog\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif info.Username != \"\" {\n\t\tdb = db.Where(\"username LIKE ?\", \"%\"+info.Username+\"%\")\n\t}\n\tif info.Status != false {\n\t\tdb = db.Where(\"status = ?\", info.Status)\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\terr = db.Limit(limit).Offset(offset).Order(\"id desc\").Preload(\"User\").Find(&loginLogs).Error\n\treturn loginLogs, total, err\n}\n"
  },
  {
    "path": "server/service/system/sys_menu.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"gorm.io/gorm\"\n\t\"strconv\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: getMenuTreeMap\n//@description: 获取路由总树map\n//@param: authorityId string\n//@return: treeMap map[string][]system.SysMenu, err error\n\ntype MenuService struct{}\n\nvar MenuServiceApp = new(MenuService)\n\nfunc (menuService *MenuService) getMenuTreeMap(authorityId uint) (treeMap map[uint][]system.SysMenu, err error) {\n\tvar allMenus []system.SysMenu\n\tvar baseMenu []system.SysBaseMenu\n\tvar btns []system.SysAuthorityBtn\n\ttreeMap = make(map[uint][]system.SysMenu)\n\n\tvar SysAuthorityMenus []system.SysAuthorityMenu\n\terr = global.GVA_DB.Where(\"sys_authority_authority_id = ?\", authorityId).Find(&SysAuthorityMenus).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar MenuIds []string\n\n\tfor i := range SysAuthorityMenus {\n\t\tMenuIds = append(MenuIds, SysAuthorityMenus[i].MenuId)\n\t}\n\n\terr = global.GVA_DB.Where(\"id in (?)\", MenuIds).Order(\"sort\").Preload(\"Parameters\").Find(&baseMenu).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor i := range baseMenu {\n\t\tallMenus = append(allMenus, system.SysMenu{\n\t\t\tSysBaseMenu: baseMenu[i],\n\t\t\tAuthorityId: authorityId,\n\t\t\tMenuId:      baseMenu[i].ID,\n\t\t\tParameters:  baseMenu[i].Parameters,\n\t\t})\n\t}\n\n\terr = global.GVA_DB.Where(\"authority_id = ?\", authorityId).Preload(\"SysBaseMenuBtn\").Find(&btns).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tvar btnMap = make(map[uint]map[string]uint)\n\tfor _, v := range btns {\n\t\tif btnMap[v.SysMenuID] == nil {\n\t\t\tbtnMap[v.SysMenuID] = make(map[string]uint)\n\t\t}\n\t\tbtnMap[v.SysMenuID][v.SysBaseMenuBtn.Name] = authorityId\n\t}\n\tfor _, v := range allMenus {\n\t\tv.Btns = btnMap[v.SysBaseMenu.ID]\n\t\ttreeMap[v.ParentId] = append(treeMap[v.ParentId], v)\n\t}\n\treturn treeMap, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetMenuTree\n//@description: 获取动态菜单树\n//@param: authorityId string\n//@return: menus []system.SysMenu, err error\n\nfunc (menuService *MenuService) GetMenuTree(authorityId uint) (menus []system.SysMenu, err error) {\n\tmenuTree, err := menuService.getMenuTreeMap(authorityId)\n\tmenus = menuTree[0]\n\tfor i := 0; i < len(menus); i++ {\n\t\terr = menuService.getChildrenList(&menus[i], menuTree)\n\t}\n\treturn menus, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: getChildrenList\n//@description: 获取子菜单\n//@param: menu *model.SysMenu, treeMap map[string][]model.SysMenu\n//@return: err error\n\nfunc (menuService *MenuService) getChildrenList(menu *system.SysMenu, treeMap map[uint][]system.SysMenu) (err error) {\n\tmenu.Children = treeMap[menu.MenuId]\n\tfor i := 0; i < len(menu.Children); i++ {\n\t\terr = menuService.getChildrenList(&menu.Children[i], treeMap)\n\t}\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetInfoList\n//@description: 获取路由分页\n//@return: list interface{}, total int64,err error\n\nfunc (menuService *MenuService) GetInfoList(authorityID uint) (list interface{}, err error) {\n\tvar menuList []system.SysBaseMenu\n\ttreeMap, err := menuService.getBaseMenuTreeMap(authorityID)\n\tmenuList = treeMap[0]\n\tfor i := 0; i < len(menuList); i++ {\n\t\terr = menuService.getBaseChildrenList(&menuList[i], treeMap)\n\t}\n\treturn menuList, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: getBaseChildrenList\n//@description: 获取菜单的子菜单\n//@param: menu *model.SysBaseMenu, treeMap map[string][]model.SysBaseMenu\n//@return: err error\n\nfunc (menuService *MenuService) getBaseChildrenList(menu *system.SysBaseMenu, treeMap map[uint][]system.SysBaseMenu) (err error) {\n\tmenu.Children = treeMap[menu.ID]\n\tfor i := 0; i < len(menu.Children); i++ {\n\t\terr = menuService.getBaseChildrenList(&menu.Children[i], treeMap)\n\t}\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: AddBaseMenu\n//@description: 添加基础路由\n//@param: menu model.SysBaseMenu\n//@return: error\n\nfunc (menuService *MenuService) AddBaseMenu(menu system.SysBaseMenu) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\t// 检查name是否重复\n\t\tif !errors.Is(tx.Where(\"name = ?\", menu.Name).First(&system.SysBaseMenu{}).Error, gorm.ErrRecordNotFound) {\n\t\t\treturn errors.New(\"存在重复name，请修改name\")\n\t\t}\n\n\t\tif menu.ParentId != 0 {\n\t\t\t// 检查父菜单是否存在\n\t\t\tvar parentMenu system.SysBaseMenu\n\t\t\tif err := tx.First(&parentMenu, menu.ParentId).Error; err != nil {\n\t\t\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\t\t\treturn errors.New(\"父菜单不存在\")\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// 检查父菜单下现有子菜单数量\n\t\t\tvar existingChildrenCount int64\n\t\t\terr := tx.Model(&system.SysBaseMenu{}).Where(\"parent_id = ?\", menu.ParentId).Count(&existingChildrenCount).Error\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// 如果父菜单原本是叶子菜单（没有子菜单），现在要变成枝干菜单，需要清空其权限分配\n\t\t\tif existingChildrenCount == 0 {\n\t\t\t\t// 检查父菜单是否被其他角色设置为首页\n\t\t\t\tvar defaultRouterCount int64\n\t\t\t\terr := tx.Model(&system.SysAuthority{}).Where(\"default_router = ?\", parentMenu.Name).Count(&defaultRouterCount).Error\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif defaultRouterCount > 0 {\n\t\t\t\t\treturn errors.New(\"父菜单已被其他角色的首页占用，请先释放父菜单的首页权限\")\n\t\t\t\t}\n\n\t\t\t\t// 清空父菜单的所有权限分配\n\t\t\t\terr = tx.Where(\"sys_base_menu_id = ?\", menu.ParentId).Delete(&system.SysAuthorityMenu{}).Error\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 创建菜单\n\t\treturn tx.Create(&menu).Error\n\t})\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: getBaseMenuTreeMap\n//@description: 获取路由总树map\n//@return: treeMap map[string][]system.SysBaseMenu, err error\n\nfunc (menuService *MenuService) getBaseMenuTreeMap(authorityID uint) (treeMap map[uint][]system.SysBaseMenu, err error) {\n\tparentAuthorityID, err := AuthorityServiceApp.GetParentAuthorityID(authorityID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar allMenus []system.SysBaseMenu\n\ttreeMap = make(map[uint][]system.SysBaseMenu)\n\tdb := global.GVA_DB.Order(\"sort\").Preload(\"MenuBtn\").Preload(\"Parameters\")\n\n\t// 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选\n\tif global.GVA_CONFIG.System.UseStrictAuth && parentAuthorityID != 0 {\n\t\tvar authorityMenus []system.SysAuthorityMenu\n\t\terr = global.GVA_DB.Where(\"sys_authority_authority_id = ?\", authorityID).Find(&authorityMenus).Error\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar menuIds []string\n\t\tfor i := range authorityMenus {\n\t\t\tmenuIds = append(menuIds, authorityMenus[i].MenuId)\n\t\t}\n\t\tdb = db.Where(\"id in (?)\", menuIds)\n\t}\n\n\terr = db.Find(&allMenus).Error\n\tfor _, v := range allMenus {\n\t\ttreeMap[v.ParentId] = append(treeMap[v.ParentId], v)\n\t}\n\treturn treeMap, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetBaseMenuTree\n//@description: 获取基础路由树\n//@return: menus []system.SysBaseMenu, err error\n\nfunc (menuService *MenuService) GetBaseMenuTree(authorityID uint) (menus []system.SysBaseMenu, err error) {\n\ttreeMap, err := menuService.getBaseMenuTreeMap(authorityID)\n\tmenus = treeMap[0]\n\tfor i := 0; i < len(menus); i++ {\n\t\terr = menuService.getBaseChildrenList(&menus[i], treeMap)\n\t}\n\treturn menus, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: AddMenuAuthority\n//@description: 为角色增加menu树\n//@param: menus []model.SysBaseMenu, authorityId string\n//@return: err error\n\nfunc (menuService *MenuService) AddMenuAuthority(menus []system.SysBaseMenu, adminAuthorityID, authorityId uint) (err error) {\n\tvar auth system.SysAuthority\n\tauth.AuthorityId = authorityId\n\tauth.SysBaseMenus = menus\n\n\terr = AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, authorityId)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar authority system.SysAuthority\n\t_ = global.GVA_DB.First(&authority, \"authority_id = ?\", adminAuthorityID).Error\n\tvar menuIds []string\n\n\t// 当开启了严格的树角色并且父角色不为0时需要进行菜单筛选\n\tif global.GVA_CONFIG.System.UseStrictAuth && *authority.ParentId != 0 {\n\t\tvar authorityMenus []system.SysAuthorityMenu\n\t\terr = global.GVA_DB.Where(\"sys_authority_authority_id = ?\", adminAuthorityID).Find(&authorityMenus).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor i := range authorityMenus {\n\t\t\tmenuIds = append(menuIds, authorityMenus[i].MenuId)\n\t\t}\n\n\t\tfor i := range menus {\n\t\t\thasMenu := false\n\t\t\tfor j := range menuIds {\n\t\t\t\tidStr := strconv.Itoa(int(menus[i].ID))\n\t\t\t\tif idStr == menuIds[j] {\n\t\t\t\t\thasMenu = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !hasMenu {\n\t\t\t\treturn errors.New(\"添加失败,请勿跨级操作\")\n\t\t\t}\n\t\t}\n\t}\n\n\terr = AuthorityServiceApp.SetMenuAuthority(&auth)\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetMenuAuthority\n//@description: 查看当前角色树\n//@param: info *request.GetAuthorityId\n//@return: menus []system.SysMenu, err error\n\nfunc (menuService *MenuService) GetMenuAuthority(info *request.GetAuthorityId) (menus []system.SysMenu, err error) {\n\tvar baseMenu []system.SysBaseMenu\n\tvar SysAuthorityMenus []system.SysAuthorityMenu\n\terr = global.GVA_DB.Where(\"sys_authority_authority_id = ?\", info.AuthorityId).Find(&SysAuthorityMenus).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvar MenuIds []string\n\n\tfor i := range SysAuthorityMenus {\n\t\tMenuIds = append(MenuIds, SysAuthorityMenus[i].MenuId)\n\t}\n\n\terr = global.GVA_DB.Where(\"id in (?) \", MenuIds).Order(\"sort\").Find(&baseMenu).Error\n\n\tfor i := range baseMenu {\n\t\tmenus = append(menus, system.SysMenu{\n\t\t\tSysBaseMenu: baseMenu[i],\n\t\t\tAuthorityId: info.AuthorityId,\n\t\t\tMenuId:      baseMenu[i].ID,\n\t\t\tParameters:  baseMenu[i].Parameters,\n\t\t})\n\t}\n\treturn menus, err\n}\n\n// GetAuthoritiesByMenuId 获取拥有指定菜单的所有角色ID\nfunc (menuService *MenuService) GetAuthoritiesByMenuId(menuId uint) (authorityIds []uint, err error) {\n\tvar records []system.SysAuthorityMenu\n\terr = global.GVA_DB.Where(\"sys_base_menu_id = ?\", menuId).Find(&records).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, r := range records {\n\t\tid, e := strconv.Atoi(r.AuthorityId)\n\t\tif e == nil {\n\t\t\tauthorityIds = append(authorityIds, uint(id))\n\t\t}\n\t}\n\treturn authorityIds, nil\n}\n\n// GetDefaultRouterAuthorityIds 获取将指定菜单设为首页的角色ID列表\nfunc (menuService *MenuService) GetDefaultRouterAuthorityIds(menuId uint) (authorityIds []uint, err error) {\n\tvar menu system.SysBaseMenu\n\terr = global.GVA_DB.First(&menu, menuId).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar authorities []system.SysAuthority\n\terr = global.GVA_DB.Where(\"default_router = ?\", menu.Name).Find(&authorities).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, auth := range authorities {\n\t\tauthorityIds = append(authorityIds, auth.AuthorityId)\n\t}\n\treturn authorityIds, nil\n}\n\n// SetMenuAuthorities 全量覆盖某菜单关联的角色列表\nfunc (menuService *MenuService) SetMenuAuthorities(menuId uint, authorityIds []uint) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\t// 1. 删除该菜单所有已有的角色关联\n\t\tif err := tx.Where(\"sys_base_menu_id = ?\", menuId).Delete(&system.SysAuthorityMenu{}).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// 2. 批量插入新的关联记录\n\t\tif len(authorityIds) > 0 {\n\t\t\tmenuIdStr := strconv.Itoa(int(menuId))\n\t\t\tnewRecords := make([]system.SysAuthorityMenu, 0, len(authorityIds))\n\t\t\tfor _, authorityId := range authorityIds {\n\t\t\t\tnewRecords = append(newRecords, system.SysAuthorityMenu{\n\t\t\t\t\tMenuId:      menuIdStr,\n\t\t\t\t\tAuthorityId: strconv.Itoa(int(authorityId)),\n\t\t\t\t})\n\t\t\t}\n\t\t\tif err := tx.Create(&newRecords).Error; err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// UserAuthorityDefaultRouter 用户角色默认路由检查\n//\n//\tAuthor [SliverHorn](https://github.com/SliverHorn)\nfunc (menuService *MenuService) UserAuthorityDefaultRouter(user *system.SysUser) {\n\tvar menuIds []string\n\terr := global.GVA_DB.Model(&system.SysAuthorityMenu{}).Where(\"sys_authority_authority_id = ?\", user.AuthorityId).Pluck(\"sys_base_menu_id\", &menuIds).Error\n\tif err != nil {\n\t\treturn\n\t}\n\tvar am system.SysBaseMenu\n\terr = global.GVA_DB.First(&am, \"name = ? and id in (?)\", user.Authority.DefaultRouter, menuIds).Error\n\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\tuser.Authority.DefaultRouter = \"404\"\n\t}\n}\n"
  },
  {
    "path": "server/service/system/sys_operation_record.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\n//@author: [granty1](https://github.com/granty1)\n//@function: CreateSysOperationRecord\n//@description: 创建记录\n//@param: sysOperationRecord model.SysOperationRecord\n//@return: err error\n\ntype OperationRecordService struct{}\n\nvar OperationRecordServiceApp = new(OperationRecordService)\n\n//@author: [granty1](https://github.com/granty1)\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteSysOperationRecordByIds\n//@description: 批量删除记录\n//@param: ids request.IdsReq\n//@return: err error\n\nfunc (operationRecordService *OperationRecordService) DeleteSysOperationRecordByIds(ids request.IdsReq) (err error) {\n\terr = global.GVA_DB.Delete(&[]system.SysOperationRecord{}, \"id in (?)\", ids.Ids).Error\n\treturn err\n}\n\n//@author: [granty1](https://github.com/granty1)\n//@function: DeleteSysOperationRecord\n//@description: 删除操作记录\n//@param: sysOperationRecord model.SysOperationRecord\n//@return: err error\n\nfunc (operationRecordService *OperationRecordService) DeleteSysOperationRecord(sysOperationRecord system.SysOperationRecord) (err error) {\n\terr = global.GVA_DB.Delete(&sysOperationRecord).Error\n\treturn err\n}\n\n//@author: [granty1](https://github.com/granty1)\n//@function: GetSysOperationRecord\n//@description: 根据id获取单条操作记录\n//@param: id uint\n//@return: sysOperationRecord system.SysOperationRecord, err error\n\nfunc (operationRecordService *OperationRecordService) GetSysOperationRecord(id uint) (sysOperationRecord system.SysOperationRecord, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", id).First(&sysOperationRecord).Error\n\treturn\n}\n\n//@author: [granty1](https://github.com/granty1)\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetSysOperationRecordInfoList\n//@description: 分页获取操作记录列表\n//@param: info systemReq.SysOperationRecordSearch\n//@return: list interface{}, total int64, err error\n\nfunc (operationRecordService *OperationRecordService) GetSysOperationRecordInfoList(info systemReq.SysOperationRecordSearch) (list interface{}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysOperationRecord{})\n\tvar sysOperationRecords []system.SysOperationRecord\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif info.Method != \"\" {\n\t\tdb = db.Where(\"method = ?\", info.Method)\n\t}\n\tif info.Path != \"\" {\n\t\tdb = db.Where(\"path LIKE ?\", \"%\"+info.Path+\"%\")\n\t}\n\tif info.Status != 0 {\n\t\tdb = db.Where(\"status = ?\", info.Status)\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\terr = db.Order(\"id desc\").Limit(limit).Offset(offset).Preload(\"User\").Find(&sysOperationRecords).Error\n\treturn sysOperationRecords, total, err\n}\n"
  },
  {
    "path": "server/service/system/sys_params.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n)\n\ntype SysParamsService struct{}\n\n// CreateSysParams 创建参数记录\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) CreateSysParams(sysParams *system.SysParams) (err error) {\n\terr = global.GVA_DB.Create(sysParams).Error\n\treturn err\n}\n\n// DeleteSysParams 删除参数记录\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) DeleteSysParams(ID string) (err error) {\n\terr = global.GVA_DB.Delete(&system.SysParams{}, \"id = ?\", ID).Error\n\treturn err\n}\n\n// DeleteSysParamsByIds 批量删除参数记录\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) DeleteSysParamsByIds(IDs []string) (err error) {\n\terr = global.GVA_DB.Delete(&[]system.SysParams{}, \"id in ?\", IDs).Error\n\treturn err\n}\n\n// UpdateSysParams 更新参数记录\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) UpdateSysParams(sysParams system.SysParams) (err error) {\n\terr = global.GVA_DB.Model(&system.SysParams{}).Where(\"id = ?\", sysParams.ID).Updates(&sysParams).Error\n\treturn err\n}\n\n// GetSysParams 根据ID获取参数记录\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) GetSysParams(ID string) (sysParams system.SysParams, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", ID).First(&sysParams).Error\n\treturn\n}\n\n// GetSysParamsInfoList 分页获取参数记录\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) GetSysParamsInfoList(info systemReq.SysParamsSearch) (list []system.SysParams, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysParams{})\n\tvar sysParamss []system.SysParams\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif info.StartCreatedAt != nil && info.EndCreatedAt != nil {\n\t\tdb = db.Where(\"created_at BETWEEN ? AND ?\", info.StartCreatedAt, info.EndCreatedAt)\n\t}\n\tif info.Name != \"\" {\n\t\tdb = db.Where(\"name LIKE ?\", \"%\"+info.Name+\"%\")\n\t}\n\tif info.Key != \"\" {\n\t\tdb = db.Where(\"key LIKE ?\", \"%\"+info.Key+\"%\")\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif limit != 0 {\n\t\tdb = db.Limit(limit).Offset(offset)\n\t}\n\n\terr = db.Find(&sysParamss).Error\n\treturn sysParamss, total, err\n}\n\n// GetSysParam 根据key获取参数value\n// Author [Mr.奇淼](https://github.com/pixelmaxQm)\nfunc (sysParamsService *SysParamsService) GetSysParam(key string) (param system.SysParams, err error) {\n\terr = global.GVA_DB.Where(system.SysParams{Key: key}).First(&param).Error\n\treturn\n}\n"
  },
  {
    "path": "server/service/system/sys_skills.go",
    "content": "package system\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"gopkg.in/yaml.v3\"\n)\n\nconst (\n\tskillFileName            = \"SKILL.md\"\n\tglobalConstraintFileName = \"README.md\"\n)\n\nvar skillToolOrder = []string{\"copilot\", \"claude\", \"cursor\", \"trae\", \"codex\"}\n\nvar skillToolDirs = map[string]string{\n\t\"copilot\": \".aone_copilot\",\n\t\"claude\":  \".claude\",\n\t\"trae\":    \".trae\",\n\t\"codex\":   \".codex\",\n\t\"cursor\":  \".cursor\",\n}\n\nvar skillToolLabels = map[string]string{\n\t\"copilot\": \"Copilot\",\n\t\"claude\":  \"Claude\",\n\t\"trae\":    \"Trae\",\n\t\"codex\":   \"Codex\",\n\t\"cursor\":  \"Cursor\",\n}\n\nconst defaultSkillMarkdown = \"## 技能用途\\n请在这里描述技能的目标、适用场景与限制条件。\\n\\n## 输入\\n- 请补充输入格式与示例。\\n\\n## 输出\\n- 请补充输出格式与示例。\\n\\n## 关键步骤\\n1. 第一步\\n2. 第二步\\n\\n## 示例\\n在此补充一到两个典型示例。\\n\"\n\nconst defaultResourceMarkdown = \"# 资源说明\\n请在这里补充资源内容。\\n\"\n\nconst defaultReferenceMarkdown = \"# 参考资料\\n请在这里补充参考资料内容。\\n\"\n\nconst defaultTemplateMarkdown = \"# 模板\\n请在这里补充模板内容。\\n\"\n\nconst defaultGlobalConstraintMarkdown = \"# 全局约束\\n请在这里补充该工具的统一约束与使用规范。\\n\"\n\ntype SkillsService struct{}\n\nfunc (s *SkillsService) Tools(_ context.Context) ([]system.SkillTool, error) {\n\ttools := make([]system.SkillTool, 0, len(skillToolOrder))\n\tfor _, key := range skillToolOrder {\n\t\tif _, err := s.toolSkillsDir(key); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttools = append(tools, system.SkillTool{Key: key, Label: skillToolLabels[key]})\n\t}\n\treturn tools, nil\n}\n\nfunc (s *SkillsService) List(_ context.Context, tool string) ([]string, error) {\n\tskillsDir, err := s.toolSkillsDir(tool)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tentries, err := os.ReadDir(skillsDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar skills []string\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\tskills = append(skills, entry.Name())\n\t\t}\n\t}\n\tsort.Strings(skills)\n\treturn skills, nil\n}\n\nfunc (s *SkillsService) Detail(_ context.Context, tool, skill string) (system.SkillDetail, error) {\n\tvar detail system.SkillDetail\n\tif !isSafeName(skill) {\n\t\treturn detail, errors.New(\"技能名称不合法\")\n\t}\n\tdetail.Tool = tool\n\tdetail.Skill = skill\n\n\tskillDir, err := s.skillDir(tool, skill)\n\tif err != nil {\n\t\treturn detail, err\n\t}\n\n\tskillFilePath := filepath.Join(skillDir, skillFileName)\n\tcontent, err := os.ReadFile(skillFilePath)\n\tif err != nil {\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn detail, err\n\t\t}\n\t\tdetail.Meta = system.SkillMeta{Name: skill}\n\t\tdetail.Markdown = defaultSkillMarkdown\n\t} else {\n\t\tmeta, body, parseErr := parseSkillContent(string(content))\n\t\tif parseErr != nil {\n\t\t\tmeta = system.SkillMeta{Name: skill}\n\t\t\tbody = string(content)\n\t\t}\n\t\tif meta.Name == \"\" {\n\t\t\tmeta.Name = skill\n\t\t}\n\t\tdetail.Meta = meta\n\t\tdetail.Markdown = body\n\t}\n\n\tdetail.Scripts = listFiles(filepath.Join(skillDir, \"scripts\"))\n\tdetail.Resources = listFiles(filepath.Join(skillDir, \"resources\"))\n\tdetail.References = listFiles(filepath.Join(skillDir, \"references\"))\n\tdetail.Templates = listFiles(filepath.Join(skillDir, \"templates\"))\n\treturn detail, nil\n}\n\nfunc (s *SkillsService) Save(_ context.Context, req request.SkillSaveRequest) error {\n\tif !isSafeName(req.Skill) {\n\t\treturn errors.New(\"技能名称不合法\")\n\t}\n\tskillDir, err := s.ensureSkillDir(req.Tool, req.Skill)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif req.Meta.Name == \"\" {\n\t\treq.Meta.Name = req.Skill\n\t}\n\tcontent, err := buildSkillContent(req.Meta, req.Markdown)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(filepath.Join(skillDir, skillFileName), []byte(content), 0644); err != nil {\n\t\treturn err\n\t}\n\n\tif len(req.SyncTools) > 0 {\n\t\tfor _, tool := range req.SyncTools {\n\t\t\tif tool == req.Tool {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttargetDir, err := s.ensureSkillDir(tool, req.Skill)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := copySkillDir(skillDir, targetDir); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SkillsService) Delete(_ context.Context, req request.SkillDeleteRequest) error {\n\tif strings.TrimSpace(req.Tool) == \"\" {\n\t\treturn errors.New(\"工具类型不能为空\")\n\t}\n\tif !isSafeName(req.Skill) {\n\t\treturn errors.New(\"技能名称不合法\")\n\t}\n\tskillDir, err := s.skillDir(req.Tool, req.Skill)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinfo, err := os.Stat(skillDir)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn errors.New(\"技能不存在\")\n\t\t}\n\t\treturn err\n\t}\n\tif !info.IsDir() {\n\t\treturn errors.New(\"技能目录异常\")\n\t}\n\treturn os.RemoveAll(skillDir)\n}\n\nfunc (s *SkillsService) Package(_ context.Context, req request.SkillPackageRequest) (string, []byte, error) {\n\tif strings.TrimSpace(req.Tool) == \"\" {\n\t\treturn \"\", nil, errors.New(\"工具类型不能为空\")\n\t}\n\tif !isSafeName(req.Skill) {\n\t\treturn \"\", nil, errors.New(\"技能名称不合法\")\n\t}\n\n\tskillDir, err := s.skillDir(req.Tool, req.Skill)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tinfo, err := os.Stat(skillDir)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn \"\", nil, errors.New(\"技能不存在\")\n\t\t}\n\t\treturn \"\", nil, err\n\t}\n\tif !info.IsDir() {\n\t\treturn \"\", nil, errors.New(\"技能目录异常\")\n\t}\n\n\tbuf := bytes.NewBuffer(nil)\n\tzw := zip.NewWriter(buf)\n\n\twalkErr := filepath.WalkDir(skillDir, func(path string, d fs.DirEntry, walkErr error) error {\n\t\tif walkErr != nil {\n\t\t\treturn walkErr\n\t\t}\n\t\trel, err := filepath.Rel(skillDir, path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif rel == \".\" {\n\t\t\treturn nil\n\t\t}\n\t\tzipName := filepath.ToSlash(rel)\n\t\tif d.IsDir() {\n\t\t\t_, err = zw.Create(strings.TrimSuffix(zipName, \"/\") + \"/\")\n\t\t\treturn err\n\t\t}\n\n\t\tfileInfo, err := d.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\theader, err := zip.FileInfoHeader(fileInfo)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\theader.Name = zipName\n\t\theader.Method = zip.Deflate\n\n\t\twriter, err := zw.CreateHeader(header)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcontent, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = writer.Write(content)\n\t\treturn err\n\t})\n\tif walkErr != nil {\n\t\t_ = zw.Close()\n\t\treturn \"\", nil, walkErr\n\t}\n\n\tif err = zw.Close(); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\treturn req.Skill + \".zip\", buf.Bytes(), nil\n}\n\nfunc (s *SkillsService) CreateScript(_ context.Context, req request.SkillScriptCreateRequest) (string, string, error) {\n\tif !isSafeName(req.Skill) {\n\t\treturn \"\", \"\", errors.New(\"技能名称不合法\")\n\t}\n\tfileName, lang, err := buildScriptFileName(req.FileName, req.ScriptType)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tif lang == \"\" {\n\t\treturn \"\", \"\", errors.New(\"脚本类型不支持\")\n\t}\n\tskillDir, err := s.ensureSkillDir(req.Tool, req.Skill)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tfilePath := filepath.Join(skillDir, \"scripts\", fileName)\n\tif _, err := os.Stat(filePath); err == nil {\n\t\treturn \"\", \"\", errors.New(\"脚本已存在\")\n\t}\n\tif err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tcontent := scriptTemplate(lang)\n\tif err := os.WriteFile(filePath, []byte(content), 0644); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\treturn fileName, content, nil\n}\n\nfunc (s *SkillsService) GetScript(_ context.Context, req request.SkillFileRequest) (string, error) {\n\treturn s.readSkillFile(req.Tool, req.Skill, \"scripts\", req.FileName)\n}\n\nfunc (s *SkillsService) SaveScript(_ context.Context, req request.SkillFileSaveRequest) error {\n\treturn s.writeSkillFile(req.Tool, req.Skill, \"scripts\", req.FileName, req.Content)\n}\n\nfunc (s *SkillsService) CreateResource(_ context.Context, req request.SkillResourceCreateRequest) (string, string, error) {\n\treturn s.createMarkdownFile(req.Tool, req.Skill, \"resources\", req.FileName, defaultResourceMarkdown, \"资源\")\n}\n\nfunc (s *SkillsService) GetResource(_ context.Context, req request.SkillFileRequest) (string, error) {\n\treturn s.readSkillFile(req.Tool, req.Skill, \"resources\", req.FileName)\n}\n\nfunc (s *SkillsService) SaveResource(_ context.Context, req request.SkillFileSaveRequest) error {\n\treturn s.writeSkillFile(req.Tool, req.Skill, \"resources\", req.FileName, req.Content)\n}\n\nfunc (s *SkillsService) CreateReference(_ context.Context, req request.SkillReferenceCreateRequest) (string, string, error) {\n\treturn s.createMarkdownFile(req.Tool, req.Skill, \"references\", req.FileName, defaultReferenceMarkdown, \"参考\")\n}\n\nfunc (s *SkillsService) GetReference(_ context.Context, req request.SkillFileRequest) (string, error) {\n\treturn s.readSkillFile(req.Tool, req.Skill, \"references\", req.FileName)\n}\n\nfunc (s *SkillsService) SaveReference(_ context.Context, req request.SkillFileSaveRequest) error {\n\treturn s.writeSkillFile(req.Tool, req.Skill, \"references\", req.FileName, req.Content)\n}\n\nfunc (s *SkillsService) CreateTemplate(_ context.Context, req request.SkillTemplateCreateRequest) (string, string, error) {\n\treturn s.createMarkdownFile(req.Tool, req.Skill, \"templates\", req.FileName, defaultTemplateMarkdown, \"模板\")\n}\n\nfunc (s *SkillsService) GetTemplate(_ context.Context, req request.SkillFileRequest) (string, error) {\n\treturn s.readSkillFile(req.Tool, req.Skill, \"templates\", req.FileName)\n}\n\nfunc (s *SkillsService) SaveTemplate(_ context.Context, req request.SkillFileSaveRequest) error {\n\treturn s.writeSkillFile(req.Tool, req.Skill, \"templates\", req.FileName, req.Content)\n}\n\nfunc (s *SkillsService) GetGlobalConstraint(_ context.Context, tool string) (string, bool, error) {\n\tskillsDir, err := s.toolSkillsDir(tool)\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\tfilePath := filepath.Join(skillsDir, globalConstraintFileName)\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn defaultGlobalConstraintMarkdown, false, nil\n\t\t}\n\t\treturn \"\", false, err\n\t}\n\treturn string(content), true, nil\n}\n\nfunc (s *SkillsService) SaveGlobalConstraint(_ context.Context, req request.SkillGlobalConstraintSaveRequest) error {\n\tif strings.TrimSpace(req.Tool) == \"\" {\n\t\treturn errors.New(\"工具类型不能为空\")\n\t}\n\twriteConstraint := func(tool, content string) error {\n\t\tskillsDir, err := s.toolSkillsDir(tool)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfilePath := filepath.Join(skillsDir, globalConstraintFileName)\n\t\tif err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn os.WriteFile(filePath, []byte(content), 0644)\n\t}\n\tif err := writeConstraint(req.Tool, req.Content); err != nil {\n\t\treturn err\n\t}\n\tif len(req.SyncTools) == 0 {\n\t\treturn nil\n\t}\n\tfor _, tool := range req.SyncTools {\n\t\tif tool == \"\" || tool == req.Tool {\n\t\t\tcontinue\n\t\t}\n\t\tif err := writeConstraint(tool, req.Content); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SkillsService) DownloadOnlineSkill(_ context.Context, req request.DownloadOnlineSkillReq) error {\n\tskillsDir, err := s.toolSkillsDir(req.Tool)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbody, err := json.Marshal(map[string]interface{}{\n\t\t\"plugin_id\": req.ID,\n\t\t\"version\":   req.Version,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"构建下载请求失败: %w\", err)\n\t}\n\n\tdownloadReq, err := http.NewRequest(http.MethodPost, \"https://plugin.gin-vue-admin.com/api/shopPlugin/downloadSkill\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"构建下载请求失败: %w\", err)\n\t}\n\tdownloadReq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tdownloadResp, err := http.DefaultClient.Do(downloadReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"下载技能失败: %w\", err)\n\t}\n\tdefer downloadResp.Body.Close()\n\n\tif downloadResp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"下载技能失败, HTTP状态码: %d\", downloadResp.StatusCode)\n\t}\n\n\tmetaBody, err := io.ReadAll(downloadResp.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"读取下载结果失败: %w\", err)\n\t}\n\n\tvar meta struct {\n\t\tData struct {\n\t\t\tURL string `json:\"url\"`\n\t\t} `json:\"data\"`\n\t}\n\tif err = json.Unmarshal(metaBody, &meta); err != nil {\n\t\treturn fmt.Errorf(\"解析下载结果失败: %w\", err)\n\t}\n\n\trealDownloadURL := strings.TrimSpace(meta.Data.URL)\n\tif realDownloadURL == \"\" {\n\t\treturn errors.New(\"下载结果缺少 url\")\n\t}\n\n\tzipResp, err := http.Get(realDownloadURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"下载压缩包失败: %w\", err)\n\t}\n\tdefer zipResp.Body.Close()\n\n\tif zipResp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"下载压缩包失败, HTTP状态码: %d\", zipResp.StatusCode)\n\t}\n\n\ttmpFile, err := os.CreateTemp(\"\", \"gva-skill-*.zip\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"创建临时文件失败: %w\", err)\n\t}\n\ttmpPath := tmpFile.Name()\n\tdefer os.Remove(tmpPath)\n\n\tif _, err = io.Copy(tmpFile, zipResp.Body); err != nil {\n\t\ttmpFile.Close()\n\t\treturn fmt.Errorf(\"保存技能包失败: %w\", err)\n\t}\n\ttmpFile.Close()\n\n\tif err = extractZipToDir(tmpPath, skillsDir); err != nil {\n\t\treturn fmt.Errorf(\"解压技能包失败: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc extractZipToDir(zipPath, destDir string) error {\n\tr, err := zip.OpenReader(zipPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\tfor _, f := range r.File {\n\t\tname := filepath.FromSlash(f.Name)\n\t\tif strings.Contains(name, \"..\") {\n\t\t\tcontinue\n\t\t}\n\n\t\ttarget := filepath.Join(destDir, name)\n\t\tif !strings.HasPrefix(filepath.Clean(target), filepath.Clean(destDir)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif f.FileInfo().IsDir() {\n\t\t\tif err := os.MkdirAll(target, os.ModePerm); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trc, err := f.Open()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tout, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())\n\t\tif err != nil {\n\t\t\trc.Close()\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = io.Copy(out, rc)\n\t\trc.Close()\n\t\tout.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *SkillsService) toolSkillsDir(tool string) (string, error) {\n\ttoolDir, ok := skillToolDirs[tool]\n\tif !ok {\n\t\treturn \"\", errors.New(\"工具类型不支持\")\n\t}\n\troot := strings.TrimSpace(global.GVA_CONFIG.AutoCode.Root)\n\tif root == \"\" {\n\t\troot = \".\"\n\t}\n\tskillsDir := filepath.Join(root, toolDir, \"skills\")\n\tif err := os.MkdirAll(skillsDir, os.ModePerm); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn skillsDir, nil\n}\n\nfunc (s *SkillsService) skillDir(tool, skill string) (string, error) {\n\tskillsDir, err := s.toolSkillsDir(tool)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(skillsDir, skill), nil\n}\n\nfunc (s *SkillsService) ensureSkillDir(tool, skill string) (string, error) {\n\tif !isSafeName(skill) {\n\t\treturn \"\", errors.New(\"技能名称不合法\")\n\t}\n\tskillDir, err := s.skillDir(tool, skill)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := os.MkdirAll(skillDir, os.ModePerm); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn skillDir, nil\n}\n\nfunc (s *SkillsService) createMarkdownFile(tool, skill, subDir, fileName, defaultContent, label string) (string, string, error) {\n\tif !isSafeName(skill) {\n\t\treturn \"\", \"\", errors.New(\"技能名称不合法\")\n\t}\n\tcleanName, err := buildResourceFileName(fileName)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tskillDir, err := s.ensureSkillDir(tool, skill)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tfilePath := filepath.Join(skillDir, subDir, cleanName)\n\tif _, err := os.Stat(filePath); err == nil {\n\t\tif label == \"\" {\n\t\t\tlabel = \"文件\"\n\t\t}\n\t\treturn \"\", \"\", fmt.Errorf(\"%s已存在\", label)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tcontent := defaultContent\n\tif err := os.WriteFile(filePath, []byte(content), 0644); err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\treturn cleanName, content, nil\n}\n\nfunc (s *SkillsService) readSkillFile(tool, skill, subDir, fileName string) (string, error) {\n\tif !isSafeName(skill) {\n\t\treturn \"\", errors.New(\"技能名称不合法\")\n\t}\n\tif !isSafeFileName(fileName) {\n\t\treturn \"\", errors.New(\"文件名不合法\")\n\t}\n\tskillDir, err := s.skillDir(tool, skill)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfilePath := filepath.Join(skillDir, subDir, fileName)\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(content), nil\n}\n\nfunc (s *SkillsService) writeSkillFile(tool, skill, subDir, fileName, content string) error {\n\tif !isSafeName(skill) {\n\t\treturn errors.New(\"技能名称不合法\")\n\t}\n\tif !isSafeFileName(fileName) {\n\t\treturn errors.New(\"文件名不合法\")\n\t}\n\tskillDir, err := s.ensureSkillDir(tool, skill)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilePath := filepath.Join(skillDir, subDir, fileName)\n\tif err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\treturn os.WriteFile(filePath, []byte(content), 0644)\n}\n\nfunc parseSkillContent(content string) (system.SkillMeta, string, error) {\n\tclean := strings.TrimPrefix(content, \"\\ufeff\")\n\tlines := strings.Split(clean, \"\\n\")\n\tif len(lines) == 0 || strings.TrimSpace(lines[0]) != \"---\" {\n\t\treturn system.SkillMeta{}, clean, nil\n\t}\n\tend := -1\n\tfor i := 1; i < len(lines); i++ {\n\t\tif strings.TrimSpace(lines[i]) == \"---\" {\n\t\t\tend = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif end == -1 {\n\t\treturn system.SkillMeta{}, clean, nil\n\t}\n\tyamlText := strings.Join(lines[1:end], \"\\n\")\n\tbody := strings.Join(lines[end+1:], \"\\n\")\n\tvar meta system.SkillMeta\n\tif err := yaml.Unmarshal([]byte(yamlText), &meta); err != nil {\n\t\treturn system.SkillMeta{}, body, err\n\t}\n\treturn meta, body, nil\n}\n\nfunc buildSkillContent(meta system.SkillMeta, markdown string) (string, error) {\n\tif meta.Name == \"\" {\n\t\treturn \"\", errors.New(\"name不能为空\")\n\t}\n\tdata, err := yaml.Marshal(meta)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tyamlText := strings.TrimRight(string(data), \"\\n\")\n\tbody := strings.TrimLeft(markdown, \"\\n\")\n\tif body != \"\" {\n\t\tbody = body + \"\\n\"\n\t}\n\treturn fmt.Sprintf(\"---\\n%s\\n---\\n%s\", yamlText, body), nil\n}\n\nfunc listFiles(dir string) []string {\n\tentries, err := os.ReadDir(dir)\n\tif err != nil {\n\t\treturn []string{}\n\t}\n\tfiles := make([]string, 0, len(entries))\n\tfor _, entry := range entries {\n\t\tif entry.Type().IsRegular() {\n\t\t\tfiles = append(files, entry.Name())\n\t\t}\n\t}\n\tsort.Strings(files)\n\treturn files\n}\n\nfunc isSafeName(name string) bool {\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn false\n\t}\n\tif strings.Contains(name, \"..\") {\n\t\treturn false\n\t}\n\tif strings.ContainsAny(name, \"/\\\\\") {\n\t\treturn false\n\t}\n\treturn name == filepath.Base(name)\n}\n\nfunc isSafeFileName(name string) bool {\n\tif strings.TrimSpace(name) == \"\" {\n\t\treturn false\n\t}\n\tif strings.Contains(name, \"..\") {\n\t\treturn false\n\t}\n\tif strings.ContainsAny(name, \"/\\\\\") {\n\t\treturn false\n\t}\n\treturn name == filepath.Base(name)\n}\n\nfunc buildScriptFileName(fileName, scriptType string) (string, string, error) {\n\tclean := strings.TrimSpace(fileName)\n\tif clean == \"\" {\n\t\treturn \"\", \"\", errors.New(\"文件名不能为空\")\n\t}\n\tif !isSafeFileName(clean) {\n\t\treturn \"\", \"\", errors.New(\"文件名不合法\")\n\t}\n\tbase := strings.TrimSuffix(clean, filepath.Ext(clean))\n\tif base == \"\" {\n\t\treturn \"\", \"\", errors.New(\"文件名不合法\")\n\t}\n\n\tswitch strings.ToLower(scriptType) {\n\tcase \"py\", \"python\":\n\t\treturn base + \".py\", \"python\", nil\n\tcase \"js\", \"javascript\", \"script\":\n\t\treturn base + \".js\", \"javascript\", nil\n\tcase \"sh\", \"shell\", \"bash\":\n\t\treturn base + \".sh\", \"sh\", nil\n\tdefault:\n\t\treturn \"\", \"\", errors.New(\"脚本类型不支持\")\n\t}\n}\n\nfunc buildResourceFileName(fileName string) (string, error) {\n\tclean := strings.TrimSpace(fileName)\n\tif clean == \"\" {\n\t\treturn \"\", errors.New(\"文件名不能为空\")\n\t}\n\tif !isSafeFileName(clean) {\n\t\treturn \"\", errors.New(\"文件名不合法\")\n\t}\n\tbase := strings.TrimSuffix(clean, filepath.Ext(clean))\n\tif base == \"\" {\n\t\treturn \"\", errors.New(\"文件名不合法\")\n\t}\n\treturn base + \".md\", nil\n}\n\nfunc scriptTemplate(lang string) string {\n\tswitch lang {\n\tcase \"python\":\n\t\treturn \"# -*- coding: utf-8 -*-\\n# TODO: 在这里实现脚本逻辑\\n\"\n\tcase \"javascript\":\n\t\treturn \"// TODO: 在这里实现脚本逻辑\\n\"\n\tcase \"sh\":\n\t\treturn \"#!/usr/bin/env bash\\nset -euo pipefail\\n\\n# TODO: 在这里实现脚本逻辑\\n\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc copySkillDir(src, dst string) error {\n\treturn filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trel, err := filepath.Rel(src, path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif rel == \".\" {\n\t\t\treturn nil\n\t\t}\n\t\ttarget := filepath.Join(dst, rel)\n\t\tif d.IsDir() {\n\t\t\treturn os.MkdirAll(target, os.ModePerm)\n\t\t}\n\t\tif !d.Type().IsRegular() {\n\t\t\treturn nil\n\t\t}\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn os.WriteFile(target, data, 0644)\n\t})\n}\n"
  },
  {
    "path": "server/service/system/sys_system.go",
    "content": "package system\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/config\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"go.uber.org/zap\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetSystemConfig\n//@description: 读取配置文件\n//@return: conf config.Server, err error\n\ntype SystemConfigService struct{}\n\nvar SystemConfigServiceApp = new(SystemConfigService)\n\nfunc (systemConfigService *SystemConfigService) GetSystemConfig() (conf config.Server, err error) {\n\treturn global.GVA_CONFIG, nil\n}\n\n// @description   set system config,\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetSystemConfig\n//@description: 设置配置文件\n//@param: system model.System\n//@return: err error\n\nfunc (systemConfigService *SystemConfigService) SetSystemConfig(system system.System) (err error) {\n\tcs := utils.StructToMap(system.Config)\n\tfor k, v := range cs {\n\t\tglobal.GVA_VP.Set(k, v)\n\t}\n\terr = global.GVA_VP.WriteConfig()\n\treturn err\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: GetServerInfo\n//@description: 获取服务器信息\n//@return: server *utils.Server, err error\n\nfunc (systemConfigService *SystemConfigService) GetServerInfo() (server *utils.Server, err error) {\n\tvar s utils.Server\n\ts.Os = utils.InitOS()\n\tif s.Cpu, err = utils.InitCPU(); err != nil {\n\t\tglobal.GVA_LOG.Error(\"func utils.InitCPU() Failed\", zap.String(\"err\", err.Error()))\n\t\treturn &s, err\n\t}\n\tif s.Ram, err = utils.InitRAM(); err != nil {\n\t\tglobal.GVA_LOG.Error(\"func utils.InitRAM() Failed\", zap.String(\"err\", err.Error()))\n\t\treturn &s, err\n\t}\n\tif s.Disk, err = utils.InitDisk(); err != nil {\n\t\tglobal.GVA_LOG.Error(\"func utils.InitDisk() Failed\", zap.String(\"err\", err.Error()))\n\t\treturn &s, err\n\t}\n\n\treturn &s, nil\n}\n"
  },
  {
    "path": "server/service/system/sys_user.go",
    "content": "package system\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/google/uuid\"\n\t\"gorm.io/gorm\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Register\n//@description: 用户注册\n//@param: u model.SysUser\n//@return: userInter system.SysUser, err error\n\ntype UserService struct{}\n\nvar UserServiceApp = new(UserService)\n\nfunc (userService *UserService) Register(u system.SysUser) (userInter system.SysUser, err error) {\n\tvar user system.SysUser\n\tif !errors.Is(global.GVA_DB.Where(\"username = ?\", u.Username).First(&user).Error, gorm.ErrRecordNotFound) { // 判断用户名是否注册\n\t\treturn userInter, errors.New(\"用户名已注册\")\n\t}\n\t// 否则 附加uuid 密码hash加密 注册\n\tu.Password = utils.BcryptHash(u.Password)\n\tu.UUID = uuid.New()\n\terr = global.GVA_DB.Create(&u).Error\n\treturn u, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: Login\n//@description: 用户登录\n//@param: u *model.SysUser\n//@return: err error, userInter *model.SysUser\n\nfunc (userService *UserService) Login(u *system.SysUser) (userInter *system.SysUser, err error) {\n\tif nil == global.GVA_DB {\n\t\treturn nil, fmt.Errorf(\"db not init\")\n\t}\n\n\tvar user system.SysUser\n\terr = global.GVA_DB.Where(\"username = ?\", u.Username).Preload(\"Authorities\").Preload(\"Authority\").First(&user).Error\n\tif err == nil {\n\t\tif ok := utils.BcryptCheck(u.Password, user.Password); !ok {\n\t\t\treturn nil, errors.New(\"密码错误\")\n\t\t}\n\t\tMenuServiceApp.UserAuthorityDefaultRouter(&user)\n\t}\n\treturn &user, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: ChangePassword\n//@description: 修改用户密码\n//@param: u *model.SysUser, newPassword string\n//@return: err error\n\nfunc (userService *UserService) ChangePassword(u *system.SysUser, newPassword string) (err error) {\n\tvar user system.SysUser\n\terr = global.GVA_DB.Select(\"id, password\").Where(\"id = ?\", u.ID).First(&user).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tif ok := utils.BcryptCheck(u.Password, user.Password); !ok {\n\t\treturn errors.New(\"原密码错误\")\n\t}\n\tpwd := utils.BcryptHash(newPassword)\n\terr = global.GVA_DB.Model(&user).Update(\"password\", pwd).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: GetUserInfoList\n//@description: 分页获取数据\n//@param: info request.PageInfo\n//@return: err error, list interface{}, total int64\n\nfunc (userService *UserService) GetUserInfoList(info systemReq.GetUserList) (list interface{}, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\tdb := global.GVA_DB.Model(&system.SysUser{})\n\tvar userList []system.SysUser\n\n\tif info.NickName != \"\" {\n\t\tdb = db.Where(\"nick_name LIKE ?\", \"%\"+info.NickName+\"%\")\n\t}\n\tif info.Phone != \"\" {\n\t\tdb = db.Where(\"phone LIKE ?\", \"%\"+info.Phone+\"%\")\n\t}\n\tif info.Username != \"\" {\n\t\tdb = db.Where(\"username LIKE ?\", \"%\"+info.Username+\"%\")\n\t}\n\tif info.Email != \"\" {\n\t\tdb = db.Where(\"email LIKE ?\", \"%\"+info.Email+\"%\")\n\t}\n\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\torderStr := \"id desc\"\n\tif info.OrderKey != \"\" {\n\t\tallowedOrders := map[string]bool{\n\t\t\t\"id\":        true,\n\t\t\t\"username\":  true,\n\t\t\t\"nick_name\": true,\n\t\t\t\"phone\":     true,\n\t\t\t\"email\":     true,\n\t\t}\n\t\tif allowedOrders[info.OrderKey] {\n\t\t\torderStr = info.OrderKey\n\t\t\tif info.Desc {\n\t\t\t\torderStr = info.OrderKey + \" desc\"\n\t\t\t}\n\t\t}\n\t}\n\n\terr = db.Limit(limit).Offset(offset).Order(orderStr).Preload(\"Authorities\").Preload(\"Authority\").Find(&userList).Error\n\treturn userList, total, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetUserAuthority\n//@description: 设置一个用户的权限\n//@param: uuid uuid.UUID, authorityId string\n//@return: err error\n\nfunc (userService *UserService) SetUserAuthority(id uint, authorityId uint) (err error) {\n\n\tassignErr := global.GVA_DB.Where(\"sys_user_id = ? AND sys_authority_authority_id = ?\", id, authorityId).First(&system.SysUserAuthority{}).Error\n\tif errors.Is(assignErr, gorm.ErrRecordNotFound) {\n\t\treturn errors.New(\"该用户无此角色\")\n\t}\n\n\tvar authority system.SysAuthority\n\terr = global.GVA_DB.Where(\"authority_id = ?\", authorityId).First(&authority).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar authorityMenu []system.SysAuthorityMenu\n\tvar authorityMenuIDs []string\n\terr = global.GVA_DB.Where(\"sys_authority_authority_id = ?\", authorityId).Find(&authorityMenu).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor i := range authorityMenu {\n\t\tauthorityMenuIDs = append(authorityMenuIDs, authorityMenu[i].MenuId)\n\t}\n\n\tvar authorityMenus []system.SysBaseMenu\n\terr = global.GVA_DB.Preload(\"Parameters\").Where(\"id in (?)\", authorityMenuIDs).Find(&authorityMenus).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\thasMenu := false\n\tfor i := range authorityMenus {\n\t\tif authorityMenus[i].Name == authority.DefaultRouter {\n\t\t\thasMenu = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasMenu {\n\t\treturn errors.New(\"找不到默认路由,无法切换本角色\")\n\t}\n\n\terr = global.GVA_DB.Model(&system.SysUser{}).Where(\"id = ?\", id).Update(\"authority_id\", authorityId).Error\n\treturn err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetUserAuthorities\n//@description: 设置一个用户的权限\n//@param: id uint, authorityIds []string\n//@return: err error\n\nfunc (userService *UserService) SetUserAuthorities(adminAuthorityID, id uint, authorityIds []uint) (err error) {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tvar user system.SysUser\n\t\tTxErr := tx.Where(\"id = ?\", id).First(&user).Error\n\t\tif TxErr != nil {\n\t\t\tglobal.GVA_LOG.Debug(TxErr.Error())\n\t\t\treturn errors.New(\"查询用户数据失败\")\n\t\t}\n\t\tTxErr = tx.Delete(&[]system.SysUserAuthority{}, \"sys_user_id = ?\", id).Error\n\t\tif TxErr != nil {\n\t\t\treturn TxErr\n\t\t}\n\t\tvar useAuthority []system.SysUserAuthority\n\t\tfor _, v := range authorityIds {\n\t\t\te := AuthorityServiceApp.CheckAuthorityIDAuth(adminAuthorityID, v)\n\t\t\tif e != nil {\n\t\t\t\treturn e\n\t\t\t}\n\t\t\tuseAuthority = append(useAuthority, system.SysUserAuthority{\n\t\t\t\tSysUserId: id, SysAuthorityAuthorityId: v,\n\t\t\t})\n\t\t}\n\t\tTxErr = tx.Create(&useAuthority).Error\n\t\tif TxErr != nil {\n\t\t\treturn TxErr\n\t\t}\n\t\tTxErr = tx.Model(&user).Update(\"authority_id\", authorityIds[0]).Error\n\t\tif TxErr != nil {\n\t\t\treturn TxErr\n\t\t}\n\t\t// 返回 nil 提交事务\n\t\treturn nil\n\t})\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: DeleteUser\n//@description: 删除用户\n//@param: id float64\n//@return: err error\n\nfunc (userService *UserService) DeleteUser(id int) (err error) {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tif err := tx.Where(\"id = ?\", id).Delete(&system.SysUser{}).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := tx.Delete(&[]system.SysUserAuthority{}, \"sys_user_id = ?\", id).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetUserInfo\n//@description: 设置用户信息\n//@param: reqUser model.SysUser\n//@return: err error, user model.SysUser\n\nfunc (userService *UserService) SetUserInfo(req system.SysUser) error {\n\treturn global.GVA_DB.Model(&system.SysUser{}).\n\t\tSelect(\"updated_at\", \"nick_name\", \"header_img\", \"phone\", \"email\", \"enable\").\n\t\tWhere(\"id=?\", req.ID).\n\t\tUpdates(map[string]interface{}{\n\t\t\t\"updated_at\": time.Now(),\n\t\t\t\"nick_name\":  req.NickName,\n\t\t\t\"header_img\": req.HeaderImg,\n\t\t\t\"phone\":      req.Phone,\n\t\t\t\"email\":      req.Email,\n\t\t\t\"enable\":     req.Enable,\n\t\t}).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetSelfInfo\n//@description: 设置用户信息\n//@param: reqUser model.SysUser\n//@return: err error, user model.SysUser\n\nfunc (userService *UserService) SetSelfInfo(req system.SysUser) error {\n\treturn global.GVA_DB.Model(&system.SysUser{}).\n\t\tWhere(\"id=?\", req.ID).\n\t\tUpdates(req).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetSelfSetting\n//@description: 设置用户配置\n//@param: req datatypes.JSON, uid uint\n//@return: err error\n\nfunc (userService *UserService) SetSelfSetting(req common.JSONMap, uid uint) error {\n\treturn global.GVA_DB.Model(&system.SysUser{}).Where(\"id = ?\", uid).Update(\"origin_setting\", req).Error\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: GetUserInfo\n//@description: 获取用户信息\n//@param: uuid uuid.UUID\n//@return: err error, user system.SysUser\n\nfunc (userService *UserService) GetUserInfo(uuid uuid.UUID) (user system.SysUser, err error) {\n\tvar reqUser system.SysUser\n\terr = global.GVA_DB.Preload(\"Authorities\").Preload(\"Authority\").First(&reqUser, \"uuid = ?\", uuid).Error\n\tif err != nil {\n\t\treturn reqUser, err\n\t}\n\tMenuServiceApp.UserAuthorityDefaultRouter(&reqUser)\n\treturn reqUser, err\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: FindUserById\n//@description: 通过id获取用户信息\n//@param: id int\n//@return: err error, user *model.SysUser\n\nfunc (userService *UserService) FindUserById(id int) (user *system.SysUser, err error) {\n\tvar u system.SysUser\n\terr = global.GVA_DB.Where(\"id = ?\", id).First(&u).Error\n\treturn &u, err\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: FindUserByUuid\n//@description: 通过uuid获取用户信息\n//@param: uuid string\n//@return: err error, user *model.SysUser\n\nfunc (userService *UserService) FindUserByUuid(uuid string) (user *system.SysUser, err error) {\n\tvar u system.SysUser\n\tif err = global.GVA_DB.Where(\"uuid = ?\", uuid).First(&u).Error; err != nil {\n\t\treturn &u, errors.New(\"用户不存在\")\n\t}\n\treturn &u, nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: ResetPassword\n//@description: 修改用户密码\n//@param: ID uint\n//@return: err error\n\nfunc (userService *UserService) ResetPassword(ID uint, password string) (err error) {\n\terr = global.GVA_DB.Model(&system.SysUser{}).Where(\"id = ?\", ID).Update(\"password\", utils.BcryptHash(password)).Error\n\treturn err\n}\n"
  },
  {
    "path": "server/service/system/sys_version.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"gorm.io/gorm\"\n)\n\ntype SysVersionService struct{}\n\n// CreateSysVersion 创建版本管理记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysVersionService *SysVersionService) CreateSysVersion(ctx context.Context, sysVersion *system.SysVersion) (err error) {\n\terr = global.GVA_DB.Create(sysVersion).Error\n\treturn err\n}\n\n// DeleteSysVersion 删除版本管理记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysVersionService *SysVersionService) DeleteSysVersion(ctx context.Context, ID string) (err error) {\n\terr = global.GVA_DB.Delete(&system.SysVersion{}, \"id = ?\", ID).Error\n\treturn err\n}\n\n// DeleteSysVersionByIds 批量删除版本管理记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysVersionService *SysVersionService) DeleteSysVersionByIds(ctx context.Context, IDs []string) (err error) {\n\terr = global.GVA_DB.Where(\"id in ?\", IDs).Delete(&system.SysVersion{}).Error\n\treturn err\n}\n\n// GetSysVersion 根据ID获取版本管理记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysVersionService *SysVersionService) GetSysVersion(ctx context.Context, ID string) (sysVersion system.SysVersion, err error) {\n\terr = global.GVA_DB.Where(\"id = ?\", ID).First(&sysVersion).Error\n\treturn\n}\n\n// GetSysVersionInfoList 分页获取版本管理记录\n// Author [yourname](https://github.com/yourname)\nfunc (sysVersionService *SysVersionService) GetSysVersionInfoList(ctx context.Context, info systemReq.SysVersionSearch) (list []system.SysVersion, total int64, err error) {\n\tlimit := info.PageSize\n\toffset := info.PageSize * (info.Page - 1)\n\t// 创建db\n\tdb := global.GVA_DB.Model(&system.SysVersion{})\n\tvar sysVersions []system.SysVersion\n\t// 如果有条件搜索 下方会自动创建搜索语句\n\tif len(info.CreatedAtRange) == 2 {\n\t\tdb = db.Where(\"created_at BETWEEN ? AND ?\", info.CreatedAtRange[0], info.CreatedAtRange[1])\n\t}\n\n\tif info.VersionName != nil && *info.VersionName != \"\" {\n\t\tdb = db.Where(\"version_name LIKE ?\", \"%\"+*info.VersionName+\"%\")\n\t}\n\tif info.VersionCode != nil && *info.VersionCode != \"\" {\n\t\tdb = db.Where(\"version_code = ?\", *info.VersionCode)\n\t}\n\terr = db.Count(&total).Error\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif limit != 0 {\n\t\tdb = db.Limit(limit).Offset(offset)\n\t}\n\n\terr = db.Find(&sysVersions).Error\n\treturn sysVersions, total, err\n}\nfunc (sysVersionService *SysVersionService) GetSysVersionPublic(ctx context.Context) {\n\t// 此方法为获取数据源定义的数据\n\t// 请自行实现\n}\n\n// GetMenusByIds 根据ID列表获取菜单数据\nfunc (sysVersionService *SysVersionService) GetMenusByIds(ctx context.Context, ids []uint) (menus []system.SysBaseMenu, err error) {\n\terr = global.GVA_DB.Where(\"id in ?\", ids).Preload(\"Parameters\").Preload(\"MenuBtn\").Find(&menus).Error\n\treturn\n}\n\n// GetApisByIds 根据ID列表获取API数据\nfunc (sysVersionService *SysVersionService) GetApisByIds(ctx context.Context, ids []uint) (apis []system.SysApi, err error) {\n\terr = global.GVA_DB.Where(\"id in ?\", ids).Find(&apis).Error\n\treturn\n}\n\n// GetDictionariesByIds 根据ID列表获取字典数据\nfunc (sysVersionService *SysVersionService) GetDictionariesByIds(ctx context.Context, ids []uint) (dictionaries []system.SysDictionary, err error) {\n\terr = global.GVA_DB.Where(\"id in ?\", ids).Preload(\"SysDictionaryDetails\").Find(&dictionaries).Error\n\treturn\n}\n\n// ImportMenus 导入菜单数据\nfunc (sysVersionService *SysVersionService) ImportMenus(ctx context.Context, menus []system.SysBaseMenu) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\t// 递归创建菜单\n\t\treturn sysVersionService.createMenusRecursively(tx, menus, 0)\n\t})\n}\n\n// createMenusRecursively 递归创建菜单\nfunc (sysVersionService *SysVersionService) createMenusRecursively(tx *gorm.DB, menus []system.SysBaseMenu, parentId uint) error {\n\tfor _, menu := range menus {\n\t\t// 检查菜单是否已存在\n\t\tvar existingMenu system.SysBaseMenu\n\t\tif err := tx.Where(\"name = ? AND path = ?\", menu.Name, menu.Path).First(&existingMenu).Error; err == nil {\n\t\t\t// 菜单已存在，使用现有菜单ID继续处理子菜单\n\t\t\tif len(menu.Children) > 0 {\n\t\t\t\tif err := sysVersionService.createMenusRecursively(tx, menu.Children, existingMenu.ID); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// 保存参数和按钮数据，稍后处理\n\t\tparameters := menu.Parameters\n\t\tmenuBtns := menu.MenuBtn\n\t\tchildren := menu.Children\n\n\t\t// 创建新菜单（不包含关联数据）\n\t\tnewMenu := system.SysBaseMenu{\n\t\t\tParentId:  parentId,\n\t\t\tPath:      menu.Path,\n\t\t\tName:      menu.Name,\n\t\t\tHidden:    menu.Hidden,\n\t\t\tComponent: menu.Component,\n\t\t\tSort:      menu.Sort,\n\t\t\tMeta:      menu.Meta,\n\t\t}\n\n\t\tif err := tx.Create(&newMenu).Error; err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 创建参数\n\t\tif len(parameters) > 0 {\n\t\t\tfor _, param := range parameters {\n\t\t\t\tnewParam := system.SysBaseMenuParameter{\n\t\t\t\t\tSysBaseMenuID: newMenu.ID,\n\t\t\t\t\tType:          param.Type,\n\t\t\t\t\tKey:           param.Key,\n\t\t\t\t\tValue:         param.Value,\n\t\t\t\t}\n\t\t\t\tif err := tx.Create(&newParam).Error; err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 创建菜单按钮\n\t\tif len(menuBtns) > 0 {\n\t\t\tfor _, btn := range menuBtns {\n\t\t\t\tnewBtn := system.SysBaseMenuBtn{\n\t\t\t\t\tSysBaseMenuID: newMenu.ID,\n\t\t\t\t\tName:          btn.Name,\n\t\t\t\t\tDesc:          btn.Desc,\n\t\t\t\t}\n\t\t\t\tif err := tx.Create(&newBtn).Error; err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// 递归处理子菜单\n\t\tif len(children) > 0 {\n\t\t\tif err := sysVersionService.createMenusRecursively(tx, children, newMenu.ID); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// ImportApis 导入API数据\nfunc (sysVersionService *SysVersionService) ImportApis(apis []system.SysApi) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tfor _, api := range apis {\n\t\t\t// 检查API是否已存在\n\t\t\tvar existingApi system.SysApi\n\t\t\tif err := tx.Where(\"path = ? AND method = ?\", api.Path, api.Method).First(&existingApi).Error; err == nil {\n\t\t\t\t// API已存在，跳过\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 创建新API\n\t\t\tnewApi := system.SysApi{\n\t\t\t\tPath:        api.Path,\n\t\t\t\tDescription: api.Description,\n\t\t\t\tApiGroup:    api.ApiGroup,\n\t\t\t\tMethod:      api.Method,\n\t\t\t}\n\n\t\t\tif err := tx.Create(&newApi).Error; err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// ImportDictionaries 导入字典数据\nfunc (sysVersionService *SysVersionService) ImportDictionaries(dictionaries []system.SysDictionary) error {\n\treturn global.GVA_DB.Transaction(func(tx *gorm.DB) error {\n\t\tfor _, dict := range dictionaries {\n\t\t\t// 检查字典是否已存在\n\t\t\tvar existingDict system.SysDictionary\n\t\t\tif err := tx.Where(\"type = ?\", dict.Type).First(&existingDict).Error; err == nil {\n\t\t\t\t// 字典已存在，跳过\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// 创建新字典\n\t\t\tnewDict := system.SysDictionary{\n\t\t\t\tName:                 dict.Name,\n\t\t\t\tType:                 dict.Type,\n\t\t\t\tStatus:               dict.Status,\n\t\t\t\tDesc:                 dict.Desc,\n\t\t\t\tSysDictionaryDetails: dict.SysDictionaryDetails,\n\t\t\t}\n\n\t\t\tif err := tx.Create(&newDict).Error; err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "server/source/example/file_upload_download.go",
    "content": "package example\n\nimport (\n\t\"context\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderExaFile = system.InitOrderInternal + 1\n\ntype initExaFileMysql struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderExaFile, &initExaFileMysql{})\n}\n\nfunc (i *initExaFileMysql) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&example.ExaFileUploadAndDownload{})\n}\n\nfunc (i *initExaFileMysql) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&example.ExaFileUploadAndDownload{})\n}\n\nfunc (i *initExaFileMysql) InitializerName() string {\n\treturn example.ExaFileUploadAndDownload{}.TableName()\n}\n\nfunc (i *initExaFileMysql) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tentities := []example.ExaFileUploadAndDownload{\n\t\t{Name: \"10.png\", Url: \"https://qmplusimg.henrongyi.top/gvalogo.png\", Tag: \"png\", Key: \"158787308910.png\"},\n\t\t{Name: \"logo.png\", Url: \"https://qmplusimg.henrongyi.top/1576554439myAvatar.png\", Tag: \"png\", Key: \"1587973709logo.png\"},\n\t}\n\tif err := db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, example.ExaFileUploadAndDownload{}.TableName()+\"表数据初始化失败!\")\n\t}\n\treturn ctx, nil\n}\n\nfunc (i *initExaFileMysql) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tlookup := example.ExaFileUploadAndDownload{Name: \"logo.png\", Key: \"1587973709logo.png\"}\n\tif errors.Is(db.First(&lookup, &lookup).Error, gorm.ErrRecordNotFound) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/api.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\ntype initApi struct{}\n\nconst initOrderApi = system.InitOrderSystem + 1\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderApi, &initApi{})\n}\n\nfunc (i *initApi) InitializerName() string {\n\treturn sysModel.SysApi{}.TableName()\n}\n\nfunc (i *initApi) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysApi{})\n}\n\nfunc (i *initApi) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysApi{})\n}\n\nfunc (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tentities := []sysModel.SysApi{\n\t\t{ApiGroup: \"jwt\", Method: \"POST\", Path: \"/jwt/jsonInBlacklist\", Description: \"jwt加入黑名单(退出，必选)\"},\n\n\t\t{ApiGroup: \"登录日志\", Method: \"DELETE\", Path: \"/sysLoginLog/deleteLoginLog\", Description: \"删除登录日志\"},\n\t\t{ApiGroup: \"登录日志\", Method: \"DELETE\", Path: \"/sysLoginLog/deleteLoginLogByIds\", Description: \"批量删除登录日志\"},\n\t\t{ApiGroup: \"登录日志\", Method: \"GET\", Path: \"/sysLoginLog/findLoginLog\", Description: \"根据ID获取登录日志\"},\n\t\t{ApiGroup: \"登录日志\", Method: \"GET\", Path: \"/sysLoginLog/getLoginLogList\", Description: \"获取登录日志列表\"},\n\n\t\t{ApiGroup: \"API Token\", Method: \"POST\", Path: \"/sysApiToken/createApiToken\", Description: \"签发API Token\"},\n\t\t{ApiGroup: \"API Token\", Method: \"POST\", Path: \"/sysApiToken/getApiTokenList\", Description: \"获取API Token列表\"},\n\t\t{ApiGroup: \"API Token\", Method: \"POST\", Path: \"/sysApiToken/deleteApiToken\", Description: \"作废API Token\"},\n\n\t\t{ApiGroup: \"系统用户\", Method: \"DELETE\", Path: \"/user/deleteUser\", Description: \"删除用户\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"POST\", Path: \"/user/admin_register\", Description: \"用户注册\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"POST\", Path: \"/user/getUserList\", Description: \"获取用户列表\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"PUT\", Path: \"/user/setUserInfo\", Description: \"设置用户信息\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"PUT\", Path: \"/user/setSelfInfo\", Description: \"设置自身信息(必选)\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"GET\", Path: \"/user/getUserInfo\", Description: \"获取自身信息(必选)\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"POST\", Path: \"/user/setUserAuthorities\", Description: \"设置权限组\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"POST\", Path: \"/user/changePassword\", Description: \"修改密码（建议选择)\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"POST\", Path: \"/user/setUserAuthority\", Description: \"修改用户角色(必选)\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"POST\", Path: \"/user/resetPassword\", Description: \"重置用户密码\"},\n\t\t{ApiGroup: \"系统用户\", Method: \"PUT\", Path: \"/user/setSelfSetting\", Description: \"用户界面配置\"},\n\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/createApi\", Description: \"创建api\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/deleteApi\", Description: \"删除Api\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/updateApi\", Description: \"更新Api\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/getApiList\", Description: \"获取api列表\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/getAllApis\", Description: \"获取所有api\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/getApiById\", Description: \"获取api详细信息\"},\n\t\t{ApiGroup: \"api\", Method: \"DELETE\", Path: \"/api/deleteApisByIds\", Description: \"批量删除api\"},\n\t\t{ApiGroup: \"api\", Method: \"GET\", Path: \"/api/syncApi\", Description: \"获取待同步API\"},\n\t\t{ApiGroup: \"api\", Method: \"GET\", Path: \"/api/getApiGroups\", Description: \"获取路由组\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/enterSyncApi\", Description: \"确认同步API\"},\n\t\t{ApiGroup: \"api\", Method: \"POST\", Path: \"/api/ignoreApi\", Description: \"忽略API\"},\n\n\t\t{ApiGroup: \"角色\", Method: \"POST\", Path: \"/authority/copyAuthority\", Description: \"拷贝角色\"},\n\t\t{ApiGroup: \"角色\", Method: \"POST\", Path: \"/authority/createAuthority\", Description: \"创建角色\"},\n\t\t{ApiGroup: \"角色\", Method: \"POST\", Path: \"/authority/deleteAuthority\", Description: \"删除角色\"},\n\t\t{ApiGroup: \"角色\", Method: \"PUT\", Path: \"/authority/updateAuthority\", Description: \"更新角色信息\"},\n\t\t{ApiGroup: \"角色\", Method: \"POST\", Path: \"/authority/getAuthorityList\", Description: \"获取角色列表\"},\n\t\t{ApiGroup: \"角色\", Method: \"POST\", Path: \"/authority/setDataAuthority\", Description: \"设置角色资源权限\"},\n\t\t{ApiGroup: \"角色\", Method: \"GET\", Path: \"/authority/getUsersByAuthority\", Description: \"获取角色关联用户ID列表\"},\n\t\t{ApiGroup: \"角色\", Method: \"POST\", Path: \"/authority/setRoleUsers\", Description: \"全量覆盖角色关联用户\"},\n\n\t\t{ApiGroup: \"casbin\", Method: \"POST\", Path: \"/casbin/updateCasbin\", Description: \"更改角色api权限\"},\n\t\t{ApiGroup: \"casbin\", Method: \"POST\", Path: \"/casbin/getPolicyPathByAuthorityId\", Description: \"获取权限列表\"},\n\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/addBaseMenu\", Description: \"新增菜单\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/getMenu\", Description: \"获取菜单树(必选)\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/deleteBaseMenu\", Description: \"删除菜单\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/updateBaseMenu\", Description: \"更新菜单\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/getBaseMenuById\", Description: \"根据id获取菜单\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/getMenuList\", Description: \"分页获取基础menu列表\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/getBaseMenuTree\", Description: \"获取用户动态路由\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/getMenuAuthority\", Description: \"获取指定角色menu\"},\n\t\t{ApiGroup: \"菜单\", Method: \"POST\", Path: \"/menu/addMenuAuthority\", Description: \"增加menu和角色关联关系\"},\n\n\t\t{ApiGroup: \"分片上传\", Method: \"GET\", Path: \"/fileUploadAndDownload/findFile\", Description: \"寻找目标文件（秒传）\"},\n\t\t{ApiGroup: \"分片上传\", Method: \"POST\", Path: \"/fileUploadAndDownload/breakpointContinue\", Description: \"断点续传\"},\n\t\t{ApiGroup: \"分片上传\", Method: \"POST\", Path: \"/fileUploadAndDownload/breakpointContinueFinish\", Description: \"断点续传完成\"},\n\t\t{ApiGroup: \"分片上传\", Method: \"POST\", Path: \"/fileUploadAndDownload/removeChunk\", Description: \"上传完成移除文件\"},\n\n\t\t{ApiGroup: \"文件上传与下载\", Method: \"POST\", Path: \"/fileUploadAndDownload/upload\", Description: \"文件上传（建议选择）\"},\n\t\t{ApiGroup: \"文件上传与下载\", Method: \"POST\", Path: \"/fileUploadAndDownload/deleteFile\", Description: \"删除文件\"},\n\t\t{ApiGroup: \"文件上传与下载\", Method: \"POST\", Path: \"/fileUploadAndDownload/editFileName\", Description: \"文件名或者备注编辑\"},\n\t\t{ApiGroup: \"文件上传与下载\", Method: \"POST\", Path: \"/fileUploadAndDownload/getFileList\", Description: \"获取上传文件列表\"},\n\t\t{ApiGroup: \"文件上传与下载\", Method: \"POST\", Path: \"/fileUploadAndDownload/importURL\", Description: \"导入URL\"},\n\n\t\t{ApiGroup: \"系统服务\", Method: \"POST\", Path: \"/system/getServerInfo\", Description: \"获取服务器信息\"},\n\t\t{ApiGroup: \"系统服务\", Method: \"POST\", Path: \"/system/getSystemConfig\", Description: \"获取配置文件内容\"},\n\t\t{ApiGroup: \"系统服务\", Method: \"POST\", Path: \"/system/setSystemConfig\", Description: \"设置配置文件内容\"},\n\n\t\t{ApiGroup: \"skills\", Method: \"GET\", Path: \"/skills/getTools\", Description: \"获取技能工具列表\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getSkillList\", Description: \"获取技能列表\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getSkillDetail\", Description: \"获取技能详情\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/saveSkill\", Description: \"保存技能定义\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/deleteSkill\", Description: \"删除技能\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/createScript\", Description: \"创建技能脚本\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getScript\", Description: \"读取技能脚本\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/saveScript\", Description: \"保存技能脚本\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/createResource\", Description: \"创建技能资源\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getResource\", Description: \"读取技能资源\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/saveResource\", Description: \"保存技能资源\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/createReference\", Description: \"创建技能参考\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getReference\", Description: \"读取技能参考\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/saveReference\", Description: \"保存技能参考\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/createTemplate\", Description: \"创建技能模板\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getTemplate\", Description: \"读取技能模板\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/saveTemplate\", Description: \"保存技能模板\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/getGlobalConstraint\", Description: \"读取全局约束\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/saveGlobalConstraint\", Description: \"保存全局约束\"},\n\t\t{ApiGroup: \"skills\", Method: \"POST\", Path: \"/skills/packageSkill\", Description: \"打包技能\"},\n\n\t\t{ApiGroup: \"客户\", Method: \"PUT\", Path: \"/customer/customer\", Description: \"更新客户\"},\n\t\t{ApiGroup: \"客户\", Method: \"POST\", Path: \"/customer/customer\", Description: \"创建客户\"},\n\t\t{ApiGroup: \"客户\", Method: \"DELETE\", Path: \"/customer/customer\", Description: \"删除客户\"},\n\t\t{ApiGroup: \"客户\", Method: \"GET\", Path: \"/customer/customer\", Description: \"获取单一客户\"},\n\t\t{ApiGroup: \"客户\", Method: \"GET\", Path: \"/customer/customerList\", Description: \"获取客户列表\"},\n\n\t\t{ApiGroup: \"代码生成器\", Method: \"GET\", Path: \"/autoCode/getDB\", Description: \"获取所有数据库\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"GET\", Path: \"/autoCode/getTables\", Description: \"获取数据库表\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/createTemp\", Description: \"自动化代码\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/preview\", Description: \"预览自动化代码\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"GET\", Path: \"/autoCode/getColumn\", Description: \"获取所选table的所有字段\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/installPlugin\", Description: \"安装插件\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/pubPlug\", Description: \"打包插件\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/removePlugin\", Description: \"卸载插件\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"GET\", Path: \"/autoCode/getPluginList\", Description: \"获取已安装插件\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/mcp\", Description: \"自动生成 MCP Tool 模板\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/mcpTest\", Description: \"MCP Tool 测试\"},\n\t\t{ApiGroup: \"代码生成器\", Method: \"POST\", Path: \"/autoCode/mcpList\", Description: \"获取 MCP ToolList\"},\n\n\t\t{ApiGroup: \"模板配置\", Method: \"POST\", Path: \"/autoCode/createPackage\", Description: \"配置模板\"},\n\t\t{ApiGroup: \"模板配置\", Method: \"GET\", Path: \"/autoCode/getTemplates\", Description: \"获取模板文件\"},\n\t\t{ApiGroup: \"模板配置\", Method: \"POST\", Path: \"/autoCode/getPackage\", Description: \"获取所有模板\"},\n\t\t{ApiGroup: \"模板配置\", Method: \"POST\", Path: \"/autoCode/delPackage\", Description: \"删除模板\"},\n\n\t\t{ApiGroup: \"代码生成器历史\", Method: \"POST\", Path: \"/autoCode/getMeta\", Description: \"获取meta信息\"},\n\t\t{ApiGroup: \"代码生成器历史\", Method: \"POST\", Path: \"/autoCode/rollback\", Description: \"回滚自动生成代码\"},\n\t\t{ApiGroup: \"代码生成器历史\", Method: \"POST\", Path: \"/autoCode/getSysHistory\", Description: \"查询回滚记录\"},\n\t\t{ApiGroup: \"代码生成器历史\", Method: \"POST\", Path: \"/autoCode/delSysHistory\", Description: \"删除回滚记录\"},\n\t\t{ApiGroup: \"代码生成器历史\", Method: \"POST\", Path: \"/autoCode/addFunc\", Description: \"增加模板方法\"},\n\n\t\t{ApiGroup: \"系统字典详情\", Method: \"PUT\", Path: \"/sysDictionaryDetail/updateSysDictionaryDetail\", Description: \"更新字典内容\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"POST\", Path: \"/sysDictionaryDetail/createSysDictionaryDetail\", Description: \"新增字典内容\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"DELETE\", Path: \"/sysDictionaryDetail/deleteSysDictionaryDetail\", Description: \"删除字典内容\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"GET\", Path: \"/sysDictionaryDetail/findSysDictionaryDetail\", Description: \"根据ID获取字典内容\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"GET\", Path: \"/sysDictionaryDetail/getSysDictionaryDetailList\", Description: \"获取字典内容列表\"},\n\n\t\t{ApiGroup: \"系统字典详情\", Method: \"GET\", Path: \"/sysDictionaryDetail/getDictionaryTreeList\", Description: \"获取字典数列表\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"GET\", Path: \"/sysDictionaryDetail/getDictionaryTreeListByType\", Description: \"根据分类获取字典数列表\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"GET\", Path: \"/sysDictionaryDetail/getDictionaryDetailsByParent\", Description: \"根据父级ID获取字典详情\"},\n\t\t{ApiGroup: \"系统字典详情\", Method: \"GET\", Path: \"/sysDictionaryDetail/getDictionaryPath\", Description: \"获取字典详情的完整路径\"},\n\n\t\t{ApiGroup: \"系统字典\", Method: \"POST\", Path: \"/sysDictionary/createSysDictionary\", Description: \"新增字典\"},\n\t\t{ApiGroup: \"系统字典\", Method: \"DELETE\", Path: \"/sysDictionary/deleteSysDictionary\", Description: \"删除字典\"},\n\t\t{ApiGroup: \"系统字典\", Method: \"PUT\", Path: \"/sysDictionary/updateSysDictionary\", Description: \"更新字典\"},\n\t\t{ApiGroup: \"系统字典\", Method: \"GET\", Path: \"/sysDictionary/findSysDictionary\", Description: \"根据ID获取字典（建议选择）\"},\n\t\t{ApiGroup: \"系统字典\", Method: \"GET\", Path: \"/sysDictionary/getSysDictionaryList\", Description: \"获取字典列表\"},\n\t\t{ApiGroup: \"系统字典\", Method: \"POST\", Path: \"/sysDictionary/importSysDictionary\", Description: \"导入字典JSON\"},\n\t\t{ApiGroup: \"系统字典\", Method: \"GET\", Path: \"/sysDictionary/exportSysDictionary\", Description: \"导出字典JSON\"},\n\n\t\t{ApiGroup: \"操作记录\", Method: \"POST\", Path: \"/sysOperationRecord/createSysOperationRecord\", Description: \"新增操作记录\"},\n\t\t{ApiGroup: \"操作记录\", Method: \"GET\", Path: \"/sysOperationRecord/findSysOperationRecord\", Description: \"根据ID获取操作记录\"},\n\t\t{ApiGroup: \"操作记录\", Method: \"GET\", Path: \"/sysOperationRecord/getSysOperationRecordList\", Description: \"获取操作记录列表\"},\n\t\t{ApiGroup: \"操作记录\", Method: \"DELETE\", Path: \"/sysOperationRecord/deleteSysOperationRecord\", Description: \"删除操作记录\"},\n\t\t{ApiGroup: \"操作记录\", Method: \"DELETE\", Path: \"/sysOperationRecord/deleteSysOperationRecordByIds\", Description: \"批量删除操作历史\"},\n\n\t\t{ApiGroup: \"断点续传(插件版)\", Method: \"POST\", Path: \"/simpleUploader/upload\", Description: \"插件版分片上传\"},\n\t\t{ApiGroup: \"断点续传(插件版)\", Method: \"GET\", Path: \"/simpleUploader/checkFileMd5\", Description: \"文件完整度验证\"},\n\t\t{ApiGroup: \"断点续传(插件版)\", Method: \"GET\", Path: \"/simpleUploader/mergeFileMd5\", Description: \"上传完成合并文件\"},\n\n\t\t{ApiGroup: \"email\", Method: \"POST\", Path: \"/email/emailTest\", Description: \"发送测试邮件\"},\n\t\t{ApiGroup: \"email\", Method: \"POST\", Path: \"/email/sendEmail\", Description: \"发送邮件\"},\n\n\t\t{ApiGroup: \"按钮权限\", Method: \"POST\", Path: \"/authorityBtn/setAuthorityBtn\", Description: \"设置按钮权限\"},\n\t\t{ApiGroup: \"按钮权限\", Method: \"POST\", Path: \"/authorityBtn/getAuthorityBtn\", Description: \"获取已有按钮权限\"},\n\t\t{ApiGroup: \"按钮权限\", Method: \"POST\", Path: \"/authorityBtn/canRemoveAuthorityBtn\", Description: \"删除按钮\"},\n\n\t\t{ApiGroup: \"导出模板\", Method: \"POST\", Path: \"/sysExportTemplate/createSysExportTemplate\", Description: \"新增导出模板\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"DELETE\", Path: \"/sysExportTemplate/deleteSysExportTemplate\", Description: \"删除导出模板\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"DELETE\", Path: \"/sysExportTemplate/deleteSysExportTemplateByIds\", Description: \"批量删除导出模板\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"PUT\", Path: \"/sysExportTemplate/updateSysExportTemplate\", Description: \"更新导出模板\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"GET\", Path: \"/sysExportTemplate/findSysExportTemplate\", Description: \"根据ID获取导出模板\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"GET\", Path: \"/sysExportTemplate/getSysExportTemplateList\", Description: \"获取导出模板列表\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"GET\", Path: \"/sysExportTemplate/exportExcel\", Description: \"导出Excel\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"GET\", Path: \"/sysExportTemplate/exportTemplate\", Description: \"下载模板\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"GET\", Path: \"/sysExportTemplate/previewSQL\", Description: \"预览SQL\"},\n\t\t{ApiGroup: \"导出模板\", Method: \"POST\", Path: \"/sysExportTemplate/importExcel\", Description: \"导入Excel\"},\n\n\t\t{ApiGroup: \"错误日志\", Method: \"POST\", Path: \"/sysError/createSysError\", Description: \"新建错误日志\"},\n\t\t{ApiGroup: \"错误日志\", Method: \"DELETE\", Path: \"/sysError/deleteSysError\", Description: \"删除错误日志\"},\n\t\t{ApiGroup: \"错误日志\", Method: \"DELETE\", Path: \"/sysError/deleteSysErrorByIds\", Description: \"批量删除错误日志\"},\n\t\t{ApiGroup: \"错误日志\", Method: \"PUT\", Path: \"/sysError/updateSysError\", Description: \"更新错误日志\"},\n\t\t{ApiGroup: \"错误日志\", Method: \"GET\", Path: \"/sysError/findSysError\", Description: \"根据ID获取错误日志\"},\n\t\t{ApiGroup: \"错误日志\", Method: \"GET\", Path: \"/sysError/getSysErrorList\", Description: \"获取错误日志列表\"},\n\t\t{ApiGroup: \"错误日志\", Method: \"GET\", Path: \"/sysError/getSysErrorSolution\", Description: \"触发错误处理(异步)\"},\n\n\t\t{ApiGroup: \"公告\", Method: \"POST\", Path: \"/info/createInfo\", Description: \"新建公告\"},\n\t\t{ApiGroup: \"公告\", Method: \"DELETE\", Path: \"/info/deleteInfo\", Description: \"删除公告\"},\n\t\t{ApiGroup: \"公告\", Method: \"DELETE\", Path: \"/info/deleteInfoByIds\", Description: \"批量删除公告\"},\n\t\t{ApiGroup: \"公告\", Method: \"PUT\", Path: \"/info/updateInfo\", Description: \"更新公告\"},\n\t\t{ApiGroup: \"公告\", Method: \"GET\", Path: \"/info/findInfo\", Description: \"根据ID获取公告\"},\n\t\t{ApiGroup: \"公告\", Method: \"GET\", Path: \"/info/getInfoList\", Description: \"获取公告列表\"},\n\n\t\t{ApiGroup: \"参数管理\", Method: \"POST\", Path: \"/sysParams/createSysParams\", Description: \"新建参数\"},\n\t\t{ApiGroup: \"参数管理\", Method: \"DELETE\", Path: \"/sysParams/deleteSysParams\", Description: \"删除参数\"},\n\t\t{ApiGroup: \"参数管理\", Method: \"DELETE\", Path: \"/sysParams/deleteSysParamsByIds\", Description: \"批量删除参数\"},\n\t\t{ApiGroup: \"参数管理\", Method: \"PUT\", Path: \"/sysParams/updateSysParams\", Description: \"更新参数\"},\n\t\t{ApiGroup: \"参数管理\", Method: \"GET\", Path: \"/sysParams/findSysParams\", Description: \"根据ID获取参数\"},\n\t\t{ApiGroup: \"参数管理\", Method: \"GET\", Path: \"/sysParams/getSysParamsList\", Description: \"获取参数列表\"},\n\t\t{ApiGroup: \"参数管理\", Method: \"GET\", Path: \"/sysParams/getSysParam\", Description: \"获取参数列表\"},\n\t\t{ApiGroup: \"媒体库分类\", Method: \"GET\", Path: \"/attachmentCategory/getCategoryList\", Description: \"分类列表\"},\n\t\t{ApiGroup: \"媒体库分类\", Method: \"POST\", Path: \"/attachmentCategory/addCategory\", Description: \"添加/编辑分类\"},\n\t\t{ApiGroup: \"媒体库分类\", Method: \"POST\", Path: \"/attachmentCategory/deleteCategory\", Description: \"删除分类\"},\n\n\t\t{ApiGroup: \"版本控制\", Method: \"GET\", Path: \"/sysVersion/findSysVersion\", Description: \"获取单一版本\"},\n\t\t{ApiGroup: \"版本控制\", Method: \"GET\", Path: \"/sysVersion/getSysVersionList\", Description: \"获取版本列表\"},\n\t\t{ApiGroup: \"版本控制\", Method: \"GET\", Path: \"/sysVersion/downloadVersionJson\", Description: \"下载版本json\"},\n\t\t{ApiGroup: \"版本控制\", Method: \"POST\", Path: \"/sysVersion/exportVersion\", Description: \"创建版本\"},\n\t\t{ApiGroup: \"版本控制\", Method: \"POST\", Path: \"/sysVersion/importVersion\", Description: \"同步版本\"},\n\t\t{ApiGroup: \"版本控制\", Method: \"DELETE\", Path: \"/sysVersion/deleteSysVersion\", Description: \"删除版本\"},\n\t\t{ApiGroup: \"版本控制\", Method: \"DELETE\", Path: \"/sysVersion/deleteSysVersionByIds\", Description: \"批量删除版本\"},\n\t}\n\tif err := db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, sysModel.SysApi{}.TableName()+\"表数据初始化失败!\")\n\t}\n\tnext := context.WithValue(ctx, i.InitializerName(), entities)\n\treturn next, nil\n}\n\nfunc (i *initApi) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.Where(\"path = ? AND method = ?\", \"/authorityBtn/canRemoveAuthorityBtn\", \"POST\").\n\t\tFirst(&sysModel.SysApi{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/api_ignore.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\ntype initApiIgnore struct{}\n\nconst initOrderApiIgnore = initOrderApi + 1\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderApiIgnore, &initApiIgnore{})\n}\n\nfunc (i *initApiIgnore) InitializerName() string {\n\treturn sysModel.SysIgnoreApi{}.TableName()\n}\n\nfunc (i *initApiIgnore) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysIgnoreApi{})\n}\n\nfunc (i *initApiIgnore) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysIgnoreApi{})\n}\n\nfunc (i *initApiIgnore) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tentities := []sysModel.SysIgnoreApi{\n\t\t{Method: \"GET\", Path: \"/swagger/*any\"},\n\t\t{Method: \"GET\", Path: \"/api/freshCasbin\"},\n\t\t{Method: \"GET\", Path: \"/uploads/file/*filepath\"},\n\t\t{Method: \"GET\", Path: \"/health\"},\n\t\t{Method: \"HEAD\", Path: \"/uploads/file/*filepath\"},\n\t\t{Method: \"POST\", Path: \"/autoCode/llmAuto\"},\n\t\t{Method: \"POST\", Path: \"/system/reloadSystem\"},\n\t\t{Method: \"POST\", Path: \"/base/login\"},\n\t\t{Method: \"POST\", Path: \"/base/captcha\"},\n\t\t{Method: \"POST\", Path: \"/init/initdb\"},\n\t\t{Method: \"POST\", Path: \"/init/checkdb\"},\n\t\t{Method: \"GET\", Path: \"/info/getInfoDataSource\"},\n\t\t{Method: \"GET\", Path: \"/info/getInfoPublic\"},\n\t}\n\tif err := db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, sysModel.SysIgnoreApi{}.TableName()+\"表数据初始化失败!\")\n\t}\n\tnext := context.WithValue(ctx, i.InitializerName(), entities)\n\treturn next, nil\n}\n\nfunc (i *initApiIgnore) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.Where(\"path = ? AND method = ?\", \"/swagger/*any\", \"GET\").\n\t\tFirst(&sysModel.SysIgnoreApi{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/authorities_menus.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderMenuAuthority = initOrderMenu + initOrderAuthority\n\ntype initMenuAuthority struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderMenuAuthority, &initMenuAuthority{})\n}\n\nfunc (i *initMenuAuthority) MigrateTable(ctx context.Context) (context.Context, error) {\n\treturn ctx, nil // do nothing\n}\n\nfunc (i *initMenuAuthority) TableCreated(ctx context.Context) bool {\n\treturn false // always replace\n}\n\nfunc (i *initMenuAuthority) InitializerName() string {\n\treturn \"sys_menu_authorities\"\n}\n\nfunc (i *initMenuAuthority) InitializeData(ctx context.Context) (next context.Context, err error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\n\tinitAuth := &initAuthority{}\n\tauthorities, ok := ctx.Value(initAuth.InitializerName()).([]sysModel.SysAuthority)\n\tif !ok {\n\t\treturn ctx, errors.Wrap(system.ErrMissingDependentContext, \"创建 [菜单-权限] 关联失败, 未找到权限表初始化数据\")\n\t}\n\n\tallMenus, ok := ctx.Value(new(initMenu).InitializerName()).([]sysModel.SysBaseMenu)\n\tif !ok {\n\t\treturn next, errors.Wrap(errors.New(\"\"), \"创建 [菜单-权限] 关联失败, 未找到菜单表初始化数据\")\n\t}\n\tnext = ctx\n\n\t// 构建菜单ID映射，方便快速查找\n\tmenuMap := make(map[uint]sysModel.SysBaseMenu)\n\tfor _, menu := range allMenus {\n\t\tmenuMap[menu.ID] = menu\n\t}\n\n\t// 为不同角色分配不同权限\n\t// 1. 超级管理员角色(888) - 拥有所有菜单权限\n\tif err = db.Model(&authorities[0]).Association(\"SysBaseMenus\").Replace(allMenus); err != nil {\n\t\treturn next, errors.Wrap(err, \"为超级管理员分配菜单失败\")\n\t}\n\n\t// 2. 普通用户角色(8881) - 仅拥有基础功能菜单\n\t// 仅选择部分父级菜单及其子菜单\n\tvar menu8881 []sysModel.SysBaseMenu\n\n\t// 添加仪表盘、关于我们和个人信息菜单\n\tfor _, menu := range allMenus {\n\t\tif menu.ParentId == 0 && (menu.Name == \"dashboard\" || menu.Name == \"about\" || menu.Name == \"person\" || menu.Name == \"state\") {\n\t\t\tmenu8881 = append(menu8881, menu)\n\t\t}\n\t}\n\n\tif err = db.Model(&authorities[1]).Association(\"SysBaseMenus\").Replace(menu8881); err != nil {\n\t\treturn next, errors.Wrap(err, \"为普通用户分配菜单失败\")\n\t}\n\n\t// 3. 测试角色(9528) - 拥有部分菜单权限\n\tvar menu9528 []sysModel.SysBaseMenu\n\n\t// 添加所有父级菜单\n\tfor _, menu := range allMenus {\n\t\tif menu.ParentId == 0 {\n\t\t\tmenu9528 = append(menu9528, menu)\n\t\t}\n\t}\n\n\t// 添加部分子菜单 - 系统工具、示例文件等模块的子菜单\n\tfor _, menu := range allMenus {\n\t\tparentName := \"\"\n\t\tif menu.ParentId > 0 && menuMap[menu.ParentId].Name != \"\" {\n\t\t\tparentName = menuMap[menu.ParentId].Name\n\t\t}\n\n\t\tif menu.ParentId > 0 && (parentName == \"systemTools\" || parentName == \"example\") {\n\t\t\tmenu9528 = append(menu9528, menu)\n\t\t}\n\t}\n\n\tif err = db.Model(&authorities[2]).Association(\"SysBaseMenus\").Replace(menu9528); err != nil {\n\t\treturn next, errors.Wrap(err, \"为测试角色分配菜单失败\")\n\t}\n\n\treturn next, nil\n}\n\nfunc (i *initMenuAuthority) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tauth := &sysModel.SysAuthority{}\n\tif ret := db.Model(auth).\n\t\tWhere(\"authority_id = ?\", 9528).Preload(\"SysBaseMenus\").Find(auth); ret != nil {\n\t\tif ret.Error != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn len(auth.SysBaseMenus) > 0\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "server/source/system/authority.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderAuthority = initOrderCasbin + 1\n\ntype initAuthority struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderAuthority, &initAuthority{})\n}\n\nfunc (i *initAuthority) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysAuthority{})\n}\n\nfunc (i *initAuthority) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysAuthority{})\n}\n\nfunc (i *initAuthority) InitializerName() string {\n\treturn sysModel.SysAuthority{}.TableName()\n}\n\nfunc (i *initAuthority) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tentities := []sysModel.SysAuthority{\n\t\t{AuthorityId: 888, AuthorityName: \"普通用户\", ParentId: utils.Pointer[uint](0), DefaultRouter: \"dashboard\"},\n\t\t{AuthorityId: 9528, AuthorityName: \"测试角色\", ParentId: utils.Pointer[uint](0), DefaultRouter: \"dashboard\"},\n\t\t{AuthorityId: 8881, AuthorityName: \"普通用户子角色\", ParentId: utils.Pointer[uint](888), DefaultRouter: \"dashboard\"},\n\t}\n\n\tif err := db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrapf(err, \"%s表数据初始化失败!\", sysModel.SysAuthority{}.TableName())\n\t}\n\t// data authority\n\tif err := db.Model(&entities[0]).Association(\"DataAuthorityId\").Replace(\n\t\t[]*sysModel.SysAuthority{\n\t\t\t{AuthorityId: 888},\n\t\t\t{AuthorityId: 9528},\n\t\t\t{AuthorityId: 8881},\n\t\t}); err != nil {\n\t\treturn ctx, errors.Wrapf(err, \"%s表数据初始化失败!\",\n\t\t\tdb.Model(&entities[0]).Association(\"DataAuthorityId\").Relationship.JoinTable.Name)\n\t}\n\tif err := db.Model(&entities[1]).Association(\"DataAuthorityId\").Replace(\n\t\t[]*sysModel.SysAuthority{\n\t\t\t{AuthorityId: 9528},\n\t\t\t{AuthorityId: 8881},\n\t\t}); err != nil {\n\t\treturn ctx, errors.Wrapf(err, \"%s表数据初始化失败!\",\n\t\t\tdb.Model(&entities[1]).Association(\"DataAuthorityId\").Relationship.JoinTable.Name)\n\t}\n\n\tnext := context.WithValue(ctx, i.InitializerName(), entities)\n\treturn next, nil\n}\n\nfunc (i *initAuthority) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.Where(\"authority_id = ?\", \"8881\").\n\t\tFirst(&sysModel.SysAuthority{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/casbin.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\n\tadapter \"github.com/casbin/gorm-adapter/v3\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderCasbin = initOrderApiIgnore + 1\n\ntype initCasbin struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderCasbin, &initCasbin{})\n}\n\nfunc (i *initCasbin) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&adapter.CasbinRule{})\n}\n\nfunc (i *initCasbin) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&adapter.CasbinRule{})\n}\n\nfunc (i *initCasbin) InitializerName() string {\n\tvar entity adapter.CasbinRule\n\treturn entity.TableName()\n}\n\nfunc (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tentities := []adapter.CasbinRule{\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/admin_register\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysLoginLog/deleteLoginLog\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysLoginLog/deleteLoginLogByIds\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysLoginLog/findLoginLog\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysLoginLog/getLoginLogList\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysApiToken/createApiToken\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysApiToken/getApiTokenList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysApiToken/deleteApiToken\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/createApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/getApiList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/getApiById\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/deleteApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/updateApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/getAllApis\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/deleteApisByIds\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/syncApi\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/getApiGroups\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/enterSyncApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/api/ignoreApi\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/copyAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/updateAuthority\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/createAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/deleteAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/getAuthorityList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/setDataAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/getUsersByAuthority\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authority/setRoleUsers\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/getMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/getMenuList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/addBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/getBaseMenuTree\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/addMenuAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/getMenuAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/deleteBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/updateBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/menu/getBaseMenuById\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/getUserInfo\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/setUserInfo\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/setSelfInfo\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/getUserList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/deleteUser\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/changePassword\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/setUserAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/setUserAuthorities\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/resetPassword\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/user/setSelfSetting\", V2: \"PUT\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/findFile\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/breakpointContinueFinish\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/breakpointContinue\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/removeChunk\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/upload\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/deleteFile\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/editFileName\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/getFileList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/fileUploadAndDownload/importURL\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/casbin/updateCasbin\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/casbin/getPolicyPathByAuthorityId\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/jwt/jsonInBlacklist\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/system/getSystemConfig\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/system/setSystemConfig\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/system/getServerInfo\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getTools\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getSkillList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getSkillDetail\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/saveSkill\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/deleteSkill\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/createScript\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getScript\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/saveScript\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/createResource\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getResource\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/saveResource\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/createReference\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getReference\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/saveReference\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/createTemplate\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getTemplate\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/saveTemplate\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/getGlobalConstraint\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/saveGlobalConstraint\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/skills/packageSkill\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/customer/customer\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/customer/customer\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/customer/customer\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/customer/customer\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/customer/customerList\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getDB\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getMeta\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/preview\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getTables\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getColumn\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/rollback\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/createTemp\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/delSysHistory\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getSysHistory\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/createPackage\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getTemplates\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getPackage\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/delPackage\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/createPlug\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/installPlugin\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/pubPlug\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/removePlugin\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/getPluginList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/addFunc\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/mcp\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/mcpTest\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/autoCode/mcpList\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/findSysDictionaryDetail\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/updateSysDictionaryDetail\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/createSysDictionaryDetail\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/getSysDictionaryDetailList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/deleteSysDictionaryDetail\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/getDictionaryTreeList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/getDictionaryTreeListByType\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/getDictionaryDetailsByParent\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionaryDetail/getDictionaryPath\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/findSysDictionary\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/updateSysDictionary\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/getSysDictionaryList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/createSysDictionary\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/deleteSysDictionary\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/importSysDictionary\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysDictionary/exportSysDictionary\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysOperationRecord/findSysOperationRecord\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysOperationRecord/updateSysOperationRecord\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysOperationRecord/createSysOperationRecord\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysOperationRecord/getSysOperationRecordList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysOperationRecord/deleteSysOperationRecord\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysOperationRecord/deleteSysOperationRecordByIds\", V2: \"DELETE\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/email/emailTest\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/email/sendEmail\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/simpleUploader/upload\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/simpleUploader/checkFileMd5\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/simpleUploader/mergeFileMd5\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authorityBtn/setAuthorityBtn\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authorityBtn/getAuthorityBtn\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/authorityBtn/canRemoveAuthorityBtn\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/createSysExportTemplate\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/deleteSysExportTemplate\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/deleteSysExportTemplateByIds\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/updateSysExportTemplate\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/findSysExportTemplate\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/getSysExportTemplateList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/exportExcel\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/exportTemplate\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/previewSQL\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysExportTemplate/importExcel\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/createSysError\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/deleteSysError\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/deleteSysErrorByIds\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/updateSysError\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/findSysError\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/getSysErrorList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysError/getSysErrorSolution\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/info/createInfo\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/info/deleteInfo\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/info/deleteInfoByIds\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/info/updateInfo\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/info/findInfo\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/info/getInfoList\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/createSysParams\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/deleteSysParams\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/deleteSysParamsByIds\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/updateSysParams\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/findSysParams\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/getSysParamsList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysParams/getSysParam\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/attachmentCategory/getCategoryList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/attachmentCategory/addCategory\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/attachmentCategory/deleteCategory\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/findSysVersion\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/getSysVersionList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/downloadVersionJson\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/exportVersion\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/importVersion\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/deleteSysVersion\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"888\", V1: \"/sysVersion/deleteSysVersionByIds\", V2: \"DELETE\"},\n\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/user/admin_register\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/api/createApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/api/getApiList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/api/getApiById\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/api/deleteApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/api/updateApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/api/getAllApis\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/authority/createAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/authority/deleteAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/authority/getAuthorityList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/authority/setDataAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/authority/getUsersByAuthority\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/authority/setRoleUsers\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/getMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/getMenuList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/addBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/getBaseMenuTree\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/addMenuAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/getMenuAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/deleteBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/updateBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/menu/getBaseMenuById\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/user/changePassword\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/user/getUserList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/user/setUserAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/fileUploadAndDownload/upload\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/fileUploadAndDownload/getFileList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/fileUploadAndDownload/deleteFile\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/fileUploadAndDownload/editFileName\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/fileUploadAndDownload/importURL\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/casbin/updateCasbin\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/casbin/getPolicyPathByAuthorityId\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/jwt/jsonInBlacklist\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/system/getSystemConfig\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/system/setSystemConfig\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/customer/customer\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/customer/customer\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/customer/customer\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/customer/customer\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/customer/customerList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"8881\", V1: \"/user/getUserInfo\", V2: \"GET\"},\n\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/user/admin_register\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/api/createApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/api/getApiList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/api/getApiById\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/api/deleteApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/api/updateApi\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/api/getAllApis\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/authority/createAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/authority/deleteAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/authority/getAuthorityList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/authority/setDataAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/authority/getUsersByAuthority\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/authority/setRoleUsers\", V2: \"POST\"},\n\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/getMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/getMenuList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/addBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/getBaseMenuTree\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/addMenuAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/getMenuAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/deleteBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/updateBaseMenu\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/menu/getBaseMenuById\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/user/changePassword\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/user/getUserList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/user/setUserAuthority\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/fileUploadAndDownload/upload\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/fileUploadAndDownload/getFileList\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/fileUploadAndDownload/deleteFile\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/fileUploadAndDownload/editFileName\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/fileUploadAndDownload/importURL\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/casbin/updateCasbin\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/casbin/getPolicyPathByAuthorityId\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/jwt/jsonInBlacklist\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/system/getSystemConfig\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/system/setSystemConfig\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/customer/customer\", V2: \"PUT\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/customer/customer\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/customer/customer\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/customer/customer\", V2: \"DELETE\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/customer/customerList\", V2: \"GET\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/autoCode/createTemp\", V2: \"POST\"},\n\t\t{Ptype: \"p\", V0: \"9528\", V1: \"/user/getUserInfo\", V2: \"GET\"},\n\t}\n\tif err := db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, \"Casbin 表 (\"+i.InitializerName()+\") 数据初始化失败!\")\n\t}\n\tnext := context.WithValue(ctx, i.InitializerName(), entities)\n\treturn next, nil\n}\n\nfunc (i *initCasbin) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.Where(adapter.CasbinRule{Ptype: \"p\", V0: \"9528\", V1: \"/user/getUserInfo\", V2: \"GET\"}).\n\t\tFirst(&adapter.CasbinRule{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/dictionary.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderDict = initOrderCasbin + 1\n\ntype initDict struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderDict, &initDict{})\n}\n\nfunc (i *initDict) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysDictionary{})\n}\n\nfunc (i *initDict) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysDictionary{})\n}\n\nfunc (i *initDict) InitializerName() string {\n\treturn sysModel.SysDictionary{}.TableName()\n}\n\nfunc (i *initDict) InitializeData(ctx context.Context) (next context.Context, err error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tTrue := true\n\tentities := []sysModel.SysDictionary{\n\t\t{Name: \"性别\", Type: \"gender\", Status: &True, Desc: \"性别字典\"},\n\t\t{Name: \"数据库int类型\", Type: \"int\", Status: &True, Desc: \"int类型对应的数据库类型\"},\n\t\t{Name: \"数据库时间日期类型\", Type: \"time.Time\", Status: &True, Desc: \"数据库时间日期类型\"},\n\t\t{Name: \"数据库浮点型\", Type: \"float64\", Status: &True, Desc: \"数据库浮点型\"},\n\t\t{Name: \"数据库字符串\", Type: \"string\", Status: &True, Desc: \"数据库字符串\"},\n\t\t{Name: \"数据库bool类型\", Type: \"bool\", Status: &True, Desc: \"数据库bool类型\"},\n\t}\n\n\tif err = db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, sysModel.SysDictionary{}.TableName()+\"表数据初始化失败!\")\n\t}\n\tnext = context.WithValue(ctx, i.InitializerName(), entities)\n\treturn next, nil\n}\n\nfunc (i *initDict) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.Where(\"type = ?\", \"bool\").First(&sysModel.SysDictionary{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/dictionary_detail.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderDictDetail = initOrderDict + 1\n\ntype initDictDetail struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderDictDetail, &initDictDetail{})\n}\n\nfunc (i *initDictDetail) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysDictionaryDetail{})\n}\n\nfunc (i *initDictDetail) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysDictionaryDetail{})\n}\n\nfunc (i *initDictDetail) InitializerName() string {\n\treturn sysModel.SysDictionaryDetail{}.TableName()\n}\n\nfunc (i *initDictDetail) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\tdicts, ok := ctx.Value(new(initDict).InitializerName()).([]sysModel.SysDictionary)\n\tif !ok {\n\t\treturn ctx, errors.Wrap(system.ErrMissingDependentContext,\n\t\t\tfmt.Sprintf(\"未找到 %s 表初始化数据\", sysModel.SysDictionary{}.TableName()))\n\t}\n\tTrue := true\n\tdicts[0].SysDictionaryDetails = []sysModel.SysDictionaryDetail{\n\t\t{Label: \"男\", Value: \"1\", Status: &True, Sort: 1},\n\t\t{Label: \"女\", Value: \"2\", Status: &True, Sort: 2},\n\t}\n\n\tdicts[1].SysDictionaryDetails = []sysModel.SysDictionaryDetail{\n\t\t{Label: \"smallint\", Value: \"1\", Status: &True, Extend: \"mysql\", Sort: 1},\n\t\t{Label: \"mediumint\", Value: \"2\", Status: &True, Extend: \"mysql\", Sort: 2},\n\t\t{Label: \"int\", Value: \"3\", Status: &True, Extend: \"mysql\", Sort: 3},\n\t\t{Label: \"bigint\", Value: \"4\", Status: &True, Extend: \"mysql\", Sort: 4},\n\t\t{Label: \"int2\", Value: \"5\", Status: &True, Extend: \"pgsql\", Sort: 5},\n\t\t{Label: \"int4\", Value: \"6\", Status: &True, Extend: \"pgsql\", Sort: 6},\n\t\t{Label: \"int6\", Value: \"7\", Status: &True, Extend: \"pgsql\", Sort: 7},\n\t\t{Label: \"int8\", Value: \"8\", Status: &True, Extend: \"pgsql\", Sort: 8},\n\t}\n\n\tdicts[2].SysDictionaryDetails = []sysModel.SysDictionaryDetail{\n\t\t{Label: \"date\", Value: \"0\", Status: &True, Extend: \"mysql\", Sort: 0},\n\t\t{Label: \"time\", Value: \"1\", Status: &True, Extend: \"mysql\", Sort: 1},\n\t\t{Label: \"year\", Value: \"2\", Status: &True, Extend: \"mysql\", Sort: 2},\n\t\t{Label: \"datetime\", Value: \"3\", Status: &True, Extend: \"mysql\", Sort: 3},\n\t\t{Label: \"timestamp\", Value: \"5\", Status: &True, Extend: \"mysql\", Sort: 5},\n\t\t{Label: \"timestamptz\", Value: \"6\", Status: &True, Extend: \"pgsql\", Sort: 5},\n\t}\n\tdicts[3].SysDictionaryDetails = []sysModel.SysDictionaryDetail{\n\t\t{Label: \"float\", Value: \"0\", Status: &True, Extend: \"mysql\", Sort: 0},\n\t\t{Label: \"double\", Value: \"1\", Status: &True, Extend: \"mysql\", Sort: 1},\n\t\t{Label: \"decimal\", Value: \"2\", Status: &True, Extend: \"mysql\", Sort: 2},\n\t\t{Label: \"numeric\", Value: \"3\", Status: &True, Extend: \"pgsql\", Sort: 3},\n\t\t{Label: \"smallserial\", Value: \"4\", Status: &True, Extend: \"pgsql\", Sort: 4},\n\t}\n\n\tdicts[4].SysDictionaryDetails = []sysModel.SysDictionaryDetail{\n\t\t{Label: \"char\", Value: \"0\", Status: &True, Extend: \"mysql\", Sort: 0},\n\t\t{Label: \"varchar\", Value: \"1\", Status: &True, Extend: \"mysql\", Sort: 1},\n\t\t{Label: \"tinyblob\", Value: \"2\", Status: &True, Extend: \"mysql\", Sort: 2},\n\t\t{Label: \"tinytext\", Value: \"3\", Status: &True, Extend: \"mysql\", Sort: 3},\n\t\t{Label: \"text\", Value: \"4\", Status: &True, Extend: \"mysql\", Sort: 4},\n\t\t{Label: \"blob\", Value: \"5\", Status: &True, Extend: \"mysql\", Sort: 5},\n\t\t{Label: \"mediumblob\", Value: \"6\", Status: &True, Extend: \"mysql\", Sort: 6},\n\t\t{Label: \"mediumtext\", Value: \"7\", Status: &True, Extend: \"mysql\", Sort: 7},\n\t\t{Label: \"longblob\", Value: \"8\", Status: &True, Extend: \"mysql\", Sort: 8},\n\t\t{Label: \"longtext\", Value: \"9\", Status: &True, Extend: \"mysql\", Sort: 9},\n\t}\n\n\tdicts[5].SysDictionaryDetails = []sysModel.SysDictionaryDetail{\n\t\t{Label: \"tinyint\", Value: \"1\", Extend: \"mysql\", Status: &True},\n\t\t{Label: \"bool\", Value: \"2\", Extend: \"pgsql\", Status: &True},\n\t}\n\tfor _, dict := range dicts {\n\t\tif err := db.Model(&dict).Association(\"SysDictionaryDetails\").\n\t\t\tReplace(dict.SysDictionaryDetails); err != nil {\n\t\t\treturn ctx, errors.Wrap(err, sysModel.SysDictionaryDetail{}.TableName()+\"表数据初始化失败!\")\n\t\t}\n\t}\n\treturn ctx, nil\n}\n\nfunc (i *initDictDetail) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tvar dict sysModel.SysDictionary\n\tif err := db.Preload(\"SysDictionaryDetails\").\n\t\tFirst(&dict, &sysModel.SysDictionary{Name: \"数据库bool类型\"}).Error; err != nil {\n\t\treturn false\n\t}\n\treturn len(dict.SysDictionaryDetails) > 0 && dict.SysDictionaryDetails[0].Label == \"tinyint\"\n}\n"
  },
  {
    "path": "server/source/system/excel_template.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\ntype initExcelTemplate struct{}\n\nconst initOrderExcelTemplate = initOrderDictDetail + 1\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderExcelTemplate, &initExcelTemplate{})\n}\n\nfunc (i *initExcelTemplate) InitializerName() string {\n\treturn \"sys_export_templates\"\n}\n\nfunc (i *initExcelTemplate) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysExportTemplate{})\n}\n\nfunc (i *initExcelTemplate) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysExportTemplate{})\n}\n\nfunc (i *initExcelTemplate) InitializeData(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\n\tentities := []sysModel.SysExportTemplate{\n\t\t{\n\t\t\tName:       \"api\",\n\t\t\tTableName:  \"sys_apis\",\n\t\t\tTemplateID: \"api\",\n\t\t\tTemplateInfo: `{\n\"path\":\"路径\",\n\"method\":\"方法（大写）\",\n\"description\":\"方法介绍\",\n\"api_group\":\"方法分组\"\n}`,\n\t\t},\n\t}\n\tif err := db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, \"sys_export_templates\"+\"表数据初始化失败!\")\n\t}\n\tnext := context.WithValue(ctx, i.InitializerName(), entities)\n\treturn next, nil\n}\n\nfunc (i *initExcelTemplate) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.First(&sysModel.SysExportTemplate{}).Error, gorm.ErrRecordNotFound) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/menu.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\n\t. \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderMenu = initOrderAuthority + 1\n\ntype initMenu struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderMenu, &initMenu{})\n}\n\nfunc (i *initMenu) InitializerName() string {\n\treturn SysBaseMenu{}.TableName()\n}\n\nfunc (i *initMenu) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(\n\t\t&SysBaseMenu{},\n\t\t&SysBaseMenuParameter{},\n\t\t&SysBaseMenuBtn{},\n\t)\n}\n\nfunc (i *initMenu) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tm := db.Migrator()\n\treturn m.HasTable(&SysBaseMenu{}) &&\n\t\tm.HasTable(&SysBaseMenuParameter{}) &&\n\t\tm.HasTable(&SysBaseMenuBtn{})\n}\n\nfunc (i *initMenu) InitializeData(ctx context.Context) (next context.Context, err error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\n\t// 定义所有菜单\n\tallMenus := []SysBaseMenu{\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"dashboard\", Name: \"dashboard\", Component: \"view/dashboard/index.vue\", Sort: 1, Meta: Meta{Title: \"仪表盘\", Icon: \"odometer\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"about\", Name: \"about\", Component: \"view/about/index.vue\", Sort: 9, Meta: Meta{Title: \"关于我们\", Icon: \"info-filled\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"admin\", Name: \"superAdmin\", Component: \"view/superAdmin/index.vue\", Sort: 3, Meta: Meta{Title: \"超级管理员\", Icon: \"user\"}},\n\t\t{MenuLevel: 0, Hidden: true, ParentId: 0, Path: \"person\", Name: \"person\", Component: \"view/person/person.vue\", Sort: 4, Meta: Meta{Title: \"个人信息\", Icon: \"message\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"example\", Name: \"example\", Component: \"view/example/index.vue\", Sort: 7, Meta: Meta{Title: \"示例文件\", Icon: \"management\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"systemTools\", Name: \"systemTools\", Component: \"view/systemTools/index.vue\", Sort: 5, Meta: Meta{Title: \"系统工具\", Icon: \"tools\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"https://www.gin-vue-admin.com\", Name: \"https://www.gin-vue-admin.com\", Component: \"/\", Sort: 0, Meta: Meta{Title: \"官方网站\", Icon: \"customer-gva\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"state\", Name: \"state\", Component: \"view/system/state.vue\", Sort: 8, Meta: Meta{Title: \"服务器状态\", Icon: \"cloudy\"}},\n\t\t{MenuLevel: 0, Hidden: false, ParentId: 0, Path: \"plugin\", Name: \"plugin\", Component: \"view/routerHolder.vue\", Sort: 6, Meta: Meta{Title: \"插件系统\", Icon: \"cherry\"}},\n\t}\n\n\t// 先创建父级菜单（ParentId = 0 的菜单）\n\tif err = db.Create(&allMenus).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+\"父级菜单初始化失败!\")\n\t}\n\n\t// 建立菜单映射 - 通过Name查找已创建的菜单及其ID\n\tmenuNameMap := make(map[string]uint)\n\tfor _, menu := range allMenus {\n\t\tmenuNameMap[menu.Name] = menu.ID\n\t}\n\n\t// 定义子菜单，并设置正确的ParentId\n\tchildMenus := []SysBaseMenu{\n\t\t// superAdmin子菜单\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"authority\", Name: \"authority\", Component: \"view/superAdmin/authority/authority.vue\", Sort: 1, Meta: Meta{Title: \"角色管理\", Icon: \"avatar\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"menu\", Name: \"menu\", Component: \"view/superAdmin/menu/menu.vue\", Sort: 2, Meta: Meta{Title: \"菜单管理\", Icon: \"tickets\", KeepAlive: true}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"api\", Name: \"api\", Component: \"view/superAdmin/api/api.vue\", Sort: 3, Meta: Meta{Title: \"api管理\", Icon: \"platform\", KeepAlive: true}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"user\", Name: \"user\", Component: \"view/superAdmin/user/user.vue\", Sort: 4, Meta: Meta{Title: \"用户管理\", Icon: \"coordinate\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"dictionary\", Name: \"dictionary\", Component: \"view/superAdmin/dictionary/sysDictionary.vue\", Sort: 5, Meta: Meta{Title: \"字典管理\", Icon: \"notebook\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"operation\", Name: \"operation\", Component: \"view/superAdmin/operation/sysOperationRecord.vue\", Sort: 6, Meta: Meta{Title: \"操作历史\", Icon: \"pie-chart\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"superAdmin\"], Path: \"sysParams\", Name: \"sysParams\", Component: \"view/superAdmin/params/sysParams.vue\", Sort: 7, Meta: Meta{Title: \"参数管理\", Icon: \"compass\"}},\n\n\t\t// example子菜单\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"example\"], Path: \"upload\", Name: \"upload\", Component: \"view/example/upload/upload.vue\", Sort: 5, Meta: Meta{Title: \"媒体库（上传下载）\", Icon: \"upload\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"example\"], Path: \"breakpoint\", Name: \"breakpoint\", Component: \"view/example/breakpoint/breakpoint.vue\", Sort: 6, Meta: Meta{Title: \"断点续传\", Icon: \"upload-filled\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"example\"], Path: \"customer\", Name: \"customer\", Component: \"view/example/customer/customer.vue\", Sort: 7, Meta: Meta{Title: \"客户列表（资源示例）\", Icon: \"avatar\"}},\n\n\t\t// systemTools子菜单\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"autoCode\", Name: \"autoCode\", Component: \"view/systemTools/autoCode/index.vue\", Sort: 1, Meta: Meta{Title: \"代码生成器\", Icon: \"cpu\", KeepAlive: true}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"formCreate\", Name: \"formCreate\", Component: \"view/systemTools/formCreate/index.vue\", Sort: 3, Meta: Meta{Title: \"表单生成器\", Icon: \"magic-stick\", KeepAlive: true}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"system\", Name: \"system\", Component: \"view/systemTools/system/system.vue\", Sort: 4, Meta: Meta{Title: \"系统配置\", Icon: \"operation\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"autoCodeAdmin\", Name: \"autoCodeAdmin\", Component: \"view/systemTools/autoCodeAdmin/index.vue\", Sort: 2, Meta: Meta{Title: \"自动化代码管理\", Icon: \"magic-stick\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"loginLog\", Name: \"loginLog\", Component: \"view/systemTools/loginLog/index.vue\", Sort: 5, Meta: Meta{Title: \"登录日志\", Icon: \"monitor\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"apiToken\", Name: \"apiToken\", Component: \"view/systemTools/apiToken/index.vue\", Sort: 6, Meta: Meta{Title: \"API Token\", Icon: \"key\"}},\n\t\t{MenuLevel: 1, Hidden: true, ParentId: menuNameMap[\"systemTools\"], Path: \"autoCodeEdit/:id\", Name: \"autoCodeEdit\", Component: \"view/systemTools/autoCode/index.vue\", Sort: 0, Meta: Meta{Title: \"自动化代码-${id}\", Icon: \"magic-stick\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"autoPkg\", Name: \"autoPkg\", Component: \"view/systemTools/autoPkg/autoPkg.vue\", Sort: 0, Meta: Meta{Title: \"模板配置\", Icon: \"folder\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"exportTemplate\", Name: \"exportTemplate\", Component: \"view/systemTools/exportTemplate/exportTemplate.vue\", Sort: 5, Meta: Meta{Title: \"导出模板\", Icon: \"reading\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"skills\", Name: \"skills\", Component: \"view/systemTools/skills/index.vue\", Sort: 6, Meta: Meta{Title: \"Skills管理\", Icon: \"document\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"picture\", Name: \"picture\", Component: \"view/systemTools/autoCode/picture.vue\", Sort: 6, Meta: Meta{Title: \"AI页面绘制\", Icon: \"picture-filled\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"mcpTool\", Name: \"mcpTool\", Component: \"view/systemTools/autoCode/mcp.vue\", Sort: 7, Meta: Meta{Title: \"Mcp Tools模板\", Icon: \"magnet\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"mcpTest\", Name: \"mcpTest\", Component: \"view/systemTools/autoCode/mcpTest.vue\", Sort: 7, Meta: Meta{Title: \"Mcp Tools测试\", Icon: \"partly-cloudy\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"sysVersion\", Name: \"sysVersion\", Component: \"view/systemTools/version/version.vue\", Sort: 8, Meta: Meta{Title: \"版本管理\", Icon: \"server\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"systemTools\"], Path: \"sysError\", Name: \"sysError\", Component: \"view/systemTools/sysError/sysError.vue\", Sort: 9, Meta: Meta{Title: \"错误日志\", Icon: \"warn\"}},\n\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"plugin\"], Path: \"https://plugin.gin-vue-admin.com/\", Name: \"https://plugin.gin-vue-admin.com/\", Component: \"https://plugin.gin-vue-admin.com/\", Sort: 0, Meta: Meta{Title: \"插件市场\", Icon: \"shop\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"plugin\"], Path: \"installPlugin\", Name: \"installPlugin\", Component: \"view/systemTools/installPlugin/index.vue\", Sort: 1, Meta: Meta{Title: \"插件安装\", Icon: \"box\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"plugin\"], Path: \"pubPlug\", Name: \"pubPlug\", Component: \"view/systemTools/pubPlug/pubPlug.vue\", Sort: 3, Meta: Meta{Title: \"打包插件\", Icon: \"files\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"plugin\"], Path: \"plugin-email\", Name: \"plugin-email\", Component: \"plugin/email/view/index.vue\", Sort: 4, Meta: Meta{Title: \"邮件插件\", Icon: \"message\"}},\n\t\t{MenuLevel: 1, Hidden: false, ParentId: menuNameMap[\"plugin\"], Path: \"anInfo\", Name: \"anInfo\", Component: \"plugin/announcement/view/info.vue\", Sort: 5, Meta: Meta{Title: \"公告管理[示例]\", Icon: \"scaleToOriginal\"}},\n\t}\n\n\t// 创建子菜单\n\tif err = db.Create(&childMenus).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+\"子菜单初始化失败!\")\n\t}\n\n\t// 组合所有菜单作为返回结果\n\tallEntities := append(allMenus, childMenus...)\n\tnext = context.WithValue(ctx, i.InitializerName(), allEntities)\n\treturn next, nil\n}\n\nfunc (i *initMenu) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tif errors.Is(db.Where(\"path = ?\", \"autoPkg\").First(&SysBaseMenu{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "server/source/system/user.go",
    "content": "package system\n\nimport (\n\t\"context\"\n\tsysModel \"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/service/system\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/google/uuid\"\n\t\"github.com/pkg/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst initOrderUser = initOrderAuthority + 1\n\ntype initUser struct{}\n\n// auto run\nfunc init() {\n\tsystem.RegisterInit(initOrderUser, &initUser{})\n}\n\nfunc (i *initUser) MigrateTable(ctx context.Context) (context.Context, error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\treturn ctx, db.AutoMigrate(&sysModel.SysUser{})\n}\n\nfunc (i *initUser) TableCreated(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn db.Migrator().HasTable(&sysModel.SysUser{})\n}\n\nfunc (i *initUser) InitializerName() string {\n\treturn sysModel.SysUser{}.TableName()\n}\n\nfunc (i *initUser) InitializeData(ctx context.Context) (next context.Context, err error) {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn ctx, system.ErrMissingDBContext\n\t}\n\n\tap := ctx.Value(\"adminPassword\")\n\tapStr, ok := ap.(string)\n\tif !ok {\n\t\tapStr = \"123456\"\n\t}\n\n\tpassword := utils.BcryptHash(apStr)\n\tadminPassword := utils.BcryptHash(apStr)\n\n\tentities := []sysModel.SysUser{\n\t\t{\n\t\t\tUUID:        uuid.New(),\n\t\t\tUsername:    \"admin\",\n\t\t\tPassword:    adminPassword,\n\t\t\tNickName:    \"Mr.奇淼\",\n\t\t\tHeaderImg:   \"https://qmplusimg.henrongyi.top/gva_header.jpg\",\n\t\t\tAuthorityId: 888,\n\t\t\tPhone:       \"17611111111\",\n\t\t\tEmail:       \"333333333@qq.com\",\n\t\t},\n\t\t{\n\t\t\tUUID:        uuid.New(),\n\t\t\tUsername:    \"a303176530\",\n\t\t\tPassword:    password,\n\t\t\tNickName:    \"用户1\",\n\t\t\tHeaderImg:   \"https://qmplusimg.henrongyi.top/1572075907logo.png\",\n\t\t\tAuthorityId: 9528,\n\t\t\tPhone:       \"17611111111\",\n\t\t\tEmail:       \"333333333@qq.com\"},\n\t}\n\tif err = db.Create(&entities).Error; err != nil {\n\t\treturn ctx, errors.Wrap(err, sysModel.SysUser{}.TableName()+\"表数据初始化失败!\")\n\t}\n\tnext = context.WithValue(ctx, i.InitializerName(), entities)\n\tauthorityEntities, ok := ctx.Value(new(initAuthority).InitializerName()).([]sysModel.SysAuthority)\n\tif !ok {\n\t\treturn next, errors.Wrap(system.ErrMissingDependentContext, \"创建 [用户-权限] 关联失败, 未找到权限表初始化数据\")\n\t}\n\tif err = db.Model(&entities[0]).Association(\"Authorities\").Replace(authorityEntities); err != nil {\n\t\treturn next, err\n\t}\n\tif err = db.Model(&entities[1]).Association(\"Authorities\").Replace(authorityEntities[:1]); err != nil {\n\t\treturn next, err\n\t}\n\treturn next, err\n}\n\nfunc (i *initUser) DataInserted(ctx context.Context) bool {\n\tdb, ok := ctx.Value(\"db\").(*gorm.DB)\n\tif !ok {\n\t\treturn false\n\t}\n\tvar record sysModel.SysUser\n\tif errors.Is(db.Where(\"username = ?\", \"a303176530\").\n\t\tPreload(\"Authorities\").First(&record).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据\n\t\treturn false\n\t}\n\treturn len(record.Authorities) > 0 && record.Authorities[0].AuthorityId == 888\n}\n"
  },
  {
    "path": "server/task/clearTable.go",
    "content": "package task\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n)\n\n//@author: [songzhibin97](https://github.com/songzhibin97)\n//@function: ClearTable\n//@description: 清理数据库表数据\n//@param: db(数据库对象) *gorm.DB, tableName(表名) string, compareField(比较字段) string, interval(间隔) string\n//@return: error\n\nfunc ClearTable(db *gorm.DB) error {\n\tvar ClearTableDetail []common.ClearDB\n\n\tClearTableDetail = append(ClearTableDetail, common.ClearDB{\n\t\tTableName:    \"sys_operation_records\",\n\t\tCompareField: \"created_at\",\n\t\tInterval:     \"2160h\",\n\t})\n\n\tClearTableDetail = append(ClearTableDetail, common.ClearDB{\n\t\tTableName:    \"jwt_blacklists\",\n\t\tCompareField: \"created_at\",\n\t\tInterval:     \"168h\",\n\t})\n\n\tif db == nil {\n\t\treturn errors.New(\"db Cannot be empty\")\n\t}\n\n\tfor _, detail := range ClearTableDetail {\n\t\tduration, err := time.ParseDuration(detail.Interval)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif duration < 0 {\n\t\t\treturn errors.New(\"parse duration < 0\")\n\t\t}\n\t\terr = db.Debug().Exec(fmt.Sprintf(\"DELETE FROM %s WHERE %s < ?\", detail.TableName, detail.CompareField), time.Now().Add(-duration)).Error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/utils/ast/ast.go",
    "content": "package ast\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"log\"\n)\n\n// AddImport 增加 import 方法\nfunc AddImport(astNode ast.Node, imp string) {\n\timpStr := fmt.Sprintf(\"\\\"%s\\\"\", imp)\n\tast.Inspect(astNode, func(node ast.Node) bool {\n\t\tif genDecl, ok := node.(*ast.GenDecl); ok {\n\t\t\tif genDecl.Tok == token.IMPORT {\n\t\t\t\tfor i := range genDecl.Specs {\n\t\t\t\t\tif impNode, ok := genDecl.Specs[i].(*ast.ImportSpec); ok {\n\t\t\t\t\t\tif impNode.Path.Value == impStr {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tgenDecl.Specs = append(genDecl.Specs, &ast.ImportSpec{\n\t\t\t\t\tPath: &ast.BasicLit{\n\t\t\t\t\t\tKind:  token.STRING,\n\t\t\t\t\t\tValue: impStr,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n}\n\n// FindFunction 查询特定function方法\nfunc FindFunction(astNode ast.Node, FunctionName string) *ast.FuncDecl {\n\tvar funcDeclP *ast.FuncDecl\n\tast.Inspect(astNode, func(node ast.Node) bool {\n\t\tif funcDecl, ok := node.(*ast.FuncDecl); ok {\n\t\t\tif funcDecl.Name.String() == FunctionName {\n\t\t\t\tfuncDeclP = funcDecl\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn funcDeclP\n}\n\n// FindArray 查询特定数组方法\nfunc FindArray(astNode ast.Node, identName, selectorExprName string) *ast.CompositeLit {\n\tvar assignStmt *ast.CompositeLit\n\tast.Inspect(astNode, func(n ast.Node) bool {\n\t\tswitch node := n.(type) {\n\t\tcase *ast.AssignStmt:\n\t\t\tfor _, expr := range node.Rhs {\n\t\t\t\tif exprType, ok := expr.(*ast.CompositeLit); ok {\n\t\t\t\t\tif arrayType, ok := exprType.Type.(*ast.ArrayType); ok {\n\t\t\t\t\t\tsel, ok1 := arrayType.Elt.(*ast.SelectorExpr)\n\t\t\t\t\t\tx, ok2 := sel.X.(*ast.Ident)\n\t\t\t\t\t\tif ok1 && ok2 && x.Name == identName && sel.Sel.Name == selectorExprName {\n\t\t\t\t\t\t\tassignStmt = exprType\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn assignStmt\n}\n\nfunc CreateMenuStructAst(menus []system.SysBaseMenu) *[]ast.Expr {\n\tvar menuElts []ast.Expr\n\tfor i := range menus {\n\t\telts := []ast.Expr{ // 结构体的字段\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"ParentId\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.INT, Value: \"0\"},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Path\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", menus[i].Path)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Name\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", menus[i].Name)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Hidden\"},\n\t\t\t\tValue: &ast.Ident{Name: \"false\"},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Component\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", menus[i].Component)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Sort\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf(\"%d\", menus[i].Sort)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey: &ast.Ident{Name: \"Meta\"},\n\t\t\t\tValue: &ast.CompositeLit{\n\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"Meta\"},\n\t\t\t\t\t},\n\t\t\t\t\tElts: []ast.Expr{\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Title\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", menus[i].Title)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Icon\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", menus[i].Icon)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t// 添加菜单参数\n\t\tif len(menus[i].Parameters) > 0 {\n\t\t\tvar paramElts []ast.Expr\n\t\t\tfor _, param := range menus[i].Parameters {\n\t\t\t\tparamElts = append(paramElts, &ast.CompositeLit{\n\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"SysBaseMenuParameter\"},\n\t\t\t\t\t},\n\t\t\t\t\tElts: []ast.Expr{\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Type\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", param.Type)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Key\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", param.Key)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Value\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", param.Value)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t\telts = append(elts, &ast.KeyValueExpr{\n\t\t\t\tKey: &ast.Ident{Name: \"Parameters\"},\n\t\t\t\tValue: &ast.CompositeLit{\n\t\t\t\t\tType: &ast.ArrayType{\n\t\t\t\t\t\tElt: &ast.SelectorExpr{\n\t\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\t\tSel: &ast.Ident{Name: \"SysBaseMenuParameter\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tElts: paramElts,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\t// 添加菜单按钮\n\t\tif len(menus[i].MenuBtn) > 0 {\n\t\t\tvar btnElts []ast.Expr\n\t\t\tfor _, btn := range menus[i].MenuBtn {\n\t\t\t\tbtnElts = append(btnElts, &ast.CompositeLit{\n\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"SysBaseMenuBtn\"},\n\t\t\t\t\t},\n\t\t\t\t\tElts: []ast.Expr{\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Name\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", btn.Name)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Desc\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", btn.Desc)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t\telts = append(elts, &ast.KeyValueExpr{\n\t\t\t\tKey: &ast.Ident{Name: \"MenuBtn\"},\n\t\t\t\tValue: &ast.CompositeLit{\n\t\t\t\t\tType: &ast.ArrayType{\n\t\t\t\t\t\tElt: &ast.SelectorExpr{\n\t\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\t\tSel: &ast.Ident{Name: \"SysBaseMenuBtn\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tElts: btnElts,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tmenuElts = append(menuElts, &ast.CompositeLit{\n\t\t\tType: nil,\n\t\t\tElts: elts,\n\t\t})\n\t}\n\treturn &menuElts\n}\n\nfunc CreateApiStructAst(apis []system.SysApi) *[]ast.Expr {\n\tvar apiElts []ast.Expr\n\tfor i := range apis {\n\t\telts := []ast.Expr{ // 结构体的字段\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Path\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", apis[i].Path)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Description\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", apis[i].Description)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"ApiGroup\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", apis[i].ApiGroup)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Method\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", apis[i].Method)},\n\t\t\t},\n\t\t}\n\t\tapiElts = append(apiElts, &ast.CompositeLit{\n\t\t\tType: nil,\n\t\t\tElts: elts,\n\t\t})\n\t}\n\treturn &apiElts\n}\n\n// CheckImport 检查是否存在Import\nfunc CheckImport(file *ast.File, importPath string) bool {\n\tfor _, imp := range file.Imports {\n\t\t// Remove quotes around the import path\n\t\tpath := imp.Path.Value[1 : len(imp.Path.Value)-1]\n\n\t\tif path == importPath {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc clearPosition(astNode ast.Node) {\n\tast.Inspect(astNode, func(n ast.Node) bool {\n\t\tswitch node := n.(type) {\n\t\tcase *ast.Ident:\n\t\t\t// 清除位置信息\n\t\t\tnode.NamePos = token.NoPos\n\t\tcase *ast.CallExpr:\n\t\t\t// 清除位置信息\n\t\t\tnode.Lparen = token.NoPos\n\t\t\tnode.Rparen = token.NoPos\n\t\tcase *ast.BasicLit:\n\t\t\t// 清除位置信息\n\t\t\tnode.ValuePos = token.NoPos\n\t\tcase *ast.SelectorExpr:\n\t\t\t// 清除位置信息\n\t\t\tnode.Sel.NamePos = token.NoPos\n\t\tcase *ast.BinaryExpr:\n\t\t\tnode.OpPos = token.NoPos\n\t\tcase *ast.UnaryExpr:\n\t\t\tnode.OpPos = token.NoPos\n\t\tcase *ast.StarExpr:\n\t\t\tnode.Star = token.NoPos\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc CreateStmt(statement string) *ast.ExprStmt {\n\texpr, err := parser.ParseExpr(statement)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tclearPosition(expr)\n\treturn &ast.ExprStmt{X: expr}\n}\n\nfunc IsBlockStmt(node ast.Node) bool {\n\t_, ok := node.(*ast.BlockStmt)\n\treturn ok\n}\n\nfunc VariableExistsInBlock(block *ast.BlockStmt, varName string) bool {\n\texists := false\n\tast.Inspect(block, func(n ast.Node) bool {\n\t\tswitch node := n.(type) {\n\t\tcase *ast.AssignStmt:\n\t\t\tfor _, expr := range node.Lhs {\n\t\t\t\tif ident, ok := expr.(*ast.Ident); ok && ident.Name == varName {\n\t\t\t\t\texists = true\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn exists\n}\n\nfunc CreateDictionaryStructAst(dictionaries []system.SysDictionary) *[]ast.Expr {\n\tvar dictElts []ast.Expr\n\tfor i := range dictionaries {\n\t\tstatusStr := \"true\"\n\t\tif dictionaries[i].Status != nil && !*dictionaries[i].Status {\n\t\t\tstatusStr = \"false\"\n\t\t}\n\n\t\telts := []ast.Expr{\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Name\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", dictionaries[i].Name)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Type\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", dictionaries[i].Type)},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey: &ast.Ident{Name: \"Status\"},\n\t\t\t\tValue: &ast.CallExpr{\n\t\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: \"utils\"},\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"Pointer\"},\n\t\t\t\t\t},\n\t\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t\t&ast.Ident{Name: statusStr},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&ast.KeyValueExpr{\n\t\t\t\tKey:   &ast.Ident{Name: \"Desc\"},\n\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", dictionaries[i].Desc)},\n\t\t\t},\n\t\t}\n\n\t\tif len(dictionaries[i].SysDictionaryDetails) > 0 {\n\t\t\tvar detailElts []ast.Expr\n\t\t\tfor _, detail := range dictionaries[i].SysDictionaryDetails {\n\t\t\t\tdetailStatusStr := \"true\"\n\t\t\t\tif detail.Status != nil && !*detail.Status {\n\t\t\t\t\tdetailStatusStr = \"false\"\n\t\t\t\t}\n\n\t\t\t\tdetailElts = append(detailElts, &ast.CompositeLit{\n\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"SysDictionaryDetail\"},\n\t\t\t\t\t},\n\t\t\t\t\tElts: []ast.Expr{\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Label\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", detail.Label)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Value\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", detail.Value)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Extend\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(\"\\\"%s\\\"\", detail.Extend)},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey: &ast.Ident{Name: \"Status\"},\n\t\t\t\t\t\t\tValue: &ast.CallExpr{\n\t\t\t\t\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\tX:   &ast.Ident{Name: \"utils\"},\n\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: \"Pointer\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t\t\t\t\t&ast.Ident{Name: detailStatusStr},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.KeyValueExpr{\n\t\t\t\t\t\t\tKey:   &ast.Ident{Name: \"Sort\"},\n\t\t\t\t\t\t\tValue: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf(\"%d\", detail.Sort)},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t\telts = append(elts, &ast.KeyValueExpr{\n\t\t\t\tKey: &ast.Ident{Name: \"SysDictionaryDetails\"},\n\t\t\t\tValue: &ast.CompositeLit{\n\t\t\t\t\tType: &ast.ArrayType{Elt: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\t\t\tSel: &ast.Ident{Name: \"SysDictionaryDetail\"},\n\t\t\t\t\t}},\n\t\t\t\t\tElts: detailElts,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tdictElts = append(dictElts, &ast.CompositeLit{\n\t\t\tType: &ast.SelectorExpr{\n\t\t\t\tX:   &ast.Ident{Name: \"model\"},\n\t\t\t\tSel: &ast.Ident{Name: \"SysDictionary\"},\n\t\t\t},\n\t\t\tElts: elts,\n\t\t})\n\t}\n\treturn &dictElts\n}\n"
  },
  {
    "path": "server/utils/ast/ast_auto_enter.go",
    "content": "package ast\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"os\"\n)\n\nfunc ImportForAutoEnter(path string, funcName string, code string) {\n\tsrc, err := os.ReadFile(path)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tast.Inspect(astFile, func(node ast.Node) bool {\n\t\tif typeSpec, ok := node.(*ast.TypeSpec); ok {\n\t\t\tif typeSpec.Name.Name == funcName {\n\t\t\t\tif st, ok := typeSpec.Type.(*ast.StructType); ok {\n\t\t\t\t\tfor i := range st.Fields.List {\n\t\t\t\t\t\tif t, ok := st.Fields.List[i].Type.(*ast.Ident); ok {\n\t\t\t\t\t\t\tif t.Name == code {\n\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsn := &ast.Field{\n\t\t\t\t\t\tType: &ast.Ident{Name: code},\n\t\t\t\t\t}\n\t\t\t\t\tst.Fields.List = append(st.Fields.List, sn)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\terr = printer.Fprint(bf, fileSet, astFile)\n\tif err != nil {\n\t\treturn\n\t}\n\t_ = os.WriteFile(path, bf.Bytes(), 0666)\n}\n"
  },
  {
    "path": "server/utils/ast/ast_enter.go",
    "content": "package ast\n\nimport (\n\t\"bytes\"\n\t\"go/ast\"\n\t\"go/format\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Visitor struct {\n\tImportCode  string\n\tStructName  string\n\tPackageName string\n\tGroupName   string\n}\n\nfunc (vi *Visitor) Visit(node ast.Node) ast.Visitor {\n\tswitch n := node.(type) {\n\tcase *ast.GenDecl:\n\t\t// 查找有没有import context包\n\t\t// Notice：没有考虑没有import任何包的情况\n\t\tif n.Tok == token.IMPORT && vi.ImportCode != \"\" {\n\t\t\tvi.addImport(n)\n\t\t\t// 不需要再遍历子树\n\t\t\treturn nil\n\t\t}\n\t\tif n.Tok == token.TYPE && vi.StructName != \"\" && vi.PackageName != \"\" && vi.GroupName != \"\" {\n\t\t\tvi.addStruct(n)\n\t\t\treturn nil\n\t\t}\n\tcase *ast.FuncDecl:\n\t\tif n.Name.Name == \"Routers\" {\n\t\t\tvi.addFuncBodyVar(n)\n\t\t\treturn nil\n\t\t}\n\n\t}\n\treturn vi\n}\n\nfunc (vi *Visitor) addStruct(genDecl *ast.GenDecl) ast.Visitor {\n\tfor i := range genDecl.Specs {\n\t\tswitch n := genDecl.Specs[i].(type) {\n\t\tcase *ast.TypeSpec:\n\t\t\tif strings.Index(n.Name.Name, \"Group\") > -1 {\n\t\t\t\tswitch t := n.Type.(type) {\n\t\t\t\tcase *ast.StructType:\n\t\t\t\t\tf := &ast.Field{\n\t\t\t\t\t\tNames: []*ast.Ident{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: vi.StructName,\n\t\t\t\t\t\t\t\tObj: &ast.Object{\n\t\t\t\t\t\t\t\t\tKind: ast.Var,\n\t\t\t\t\t\t\t\t\tName: vi.StructName,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\t\t\tName: vi.PackageName,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\t\t\tName: vi.GroupName,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\tt.Fields.List = append(t.Fields.List, f)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn vi\n}\n\nfunc (vi *Visitor) addImport(genDecl *ast.GenDecl) ast.Visitor {\n\t// 是否已经import\n\thasImported := false\n\tfor _, v := range genDecl.Specs {\n\t\timportSpec := v.(*ast.ImportSpec)\n\t\t// 如果已经包含\n\t\tif importSpec.Path.Value == strconv.Quote(vi.ImportCode) {\n\t\t\thasImported = true\n\t\t}\n\t}\n\tif !hasImported {\n\t\tgenDecl.Specs = append(genDecl.Specs, &ast.ImportSpec{\n\t\t\tPath: &ast.BasicLit{\n\t\t\t\tKind:  token.STRING,\n\t\t\t\tValue: strconv.Quote(vi.ImportCode),\n\t\t\t},\n\t\t})\n\t}\n\treturn vi\n}\n\nfunc (vi *Visitor) addFuncBodyVar(funDecl *ast.FuncDecl) ast.Visitor {\n\thasVar := false\n\tfor _, v := range funDecl.Body.List {\n\t\tswitch varSpec := v.(type) {\n\t\tcase *ast.AssignStmt:\n\t\t\tfor i := range varSpec.Lhs {\n\t\t\t\tswitch nn := varSpec.Lhs[i].(type) {\n\t\t\t\tcase *ast.Ident:\n\t\t\t\t\tif nn.Name == vi.PackageName+\"Router\" {\n\t\t\t\t\t\thasVar = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif !hasVar {\n\t\tassignStmt := &ast.AssignStmt{\n\t\t\tLhs: []ast.Expr{\n\t\t\t\t&ast.Ident{\n\t\t\t\t\tName: vi.PackageName + \"Router\",\n\t\t\t\t\tObj: &ast.Object{\n\t\t\t\t\t\tKind: ast.Var,\n\t\t\t\t\t\tName: vi.PackageName + \"Router\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tTok: token.DEFINE,\n\t\t\tRhs: []ast.Expr{\n\t\t\t\t&ast.SelectorExpr{\n\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\t\tName: \"router\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\t\tName: \"RouterGroupApp\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\tName: cases.Title(language.English).String(vi.PackageName),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tfunDecl.Body.List = append(funDecl.Body.List, funDecl.Body.List[1])\n\t\tindex := 1\n\t\tcopy(funDecl.Body.List[index+1:], funDecl.Body.List[index:])\n\t\tfunDecl.Body.List[index] = assignStmt\n\t}\n\treturn vi\n}\n\nfunc ImportReference(filepath, importCode, structName, packageName, groupName string) error {\n\tfSet := token.NewFileSet()\n\tfParser, err := parser.ParseFile(fSet, filepath, nil, parser.ParseComments)\n\tif err != nil {\n\t\treturn err\n\t}\n\timportCode = strings.TrimSpace(importCode)\n\tv := &Visitor{\n\t\tImportCode:  importCode,\n\t\tStructName:  structName,\n\t\tPackageName: packageName,\n\t\tGroupName:   groupName,\n\t}\n\tif importCode == \"\" {\n\t\tast.Print(fSet, fParser)\n\t}\n\n\tast.Walk(v, fParser)\n\n\tvar output []byte\n\tbuffer := bytes.NewBuffer(output)\n\terr = format.Node(buffer, fSet, fParser)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// 写回数据\n\treturn os.WriteFile(filepath, buffer.Bytes(), 0o600)\n}\n"
  },
  {
    "path": "server/utils/ast/ast_gorm.go",
    "content": "package ast\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"os\"\n)\n\n// AddRegisterTablesAst 自动为 gorm.go 注册一个自动迁移\nfunc AddRegisterTablesAst(path, funcName, pk, varName, dbName, model string) {\n\tmodelPk := fmt.Sprintf(\"github.com/flipped-aurora/gin-vue-admin/server/model/%s\", pk)\n\tsrc, err := os.ReadFile(path)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tAddImport(astFile, modelPk)\n\tFuncNode := FindFunction(astFile, funcName)\n\tif FuncNode != nil {\n\t\tast.Print(fileSet, FuncNode)\n\t}\n\taddDBVar(FuncNode.Body, varName, dbName)\n\taddAutoMigrate(FuncNode.Body, varName, pk, model)\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\n\tos.WriteFile(path, bf.Bytes(), 0666)\n}\n\n// 增加一个 db库变量\nfunc addDBVar(astBody *ast.BlockStmt, varName, dbName string) {\n\tif dbName == \"\" {\n\t\treturn\n\t}\n\tdbStr := fmt.Sprintf(\"\\\"%s\\\"\", dbName)\n\n\tfor i := range astBody.List {\n\t\tif assignStmt, ok := astBody.List[i].(*ast.AssignStmt); ok {\n\t\t\tif ident, ok := assignStmt.Lhs[0].(*ast.Ident); ok {\n\t\t\t\tif ident.Name == varName {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tassignNode := &ast.AssignStmt{\n\t\tLhs: []ast.Expr{\n\t\t\t&ast.Ident{\n\t\t\t\tName: varName,\n\t\t\t},\n\t\t},\n\t\tTok: token.DEFINE,\n\t\tRhs: []ast.Expr{\n\t\t\t&ast.CallExpr{\n\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\tName: \"global\",\n\t\t\t\t\t},\n\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\tName: \"GetGlobalDBByDBName\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t&ast.BasicLit{\n\t\t\t\t\t\tKind:  token.STRING,\n\t\t\t\t\t\tValue: dbStr,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tastBody.List = append([]ast.Stmt{assignNode}, astBody.List...)\n}\n\n// 为db库变量增加 AutoMigrate 方法\nfunc addAutoMigrate(astBody *ast.BlockStmt, dbname string, pk string, model string) {\n\tif dbname == \"\" {\n\t\tdbname = \"db\"\n\t}\n\tflag := true\n\tast.Inspect(astBody, func(node ast.Node) bool {\n\t\t// 首先判断需要加入的方法调用语句是否存在 不存在则直接走到下方逻辑\n\t\tswitch n := node.(type) {\n\t\tcase *ast.CallExpr:\n\t\t\t// 判断是否找到了AutoMigrate语句\n\t\t\tif s, ok := n.Fun.(*ast.SelectorExpr); ok {\n\t\t\t\tif x, ok := s.X.(*ast.Ident); ok {\n\t\t\t\t\tif s.Sel.Name == \"AutoMigrate\" && x.Name == dbname {\n\t\t\t\t\t\tflag = false\n\t\t\t\t\t\tif !NeedAppendModel(n, pk, model) {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 判断已经找到了AutoMigrate语句\n\t\t\t\t\t\tn.Args = append(n.Args, &ast.CompositeLit{\n\t\t\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\t\t\t\tName: pk,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\t\t\t\tName: model,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t\t//然后判断 pk.model是否存在 如果存在直接跳出 如果不存在 则向已经找到的方法调用语句的node里面push一条\n\t})\n\n\tif flag {\n\t\texprStmt := &ast.ExprStmt{\n\t\t\tX: &ast.CallExpr{\n\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\tName: dbname,\n\t\t\t\t\t},\n\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\tName: \"AutoMigrate\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t&ast.CompositeLit{\n\t\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\t\t\tName: pk,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\t\t\tName: model,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}}\n\t\tastBody.List = append(astBody.List, exprStmt)\n\t}\n}\n\n// NeedAppendModel 为automigrate增加实参\nfunc NeedAppendModel(callNode ast.Node, pk string, model string) bool {\n\tflag := true\n\tast.Inspect(callNode, func(node ast.Node) bool {\n\t\tswitch n := node.(type) {\n\t\tcase *ast.SelectorExpr:\n\t\t\tif x, ok := n.X.(*ast.Ident); ok {\n\t\t\t\tif n.Sel.Name == model && x.Name == pk {\n\t\t\t\t\tflag = false\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn flag\n}\n"
  },
  {
    "path": "server/utils/ast/ast_init_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n)\n\nfunc init() {\n\tglobal.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs(\"../../../\")\n\tglobal.GVA_CONFIG.AutoCode.Server = \"server\"\n}\n"
  },
  {
    "path": "server/utils/ast/ast_rollback.go",
    "content": "package ast\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc RollBackAst(pk, model string) {\n\tRollGormBack(pk, model)\n\tRollRouterBack(pk, model)\n}\n\nfunc RollGormBack(pk, model string) {\n\n\t// 首先分析存在多少个ttt作为调用方的node块\n\t// 如果多个 仅仅删除对应块即可\n\t// 如果单个 那么还需要剔除import\n\tpath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\")\n\tsrc, err := os.ReadFile(path)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tvar n *ast.CallExpr\n\tvar k int = -1\n\tvar pkNum = 0\n\tast.Inspect(astFile, func(node ast.Node) bool {\n\t\tif node, ok := node.(*ast.CallExpr); ok {\n\t\t\tfor i := range node.Args {\n\t\t\t\tpkOK := false\n\t\t\t\tmodelOK := false\n\t\t\t\tast.Inspect(node.Args[i], func(item ast.Node) bool {\n\t\t\t\t\tif ii, ok := item.(*ast.Ident); ok {\n\t\t\t\t\t\tif ii.Name == pk {\n\t\t\t\t\t\t\tpkOK = true\n\t\t\t\t\t\t\tpkNum++\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ii.Name == model {\n\t\t\t\t\t\t\tmodelOK = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif pkOK && modelOK {\n\t\t\t\t\t\tn = node\n\t\t\t\t\t\tk = i\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\tif k > -1 {\n\t\tn.Args = append(append([]ast.Expr{}, n.Args[:k]...), n.Args[k+1:]...)\n\t}\n\tif pkNum == 1 {\n\t\tvar imI int = -1\n\t\tvar gp *ast.GenDecl\n\t\tast.Inspect(astFile, func(node ast.Node) bool {\n\t\t\tif gen, ok := node.(*ast.GenDecl); ok {\n\t\t\t\tfor i := range gen.Specs {\n\t\t\t\t\tif imspec, ok := gen.Specs[i].(*ast.ImportSpec); ok {\n\t\t\t\t\t\tif imspec.Path.Value == \"\\\"github.com/flipped-aurora/gin-vue-admin/server/model/\"+pk+\"\\\"\" {\n\t\t\t\t\t\t\tgp = gen\n\t\t\t\t\t\t\timI = i\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\n\t\tif imI > -1 {\n\t\t\tgp.Specs = append(append([]ast.Spec{}, gp.Specs[:imI]...), gp.Specs[imI+1:]...)\n\t\t}\n\t}\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\tos.Remove(path)\n\tos.WriteFile(path, bf.Bytes(), 0666)\n\n}\n\nfunc RollRouterBack(pk, model string) {\n\n\t// 首先抓到所有的代码块结构 {}\n\t// 分析结构中是否存在一个变量叫做 pk+Router\n\t// 然后获取到代码块指针 对内部需要回滚的代码进行剔除\n\tpath := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"router_biz.go\")\n\tsrc, err := os.ReadFile(path)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, 0)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\tvar block *ast.BlockStmt\n\tvar routerStmt *ast.FuncDecl\n\n\tast.Inspect(astFile, func(node ast.Node) bool {\n\t\tif n, ok := node.(*ast.FuncDecl); ok {\n\t\t\tif n.Name.Name == \"initBizRouter\" {\n\t\t\t\trouterStmt = n\n\t\t\t}\n\t\t}\n\n\t\tif n, ok := node.(*ast.BlockStmt); ok {\n\t\t\tast.Inspect(n, func(bNode ast.Node) bool {\n\t\t\t\tif in, ok := bNode.(*ast.Ident); ok {\n\t\t\t\t\tif in.Name == pk+\"Router\" {\n\t\t\t\t\t\tblock = n\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\treturn true\n\t\t}\n\t\treturn true\n\t})\n\tvar k int\n\tfor i := range block.List {\n\t\tif stmtNode, ok := block.List[i].(*ast.ExprStmt); ok {\n\t\t\tast.Inspect(stmtNode, func(node ast.Node) bool {\n\t\t\t\tif n, ok := node.(*ast.Ident); ok {\n\t\t\t\t\tif n.Name == \"Init\"+model+\"Router\" {\n\t\t\t\t\t\tk = i\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\n\tblock.List = append(append([]ast.Stmt{}, block.List[:k]...), block.List[k+1:]...)\n\n\tif len(block.List) == 1 {\n\t\t// 说明这个块就没任何意义了\n\t\tblock.List = nil\n\t}\n\n\tfor i, n := range routerStmt.Body.List {\n\t\tif n, ok := n.(*ast.BlockStmt); ok {\n\t\t\tif n.List == nil {\n\t\t\t\trouterStmt.Body.List = append(append([]ast.Stmt{}, routerStmt.Body.List[:i]...), routerStmt.Body.List[i+1:]...)\n\t\t\t\ti--\n\t\t\t}\n\t\t}\n\t}\n\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\tos.Remove(path)\n\tos.WriteFile(path, bf.Bytes(), 0666)\n}\n"
  },
  {
    "path": "server/utils/ast/ast_router.go",
    "content": "package ast\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc AppendNodeToList(stmts []ast.Stmt, stmt ast.Stmt, index int) []ast.Stmt {\n\treturn append(stmts[:index], append([]ast.Stmt{stmt}, stmts[index:]...)...)\n}\n\nfunc AddRouterCode(path, funcName, pk, model string) {\n\tsrc, err := os.ReadFile(path)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tfileSet := token.NewFileSet()\n\tastFile, err := parser.ParseFile(fileSet, \"\", src, parser.ParseComments)\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\tFuncNode := FindFunction(astFile, funcName)\n\n\tpkName := strings.ToUpper(pk[:1]) + pk[1:]\n\trouterName := fmt.Sprintf(\"%sRouter\", pk)\n\tmodelName := fmt.Sprintf(\"Init%sRouter\", model)\n\tvar bloctPre *ast.BlockStmt\n\tfor i := len(FuncNode.Body.List) - 1; i >= 0; i-- {\n\t\tif block, ok := FuncNode.Body.List[i].(*ast.BlockStmt); ok {\n\t\t\tbloctPre = block\n\t\t}\n\t}\n\tast.Print(fileSet, FuncNode)\n\tif ok, b := needAppendRouter(FuncNode, pk); ok {\n\t\trouterNode :=\n\t\t\t&ast.BlockStmt{\n\t\t\t\tList: []ast.Stmt{\n\t\t\t\t\t&ast.AssignStmt{\n\t\t\t\t\t\tLhs: []ast.Expr{\n\t\t\t\t\t\t\t&ast.Ident{Name: routerName},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTok: token.DEFINE,\n\t\t\t\t\t\tRhs: []ast.Expr{\n\t\t\t\t\t\t\t&ast.SelectorExpr{\n\t\t\t\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\tX:   &ast.Ident{Name: \"router\"},\n\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: \"RouterGroupApp\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: pkName},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\tFuncNode.Body.List = AppendNodeToList(FuncNode.Body.List, routerNode, len(FuncNode.Body.List)-1)\n\t\tbloctPre = routerNode\n\t} else {\n\t\tbloctPre = b\n\t}\n\n\tif needAppendInit(FuncNode, routerName, modelName) {\n\t\tbloctPre.List = append(bloctPre.List,\n\t\t\t&ast.ExprStmt{\n\t\t\t\tX: &ast.CallExpr{\n\t\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: routerName},\n\t\t\t\t\t\tSel: &ast.Ident{Name: modelName},\n\t\t\t\t\t},\n\t\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t\t&ast.Ident{\n\t\t\t\t\t\t\tName: \"privateGroup\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&ast.Ident{\n\t\t\t\t\t\t\tName: \"publicGroup\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t}\n\tvar out []byte\n\tbf := bytes.NewBuffer(out)\n\tprinter.Fprint(bf, fileSet, astFile)\n\tos.WriteFile(path, bf.Bytes(), 0666)\n}\n\nfunc needAppendRouter(funcNode ast.Node, pk string) (bool, *ast.BlockStmt) {\n\tflag := true\n\tvar block *ast.BlockStmt\n\tast.Inspect(funcNode, func(node ast.Node) bool {\n\t\tswitch n := node.(type) {\n\t\tcase *ast.BlockStmt:\n\t\t\tfor i := range n.List {\n\t\t\t\tif assignNode, ok := n.List[i].(*ast.AssignStmt); ok {\n\t\t\t\t\tif identNode, ok := assignNode.Lhs[0].(*ast.Ident); ok {\n\t\t\t\t\t\tif identNode.Name == fmt.Sprintf(\"%sRouter\", pk) {\n\t\t\t\t\t\t\tflag = false\n\t\t\t\t\t\t\tblock = n\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\treturn true\n\t})\n\treturn flag, block\n}\n\nfunc needAppendInit(funcNode ast.Node, routerName string, modelName string) bool {\n\tflag := true\n\tast.Inspect(funcNode, func(node ast.Node) bool {\n\t\tswitch n := funcNode.(type) {\n\t\tcase *ast.CallExpr:\n\t\t\tif selectNode, ok := n.Fun.(*ast.SelectorExpr); ok {\n\t\t\t\tx, xok := selectNode.X.(*ast.Ident)\n\t\t\t\tif xok && x.Name == routerName && selectNode.Sel.Name == modelName {\n\t\t\t\t\tflag = false\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn flag\n}\n"
  },
  {
    "path": "server/utils/ast/ast_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestAst(t *testing.T) {\n\tfilename := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"plugin.go\")\n\tfileSet := token.NewFileSet()\n\tfile, err := parser.ParseFile(fileSet, filename, nil, parser.ParseComments)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\terr = ast.Print(fileSet, file)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\terr = printer.Fprint(os.Stdout, token.NewFileSet(), file)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n}\n"
  },
  {
    "path": "server/utils/ast/ast_type.go",
    "content": "package ast\n\ntype Type string\n\nfunc (r Type) String() string {\n\treturn string(r)\n}\n\nfunc (r Type) Group() string {\n\tswitch r {\n\tcase TypePackageApiEnter:\n\t\treturn \"ApiGroup\"\n\tcase TypePackageRouterEnter:\n\t\treturn \"RouterGroup\"\n\tcase TypePackageServiceEnter:\n\t\treturn \"ServiceGroup\"\n\tcase TypePackageApiModuleEnter:\n\t\treturn \"ApiGroup\"\n\tcase TypePackageRouterModuleEnter:\n\t\treturn \"RouterGroup\"\n\tcase TypePackageServiceModuleEnter:\n\t\treturn \"ServiceGroup\"\n\tcase TypePluginApiEnter:\n\t\treturn \"api\"\n\tcase TypePluginRouterEnter:\n\t\treturn \"router\"\n\tcase TypePluginServiceEnter:\n\t\treturn \"service\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nconst (\n\tTypePackageApiEnter           = \"PackageApiEnter\"           // server/api/v1/enter.go\n\tTypePackageRouterEnter        = \"PackageRouterEnter\"        // server/router/enter.go\n\tTypePackageServiceEnter       = \"PackageServiceEnter\"       // server/service/enter.go\n\tTypePackageApiModuleEnter     = \"PackageApiModuleEnter\"     // server/api/v1/{package}/enter.go\n\tTypePackageRouterModuleEnter  = \"PackageRouterModuleEnter\"  // server/router/{package}/enter.go\n\tTypePackageServiceModuleEnter = \"PackageServiceModuleEnter\" // server/service/{package}/enter.go\n\tTypePackageInitializeGorm     = \"PackageInitializeGorm\"     // server/initialize/gorm_biz.go\n\tTypePackageInitializeRouter   = \"PackageInitializeRouter\"   // server/initialize/router_biz.go\n\tTypePluginGen                 = \"PluginGen\"                 // server/plugin/{package}/gen/main.go\n\tTypePluginApiEnter            = \"PluginApiEnter\"            // server/plugin/{package}/enter.go\n\tTypePluginInitializeV1        = \"PluginInitializeV1\"        // server/initialize/plugin_biz_v1.go\n\tTypePluginInitializeV2        = \"PluginInitializeV2\"        // server/plugin/register.go\n\tTypePluginRouterEnter         = \"PluginRouterEnter\"         // server/plugin/{package}/enter.go\n\tTypePluginServiceEnter        = \"PluginServiceEnter\"        // server/plugin/{package}/enter.go\n\tTypePluginInitializeApi       = \"PluginInitializeApi\"       // server/plugin/{package}/initialize/api.go\n\tTypePluginInitializeGorm      = \"PluginInitializeGorm\"      // server/plugin/{package}/initialize/gorm.go\n\tTypePluginInitializeMenu      = \"PluginInitializeMenu\"      // server/plugin/{package}/initialize/menu.go\n\tTypePluginInitializeRouter    = \"PluginInitializeRouter\"    // server/plugin/{package}/initialize/router.go\n)\n"
  },
  {
    "path": "server/utils/ast/extract_func.go",
    "content": "package ast\n\nimport (\n    \"fmt\"\n    \"go/ast\"\n    \"go/parser\"\n    \"go/token\"\n    \"os\"\n)\n\n// ExtractFuncSourceByPosition 根据文件路径与行号，提取包含该行的整个方法源码\n// 返回：方法名、完整源码、起止行号\nfunc ExtractFuncSourceByPosition(filePath string, line int) (name string, source string, startLine int, endLine int, err error) {\n    // 读取源文件\n    src, readErr := os.ReadFile(filePath)\n    if readErr != nil {\n        err = fmt.Errorf(\"read file failed: %w\", readErr)\n        return\n    }\n\n    // 解析 AST\n    fset := token.NewFileSet()\n    file, parseErr := parser.ParseFile(fset, filePath, src, parser.ParseComments)\n    if parseErr != nil {\n        err = fmt.Errorf(\"parse file failed: %w\", parseErr)\n        return\n    }\n\n    // 在 AST 中定位包含指定行号的函数声明\n    var target *ast.FuncDecl\n    ast.Inspect(file, func(n ast.Node) bool {\n        fd, ok := n.(*ast.FuncDecl)\n        if !ok {\n            return true\n        }\n        s := fset.Position(fd.Pos()).Line\n        e := fset.Position(fd.End()).Line\n        if line >= s && line <= e {\n            target = fd\n            startLine = s\n            endLine = e\n            return false\n        }\n        return true\n    })\n\n    if target == nil {\n        err = fmt.Errorf(\"no function encloses line %d in %s\", line, filePath)\n        return\n    }\n\n    // 使用字节偏移精确提取源码片段（包含注释与原始格式）\n    start := fset.Position(target.Pos()).Offset\n    end := fset.Position(target.End()).Offset\n    if start < 0 || end > len(src) || start >= end {\n        err = fmt.Errorf(\"invalid offsets for function: start=%d end=%d len=%d\", start, end, len(src))\n        return\n    }\n    source = string(src[start:end])\n    name = target.Name.Name\n    return\n}"
  },
  {
    "path": "server/utils/ast/import.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n\t\"strings\"\n)\n\ntype Import struct {\n\tBase\n\tImportPath string // 导包路径\n}\n\nfunc NewImport(importPath string) *Import {\n\treturn &Import{ImportPath: importPath}\n}\n\nfunc (a *Import) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *Import) Rollback(file *ast.File) error {\n\tif a.ImportPath == \"\" {\n\t\treturn nil\n\t}\n\tfor i := 0; i < len(file.Decls); i++ {\n\t\tv1, o1 := file.Decls[i].(*ast.GenDecl)\n\t\tif o1 {\n\t\t\tif v1.Tok != token.IMPORT {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfor j := 0; j < len(v1.Specs); j++ {\n\t\t\t\tv2, o2 := v1.Specs[j].(*ast.ImportSpec)\n\t\t\t\tif o2 && strings.HasSuffix(a.ImportPath, v2.Path.Value) {\n\t\t\t\t\tv1.Specs = append(v1.Specs[:j], v1.Specs[j+1:]...)\n\t\t\t\t\tif len(v1.Specs) == 0 {\n\t\t\t\t\t\tfile.Decls = append(file.Decls[:i], file.Decls[i+1:]...)\n\t\t\t\t\t} // 如果没有import声明，就删除, 如果不删除则会出现import()\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *Import) Injection(file *ast.File) error {\n\tif a.ImportPath == \"\" {\n\t\treturn nil\n\t}\n\tvar has bool\n\tfor i := 0; i < len(file.Decls); i++ {\n\t\tv1, o1 := file.Decls[i].(*ast.GenDecl)\n\t\tif o1 {\n\t\t\tif v1.Tok != token.IMPORT {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfor j := 0; j < len(v1.Specs); j++ {\n\t\t\t\tv2, o2 := v1.Specs[j].(*ast.ImportSpec)\n\t\t\t\tif o2 && strings.HasSuffix(a.ImportPath, v2.Path.Value) {\n\t\t\t\t\thas = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !has {\n\t\t\t\tspec := &ast.ImportSpec{\n\t\t\t\t\tPath: &ast.BasicLit{Kind: token.STRING, Value: a.ImportPath},\n\t\t\t\t}\n\t\t\t\tv1.Specs = append(v1.Specs, spec)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\tif !has {\n\t\tdecls := file.Decls\n\t\tfile.Decls = make([]ast.Decl, 0, len(file.Decls)+1)\n\t\tdecl := &ast.GenDecl{\n\t\t\tTok: token.IMPORT,\n\t\t\tSpecs: []ast.Spec{\n\t\t\t\t&ast.ImportSpec{\n\t\t\t\t\tPath: &ast.BasicLit{Kind: token.STRING, Value: a.ImportPath},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tfile.Decls = append(file.Decls, decl)\n\t\tfile.Decls = append(file.Decls, decls...)\n\t} // 如果没有import声明，就创建一个, 主要要放在第一个\n\treturn nil\n}\n\nfunc (a *Import) Format(filename string, writer io.Writer, file *ast.File) error {\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/interfaces.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"io\"\n)\n\ntype Ast interface {\n\t// Parse 解析文件/代码\n\tParse(filename string, writer io.Writer) (file *ast.File, err error)\n\t// Rollback 回滚\n\tRollback(file *ast.File) error\n\t// Injection 注入\n\tInjection(file *ast.File) error\n\t// Format 格式化输出\n\tFormat(filename string, writer io.Writer, file *ast.File) error\n}\n"
  },
  {
    "path": "server/utils/ast/interfaces_base.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/pkg/errors\"\n\t\"go/ast\"\n\t\"go/format\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"io\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype Base struct{}\n\nfunc (a *Base) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tfileSet := token.NewFileSet()\n\tif writer != nil {\n\t\tfile, err = parser.ParseFile(fileSet, filename, nil, parser.ParseComments)\n\t} else {\n\t\tfile, err = parser.ParseFile(fileSet, filename, writer, parser.ParseComments)\n\t}\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"[filepath:%s]打开/解析文件失败!\", filename)\n\t}\n\treturn file, nil\n}\n\nfunc (a *Base) Rollback(file *ast.File) error {\n\treturn nil\n}\n\nfunc (a *Base) Injection(file *ast.File) error {\n\treturn nil\n}\n\nfunc (a *Base) Format(filename string, writer io.Writer, file *ast.File) error {\n\tfileSet := token.NewFileSet()\n\tif writer == nil {\n\t\topen, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0666)\n\t\tdefer open.Close()\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"[filepath:%s]打开文件失败!\", filename)\n\t\t}\n\t\twriter = open\n\t}\n\terr := format.Node(writer, fileSet, file)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"[filepath:%s]注入失败!\", filename)\n\t}\n\treturn nil\n}\n\n// RelativePath 绝对路径转相对路径\nfunc (a *Base) RelativePath(filePath string) string {\n\tserver := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)\n\thasServer := strings.Index(filePath, server)\n\tif hasServer != -1 {\n\t\tfilePath = strings.TrimPrefix(filePath, server)\n\t\tkeys := strings.Split(filePath, string(filepath.Separator))\n\t\tfilePath = path.Join(keys...)\n\t}\n\treturn filePath\n}\n\n// AbsolutePath 相对路径转绝对路径\nfunc (a *Base) AbsolutePath(filePath string) string {\n\tserver := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server)\n\tkeys := strings.Split(filePath, \"/\")\n\tfilePath = filepath.Join(keys...)\n\tfilePath = filepath.Join(server, filePath)\n\treturn filePath\n}\n"
  },
  {
    "path": "server/utils/ast/package_enter.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n)\n\n// PackageEnter 模块化入口\ntype PackageEnter struct {\n\tBase\n\tType              Type   // 类型\n\tPath              string // 文件路径\n\tImportPath        string // 导包路径\n\tStructName        string // 结构体名称\n\tPackageName       string // 包名\n\tRelativePath      string // 相对路径\n\tPackageStructName string // 包结构体名称\n}\n\nfunc (a *PackageEnter) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PackageEnter) Rollback(file *ast.File) error {\n\t// 无需回滚\n\treturn nil\n}\n\nfunc (a *PackageEnter) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tgenDecl, ok := n.(*ast.GenDecl)\n\t\tif !ok || genDecl.Tok != token.TYPE {\n\t\t\treturn true\n\t\t}\n\n\t\tfor _, spec := range genDecl.Specs {\n\t\t\ttypeSpec, specok := spec.(*ast.TypeSpec)\n\t\t\tif !specok || typeSpec.Name.Name != a.Type.Group() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tstructType, structTypeOK := typeSpec.Type.(*ast.StructType)\n\t\t\tif !structTypeOK {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, field := range structType.Fields.List {\n\t\t\t\tif len(field.Names) == 1 && field.Names[0].Name == a.StructName {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfield := &ast.Field{\n\t\t\t\tNames: []*ast.Ident{{Name: a.StructName}},\n\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\t\t\tSel: &ast.Ident{Name: a.PackageStructName},\n\t\t\t\t},\n\t\t\t}\n\t\t\tstructType.Fields.List = append(structType.Fields.List, field)\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t})\n\treturn nil\n}\n\nfunc (a *PackageEnter) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/package_enter_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPackageEnter_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType              Type\n\t\tPath              string\n\t\tImportPath        string\n\t\tStructName        string\n\t\tPackageName       string\n\t\tPackageStructName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试ExampleApiGroup回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:              TypePackageApiEnter,\n\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", \"enter.go\"),\n\t\t\t\tImportPath:        `\"github.com/flipped-aurora/gin-vue-admin/server/api/v1/example\"`,\n\t\t\t\tStructName:        \"ExampleApiGroup\",\n\t\t\t\tPackageName:       \"example\",\n\t\t\t\tPackageStructName: \"ApiGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试ExampleRouterGroup回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:              TypePackageRouterEnter,\n\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", \"enter.go\"),\n\t\t\t\tImportPath:        `\"github.com/flipped-aurora/gin-vue-admin/server/router/example\"`,\n\t\t\t\tStructName:        \"Example\",\n\t\t\t\tPackageName:       \"example\",\n\t\t\t\tPackageStructName: \"RouterGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试ExampleServiceGroup回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:              TypePackageServiceEnter,\n\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", \"enter.go\"),\n\t\t\t\tImportPath:        `\"github.com/flipped-aurora/gin-vue-admin/server/service/example\"`,\n\t\t\t\tStructName:        \"ExampleServiceGroup\",\n\t\t\t\tPackageName:       \"example\",\n\t\t\t\tPackageStructName: \"ServiceGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageEnter{\n\t\t\t\tType:              tt.fields.Type,\n\t\t\t\tPath:              tt.fields.Path,\n\t\t\t\tImportPath:        tt.fields.ImportPath,\n\t\t\t\tStructName:        tt.fields.StructName,\n\t\t\t\tPackageName:       tt.fields.PackageName,\n\t\t\t\tPackageStructName: tt.fields.PackageStructName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPackageEnter_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType              Type\n\t\tPath              string\n\t\tImportPath        string\n\t\tStructName        string\n\t\tPackageName       string\n\t\tPackageStructName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试ExampleApiGroup注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:              TypePackageApiEnter,\n\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", \"enter.go\"),\n\t\t\t\tImportPath:        `\"github.com/flipped-aurora/gin-vue-admin/server/api/v1/example\"`,\n\t\t\t\tStructName:        \"ExampleApiGroup\",\n\t\t\t\tPackageName:       \"example\",\n\t\t\t\tPackageStructName: \"ApiGroup\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试ExampleRouterGroup注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:              TypePackageRouterEnter,\n\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", \"enter.go\"),\n\t\t\t\tImportPath:        `\"github.com/flipped-aurora/gin-vue-admin/server/router/example\"`,\n\t\t\t\tStructName:        \"Example\",\n\t\t\t\tPackageName:       \"example\",\n\t\t\t\tPackageStructName: \"RouterGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试ExampleServiceGroup注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:              TypePackageServiceEnter,\n\t\t\t\tPath:              filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", \"enter.go\"),\n\t\t\t\tImportPath:        `\"github.com/flipped-aurora/gin-vue-admin/server/service/example\"`,\n\t\t\t\tStructName:        \"ExampleServiceGroup\",\n\t\t\t\tPackageName:       \"example\",\n\t\t\t\tPackageStructName: \"ServiceGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageEnter{\n\t\t\t\tType:              tt.fields.Type,\n\t\t\t\tPath:              tt.fields.Path,\n\t\t\t\tImportPath:        tt.fields.ImportPath,\n\t\t\t\tStructName:        tt.fields.StructName,\n\t\t\t\tPackageName:       tt.fields.PackageName,\n\t\t\t\tPackageStructName: tt.fields.PackageStructName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Format() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/package_initialize_gorm.go",
    "content": "package ast\n\nimport (\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n)\n\n// PackageInitializeGorm 包初始化gorm\ntype PackageInitializeGorm struct {\n\tBase\n\tType         Type   // 类型\n\tPath         string // 文件路径\n\tImportPath   string // 导包路径\n\tBusiness     string // 业务库 gva => gva, 不要传\"gva\"\n\tStructName   string // 结构体名称\n\tPackageName  string // 包名\n\tRelativePath string // 相对路径\n\tIsNew        bool   // 是否使用new关键字 true: new(PackageName.StructName) false: &PackageName.StructName{}\n}\n\nfunc (a *PackageInitializeGorm) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PackageInitializeGorm) Rollback(file *ast.File) error {\n\tpackageNameNum := 0\n\t// 寻找目标结构\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\t// 总调用的db变量根据business来决定\n\t\tvarDB := a.Business + \"Db\"\n\n\t\tif a.Business == \"\" {\n\t\t\tvarDB = \"db\"\n\t\t}\n\n\t\tcallExpr, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn true\n\t\t}\n\n\t\t// 检查是不是 db.AutoMigrate() 方法\n\t\tselExpr, ok := callExpr.Fun.(*ast.SelectorExpr)\n\t\tif !ok || selExpr.Sel.Name != \"AutoMigrate\" {\n\t\t\treturn true\n\t\t}\n\n\t\t// 检查调用方是不是 db\n\t\tident, ok := selExpr.X.(*ast.Ident)\n\t\tif !ok || ident.Name != varDB {\n\t\t\treturn true\n\t\t}\n\n\t\t// 删除结构体参数\n\t\tfor i := 0; i < len(callExpr.Args); i++ {\n\t\t\tif com, comok := callExpr.Args[i].(*ast.CompositeLit); comok {\n\t\t\t\tif selector, exprok := com.Type.(*ast.SelectorExpr); exprok {\n\t\t\t\t\tif x, identok := selector.X.(*ast.Ident); identok {\n\t\t\t\t\t\tif x.Name == a.PackageName {\n\t\t\t\t\t\t\tpackageNameNum++\n\t\t\t\t\t\t\tif selector.Sel.Name == a.StructName {\n\t\t\t\t\t\t\t\tcallExpr.Args = append(callExpr.Args[:i], callExpr.Args[i+1:]...)\n\t\t\t\t\t\t\t\ti--\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\tif packageNameNum == 1 {\n\t\t_ = NewImport(a.ImportPath).Rollback(file)\n\t}\n\treturn nil\n}\n\nfunc (a *PackageInitializeGorm) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\tbizModelDecl := FindFunction(file, \"bizModel\")\n\tif bizModelDecl != nil {\n\t\ta.addDbVar(bizModelDecl.Body)\n\t}\n\t// 寻找目标结构\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\t// 总调用的db变量根据business来决定\n\t\tvarDB := a.Business + \"Db\"\n\n\t\tif a.Business == \"\" {\n\t\t\tvarDB = \"db\"\n\t\t}\n\n\t\tcallExpr, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn true\n\t\t}\n\n\t\t// 检查是不是 db.AutoMigrate() 方法\n\t\tselExpr, ok := callExpr.Fun.(*ast.SelectorExpr)\n\t\tif !ok || selExpr.Sel.Name != \"AutoMigrate\" {\n\t\t\treturn true\n\t\t}\n\n\t\t// 检查调用方是不是 db\n\t\tident, ok := selExpr.X.(*ast.Ident)\n\t\tif !ok || ident.Name != varDB {\n\t\t\treturn true\n\t\t}\n\n\t\t// 添加结构体参数\n\t\tcallExpr.Args = append(callExpr.Args, &ast.CompositeLit{\n\t\t\tType: &ast.SelectorExpr{\n\t\t\t\tX:   ast.NewIdent(a.PackageName),\n\t\t\t\tSel: ast.NewIdent(a.StructName),\n\t\t\t},\n\t\t})\n\t\treturn true\n\t})\n\treturn nil\n}\n\nfunc (a *PackageInitializeGorm) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n\n// 创建businessDB变量\nfunc (a *PackageInitializeGorm) addDbVar(astBody *ast.BlockStmt) {\n\tfor i := range astBody.List {\n\t\tif assignStmt, ok := astBody.List[i].(*ast.AssignStmt); ok {\n\t\t\tif ident, ok := assignStmt.Lhs[0].(*ast.Ident); ok {\n\t\t\t\tif (a.Business == \"\" && ident.Name == \"db\") || ident.Name == a.Business+\"Db\" {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 添加 businessDb := global.GetGlobalDBByDBName(\"business\") 变量\n\tassignNode := &ast.AssignStmt{\n\t\tLhs: []ast.Expr{\n\t\t\t&ast.Ident{\n\t\t\t\tName: a.Business + \"Db\",\n\t\t\t},\n\t\t},\n\t\tTok: token.DEFINE,\n\t\tRhs: []ast.Expr{\n\t\t\t&ast.CallExpr{\n\t\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\t\tX: &ast.Ident{\n\t\t\t\t\t\tName: \"global\",\n\t\t\t\t\t},\n\t\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\t\tName: \"GetGlobalDBByDBName\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t&ast.BasicLit{\n\t\t\t\t\t\tKind:  token.STRING,\n\t\t\t\t\t\tValue: fmt.Sprintf(\"\\\"%s\\\"\", a.Business),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// 添加 businessDb.AutoMigrate() 方法\n\tautoMigrateCall := &ast.ExprStmt{\n\t\tX: &ast.CallExpr{\n\t\t\tFun: &ast.SelectorExpr{\n\t\t\t\tX: &ast.Ident{\n\t\t\t\t\tName: a.Business + \"Db\",\n\t\t\t\t},\n\t\t\t\tSel: &ast.Ident{\n\t\t\t\t\tName: \"AutoMigrate\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturnNode := astBody.List[len(astBody.List)-1]\n\tastBody.List = append(astBody.List[:len(astBody.List)-1], assignNode, autoMigrateCall, returnNode)\n}\n"
  },
  {
    "path": "server/utils/ast/package_initialize_gorm_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPackageInitializeGorm_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tStructName  string\n\t\tPackageName string\n\t\tIsNew       bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 &example.ExaFileUploadAndDownload{} 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaFileUploadAndDownload\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 &example.ExaCustomer{} 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaCustomer\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(example.ExaFileUploadAndDownload) 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaFileUploadAndDownload\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(example.ExaCustomer) 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaCustomer\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageInitializeGorm{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tIsNew:       tt.fields.IsNew,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPackageInitializeGorm_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tStructName  string\n\t\tPackageName string\n\t\tIsNew       bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 &example.ExaFileUploadAndDownload{} 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaFileUploadAndDownload\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 &example.ExaCustomer{} 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaCustomer\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(example.ExaFileUploadAndDownload) 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaFileUploadAndDownload\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(example.ExaCustomer) 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"gorm_biz.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/model/example\"`,\n\t\t\t\tStructName:  \"ExaCustomer\",\n\t\t\t\tPackageName: \"example\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageInitializeGorm{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tIsNew:       tt.fields.IsNew,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/package_initialize_router.go",
    "content": "package ast\n\nimport (\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n)\n\n// PackageInitializeRouter 包初始化路由\n// ModuleName := PackageName.AppName.GroupName\n// ModuleName.FunctionName(RouterGroupName)\ntype PackageInitializeRouter struct {\n\tBase\n\tType                 Type   // 类型\n\tPath                 string // 文件路径\n\tImportPath           string // 导包路径\n\tRelativePath         string // 相对路径\n\tAppName              string // 应用名称\n\tGroupName            string // 分组名称\n\tModuleName           string // 模块名称\n\tPackageName          string // 包名\n\tFunctionName         string // 函数名\n\tRouterGroupName      string // 路由分组名称\n\tLeftRouterGroupName  string // 左路由分组名称\n\tRightRouterGroupName string // 右路由分组名称\n}\n\nfunc (a *PackageInitializeRouter) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PackageInitializeRouter) Rollback(file *ast.File) error {\n\tfuncDecl := FindFunction(file, \"initBizRouter\")\n\texprNum := 0\n\tfor i := range funcDecl.Body.List {\n\t\tif IsBlockStmt(funcDecl.Body.List[i]) {\n\t\t\tif VariableExistsInBlock(funcDecl.Body.List[i].(*ast.BlockStmt), a.ModuleName) {\n\t\t\t\tfor ii, stmt := range funcDecl.Body.List[i].(*ast.BlockStmt).List {\n\t\t\t\t\t// 检查语句是否为 *ast.ExprStmt\n\t\t\t\t\texprStmt, ok := stmt.(*ast.ExprStmt)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\t// 检查表达式是否为 *ast.CallExpr\n\t\t\t\t\tcallExpr, ok := exprStmt.X.(*ast.CallExpr)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\t// 检查是否调用了我们正在寻找的函数\n\t\t\t\t\tselExpr, ok := callExpr.Fun.(*ast.SelectorExpr)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\t// 检查调用的函数是否为 systemRouter.InitApiRouter\n\t\t\t\t\tident, ok := selExpr.X.(*ast.Ident)\n\t\t\t\t\t//只要存在调用则+1\n\t\t\t\t\tif ok && ident.Name == a.ModuleName {\n\t\t\t\t\t\texprNum++\n\t\t\t\t\t}\n\t\t\t\t\t//判断是否为目标结构\n\t\t\t\t\tif !ok || ident.Name != a.ModuleName || selExpr.Sel.Name != a.FunctionName {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\texprNum--\n\t\t\t\t\t// 从语句列表中移除。\n\t\t\t\t\tfuncDecl.Body.List[i].(*ast.BlockStmt).List = append(funcDecl.Body.List[i].(*ast.BlockStmt).List[:ii], funcDecl.Body.List[i].(*ast.BlockStmt).List[ii+1:]...)\n\t\t\t\t\t// 如果不再存在任何调用，则删除导入和变量。\n\t\t\t\t\tif exprNum == 0 {\n\t\t\t\t\t\tfuncDecl.Body.List = append(funcDecl.Body.List[:i], funcDecl.Body.List[i+1:]...)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *PackageInitializeRouter) Injection(file *ast.File) error {\n\tfuncDecl := FindFunction(file, \"initBizRouter\")\n\thasRouter := false\n\tvar varBlock *ast.BlockStmt\n\tfor i := range funcDecl.Body.List {\n\t\tif IsBlockStmt(funcDecl.Body.List[i]) {\n\t\t\tif VariableExistsInBlock(funcDecl.Body.List[i].(*ast.BlockStmt), a.ModuleName) {\n\t\t\t\thasRouter = true\n\t\t\t\tvarBlock = funcDecl.Body.List[i].(*ast.BlockStmt)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif !hasRouter {\n\t\tstmt := a.CreateAssignStmt()\n\t\tvarBlock = &ast.BlockStmt{\n\t\t\tList: []ast.Stmt{\n\t\t\t\tstmt,\n\t\t\t},\n\t\t}\n\t}\n\trouterStmt := CreateStmt(fmt.Sprintf(\"%s.%s(%s,%s)\", a.ModuleName, a.FunctionName, a.LeftRouterGroupName, a.RightRouterGroupName))\n\tvarBlock.List = append(varBlock.List, routerStmt)\n\tif !hasRouter {\n\t\tfuncDecl.Body.List = append(funcDecl.Body.List, varBlock)\n\t}\n\treturn nil\n}\n\nfunc (a *PackageInitializeRouter) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n\nfunc (a *PackageInitializeRouter) CreateAssignStmt() *ast.AssignStmt {\n\t//创建左侧变量\n\tident := &ast.Ident{\n\t\tName: a.ModuleName,\n\t}\n\n\t//创建右侧的赋值语句\n\tselector := &ast.SelectorExpr{\n\t\tX: &ast.SelectorExpr{\n\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\tSel: &ast.Ident{Name: a.AppName},\n\t\t},\n\t\tSel: &ast.Ident{Name: a.GroupName},\n\t}\n\n\t// 创建一个组合的赋值语句\n\tstmt := &ast.AssignStmt{\n\t\tLhs: []ast.Expr{ident},\n\t\tTok: token.DEFINE,\n\t\tRhs: []ast.Expr{selector},\n\t}\n\n\treturn stmt\n}\n"
  },
  {
    "path": "server/utils/ast/package_initialize_router_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPackageInitializeRouter_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType            Type\n\t\tPath            string\n\t\tImportPath      string\n\t\tAppName         string\n\t\tGroupName       string\n\t\tModuleName      string\n\t\tPackageName     string\n\t\tFunctionName    string\n\t\tRouterGroupName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 InitCustomerRouter 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePackageInitializeRouter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"router_biz.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/router\"`,\n\t\t\t\tAppName:         \"RouterGroupApp\",\n\t\t\t\tGroupName:       \"Example\",\n\t\t\t\tModuleName:      \"exampleRouter\",\n\t\t\t\tPackageName:     \"router\",\n\t\t\t\tFunctionName:    \"InitCustomerRouter\",\n\t\t\t\tRouterGroupName: \"privateGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 InitFileUploadAndDownloadRouter 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePackageInitializeRouter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"router_biz.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/router\"`,\n\t\t\t\tAppName:         \"RouterGroupApp\",\n\t\t\t\tGroupName:       \"Example\",\n\t\t\t\tModuleName:      \"exampleRouter\",\n\t\t\t\tPackageName:     \"router\",\n\t\t\t\tFunctionName:    \"InitFileUploadAndDownloadRouter\",\n\t\t\t\tRouterGroupName: \"privateGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageInitializeRouter{\n\t\t\t\tType:                 tt.fields.Type,\n\t\t\t\tPath:                 tt.fields.Path,\n\t\t\t\tImportPath:           tt.fields.ImportPath,\n\t\t\t\tAppName:              tt.fields.AppName,\n\t\t\t\tGroupName:            tt.fields.GroupName,\n\t\t\t\tModuleName:           tt.fields.ModuleName,\n\t\t\t\tPackageName:          tt.fields.PackageName,\n\t\t\t\tFunctionName:         tt.fields.FunctionName,\n\t\t\t\tRouterGroupName:      tt.fields.RouterGroupName,\n\t\t\t\tLeftRouterGroupName:  \"privateGroup\",\n\t\t\t\tRightRouterGroupName: \"publicGroup\",\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPackageInitializeRouter_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType            Type\n\t\tPath            string\n\t\tImportPath      string\n\t\tAppName         string\n\t\tGroupName       string\n\t\tModuleName      string\n\t\tPackageName     string\n\t\tFunctionName    string\n\t\tRouterGroupName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\n\t\t{\n\t\t\tname: \"测试 InitCustomerRouter 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePackageInitializeRouter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"router_biz.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/router\"`,\n\t\t\t\tAppName:         \"RouterGroupApp\",\n\t\t\t\tGroupName:       \"Example\",\n\t\t\t\tModuleName:      \"exampleRouter\",\n\t\t\t\tPackageName:     \"router\",\n\t\t\t\tFunctionName:    \"InitCustomerRouter\",\n\t\t\t\tRouterGroupName: \"privateGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 InitFileUploadAndDownloadRouter 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePackageInitializeRouter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"initialize\", \"router_biz.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/router\"`,\n\t\t\t\tAppName:         \"RouterGroupApp\",\n\t\t\t\tGroupName:       \"Example\",\n\t\t\t\tModuleName:      \"exampleRouter\",\n\t\t\t\tPackageName:     \"router\",\n\t\t\t\tFunctionName:    \"InitFileUploadAndDownloadRouter\",\n\t\t\t\tRouterGroupName: \"privateGroup\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageInitializeRouter{\n\t\t\t\tType:            tt.fields.Type,\n\t\t\t\tPath:            tt.fields.Path,\n\t\t\t\tImportPath:      tt.fields.ImportPath,\n\t\t\t\tAppName:         tt.fields.AppName,\n\t\t\t\tGroupName:       tt.fields.GroupName,\n\t\t\t\tModuleName:      tt.fields.ModuleName,\n\t\t\t\tPackageName:     tt.fields.PackageName,\n\t\t\t\tFunctionName:    tt.fields.FunctionName,\n\t\t\t\tRouterGroupName: tt.fields.RouterGroupName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/package_module_enter.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n)\n\n// PackageModuleEnter 模块化入口\n// ModuleName := PackageName.AppName.GroupName.ServiceName\ntype PackageModuleEnter struct {\n\tBase\n\tType         Type   // 类型\n\tPath         string // 文件路径\n\tImportPath   string // 导包路径\n\tRelativePath string // 相对路径\n\tStructName   string // 结构体名称\n\tAppName      string // 应用名称\n\tGroupName    string // 分组名称\n\tModuleName   string // 模块名称\n\tPackageName  string // 包名\n\tServiceName  string // 服务名称\n}\n\nfunc (a *PackageModuleEnter) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PackageModuleEnter) Rollback(file *ast.File) error {\n\tfor i := 0; i < len(file.Decls); i++ {\n\t\tv1, o1 := file.Decls[i].(*ast.GenDecl)\n\t\tif o1 {\n\t\t\tfor j := 0; j < len(v1.Specs); j++ {\n\t\t\t\tv2, o2 := v1.Specs[j].(*ast.TypeSpec)\n\t\t\t\tif o2 {\n\t\t\t\t\tif v2.Name.Name != a.Type.Group() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tv3, o3 := v2.Type.(*ast.StructType)\n\t\t\t\t\tif o3 {\n\t\t\t\t\t\tfor k := 0; k < len(v3.Fields.List); k++ {\n\t\t\t\t\t\t\tv4, o4 := v3.Fields.List[k].Type.(*ast.Ident)\n\t\t\t\t\t\t\tif o4 && v4.Name == a.StructName {\n\t\t\t\t\t\t\t\tv3.Fields.List = append(v3.Fields.List[:k], v3.Fields.List[k+1:]...)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif a.Type == TypePackageServiceModuleEnter {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tv3, o3 := v1.Specs[j].(*ast.ValueSpec)\n\t\t\t\tif o3 {\n\t\t\t\t\tif len(v3.Names) == 1 && v3.Names[0].Name == a.ModuleName {\n\t\t\t\t\t\tv1.Specs = append(v1.Specs[:j], v1.Specs[j+1:]...)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif v1.Tok == token.VAR && len(v1.Specs) == 0 {\n\t\t\t\t\t_ = NewImport(a.ImportPath).Rollback(file)\n\t\t\t\t\tif i == len(file.Decls) {\n\t\t\t\t\t\tfile.Decls = append(file.Decls[:i-1])\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} // 空的var(), 如果不删除则会影响的注入变量, 因为识别不到*ast.ValueSpec\n\t\t\t\t\tfile.Decls = append(file.Decls[:i], file.Decls[i+1:]...)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *PackageModuleEnter) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\tvar hasValue bool\n\tvar hasVariables bool\n\tfor i := 0; i < len(file.Decls); i++ {\n\t\tv1, o1 := file.Decls[i].(*ast.GenDecl)\n\t\tif o1 {\n\t\t\tif v1.Tok == token.VAR {\n\t\t\t\thasVariables = true\n\t\t\t}\n\t\t\tfor j := 0; j < len(v1.Specs); j++ {\n\t\t\t\tif a.Type == TypePackageServiceModuleEnter {\n\t\t\t\t\thasValue = true\n\t\t\t\t}\n\t\t\t\tv2, o2 := v1.Specs[j].(*ast.TypeSpec)\n\t\t\t\tif o2 {\n\t\t\t\t\tif v2.Name.Name != a.Type.Group() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tv3, o3 := v2.Type.(*ast.StructType)\n\t\t\t\t\tif o3 {\n\t\t\t\t\t\tvar hasStruct bool\n\t\t\t\t\t\tfor k := 0; k < len(v3.Fields.List); k++ {\n\t\t\t\t\t\t\tv4, o4 := v3.Fields.List[k].Type.(*ast.Ident)\n\t\t\t\t\t\t\tif o4 && v4.Name == a.StructName {\n\t\t\t\t\t\t\t\thasStruct = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !hasStruct {\n\t\t\t\t\t\t\tfield := &ast.Field{Type: &ast.Ident{Name: a.StructName}}\n\t\t\t\t\t\t\tv3.Fields.List = append(v3.Fields.List, field)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tv3, o3 := v1.Specs[j].(*ast.ValueSpec)\n\t\t\t\tif o3 {\n\t\t\t\t\thasVariables = true\n\t\t\t\t\tif len(v3.Names) == 1 && v3.Names[0].Name == a.ModuleName {\n\t\t\t\t\t\thasValue = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif v1.Tok == token.VAR && len(v1.Specs) == 0 {\n\t\t\t\t\thasVariables = false\n\t\t\t\t} // 说明是空var()\n\t\t\t\tif hasVariables && !hasValue {\n\t\t\t\t\tspec := &ast.ValueSpec{\n\t\t\t\t\t\tNames: []*ast.Ident{{Name: a.ModuleName}},\n\t\t\t\t\t\tValues: []ast.Expr{\n\t\t\t\t\t\t\t&ast.SelectorExpr{\n\t\t\t\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.AppName},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.GroupName},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.ServiceName},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\tv1.Specs = append(v1.Specs, spec)\n\t\t\t\t\thasValue = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif !hasValue && !hasVariables {\n\t\tdecl := &ast.GenDecl{\n\t\t\tTok: token.VAR,\n\t\t\tSpecs: []ast.Spec{\n\t\t\t\t&ast.ValueSpec{\n\t\t\t\t\tNames: []*ast.Ident{{Name: a.ModuleName}},\n\t\t\t\t\tValues: []ast.Expr{\n\t\t\t\t\t\t&ast.SelectorExpr{\n\t\t\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.AppName},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.GroupName},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.ServiceName},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tfile.Decls = append(file.Decls, decl)\n\t}\n\treturn nil\n}\n\nfunc (a *PackageModuleEnter) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/package_module_enter_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPackageModuleEnter_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tStructName  string\n\t\tAppName     string\n\t\tGroupName   string\n\t\tModuleName  string\n\t\tPackageName string\n\t\tServiceName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 FileUploadAndDownloadRouter 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageRouterModuleEnter,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", \"example\", \"enter.go\"),\n\t\t\t\tImportPath:  `api \"github.com/flipped-aurora/gin-vue-admin/server/api/v1\"`,\n\t\t\t\tStructName:  \"FileUploadAndDownloadRouter\",\n\t\t\t\tAppName:     \"ApiGroupApp\",\n\t\t\t\tGroupName:   \"ExampleApiGroup\",\n\t\t\t\tModuleName:  \"exaFileUploadAndDownloadApi\",\n\t\t\t\tPackageName: \"api\",\n\t\t\t\tServiceName: \"FileUploadAndDownloadApi\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 FileUploadAndDownloadApi 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageApiModuleEnter,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", \"example\", \"enter.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/service\"`,\n\t\t\t\tStructName:  \"FileUploadAndDownloadApi\",\n\t\t\t\tAppName:     \"ServiceGroupApp\",\n\t\t\t\tGroupName:   \"ExampleServiceGroup\",\n\t\t\t\tModuleName:  \"fileUploadAndDownloadService\",\n\t\t\t\tPackageName: \"service\",\n\t\t\t\tServiceName: \"FileUploadAndDownloadService\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 FileUploadAndDownloadService 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageServiceModuleEnter,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", \"example\", \"enter.go\"),\n\t\t\t\tImportPath:  ``,\n\t\t\t\tStructName:  \"FileUploadAndDownloadService\",\n\t\t\t\tAppName:     \"\",\n\t\t\t\tGroupName:   \"\",\n\t\t\t\tModuleName:  \"\",\n\t\t\t\tPackageName: \"\",\n\t\t\t\tServiceName: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageModuleEnter{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tAppName:     tt.fields.AppName,\n\t\t\t\tGroupName:   tt.fields.GroupName,\n\t\t\t\tModuleName:  tt.fields.ModuleName,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tServiceName: tt.fields.ServiceName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPackageModuleEnter_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tStructName  string\n\t\tAppName     string\n\t\tGroupName   string\n\t\tModuleName  string\n\t\tPackageName string\n\t\tServiceName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 FileUploadAndDownloadRouter 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageRouterModuleEnter,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"router\", \"example\", \"enter.go\"),\n\t\t\t\tImportPath:  `api \"github.com/flipped-aurora/gin-vue-admin/server/api/v1\"`,\n\t\t\t\tStructName:  \"FileUploadAndDownloadRouter\",\n\t\t\t\tAppName:     \"ApiGroupApp\",\n\t\t\t\tGroupName:   \"ExampleApiGroup\",\n\t\t\t\tModuleName:  \"exaFileUploadAndDownloadApi\",\n\t\t\t\tPackageName: \"api\",\n\t\t\t\tServiceName: \"FileUploadAndDownloadApi\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 FileUploadAndDownloadApi 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageApiModuleEnter,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"api\", \"v1\", \"example\", \"enter.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/service\"`,\n\t\t\t\tStructName:  \"FileUploadAndDownloadApi\",\n\t\t\t\tAppName:     \"ServiceGroupApp\",\n\t\t\t\tGroupName:   \"ExampleServiceGroup\",\n\t\t\t\tModuleName:  \"fileUploadAndDownloadService\",\n\t\t\t\tPackageName: \"service\",\n\t\t\t\tServiceName: \"FileUploadAndDownloadService\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 FileUploadAndDownloadService 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePackageServiceModuleEnter,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"service\", \"example\", \"enter.go\"),\n\t\t\t\tImportPath:  ``,\n\t\t\t\tStructName:  \"FileUploadAndDownloadService\",\n\t\t\t\tAppName:     \"\",\n\t\t\t\tGroupName:   \"\",\n\t\t\t\tModuleName:  \"\",\n\t\t\t\tPackageName: \"\",\n\t\t\t\tServiceName: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PackageModuleEnter{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tAppName:     tt.fields.AppName,\n\t\t\t\tGroupName:   tt.fields.GroupName,\n\t\t\t\tModuleName:  tt.fields.ModuleName,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tServiceName: tt.fields.ServiceName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_enter.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n)\n\n// PluginEnter 插件化入口\n// ModuleName := PackageName.GroupName.ServiceName\ntype PluginEnter struct {\n\tBase\n\tType            Type   // 类型\n\tPath            string // 文件路径\n\tImportPath      string // 导包路径\n\tRelativePath    string // 相对路径\n\tStructName      string // 结构体名称\n\tStructCamelName string // 结构体小驼峰名称\n\tModuleName      string // 模块名称\n\tGroupName       string // 分组名称\n\tPackageName     string // 包名\n\tServiceName     string // 服务名称\n}\n\nfunc (a *PluginEnter) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PluginEnter) Rollback(file *ast.File) error {\n\t//回滚结构体内内容\n\tvar structType *ast.StructType\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tswitch x := n.(type) {\n\t\tcase *ast.TypeSpec:\n\t\t\tif s, ok := x.Type.(*ast.StructType); ok {\n\t\t\t\tstructType = s\n\t\t\t\tfor i, field := range x.Type.(*ast.StructType).Fields.List {\n\t\t\t\t\tif len(field.Names) > 0 && field.Names[0].Name == a.StructName {\n\t\t\t\t\t\ts.Fields.List = append(s.Fields.List[:i], s.Fields.List[i+1:]...)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\tif len(structType.Fields.List) == 0 {\n\t\t_ = NewImport(a.ImportPath).Rollback(file)\n\t}\n\n\tif a.Type == TypePluginServiceEnter {\n\t\treturn nil\n\t}\n\n\t//回滚变量内容\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tgenDecl, ok := n.(*ast.GenDecl)\n\t\tif ok && genDecl.Tok == token.VAR {\n\t\t\tfor i, spec := range genDecl.Specs {\n\t\t\t\tvalueSpec, vsok := spec.(*ast.ValueSpec)\n\t\t\t\tif vsok {\n\t\t\t\t\tfor _, name := range valueSpec.Names {\n\t\t\t\t\t\tif name.Name == a.ModuleName {\n\t\t\t\t\t\t\tgenDecl.Specs = append(genDecl.Specs[:i], genDecl.Specs[i+1:]...)\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\treturn nil\n}\n\nfunc (a *PluginEnter) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\n\thas := false\n\thasVar := false\n\tvar firstStruct *ast.StructType\n\tvar varSpec *ast.GenDecl\n\t//寻找是否存在结构且定位\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tswitch x := n.(type) {\n\t\tcase *ast.TypeSpec:\n\t\t\tif s, ok := x.Type.(*ast.StructType); ok {\n\t\t\t\tfirstStruct = s\n\t\t\t\tfor _, field := range x.Type.(*ast.StructType).Fields.List {\n\t\t\t\t\tif len(field.Names) > 0 && field.Names[0].Name == a.StructName {\n\t\t\t\t\t\thas = true\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\tif !has {\n\t\tfield := &ast.Field{\n\t\t\tNames: []*ast.Ident{{Name: a.StructName}},\n\t\t\tType:  &ast.Ident{Name: a.StructCamelName},\n\t\t}\n\t\tfirstStruct.Fields.List = append(firstStruct.Fields.List, field)\n\t}\n\n\tif a.Type == TypePluginServiceEnter {\n\t\treturn nil\n\t}\n\n\t//寻找是否存在变量且定位\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tgenDecl, ok := n.(*ast.GenDecl)\n\t\tif ok && genDecl.Tok == token.VAR {\n\t\t\tfor _, spec := range genDecl.Specs {\n\t\t\t\tvalueSpec, vsok := spec.(*ast.ValueSpec)\n\t\t\t\tif vsok {\n\t\t\t\t\tvarSpec = genDecl\n\t\t\t\t\tfor _, name := range valueSpec.Names {\n\t\t\t\t\t\tif name.Name == a.ModuleName {\n\t\t\t\t\t\t\thasVar = true\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\n\tif !hasVar {\n\t\tspec := &ast.ValueSpec{\n\t\t\tNames: []*ast.Ident{{Name: a.ModuleName}},\n\t\t\tValues: []ast.Expr{\n\t\t\t\t&ast.SelectorExpr{\n\t\t\t\t\tX: &ast.SelectorExpr{\n\t\t\t\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\t\t\t\tSel: &ast.Ident{Name: a.GroupName},\n\t\t\t\t\t},\n\t\t\t\t\tSel: &ast.Ident{Name: a.ServiceName},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tvarSpec.Specs = append(varSpec.Specs, spec)\n\t}\n\n\treturn nil\n}\n\nfunc (a *PluginEnter) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_enter_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPluginEnter_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType            Type\n\t\tPath            string\n\t\tImportPath      string\n\t\tStructName      string\n\t\tStructCamelName string\n\t\tModuleName      string\n\t\tGroupName       string\n\t\tPackageName     string\n\t\tServiceName     string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 Gva插件UserApi 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginApiEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"api\", \"enter.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/service\"`,\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"serviceUser\",\n\t\t\t\tGroupName:       \"Service\",\n\t\t\t\tPackageName:     \"service\",\n\t\t\t\tServiceName:     \"User\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 Gva插件UserRouter 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginRouterEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"router\", \"enter.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/api\"`,\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"userApi\",\n\t\t\t\tGroupName:       \"Api\",\n\t\t\t\tPackageName:     \"api\",\n\t\t\t\tServiceName:     \"User\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 Gva插件UserService 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginServiceEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"service\", \"enter.go\"),\n\t\t\t\tImportPath:      \"\",\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"\",\n\t\t\t\tGroupName:       \"\",\n\t\t\t\tPackageName:     \"\",\n\t\t\t\tServiceName:     \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 gva的User 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginServiceEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"service\", \"enter.go\"),\n\t\t\t\tImportPath:      \"\",\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"\",\n\t\t\t\tGroupName:       \"\",\n\t\t\t\tPackageName:     \"\",\n\t\t\t\tServiceName:     \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginEnter{\n\t\t\t\tType:            tt.fields.Type,\n\t\t\t\tPath:            tt.fields.Path,\n\t\t\t\tImportPath:      tt.fields.ImportPath,\n\t\t\t\tStructName:      tt.fields.StructName,\n\t\t\t\tStructCamelName: tt.fields.StructCamelName,\n\t\t\t\tModuleName:      tt.fields.ModuleName,\n\t\t\t\tGroupName:       tt.fields.GroupName,\n\t\t\t\tPackageName:     tt.fields.PackageName,\n\t\t\t\tServiceName:     tt.fields.ServiceName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPluginEnter_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType            Type\n\t\tPath            string\n\t\tImportPath      string\n\t\tStructName      string\n\t\tStructCamelName string\n\t\tModuleName      string\n\t\tGroupName       string\n\t\tPackageName     string\n\t\tServiceName     string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 Gva插件UserRouter 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginRouterEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"router\", \"enter.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/api\"`,\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"userApi\",\n\t\t\t\tGroupName:       \"Api\",\n\t\t\t\tPackageName:     \"api\",\n\t\t\t\tServiceName:     \"User\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 Gva插件UserApi 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginApiEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"api\", \"enter.go\"),\n\t\t\t\tImportPath:      `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/service\"`,\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"serviceUser\",\n\t\t\t\tGroupName:       \"Service\",\n\t\t\t\tPackageName:     \"service\",\n\t\t\t\tServiceName:     \"User\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 Gva插件UserService 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:            TypePluginServiceEnter,\n\t\t\t\tPath:            filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"service\", \"enter.go\"),\n\t\t\t\tImportPath:      \"\",\n\t\t\t\tStructName:      \"User\",\n\t\t\t\tStructCamelName: \"user\",\n\t\t\t\tModuleName:      \"\",\n\t\t\t\tGroupName:       \"\",\n\t\t\t\tPackageName:     \"\",\n\t\t\t\tServiceName:     \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginEnter{\n\t\t\t\tType:            tt.fields.Type,\n\t\t\t\tPath:            tt.fields.Path,\n\t\t\t\tImportPath:      tt.fields.ImportPath,\n\t\t\t\tStructName:      tt.fields.StructName,\n\t\t\t\tStructCamelName: tt.fields.StructCamelName,\n\t\t\t\tModuleName:      tt.fields.ModuleName,\n\t\t\t\tGroupName:       tt.fields.GroupName,\n\t\t\t\tPackageName:     tt.fields.PackageName,\n\t\t\t\tServiceName:     tt.fields.ServiceName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_gen.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n)\n\ntype PluginGen struct {\n\tBase\n\tType         Type   // 类型\n\tPath         string // 文件路径\n\tImportPath   string // 导包路径\n\tRelativePath string // 相对路径\n\tStructName   string // 结构体名称\n\tPackageName  string // 包名\n\tIsNew        bool   // 是否使用new关键字\n}\n\nfunc (a *PluginGen) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\nfunc (a *PluginGen) Rollback(file *ast.File) error {\n\tfor i := 0; i < len(file.Decls); i++ {\n\t\tv1, o1 := file.Decls[i].(*ast.FuncDecl)\n\t\tif o1 {\n\t\t\tfor j := 0; j < len(v1.Body.List); j++ {\n\t\t\t\tv2, o2 := v1.Body.List[j].(*ast.ExprStmt)\n\t\t\t\tif o2 {\n\t\t\t\t\tv3, o3 := v2.X.(*ast.CallExpr)\n\t\t\t\t\tif o3 {\n\t\t\t\t\t\tv4, o4 := v3.Fun.(*ast.SelectorExpr)\n\t\t\t\t\t\tif o4 {\n\t\t\t\t\t\t\tif v4.Sel.Name != \"ApplyBasic\" {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor k := 0; k < len(v3.Args); k++ {\n\t\t\t\t\t\t\t\tv5, o5 := v3.Args[k].(*ast.CallExpr)\n\t\t\t\t\t\t\t\tif o5 {\n\t\t\t\t\t\t\t\t\tv6, o6 := v5.Fun.(*ast.Ident)\n\t\t\t\t\t\t\t\t\tif o6 {\n\t\t\t\t\t\t\t\t\t\tif v6.Name != \"new\" {\n\t\t\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfor l := 0; l < len(v5.Args); l++ {\n\t\t\t\t\t\t\t\t\t\t\tv7, o7 := v5.Args[l].(*ast.SelectorExpr)\n\t\t\t\t\t\t\t\t\t\t\tif o7 {\n\t\t\t\t\t\t\t\t\t\t\t\tv8, o8 := v7.X.(*ast.Ident)\n\t\t\t\t\t\t\t\t\t\t\t\tif o8 {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif v8.Name == a.PackageName && v7.Sel.Name == a.StructName {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tv3.Args = append(v3.Args[:k], v3.Args[k+1:]...)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif k >= len(v3.Args) {\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tv6, o6 := v3.Args[k].(*ast.CompositeLit)\n\t\t\t\t\t\t\t\tif o6 {\n\t\t\t\t\t\t\t\t\tv7, o7 := v6.Type.(*ast.SelectorExpr)\n\t\t\t\t\t\t\t\t\tif o7 {\n\t\t\t\t\t\t\t\t\t\tv8, o8 := v7.X.(*ast.Ident)\n\t\t\t\t\t\t\t\t\t\tif o8 {\n\t\t\t\t\t\t\t\t\t\t\tif v8.Name == a.PackageName && v7.Sel.Name == a.StructName {\n\t\t\t\t\t\t\t\t\t\t\t\tv3.Args = append(v3.Args[:k], v3.Args[k+1:]...)\n\t\t\t\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif len(v3.Args) == 0 {\n\t\t\t\t\t\t\t\t_ = NewImport(a.ImportPath).Rollback(file)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *PluginGen) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\tfor i := 0; i < len(file.Decls); i++ {\n\t\tv1, o1 := file.Decls[i].(*ast.FuncDecl)\n\t\tif o1 {\n\t\t\tfor j := 0; j < len(v1.Body.List); j++ {\n\t\t\t\tv2, o2 := v1.Body.List[j].(*ast.ExprStmt)\n\t\t\t\tif o2 {\n\t\t\t\t\tv3, o3 := v2.X.(*ast.CallExpr)\n\t\t\t\t\tif o3 {\n\t\t\t\t\t\tv4, o4 := v3.Fun.(*ast.SelectorExpr)\n\t\t\t\t\t\tif o4 {\n\t\t\t\t\t\t\tif v4.Sel.Name != \"ApplyBasic\" {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvar has bool\n\t\t\t\t\t\t\tfor k := 0; k < len(v3.Args); k++ {\n\t\t\t\t\t\t\t\tv5, o5 := v3.Args[k].(*ast.CallExpr)\n\t\t\t\t\t\t\t\tif o5 {\n\t\t\t\t\t\t\t\t\tv6, o6 := v5.Fun.(*ast.Ident)\n\t\t\t\t\t\t\t\t\tif o6 {\n\t\t\t\t\t\t\t\t\t\tif v6.Name != \"new\" {\n\t\t\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfor l := 0; l < len(v5.Args); l++ {\n\t\t\t\t\t\t\t\t\t\t\tv7, o7 := v5.Args[l].(*ast.SelectorExpr)\n\t\t\t\t\t\t\t\t\t\t\tif o7 {\n\t\t\t\t\t\t\t\t\t\t\t\tv8, o8 := v7.X.(*ast.Ident)\n\t\t\t\t\t\t\t\t\t\t\t\tif o8 {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif v8.Name == a.PackageName && v7.Sel.Name == a.StructName {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thas = true\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tv6, o6 := v3.Args[k].(*ast.CompositeLit)\n\t\t\t\t\t\t\t\tif o6 {\n\t\t\t\t\t\t\t\t\tv7, o7 := v6.Type.(*ast.SelectorExpr)\n\t\t\t\t\t\t\t\t\tif o7 {\n\t\t\t\t\t\t\t\t\t\tv8, o8 := v7.X.(*ast.Ident)\n\t\t\t\t\t\t\t\t\t\tif o8 {\n\t\t\t\t\t\t\t\t\t\t\tif v8.Name == a.PackageName && v7.Sel.Name == a.StructName {\n\t\t\t\t\t\t\t\t\t\t\t\thas = true\n\t\t\t\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !has {\n\t\t\t\t\t\t\t\tif a.IsNew {\n\t\t\t\t\t\t\t\t\targ := &ast.CallExpr{\n\t\t\t\t\t\t\t\t\t\tFun: &ast.Ident{Name: \"\\n\\t\\tnew\"},\n\t\t\t\t\t\t\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t\t\t\t\t\t\t&ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\t\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\t\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.StructName},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tv3.Args = append(v3.Args, arg)\n\t\t\t\t\t\t\t\t\tv3.Args = append(v3.Args, &ast.BasicLit{\n\t\t\t\t\t\t\t\t\t\tKind:  token.STRING,\n\t\t\t\t\t\t\t\t\t\tValue: \"\\n\",\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\targ := &ast.CompositeLit{\n\t\t\t\t\t\t\t\t\tType: &ast.SelectorExpr{\n\t\t\t\t\t\t\t\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\t\t\t\t\t\t\t\tSel: &ast.Ident{Name: a.StructName},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tv3.Args = append(v3.Args, arg)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *PluginGen) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_gen_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPluginGenModel_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tPackageName string\n\t\tStructName  string\n\t\tIsNew       bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 GvaUser 结构体注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginGen,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"gen\", \"main.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 GvaUser 结构体注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginGen,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"gen\", \"main.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginGen{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tIsNew:       tt.fields.IsNew,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPluginGenModel_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tPackageName string\n\t\tStructName  string\n\t\tIsNew       bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 GvaUser 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginGen,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"gen\", \"main.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 GvaUser 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginGen,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"gen\", \"main.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginGen{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tIsNew:       tt.fields.IsNew,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_initialize_gorm.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"io\"\n)\n\ntype PluginInitializeGorm struct {\n\tBase\n\tType         Type   // 类型\n\tPath         string // 文件路径\n\tImportPath   string // 导包路径\n\tRelativePath string // 相对路径\n\tStructName   string // 结构体名称\n\tPackageName  string // 包名\n\tIsNew        bool   // 是否使用new关键字 true: new(PackageName.StructName) false: &PackageName.StructName{}\n}\n\nfunc (a *PluginInitializeGorm) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PluginInitializeGorm) Rollback(file *ast.File) error {\n\tvar needRollBackImport bool\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tcallExpr, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn true\n\t\t}\n\n\t\tselExpr, seok := callExpr.Fun.(*ast.SelectorExpr)\n\t\tif !seok || selExpr.Sel.Name != \"AutoMigrate\" {\n\t\t\treturn true\n\t\t}\n\t\tif len(callExpr.Args) <= 1 {\n\t\t\tneedRollBackImport = true\n\t\t}\n\t\t// 删除指定的参数\n\t\tfor i, arg := range callExpr.Args {\n\t\t\tcompLit, cok := arg.(*ast.CompositeLit)\n\t\t\tif !cok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcselExpr, sok := compLit.Type.(*ast.SelectorExpr)\n\t\t\tif !sok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tident, idok := cselExpr.X.(*ast.Ident)\n\t\t\tif idok && ident.Name == a.PackageName && cselExpr.Sel.Name == a.StructName {\n\t\t\t\t// 删除参数\n\t\t\t\tcallExpr.Args = append(callExpr.Args[:i], callExpr.Args[i+1:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\treturn true\n\t})\n\n\tif needRollBackImport {\n\t\t_ = NewImport(a.ImportPath).Rollback(file)\n\t}\n\n\treturn nil\n}\n\nfunc (a *PluginInitializeGorm) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\tvar call *ast.CallExpr\n\tast.Inspect(file, func(n ast.Node) bool {\n\t\tcallExpr, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn true\n\t\t}\n\n\t\tselExpr, ok := callExpr.Fun.(*ast.SelectorExpr)\n\t\tif ok && selExpr.Sel.Name == \"AutoMigrate\" {\n\t\t\tcall = callExpr\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t})\n\n\targ := &ast.CompositeLit{\n\t\tType: &ast.SelectorExpr{\n\t\t\tX:   &ast.Ident{Name: a.PackageName},\n\t\t\tSel: &ast.Ident{Name: a.StructName},\n\t\t},\n\t}\n\n\tcall.Args = append(call.Args, arg)\n\treturn nil\n}\n\nfunc (a *PluginInitializeGorm) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_initialize_gorm_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPluginInitializeGorm_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tStructName  string\n\t\tPackageName string\n\t\tIsNew       bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 &model.User{} 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"gorm.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(model.ExaCustomer) 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"gorm.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(model.SysUsers) 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"gorm.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tStructName:  \"SysUser\",\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginInitializeGorm{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tIsNew:       tt.fields.IsNew,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPluginInitializeGorm_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tImportPath  string\n\t\tStructName  string\n\t\tPackageName string\n\t\tIsNew       bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 &model.User{} 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"gorm.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tIsNew:       false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"测试 new(model.ExaCustomer) 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:        TypePluginInitializeGorm,\n\t\t\t\tPath:        filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"gorm.go\"),\n\t\t\t\tImportPath:  `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/model\"`,\n\t\t\t\tStructName:  \"User\",\n\t\t\t\tPackageName: \"model\",\n\t\t\t\tIsNew:       true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginInitializeGorm{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  tt.fields.StructName,\n\t\t\t\tPackageName: tt.fields.PackageName,\n\t\t\t\tIsNew:       tt.fields.IsNew,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_initialize_router.go",
    "content": "package ast\n\nimport (\n\t\"fmt\"\n\t\"go/ast\"\n\t\"io\"\n)\n\n// PluginInitializeRouter 插件初始化路由\n// PackageName.AppName.GroupName.FunctionName()\ntype PluginInitializeRouter struct {\n\tBase\n\tType                 Type   // 类型\n\tPath                 string // 文件路径\n\tImportPath           string // 导包路径\n\tImportGlobalPath     string // 导包全局变量路径\n\tImportMiddlewarePath string // 导包中间件路径\n\tRelativePath         string // 相对路径\n\tAppName              string // 应用名称\n\tGroupName            string // 分组名称\n\tPackageName          string // 包名\n\tFunctionName         string // 函数名\n\tLeftRouterGroupName  string // 左路由分组名称\n\tRightRouterGroupName string // 右路由分组名称\n}\n\nfunc (a *PluginInitializeRouter) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.Path\n\t\t\ta.RelativePath = a.Base.RelativePath(a.Path)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.Path = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PluginInitializeRouter) Rollback(file *ast.File) error {\n\tfuncDecl := FindFunction(file, \"Router\")\n\tdelI := 0\n\trouterNum := 0\n\tfor i := len(funcDecl.Body.List) - 1; i >= 0; i-- {\n\t\tstmt, ok := funcDecl.Body.List[i].(*ast.ExprStmt)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tcallExpr, ok := stmt.X.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tselExpr, ok := callExpr.Fun.(*ast.SelectorExpr)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tident, ok := selExpr.X.(*ast.SelectorExpr)\n\n\t\tif ok {\n\t\t\tif iExpr, ieok := ident.X.(*ast.SelectorExpr); ieok {\n\t\t\t\tif iden, idok := iExpr.X.(*ast.Ident); idok {\n\t\t\t\t\tif iden.Name == \"router\" {\n\t\t\t\t\t\trouterNum++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ident.Sel.Name == a.GroupName && selExpr.Sel.Name == a.FunctionName {\n\t\t\t\t// 删除语句\n\t\t\t\tdelI = i\n\t\t\t}\n\t\t}\n\t}\n\n\tfuncDecl.Body.List = append(funcDecl.Body.List[:delI], funcDecl.Body.List[delI+1:]...)\n\n\tif routerNum <= 1 {\n\t\t_ = NewImport(a.ImportPath).Rollback(file)\n\t}\n\n\treturn nil\n}\n\nfunc (a *PluginInitializeRouter) Injection(file *ast.File) error {\n\t_ = NewImport(a.ImportPath).Injection(file)\n\tfuncDecl := FindFunction(file, \"Router\")\n\n\tvar exists bool\n\n\tast.Inspect(funcDecl, func(n ast.Node) bool {\n\t\tcallExpr, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn true\n\t\t}\n\n\t\tselExpr, ok := callExpr.Fun.(*ast.SelectorExpr)\n\t\tif !ok {\n\t\t\treturn true\n\t\t}\n\n\t\tident, ok := selExpr.X.(*ast.SelectorExpr)\n\t\tif ok && ident.Sel.Name == a.GroupName && selExpr.Sel.Name == a.FunctionName {\n\t\t\texists = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\n\tif !exists {\n\t\tstmtStr := fmt.Sprintf(\"%s.%s.%s.%s(%s, %s)\", a.PackageName, a.AppName, a.GroupName, a.FunctionName, a.LeftRouterGroupName, a.RightRouterGroupName)\n\t\tstmt := CreateStmt(stmtStr)\n\t\tfuncDecl.Body.List = append(funcDecl.Body.List, stmt)\n\t}\n\treturn nil\n}\n\nfunc (a *PluginInitializeRouter) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.Path\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_initialize_router_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPluginInitializeRouter_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType                 Type\n\t\tPath                 string\n\t\tImportPath           string\n\t\tAppName              string\n\t\tGroupName            string\n\t\tPackageName          string\n\t\tFunctionName         string\n\t\tLeftRouterGroupName  string\n\t\tRightRouterGroupName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 Gva插件User 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:                 TypePluginInitializeRouter,\n\t\t\t\tPath:                 filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"router.go\"),\n\t\t\t\tImportPath:           `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/router\"`,\n\t\t\t\tAppName:              \"Router\",\n\t\t\t\tGroupName:            \"User\",\n\t\t\t\tPackageName:          \"router\",\n\t\t\t\tFunctionName:         \"Init\",\n\t\t\t\tLeftRouterGroupName:  \"public\",\n\t\t\t\tRightRouterGroupName: \"private\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 中文 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:                 TypePluginInitializeRouter,\n\t\t\t\tPath:                 filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"router.go\"),\n\t\t\t\tImportPath:           `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/router\"`,\n\t\t\t\tAppName:              \"Router\",\n\t\t\t\tGroupName:            \"U中文\",\n\t\t\t\tPackageName:          \"router\",\n\t\t\t\tFunctionName:         \"Init\",\n\t\t\t\tLeftRouterGroupName:  \"public\",\n\t\t\t\tRightRouterGroupName: \"private\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginInitializeRouter{\n\t\t\t\tType:                 tt.fields.Type,\n\t\t\t\tPath:                 tt.fields.Path,\n\t\t\t\tImportPath:           tt.fields.ImportPath,\n\t\t\t\tAppName:              tt.fields.AppName,\n\t\t\t\tGroupName:            tt.fields.GroupName,\n\t\t\t\tPackageName:          tt.fields.PackageName,\n\t\t\t\tFunctionName:         tt.fields.FunctionName,\n\t\t\t\tLeftRouterGroupName:  tt.fields.LeftRouterGroupName,\n\t\t\t\tRightRouterGroupName: tt.fields.RightRouterGroupName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPluginInitializeRouter_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType                 Type\n\t\tPath                 string\n\t\tImportPath           string\n\t\tAppName              string\n\t\tGroupName            string\n\t\tPackageName          string\n\t\tFunctionName         string\n\t\tLeftRouterGroupName  string\n\t\tRightRouterGroupName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 Gva插件User 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:                 TypePluginInitializeRouter,\n\t\t\t\tPath:                 filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"router.go\"),\n\t\t\t\tImportPath:           `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/router\"`,\n\t\t\t\tAppName:              \"Router\",\n\t\t\t\tGroupName:            \"User\",\n\t\t\t\tPackageName:          \"router\",\n\t\t\t\tFunctionName:         \"Init\",\n\t\t\t\tLeftRouterGroupName:  \"public\",\n\t\t\t\tRightRouterGroupName: \"private\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"测试 中文 注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:                 TypePluginInitializeRouter,\n\t\t\t\tPath:                 filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"initialize\", \"router.go\"),\n\t\t\t\tImportPath:           `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva/router\"`,\n\t\t\t\tAppName:              \"Router\",\n\t\t\t\tGroupName:            \"U中文\",\n\t\t\t\tPackageName:          \"router\",\n\t\t\t\tFunctionName:         \"Init\",\n\t\t\t\tLeftRouterGroupName:  \"public\",\n\t\t\t\tRightRouterGroupName: \"private\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := &PluginInitializeRouter{\n\t\t\t\tType:                 tt.fields.Type,\n\t\t\t\tPath:                 tt.fields.Path,\n\t\t\t\tImportPath:           tt.fields.ImportPath,\n\t\t\t\tAppName:              tt.fields.AppName,\n\t\t\t\tGroupName:            tt.fields.GroupName,\n\t\t\t\tPackageName:          tt.fields.PackageName,\n\t\t\t\tFunctionName:         tt.fields.FunctionName,\n\t\t\t\tLeftRouterGroupName:  tt.fields.LeftRouterGroupName,\n\t\t\t\tRightRouterGroupName: tt.fields.RightRouterGroupName,\n\t\t\t}\n\t\t\tfile, err := a.Parse(a.Path, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(a.Path, nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_initialize_v2.go",
    "content": "package ast\n\nimport (\n\t\"go/ast\"\n\t\"go/token\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype PluginInitializeV2 struct {\n\tBase\n\tType         Type   // 类型\n\tPath         string // 文件路径\n\tPluginPath   string // 插件路径\n\tRelativePath string // 相对路径\n\tImportPath   string // 导包路径\n\tStructName   string // 结构体名称\n\tPackageName  string // 包名\n}\n\nfunc (a *PluginInitializeV2) Parse(filename string, writer io.Writer) (file *ast.File, err error) {\n\tif filename == \"\" {\n\t\tif a.RelativePath == \"\" {\n\t\t\tfilename = a.PluginPath\n\t\t\ta.RelativePath = a.Base.RelativePath(a.PluginPath)\n\t\t\treturn a.Base.Parse(filename, writer)\n\t\t}\n\t\ta.PluginPath = a.Base.AbsolutePath(a.RelativePath)\n\t\tfilename = a.PluginPath\n\t}\n\treturn a.Base.Parse(filename, writer)\n}\n\nfunc (a *PluginInitializeV2) Injection(file *ast.File) error {\n\timportPath := strings.TrimSpace(a.ImportPath)\n\tif importPath == \"\" {\n\t\treturn nil\n\t}\n\timportPath = strings.Trim(importPath, \"\\\"\")\n\tif importPath == \"\" || CheckImport(file, importPath) {\n\t\treturn nil\n\t}\n\n\timportSpec := &ast.ImportSpec{\n\t\tName: ast.NewIdent(\"_\"),\n\t\tPath: &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(importPath)},\n\t}\n\tvar importDecl *ast.GenDecl\n\tfor _, decl := range file.Decls {\n\t\tgenDecl, ok := decl.(*ast.GenDecl)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif genDecl.Tok == token.IMPORT {\n\t\t\timportDecl = genDecl\n\t\t\tbreak\n\t\t}\n\t}\n\tif importDecl == nil {\n\t\tfile.Decls = append([]ast.Decl{\n\t\t\t&ast.GenDecl{\n\t\t\t\tTok:   token.IMPORT,\n\t\t\t\tSpecs: []ast.Spec{importSpec},\n\t\t\t},\n\t\t}, file.Decls...)\n\t\treturn nil\n\t}\n\timportDecl.Specs = append(importDecl.Specs, importSpec)\n\treturn nil\n}\n\nfunc (a *PluginInitializeV2) Rollback(file *ast.File) error {\n\treturn nil\n}\n\nfunc (a *PluginInitializeV2) Format(filename string, writer io.Writer, file *ast.File) error {\n\tif filename == \"\" {\n\t\tfilename = a.PluginPath\n\t}\n\treturn a.Base.Format(filename, writer, file)\n}\n"
  },
  {
    "path": "server/utils/ast/plugin_initialize_v2_test.go",
    "content": "package ast\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestPluginInitialize_Injection(t *testing.T) {\n\ttype fields struct {\n\t\tType       Type\n\t\tPath       string\n\t\tPluginPath string\n\t\tImportPath string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 Gva插件 注册注入\",\n\t\t\tfields: fields{\n\t\t\t\tType:       TypePluginInitializeV2,\n\t\t\t\tPath:       filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"plugin.go\"),\n\t\t\t\tPluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"register.go\"),\n\t\t\t\tImportPath: `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva\"`,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := PluginInitializeV2{\n\t\t\t\tType:       tt.fields.Type,\n\t\t\t\tPath:       tt.fields.Path,\n\t\t\t\tPluginPath: tt.fields.PluginPath,\n\t\t\t\tImportPath: tt.fields.ImportPath,\n\t\t\t}\n\t\t\tfile, err := a.Parse(\"\", nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Injection(file)\n\t\t\terr = a.Format(\"\", nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Injection() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPluginInitialize_Rollback(t *testing.T) {\n\ttype fields struct {\n\t\tType        Type\n\t\tPath        string\n\t\tPluginPath  string\n\t\tImportPath  string\n\t\tPluginName  string\n\t\tStructName  string\n\t\tPackageName string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\tfields  fields\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"测试 Gva插件 回滚\",\n\t\t\tfields: fields{\n\t\t\t\tType:       TypePluginInitializeV2,\n\t\t\t\tPath:       filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"gva\", \"plugin.go\"),\n\t\t\t\tPluginPath: filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, \"plugin\", \"register.go\"),\n\t\t\t\tImportPath: `\"github.com/flipped-aurora/gin-vue-admin/server/plugin/gva\"`,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := PluginInitializeV2{\n\t\t\t\tType:        tt.fields.Type,\n\t\t\t\tPath:        tt.fields.Path,\n\t\t\t\tPluginPath:  tt.fields.PluginPath,\n\t\t\t\tImportPath:  tt.fields.ImportPath,\n\t\t\t\tStructName:  \"Plugin\",\n\t\t\t\tPackageName: \"gva\",\n\t\t\t}\n\t\t\tfile, err := a.Parse(\"\", nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\ta.Rollback(file)\n\t\t\terr = a.Format(\"\", nil, file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rollback() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/autocode/template_funcs.go",
    "content": "package autocode\n\nimport (\n\t\"fmt\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"slices\"\n\t\"strings\"\n\t\"text/template\"\n)\n\n// GetTemplateFuncMap 返回模板函数映射，用于在模板中使用\nfunc GetTemplateFuncMap() template.FuncMap {\n\treturn template.FuncMap{\n\t\t\"title\":                    strings.Title,\n\t\t\"GenerateField\":            GenerateField,\n\t\t\"GenerateSearchField\":      GenerateSearchField,\n\t\t\"GenerateSearchConditions\": GenerateSearchConditions,\n\t\t\"GenerateSearchFormItem\":   GenerateSearchFormItem,\n\t\t\"GenerateTableColumn\":      GenerateTableColumn,\n\t\t\"GenerateFormItem\":         GenerateFormItem,\n\t\t\"GenerateDescriptionItem\":  GenerateDescriptionItem,\n\t\t\"GenerateDefaultFormValue\": GenerateDefaultFormValue,\n\t}\n}\n\n// 渲染Model中的字段\nfunc GenerateField(field systemReq.AutoCodeField) string {\n\t// 构建gorm标签\n\tgormTag := ``\n\n\tif field.FieldIndexType != \"\" {\n\t\tgormTag += field.FieldIndexType + \";\"\n\t}\n\n\tif field.PrimaryKey {\n\t\tgormTag += \"primarykey;\"\n\t}\n\n\tif field.DefaultValue != \"\" {\n\t\tgormTag += fmt.Sprintf(\"default:%s;\", field.DefaultValue)\n\t}\n\n\tif field.Comment != \"\" {\n\t\tgormTag += fmt.Sprintf(\"comment:%s;\", field.Comment)\n\t}\n\n\tgormTag += \"column:\" + field.ColumnName + \";\"\n\n\t// 对于int类型，根据DataTypeLong决定具体的Go类型，不使用size标签\n\tif field.DataTypeLong != \"\" && field.FieldType != \"enum\" && field.FieldType != \"int\" {\n\t\tgormTag += fmt.Sprintf(\"size:%s;\", field.DataTypeLong)\n\t}\n\n\trequireTag := ` binding:\"required\"` + \"`\"\n\n\t// 根据字段类型构建不同的字段定义\n\tvar result string\n\tswitch field.FieldType {\n\tcase \"enum\":\n\t\tresult = fmt.Sprintf(`%s  string `+\"`\"+`json:\"%s\" form:\"%s\" gorm:\"%stype:enum(%s);\"`+\"`\",\n\t\t\tfield.FieldName, field.FieldJson, field.FieldJson, gormTag, field.DataTypeLong)\n\tcase \"picture\", \"video\":\n\t\ttagContent := fmt.Sprintf(`json:\"%s\" form:\"%s\" gorm:\"%s\"`,\n\t\t\tfield.FieldJson, field.FieldJson, gormTag)\n\n\t\tresult = fmt.Sprintf(`%s  string `+\"`\"+`%s`+\"`\"+``, field.FieldName, tagContent)\n\tcase \"file\", \"pictures\", \"array\":\n\t\ttagContent := fmt.Sprintf(`json:\"%s\" form:\"%s\" gorm:\"%s\"`,\n\t\t\tfield.FieldJson, field.FieldJson, gormTag)\n\n\t\tresult = fmt.Sprintf(`%s  datatypes.JSON `+\"`\"+`%s swaggertype:\"array,object\"`+\"`\"+``,\n\t\t\tfield.FieldName, tagContent)\n\tcase \"richtext\":\n\t\ttagContent := fmt.Sprintf(`json:\"%s\" form:\"%s\" gorm:\"%s`,\n\t\t\tfield.FieldJson, field.FieldJson, gormTag)\n\n\t\tresult = fmt.Sprintf(`%s  *string `+\"`\"+`%stype:text;\"`+\"`\"+``,\n\t\t\tfield.FieldName, tagContent)\n\tcase \"json\":\n\t\ttagContent := fmt.Sprintf(`json:\"%s\" form:\"%s\" gorm:\"%s\"`,\n\t\t\tfield.FieldJson, field.FieldJson, gormTag)\n\n\t\tresult = fmt.Sprintf(`%s  datatypes.JSON `+\"`\"+`%s swaggertype:\"object\"`+\"`\"+``,\n\t\t\tfield.FieldName, tagContent)\n\tdefault:\n\t\ttagContent := fmt.Sprintf(`json:\"%s\" form:\"%s\" gorm:\"%s\"`,\n\t\t\tfield.FieldJson, field.FieldJson, gormTag)\n\n\t\t// 对于int类型，根据DataTypeLong决定具体的Go类型\n\t\tvar fieldType string\n\t\tif field.FieldType == \"int\" {\n\t\t\tswitch field.DataTypeLong {\n\t\t\tcase \"1\", \"2\", \"3\":\n\t\t\t\tfieldType = \"int8\"\n\t\t\tcase \"4\", \"5\":\n\t\t\t\tfieldType = \"int16\"\n\t\t\tcase \"6\", \"7\", \"8\", \"9\", \"10\":\n\t\t\t\tfieldType = \"int32\"\n\t\t\tcase \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"20\":\n\t\t\t\tfieldType = \"int64\"\n\t\t\tdefault:\n\t\t\t\tfieldType = \"int64\"\n\t\t\t}\n\t\t} else {\n\t\t\tfieldType = field.FieldType\n\t\t}\n\n\t\tresult = fmt.Sprintf(`%s  *%s `+\"`\"+`%s`+\"`\"+``,\n\t\t\tfield.FieldName, fieldType, tagContent)\n\t}\n\n\tif field.Require {\n\t\tresult = result[0:len(result)-1] + requireTag\n\t}\n\n\t// 添加字段描述\n\tif field.FieldDesc != \"\" {\n\t\tresult += fmt.Sprintf(\"  //%s\", field.FieldDesc)\n\t}\n\n\treturn result\n}\n\n// 格式化搜索条件语句\nfunc GenerateSearchConditions(fields []*systemReq.AutoCodeField) string {\n\tvar conditions []string\n\n\tfor _, field := range fields {\n\t\tif field.FieldSearchType == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar condition string\n\n\t\tif slices.Contains([]string{\"enum\", \"pictures\", \"picture\", \"video\", \"json\", \"richtext\", \"array\"}, field.FieldType) {\n\t\t\tif field.FieldType == \"enum\" {\n\t\t\t\tif field.FieldSearchType == \"LIKE\" {\n\t\t\t\t\tcondition = fmt.Sprintf(`\n    if info.%s != \"\" {\n        db = db.Where(\"%s LIKE ?\", \"%%\"+ info.%s+\"%%\")\n    }`,\n\t\t\t\t\t\tfield.FieldName, field.ColumnName, field.FieldName)\n\t\t\t\t} else {\n\t\t\t\t\tcondition = fmt.Sprintf(`\n    if info.%s != \"\" {\n        db = db.Where(\"%s %s ?\", info.%s)\n    }`,\n\t\t\t\t\t\tfield.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcondition = fmt.Sprintf(`\n    if info.%s != \"\" {\n        // TODO 数据类型为复杂类型，请根据业务需求自行实现复杂类型的查询业务\n    }`, field.FieldName)\n\t\t\t}\n\n\t\t} else if field.FieldSearchType == \"BETWEEN\" || field.FieldSearchType == \"NOT BETWEEN\" {\n\t\t\tif field.FieldType == \"time.Time\" {\n\t\t\t\tcondition = fmt.Sprintf(`\n\t\t\tif len(info.%sRange) == 2 {\n\t\t\t\tdb = db.Where(\"%s %s ? AND ? \", info.%sRange[0], info.%sRange[1])\n\t\t\t}`,\n\t\t\t\t\tfield.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName, field.FieldName)\n\t\t\t} else {\n\t\t\t\tcondition = fmt.Sprintf(`\n\tif info.Start%s != nil && info.End%s != nil {\n\t\tdb = db.Where(\"%s %s ? AND ? \", *info.Start%s, *info.End%s)\n\t}`,\n\t\t\t\t\tfield.FieldName, field.FieldName, field.ColumnName,\n\t\t\t\t\tfield.FieldSearchType, field.FieldName, field.FieldName)\n\t\t\t}\n\t\t} else {\n\t\t\tnullCheck := \"info.\" + field.FieldName + \" != nil\"\n\t\t\tif field.FieldType == \"string\" {\n\t\t\t\tcondition = fmt.Sprintf(`\n    if %s && *info.%s != \"\" {`, nullCheck, field.FieldName)\n\t\t\t} else {\n\t\t\t\tcondition = fmt.Sprintf(`\n    if %s {`, nullCheck)\n\t\t\t}\n\n\t\t\tif field.FieldSearchType == \"LIKE\" {\n\t\t\t\tcondition += fmt.Sprintf(`\n        db = db.Where(\"%s LIKE ?\", \"%%\"+ *info.%s+\"%%\")\n    }`,\n\t\t\t\t\tfield.ColumnName, field.FieldName)\n\t\t\t} else {\n\t\t\t\tcondition += fmt.Sprintf(`\n        db = db.Where(\"%s %s ?\", *info.%s)\n    }`,\n\t\t\t\t\tfield.ColumnName, field.FieldSearchType, field.FieldName)\n\t\t\t}\n\t\t}\n\n\t\tconditions = append(conditions, condition)\n\t}\n\n\treturn strings.Join(conditions, \"\")\n}\n\n// 格式化前端搜索条件\nfunc GenerateSearchFormItem(field systemReq.AutoCodeField) string {\n\t// 开始构建表单项\n\tresult := fmt.Sprintf(`<el-form-item label=\"%s\" prop=\"%s\">\n`, field.FieldDesc, field.FieldJson)\n\n\t// 根据字段属性生成不同的输入类型\n\tif field.FieldType == \"bool\" {\n\t\tresult += fmt.Sprintf(`  <el-select v-model=\"searchInfo.%s\" clearable placeholder=\"请选择\">\n`, field.FieldJson)\n\t\tresult += `    <el-option key=\"true\" label=\"是\" value=\"true\"></el-option>\n`\n\t\tresult += `    <el-option key=\"false\" label=\"否\" value=\"false\"></el-option>\n`\n\t\tresult += `  </el-select>\n`\n\t} else if field.DictType != \"\" {\n\t\tmultipleAttr := \"\"\n\t\tif field.FieldType == \"array\" {\n\t\t\tmultipleAttr = \"multiple \"\n\t\t}\n\t\tresult += fmt.Sprintf(`    <el-tree-select v-model=\"searchInfo.%s\" placeholder=\"请选择%s\" :data=\"%sOptions\" style=\"width:100%%\" filterable :clearable=\"%v\" check-strictly %s></el-tree-select>\n`,\n\t\t\tfield.FieldJson, field.FieldDesc, field.DictType, field.Clearable, multipleAttr)\n\t} else if field.CheckDataSource {\n\t\tmultipleAttr := \"\"\n\t\tif field.DataSource.Association == 2 {\n\t\t\tmultipleAttr = \"multiple \"\n\t\t}\n\t\tresult += fmt.Sprintf(`  <el-select %sv-model=\"searchInfo.%s\" filterable placeholder=\"请选择%s\" :clearable=\"%v\">\n`,\n\t\t\tmultipleAttr, field.FieldJson, field.FieldDesc, field.Clearable)\n\t\tresult += fmt.Sprintf(`    <el-option v-for=\"(item,key) in dataSource.%s\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n`,\n\t\t\tfield.FieldJson)\n\t\tresult += `  </el-select>\n`\n\t} else if field.FieldType == \"float64\" || field.FieldType == \"int\" {\n\t\tif field.FieldSearchType == \"BETWEEN\" || field.FieldSearchType == \"NOT BETWEEN\" {\n\t\t\tresult += fmt.Sprintf(`  <el-input class=\"!w-40\" v-model.number=\"searchInfo.start%s\" placeholder=\"最小值\" />\n`, field.FieldName)\n\t\t\tresult += `  —\n`\n\t\t\tresult += fmt.Sprintf(`  <el-input class=\"!w-40\" v-model.number=\"searchInfo.end%s\" placeholder=\"最大值\" />\n`, field.FieldName)\n\t\t} else {\n\t\t\tresult += fmt.Sprintf(`  <el-input v-model.number=\"searchInfo.%s\" placeholder=\"搜索条件\" />\n`, field.FieldJson)\n\t\t}\n\t} else if field.FieldType == \"time.Time\" {\n\t\tif field.FieldSearchType == \"BETWEEN\" || field.FieldSearchType == \"NOT BETWEEN\" {\n\t\t\tresult += `  <template #label>\n`\n\t\t\tresult += `    <span>\n`\n\t\t\tresult += fmt.Sprintf(`      %s\n`, field.FieldDesc)\n\t\t\tresult += `      <el-tooltip content=\"搜索范围是开始日期（包含）至结束日期（不包含）\">\n`\n\t\t\tresult += `        <el-icon><QuestionFilled /></el-icon>\n`\n\t\t\tresult += `      </el-tooltip>\n`\n\t\t\tresult += `    </span>\n`\n\t\t\tresult += `  </template>\n`\n\t\t\tresult += fmt.Sprintf(`<el-date-picker class=\"!w-380px\" v-model=\"searchInfo.%sRange\" type=\"datetimerange\" range-separator=\"至\"  start-placeholder=\"开始时间\" end-placeholder=\"结束时间\"></el-date-picker>`, field.FieldJson)\n\t\t} else {\n\t\t\tresult += fmt.Sprintf(`<el-date-picker v-model=\"searchInfo.%s\" type=\"datetime\" placeholder=\"搜索条件\"></el-date-picker>`, field.FieldJson)\n\t\t}\n\t} else {\n\t\tresult += fmt.Sprintf(`  <el-input v-model=\"searchInfo.%s\" placeholder=\"搜索条件\" />\n`, field.FieldJson)\n\t}\n\n\t// 关闭表单项\n\tresult += `</el-form-item>`\n\n\treturn result\n}\n\n// GenerateTableColumn generates HTML for table column based on field properties\nfunc GenerateTableColumn(field systemReq.AutoCodeField) string {\n\t// Add sortable attribute if needed\n\tsortAttr := \"\"\n\tif field.Sort {\n\t\tsortAttr = \" sortable\"\n\t}\n\n\t// Handle different field types\n\tif field.CheckDataSource {\n\t\tresult := fmt.Sprintf(`<el-table-column%s align=\"left\" label=\"%s\" prop=\"%s\" width=\"120\">\n`,\n\t\t\tsortAttr, field.FieldDesc, field.FieldJson)\n\t\tresult += `    <template #default=\"scope\">\n`\n\n\t\tif field.DataSource.Association == 2 {\n\t\t\tresult += fmt.Sprintf(`        <el-tag v-for=\"(item,key) in filterDataSource(dataSource.%s,scope.row.%s)\" :key=\"key\">\n`,\n\t\t\t\tfield.FieldJson, field.FieldJson)\n\t\t\tresult += `             {{ item }}\n`\n\t\t\tresult += `        </el-tag>\n`\n\t\t} else {\n\t\t\tresult += fmt.Sprintf(`        <span>{{ filterDataSource(dataSource.%s,scope.row.%s) }}</span>\n`,\n\t\t\t\tfield.FieldJson, field.FieldJson)\n\t\t}\n\n\t\tresult += `    </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.DictType != \"\" {\n\t\tresult := fmt.Sprintf(`<el-table-column%s align=\"left\" label=\"%s\" prop=\"%s\" width=\"120\">\n`,\n\t\t\tsortAttr, field.FieldDesc, field.FieldJson)\n\t\tresult += `    <template #default=\"scope\">\n`\n\n\t\tif field.FieldType == \"array\" {\n\t\t\tresult += fmt.Sprintf(`    <el-tag class=\"mr-1\" v-for=\"item in scope.row.%s\" :key=\"item\"> {{ filterDict(item,%sOptions) }}</el-tag>\n`,\n\t\t\t\tfield.FieldJson, field.DictType)\n\t\t} else {\n\t\t\tresult += fmt.Sprintf(`    {{ filterDict(scope.row.%s,%sOptions) }}\n`,\n\t\t\t\tfield.FieldJson, field.DictType)\n\t\t}\n\n\t\tresult += `    </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"bool\" {\n\t\tresult := fmt.Sprintf(`<el-table-column%s align=\"left\" label=\"%s\" prop=\"%s\" width=\"120\">\n`,\n\t\t\tsortAttr, field.FieldDesc, field.FieldJson)\n\t\tresult += fmt.Sprintf(`    <template #default=\"scope\">{{ formatBoolean(scope.row.%s) }}</template>\n`, field.FieldJson)\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"time.Time\" {\n\t\tresult := fmt.Sprintf(`<el-table-column%s align=\"left\" label=\"%s\" prop=\"%s\" width=\"180\">\n`,\n\t\t\tsortAttr, field.FieldDesc, field.FieldJson)\n\t\tresult += fmt.Sprintf(`   <template #default=\"scope\">{{ formatDate(scope.row.%s) }}</template>\n`, field.FieldJson)\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"picture\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `    <template #default=\"scope\">\n`\n\t\tresult += fmt.Sprintf(`      <el-image preview-teleported style=\"width: 100px; height: 100px\" :src=\"getUrl(scope.row.%s)\" fit=\"cover\"/>\n`, field.FieldJson)\n\t\tresult += `    </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"pictures\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `   <template #default=\"scope\">\n`\n\t\tresult += `      <div class=\"multiple-img-box\">\n`\n\t\tresult += fmt.Sprintf(`         <el-image preview-teleported v-for=\"(item,index) in scope.row.%s\" :key=\"index\" style=\"width: 80px; height: 80px\" :src=\"getUrl(item)\" fit=\"cover\"/>\n`, field.FieldJson)\n\t\tresult += `     </div>\n`\n\t\tresult += `   </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"video\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `   <template #default=\"scope\">\n`\n\t\tresult += `    <video\n`\n\t\tresult += `       style=\"width: 100px; height: 100px\"\n`\n\t\tresult += `       muted\n`\n\t\tresult += `       preload=\"metadata\"\n`\n\t\tresult += `       >\n`\n\t\tresult += fmt.Sprintf(`         <source :src=\"getUrl(scope.row.%s) + '#t=1'\">\n`, field.FieldJson)\n\t\tresult += `       </video>\n`\n\t\tresult += `   </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"richtext\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `   <template #default=\"scope\">\n`\n\t\tresult += `      [富文本内容]\n`\n\t\tresult += `   </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"file\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `    <template #default=\"scope\">\n`\n\t\tresult += `         <div class=\"file-list\">\n`\n\t\tresult += fmt.Sprintf(`           <el-tag v-for=\"file in scope.row.%s\" :key=\"file.uid\" @click=\"onDownloadFile(file.url)\">{{ file.name }}</el-tag>\n`, field.FieldJson)\n\t\tresult += `         </div>\n`\n\t\tresult += `    </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"json\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `    <template #default=\"scope\">\n`\n\t\tresult += `        [JSON]\n`\n\t\tresult += `    </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else if field.FieldType == \"array\" {\n\t\tresult := fmt.Sprintf(`<el-table-column label=\"%s\" prop=\"%s\" width=\"200\">\n`, field.FieldDesc, field.FieldJson)\n\t\tresult += `    <template #default=\"scope\">\n`\n\t\tresult += fmt.Sprintf(`       <ArrayCtrl v-model=\"scope.row.%s\"/>\n`, field.FieldJson)\n\t\tresult += `    </template>\n`\n\t\tresult += `</el-table-column>`\n\t\treturn result\n\t} else {\n\t\treturn fmt.Sprintf(`<el-table-column%s align=\"left\" label=\"%s\" prop=\"%s\" width=\"120\" />\n`,\n\t\t\tsortAttr, field.FieldDesc, field.FieldJson)\n\t}\n}\n\nfunc GenerateFormItem(field systemReq.AutoCodeField) string {\n\t// 开始构建表单项\n\tresult := fmt.Sprintf(`<el-form-item label=\"%s:\" prop=\"%s\">\n`, field.FieldDesc, field.FieldJson)\n\n\t// 处理不同字段类型\n\tif field.CheckDataSource {\n\t\tmultipleAttr := \"\"\n\t\tif field.DataSource.Association == 2 {\n\t\t\tmultipleAttr = \" multiple\"\n\t\t}\n\t\tresult += fmt.Sprintf(`    <el-select%s v-model=\"formData.%s\" placeholder=\"请选择%s\" filterable style=\"width:100%%\" :clearable=\"%v\">\n`,\n\t\t\tmultipleAttr, field.FieldJson, field.FieldDesc, field.Clearable)\n\t\tresult += fmt.Sprintf(`        <el-option v-for=\"(item,key) in dataSource.%s\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n`,\n\t\t\tfield.FieldJson)\n\t\tresult += `    </el-select>\n`\n\t} else {\n\t\tswitch field.FieldType {\n\t\tcase \"bool\":\n\t\t\tresult += fmt.Sprintf(`    <el-switch v-model=\"formData.%s\" active-color=\"#13ce66\" inactive-color=\"#ff4949\" active-text=\"是\" inactive-text=\"否\" clearable ></el-switch>\n`,\n\t\t\t\tfield.FieldJson)\n\n\t\tcase \"string\":\n\t\t\tif field.DictType != \"\" {\n\t\t\t\tresult += fmt.Sprintf(`    <el-tree-select v-model=\"formData.%s\" placeholder=\"请选择%s\" :data=\"%sOptions\" style=\"width:100%%\" filterable :clearable=\"%v\" check-strictly></el-tree-select>\n`,\n\t\t\t\t\tfield.FieldJson, field.FieldDesc, field.DictType, field.Clearable)\n\t\t\t} else {\n\t\t\t\tresult += fmt.Sprintf(`    <el-input v-model=\"formData.%s\" :clearable=\"%v\" placeholder=\"请输入%s\" />\n`,\n\t\t\t\t\tfield.FieldJson, field.Clearable, field.FieldDesc)\n\t\t\t}\n\n\t\tcase \"richtext\":\n\t\t\tresult += fmt.Sprintf(`    <RichEdit v-model=\"formData.%s\"/>\n`, field.FieldJson)\n\n\t\tcase \"json\":\n\t\t\tresult += fmt.Sprintf(`    // 此字段为json结构，可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.%s 后端会按照json的类型进行存取\n`, field.FieldJson)\n\t\t\tresult += fmt.Sprintf(`    {{ formData.%s }}\n`, field.FieldJson)\n\n\t\tcase \"array\":\n\t\t\tif field.DictType != \"\" {\n\t\t\t\tresult += fmt.Sprintf(`    <el-select multiple v-model=\"formData.%s\" placeholder=\"请选择%s\" filterable style=\"width:100%%\" :clearable=\"%v\">\n`,\n\t\t\t\t\tfield.FieldJson, field.FieldDesc, field.Clearable)\n\t\t\t\tresult += fmt.Sprintf(`        <el-option v-for=\"(item,key) in %sOptions\" :key=\"key\" :label=\"item.label\" :value=\"item.value\" />\n`,\n\t\t\t\t\tfield.DictType)\n\t\t\t\tresult += `    </el-select>\n`\n\t\t\t} else {\n\t\t\t\tresult += fmt.Sprintf(`    <ArrayCtrl v-model=\"formData.%s\" editable/>\n`, field.FieldJson)\n\t\t\t}\n\n\t\tcase \"int\":\n\t\t\tresult += fmt.Sprintf(`    <el-input v-model.number=\"formData.%s\" :clearable=\"%v\" placeholder=\"请输入%s\" />\n`,\n\t\t\t\tfield.FieldJson, field.Clearable, field.FieldDesc)\n\n\t\tcase \"time.Time\":\n\t\t\tresult += fmt.Sprintf(`    <el-date-picker v-model=\"formData.%s\" type=\"date\" style=\"width:100%%\" placeholder=\"选择日期\" :clearable=\"%v\" />\n`,\n\t\t\t\tfield.FieldJson, field.Clearable)\n\n\t\tcase \"float64\":\n\t\t\tresult += fmt.Sprintf(`    <el-input-number v-model=\"formData.%s\" style=\"width:100%%\" :precision=\"2\" :clearable=\"%v\" />\n`,\n\t\t\t\tfield.FieldJson, field.Clearable)\n\n\t\tcase \"enum\":\n\t\t\tresult += fmt.Sprintf(`    <el-select v-model=\"formData.%s\" placeholder=\"请选择%s\" style=\"width:100%%\" filterable :clearable=\"%v\">\n`,\n\t\t\t\tfield.FieldJson, field.FieldDesc, field.Clearable)\n\t\t\tresult += fmt.Sprintf(`       <el-option v-for=\"item in [%s]\" :key=\"item\" :label=\"item\" :value=\"item\" />\n`,\n\t\t\t\tfield.DataTypeLong)\n\t\t\tresult += `    </el-select>\n`\n\n\t\tcase \"picture\":\n\t\t\tresult += fmt.Sprintf(`    <SelectImage\n     v-model=\"formData.%s\"\n     file-type=\"image\"\n    />\n`, field.FieldJson)\n\n\t\tcase \"pictures\":\n\t\t\tresult += fmt.Sprintf(`    <SelectImage\n     multiple\n     v-model=\"formData.%s\"\n     file-type=\"image\"\n     />\n`, field.FieldJson)\n\n\t\tcase \"video\":\n\t\t\tresult += fmt.Sprintf(`    <SelectImage\n    v-model=\"formData.%s\"\n    file-type=\"video\"\n    />\n`, field.FieldJson)\n\n\t\tcase \"file\":\n\t\t\tresult += fmt.Sprintf(`    <SelectFile v-model=\"formData.%s\" />\n`, field.FieldJson)\n\t\t}\n\t}\n\n\t// 关闭表单项\n\tresult += `</el-form-item>`\n\n\treturn result\n}\n\nfunc GenerateDescriptionItem(field systemReq.AutoCodeField) string {\n\t// 开始构建描述项\n\tresult := fmt.Sprintf(`<el-descriptions-item label=\"%s\">\n`, field.FieldDesc)\n\n\tif field.CheckDataSource {\n\t\tresult += `    <template #default=\"scope\">\n`\n\t\tif field.DataSource.Association == 2 {\n\t\t\tresult += fmt.Sprintf(`        <el-tag v-for=\"(item,key) in filterDataSource(dataSource.%s,detailForm.%s)\" :key=\"key\">\n`,\n\t\t\t\tfield.FieldJson, field.FieldJson)\n\t\t\tresult += `             {{ item }}\n`\n\t\t\tresult += `        </el-tag>\n`\n\t\t} else {\n\t\t\tresult += fmt.Sprintf(`        <span>{{ filterDataSource(dataSource.%s,detailForm.%s) }}</span>\n`,\n\t\t\t\tfield.FieldJson, field.FieldJson)\n\t\t}\n\t\tresult += `    </template>\n`\n\t} else if field.FieldType != \"picture\" && field.FieldType != \"pictures\" &&\n\t\tfield.FieldType != \"file\" && field.FieldType != \"array\" &&\n\t\tfield.FieldType != \"richtext\" {\n\t\tresult += fmt.Sprintf(`    {{ detailForm.%s }}\n`, field.FieldJson)\n\t} else {\n\t\tswitch field.FieldType {\n\t\tcase \"picture\":\n\t\t\tresult += fmt.Sprintf(`    <el-image style=\"width: 50px; height: 50px\" :preview-src-list=\"returnArrImg(detailForm.%s)\" :src=\"getUrl(detailForm.%s)\" fit=\"cover\" />\n`,\n\t\t\t\tfield.FieldJson, field.FieldJson)\n\t\tcase \"array\":\n\t\t\tresult += fmt.Sprintf(`    <ArrayCtrl v-model=\"detailForm.%s\"/>\n`, field.FieldJson)\n\t\tcase \"pictures\":\n\t\t\tresult += fmt.Sprintf(`    <el-image style=\"width: 50px; height: 50px; margin-right: 10px\" :preview-src-list=\"returnArrImg(detailForm.%s)\" :initial-index=\"index\" v-for=\"(item,index) in detailForm.%s\" :key=\"index\" :src=\"getUrl(item)\" fit=\"cover\" />\n`,\n\t\t\t\tfield.FieldJson, field.FieldJson)\n\t\tcase \"richtext\":\n\t\t\tresult += fmt.Sprintf(`    <RichView v-model=\"detailForm.%s\" />\n`, field.FieldJson)\n\t\tcase \"file\":\n\t\t\tresult += fmt.Sprintf(`    <div class=\"fileBtn\" v-for=\"(item,index) in detailForm.%s\" :key=\"index\">\n`, field.FieldJson)\n\t\t\tresult += `        <el-button type=\"primary\" text bg @click=\"onDownloadFile(item.url)\">\n`\n\t\t\tresult += `          <el-icon style=\"margin-right: 5px\"><Download /></el-icon>\n`\n\t\t\tresult += `          {{ item.name }}\n`\n\t\t\tresult += `        </el-button>\n`\n\t\t\tresult += `    </div>\n`\n\t\t}\n\t}\n\n\t// 关闭描述项\n\tresult += `</el-descriptions-item>`\n\n\treturn result\n}\n\nfunc GenerateDefaultFormValue(field systemReq.AutoCodeField) string {\n\t// 根据字段类型确定默认值\n\tvar defaultValue string\n\n\tswitch field.FieldType {\n\tcase \"bool\":\n\t\tdefaultValue = \"false\"\n\tcase \"string\", \"richtext\":\n\t\tdefaultValue = \"''\"\n\tcase \"int\":\n\t\tif field.DataSource != nil { // 检查数据源是否存在\n\t\t\tdefaultValue = \"undefined\"\n\t\t} else {\n\t\t\tdefaultValue = \"0\"\n\t\t}\n\tcase \"time.Time\":\n\t\tdefaultValue = \"new Date()\"\n\tcase \"float64\":\n\t\tdefaultValue = \"0\"\n\tcase \"picture\", \"video\":\n\t\tdefaultValue = \"\\\"\\\"\"\n\tcase \"pictures\", \"file\", \"array\":\n\t\tdefaultValue = \"[]\"\n\tcase \"json\":\n\t\tdefaultValue = \"{}\"\n\tdefault:\n\t\tdefaultValue = \"null\"\n\t}\n\n\t// 返回格式化后的默认值字符串\n\treturn fmt.Sprintf(`%s: %s,`, field.FieldJson, defaultValue)\n}\n\n// GenerateSearchField 根据字段属性生成搜索结构体中的字段定义\nfunc GenerateSearchField(field systemReq.AutoCodeField) string {\n\tvar result string\n\n\tif field.FieldSearchType == \"\" {\n\t\treturn \"\" // 如果没有搜索类型，返回空字符串\n\t}\n\n\tif field.FieldSearchType == \"BETWEEN\" || field.FieldSearchType == \"NOT BETWEEN\" {\n\t\t// 生成范围搜索字段\n\t\t// time 的情况\n\t\tif field.FieldType == \"time.Time\" {\n\t\t\tresult = fmt.Sprintf(\"%sRange  []time.Time  `json:\\\"%sRange\\\" form:\\\"%sRange[]\\\"`\",\n\t\t\t\tfield.FieldName, field.FieldJson, field.FieldJson)\n\t\t} else {\n\t\t\tstartField := fmt.Sprintf(\"Start%s  *%s  `json:\\\"start%s\\\" form:\\\"start%s\\\"`\",\n\t\t\t\tfield.FieldName, field.FieldType, field.FieldName, field.FieldName)\n\t\t\tendField := fmt.Sprintf(\"End%s  *%s  `json:\\\"end%s\\\" form:\\\"end%s\\\"`\",\n\t\t\t\tfield.FieldName, field.FieldType, field.FieldName, field.FieldName)\n\t\t\tresult = startField + \"\\n\" + endField\n\t\t}\n\t} else {\n\t\t// 生成普通搜索字段\n\t\tif field.FieldType == \"enum\" || field.FieldType == \"picture\" ||\n\t\t\tfield.FieldType == \"pictures\" || field.FieldType == \"video\" ||\n\t\t\tfield.FieldType == \"json\" || field.FieldType == \"richtext\" || field.FieldType == \"array\" || field.FieldType == \"file\" {\n\t\t\tresult = fmt.Sprintf(\"%s  string `json:\\\"%s\\\" form:\\\"%s\\\"` \",\n\t\t\t\tfield.FieldName, field.FieldJson, field.FieldJson)\n\t\t} else {\n\t\t\tresult = fmt.Sprintf(\"%s  *%s `json:\\\"%s\\\" form:\\\"%s\\\"` \",\n\t\t\t\tfield.FieldName, field.FieldType, field.FieldJson, field.FieldJson)\n\t\t}\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "server/utils/breakpoint_continue.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// 前端传来文件片与当前片为什么文件的第几片\n// 后端拿到以后比较次分片是否上传 或者是否为不完全片\n// 前端发送每片多大\n// 前端告知是否为最后一片且是否完成\n\nconst (\n\tbreakpointDir = \"./breakpointDir/\"\n\tfinishDir     = \"./fileDir/\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: BreakPointContinue\n//@description: 断点续传\n//@param: content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string\n//@return: error, string\n\nfunc BreakPointContinue(content []byte, fileName string, contentNumber int, contentTotal int, fileMd5 string) (string, error) {\n\tif strings.Contains(fileName, \"..\") || strings.Contains(fileMd5, \"..\") {\n\t\treturn \"\", errors.New(\"文件名或路径不合法\")\n\t}\n\tpath := breakpointDir + fileMd5 + \"/\"\n\terr := os.MkdirAll(path, os.ModePerm)\n\tif err != nil {\n\t\treturn path, err\n\t}\n\tpathC, err := makeFileContent(content, fileName, path, contentNumber)\n\treturn pathC, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CheckMd5\n//@description: 检查Md5\n//@param: content []byte, chunkMd5 string\n//@return: CanUpload bool\n\nfunc CheckMd5(content []byte, chunkMd5 string) (CanUpload bool) {\n\tfileMd5 := MD5V(content)\n\tif fileMd5 == chunkMd5 {\n\t\treturn true // 可以继续上传\n\t} else {\n\t\treturn false // 切片不完整，废弃\n\t}\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: makeFileContent\n//@description: 创建切片内容\n//@param: content []byte, fileName string, FileDir string, contentNumber int\n//@return: string, error\n\nfunc makeFileContent(content []byte, fileName string, FileDir string, contentNumber int) (string, error) {\n\tif strings.Contains(fileName, \"..\") || strings.Contains(FileDir, \"..\") {\n\t\treturn \"\", errors.New(\"文件名或路径不合法\")\n\t}\n\tpath := FileDir + fileName + \"_\" + strconv.Itoa(contentNumber)\n\tf, err := os.Create(path)\n\tif err != nil {\n\t\treturn path, err\n\t}\n\tdefer f.Close()\n\t_, err = f.Write(content)\n\tif err != nil {\n\t\treturn path, err\n\t}\n\n\treturn path, nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: makeFileContent\n//@description: 创建切片文件\n//@param: fileName string, FileMd5 string\n//@return: error, string\n\nfunc MakeFile(fileName string, FileMd5 string) (string, error) {\n\tif strings.Contains(fileName, \"..\") || strings.Contains(FileMd5, \"..\") {\n\t\treturn \"\", errors.New(\"文件名或路径不合法\")\n\t}\n\trd, err := os.ReadDir(breakpointDir + FileMd5)\n\tif err != nil {\n\t\treturn finishDir + fileName, err\n\t}\n\t_ = os.MkdirAll(finishDir, os.ModePerm)\n\tfd, err := os.OpenFile(finishDir+fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644)\n\tif err != nil {\n\t\treturn finishDir + fileName, err\n\t}\n\tdefer fd.Close()\n\tfor k := range rd {\n\t\tcontent, _ := os.ReadFile(breakpointDir + FileMd5 + \"/\" + fileName + \"_\" + strconv.Itoa(k))\n\t\t_, err = fd.Write(content)\n\t\tif err != nil {\n\t\t\t_ = os.Remove(finishDir + fileName)\n\t\t\treturn finishDir + fileName, err\n\t\t}\n\t}\n\treturn finishDir + fileName, nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: RemoveChunk\n//@description: 移除切片\n//@param: FileMd5 string\n//@return: error\n\nfunc RemoveChunk(FileMd5 string) error {\n\tif strings.Contains(FileMd5, \"..\") {\n\t\treturn errors.New(\"路径不合法\")\n\t}\n\terr := os.RemoveAll(breakpointDir + FileMd5)\n\treturn err\n}\n"
  },
  {
    "path": "server/utils/captcha/redis.go",
    "content": "package captcha\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\nfunc NewDefaultRedisStore() *RedisStore {\n\treturn &RedisStore{\n\t\tExpiration: time.Second * 180,\n\t\tPreKey:     \"CAPTCHA_\",\n\t\tContext:    context.TODO(),\n\t}\n}\n\ntype RedisStore struct {\n\tExpiration time.Duration\n\tPreKey     string\n\tContext    context.Context\n}\n\nfunc (rs *RedisStore) UseWithCtx(ctx context.Context) *RedisStore {\n\tif ctx == nil {\n\t\trs.Context = ctx\n\t}\n\treturn rs\n}\n\nfunc (rs *RedisStore) Set(id string, value string) error {\n\terr := global.GVA_REDIS.Set(rs.Context, rs.PreKey+id, value, rs.Expiration).Err()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"RedisStoreSetError!\", zap.Error(err))\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (rs *RedisStore) Get(key string, clear bool) string {\n\tval, err := global.GVA_REDIS.Get(rs.Context, key).Result()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"RedisStoreGetError!\", zap.Error(err))\n\t\treturn \"\"\n\t}\n\tif clear {\n\t\terr := global.GVA_REDIS.Del(rs.Context, key).Err()\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"RedisStoreClearError!\", zap.Error(err))\n\t\t\treturn \"\"\n\t\t}\n\t}\n\treturn val\n}\n\nfunc (rs *RedisStore) Verify(id, answer string, clear bool) bool {\n\tkey := rs.PreKey + id\n\tv := rs.Get(key, clear)\n\treturn v == answer\n}\n"
  },
  {
    "path": "server/utils/casbin_util.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/casbin/casbin/v2/model\"\n\tgormadapter \"github.com/casbin/gorm-adapter/v3\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\nvar (\n\tsyncedCachedEnforcer *casbin.SyncedCachedEnforcer\n\tonce                 sync.Once\n)\n\n// GetCasbin 获取casbin实例\nfunc GetCasbin() *casbin.SyncedCachedEnforcer {\n\tonce.Do(func() {\n\t\ta, err := gormadapter.NewAdapterByDB(global.GVA_DB)\n\t\tif err != nil {\n\t\t\tzap.L().Error(\"适配数据库失败请检查casbin表是否为InnoDB引擎!\", zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\ttext := `\n\t\t[request_definition]\n\t\tr = sub, obj, act\n\t\t\n\t\t[policy_definition]\n\t\tp = sub, obj, act\n\t\t\n\t\t[role_definition]\n\t\tg = _, _\n\t\t\n\t\t[policy_effect]\n\t\te = some(where (p.eft == allow))\n\t\t\n\t\t[matchers]\n\t\tm = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act\n\t\t`\n\t\tm, err := model.NewModelFromString(text)\n\t\tif err != nil {\n\t\t\tzap.L().Error(\"字符串加载模型失败!\", zap.Error(err))\n\t\t\treturn\n\t\t}\n\t\tsyncedCachedEnforcer, _ = casbin.NewSyncedCachedEnforcer(m, a)\n\t\tsyncedCachedEnforcer.SetExpireTime(60 * 60)\n\t\t_ = syncedCachedEnforcer.LoadPolicy()\n\t})\n\treturn syncedCachedEnforcer\n}\n"
  },
  {
    "path": "server/utils/claims.go",
    "content": "package utils\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system\"\n\tsystemReq \"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/google/uuid\"\n)\n\nfunc ClearToken(c *gin.Context) {\n\t// 增加cookie x-token 向来源的web添加\n\thost, _, err := net.SplitHostPort(c.Request.Host)\n\tif err != nil {\n\t\thost = c.Request.Host\n\t}\n\n\tif net.ParseIP(host) != nil {\n\t\tc.SetCookie(\"x-token\", \"\", -1, \"/\", \"\", false, false)\n\t} else {\n\t\tc.SetCookie(\"x-token\", \"\", -1, \"/\", host, false, false)\n\t}\n}\n\nfunc SetToken(c *gin.Context, token string, maxAge int) {\n\t// 增加cookie x-token 向来源的web添加\n\thost, _, err := net.SplitHostPort(c.Request.Host)\n\tif err != nil {\n\t\thost = c.Request.Host\n\t}\n\n\tif net.ParseIP(host) != nil {\n\t\tc.SetCookie(\"x-token\", token, maxAge, \"/\", \"\", false, false)\n\t} else {\n\t\tc.SetCookie(\"x-token\", token, maxAge, \"/\", host, false, false)\n\t}\n}\n\nfunc GetToken(c *gin.Context) string {\n\ttoken := c.Request.Header.Get(\"x-token\")\n\tif token == \"\" {\n\t\tj := NewJWT()\n\t\ttoken, _ = c.Cookie(\"x-token\")\n\t\tclaims, err := j.ParseToken(token)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Error(\"重新写入cookie token失败,未能成功解析token,请检查请求头是否存在x-token且claims是否为规定结构\")\n\t\t\treturn token\n\t\t}\n\t\tSetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))\n\t}\n\treturn token\n}\n\nfunc GetClaims(c *gin.Context) (*systemReq.CustomClaims, error) {\n\ttoken := GetToken(c)\n\tj := NewJWT()\n\tclaims, err := j.ParseToken(token)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在x-token且claims是否为规定结构\")\n\t}\n\treturn claims, err\n}\n\n// GetUserID 从Gin的Context中获取从jwt解析出来的用户ID\nfunc GetUserID(c *gin.Context) uint {\n\tif claims, exists := c.Get(\"claims\"); !exists {\n\t\tif cl, err := GetClaims(c); err != nil {\n\t\t\treturn 0\n\t\t} else {\n\t\t\treturn cl.BaseClaims.ID\n\t\t}\n\t} else {\n\t\twaitUse := claims.(*systemReq.CustomClaims)\n\t\treturn waitUse.BaseClaims.ID\n\t}\n}\n\n// GetUserUuid 从Gin的Context中获取从jwt解析出来的用户UUID\nfunc GetUserUuid(c *gin.Context) uuid.UUID {\n\tif claims, exists := c.Get(\"claims\"); !exists {\n\t\tif cl, err := GetClaims(c); err != nil {\n\t\t\treturn uuid.UUID{}\n\t\t} else {\n\t\t\treturn cl.UUID\n\t\t}\n\t} else {\n\t\twaitUse := claims.(*systemReq.CustomClaims)\n\t\treturn waitUse.UUID\n\t}\n}\n\n// GetUserAuthorityId 从Gin的Context中获取从jwt解析出来的用户角色id\nfunc GetUserAuthorityId(c *gin.Context) uint {\n\tif claims, exists := c.Get(\"claims\"); !exists {\n\t\tif cl, err := GetClaims(c); err != nil {\n\t\t\treturn 0\n\t\t} else {\n\t\t\treturn cl.AuthorityId\n\t\t}\n\t} else {\n\t\twaitUse := claims.(*systemReq.CustomClaims)\n\t\treturn waitUse.AuthorityId\n\t}\n}\n\n// GetUserInfo 从Gin的Context中获取从jwt解析出来的用户角色id\nfunc GetUserInfo(c *gin.Context) *systemReq.CustomClaims {\n\tif claims, exists := c.Get(\"claims\"); !exists {\n\t\tif cl, err := GetClaims(c); err != nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\treturn cl\n\t\t}\n\t} else {\n\t\twaitUse := claims.(*systemReq.CustomClaims)\n\t\treturn waitUse\n\t}\n}\n\n// GetUserName 从Gin的Context中获取从jwt解析出来的用户名\nfunc GetUserName(c *gin.Context) string {\n\tif claims, exists := c.Get(\"claims\"); !exists {\n\t\tif cl, err := GetClaims(c); err != nil {\n\t\t\treturn \"\"\n\t\t} else {\n\t\t\treturn cl.Username\n\t\t}\n\t} else {\n\t\twaitUse := claims.(*systemReq.CustomClaims)\n\t\treturn waitUse.Username\n\t}\n}\n\nfunc LoginToken(user system.Login) (token string, claims systemReq.CustomClaims, err error) {\n\tj := NewJWT()\n\tclaims = j.CreateClaims(systemReq.BaseClaims{\n\t\tUUID:        user.GetUUID(),\n\t\tID:          user.GetUserId(),\n\t\tNickName:    user.GetNickname(),\n\t\tUsername:    user.GetUsername(),\n\t\tAuthorityId: user.GetAuthorityId(),\n\t})\n\ttoken, err = j.CreateToken(claims)\n\treturn\n}\n"
  },
  {
    "path": "server/utils/directory.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: PathExists\n//@description: 文件目录是否存在\n//@param: path string\n//@return: bool, error\n\nfunc PathExists(path string) (bool, error) {\n\tfi, err := os.Stat(path)\n\tif err == nil {\n\t\tif fi.IsDir() {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, errors.New(\"存在同名文件\")\n\t}\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: CreateDir\n//@description: 批量创建文件夹\n//@param: dirs ...string\n//@return: err error\n\nfunc CreateDir(dirs ...string) (err error) {\n\tfor _, v := range dirs {\n\t\texist, err := PathExists(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !exist {\n\t\t\tglobal.GVA_LOG.Debug(\"create directory\" + v)\n\t\t\tif err := os.MkdirAll(v, os.ModePerm); err != nil {\n\t\t\t\tglobal.GVA_LOG.Error(\"create directory\"+v, zap.Any(\" error:\", err))\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n\n//@author: [songzhibin97](https://github.com/songzhibin97)\n//@function: FileMove\n//@description: 文件移动供外部调用\n//@param: src string, dst string(src: 源位置,绝对路径or相对路径, dst: 目标位置,绝对路径or相对路径,必须为文件夹)\n//@return: err error\n\nfunc FileMove(src string, dst string) (err error) {\n\tif dst == \"\" {\n\t\treturn nil\n\t}\n\tsrc, err = filepath.Abs(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdst, err = filepath.Abs(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\trevoke := false\n\tdir := filepath.Dir(dst)\nRedirect:\n\t_, err = os.Stat(dir)\n\tif err != nil {\n\t\terr = os.MkdirAll(dir, 0o755)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !revoke {\n\t\t\trevoke = true\n\t\t\tgoto Redirect\n\t\t}\n\t}\n\treturn os.Rename(src, dst)\n}\n\nfunc DeLFile(filePath string) error {\n\treturn os.RemoveAll(filePath)\n}\n\n//@author: [songzhibin97](https://github.com/songzhibin97)\n//@function: TrimSpace\n//@description: 去除结构体空格\n//@param: target interface (target: 目标结构体,传入必须是指针类型)\n//@return: null\n\nfunc TrimSpace(target interface{}) {\n\tt := reflect.TypeOf(target)\n\tif t.Kind() != reflect.Ptr {\n\t\treturn\n\t}\n\tt = t.Elem()\n\tv := reflect.ValueOf(target).Elem()\n\tfor i := 0; i < t.NumField(); i++ {\n\t\tswitch v.Field(i).Kind() {\n\t\tcase reflect.String:\n\t\t\tv.Field(i).SetString(strings.TrimSpace(v.Field(i).String()))\n\t\t}\n\t}\n}\n\n// FileExist 判断文件是否存在\nfunc FileExist(path string) bool {\n\tfi, err := os.Lstat(path)\n\tif err == nil {\n\t\treturn !fi.IsDir()\n\t}\n\treturn !os.IsNotExist(err)\n}\n"
  },
  {
    "path": "server/utils/fmt_plus.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common\"\n\t\"math/rand\"\n\t\"reflect\"\n\t\"strings\"\n)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: StructToMap\n//@description: 利用反射将结构体转化为map\n//@param: obj interface{}\n//@return: map[string]interface{}\n\nfunc StructToMap(obj interface{}) map[string]interface{} {\n\tobj1 := reflect.TypeOf(obj)\n\tobj2 := reflect.ValueOf(obj)\n\n\tdata := make(map[string]interface{})\n\tfor i := 0; i < obj1.NumField(); i++ {\n\t\tif obj1.Field(i).Tag.Get(\"mapstructure\") != \"\" {\n\t\t\tdata[obj1.Field(i).Tag.Get(\"mapstructure\")] = obj2.Field(i).Interface()\n\t\t} else {\n\t\t\tdata[obj1.Field(i).Name] = obj2.Field(i).Interface()\n\t\t}\n\t}\n\treturn data\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: ArrayToString\n//@description: 将数组格式化为字符串\n//@param: array []interface{}\n//@return: string\n\nfunc ArrayToString(array []interface{}) string {\n\treturn strings.Replace(strings.Trim(fmt.Sprint(array), \"[]\"), \" \", \",\", -1)\n}\n\nfunc Pointer[T any](in T) (out *T) {\n\treturn &in\n}\n\nfunc FirstUpper(s string) string {\n\tif s == \"\" {\n\t\treturn \"\"\n\t}\n\treturn strings.ToUpper(s[:1]) + s[1:]\n}\n\nfunc FirstLower(s string) string {\n\tif s == \"\" {\n\t\treturn \"\"\n\t}\n\treturn strings.ToLower(s[:1]) + s[1:]\n}\n\n// MaheHump 将字符串转换为驼峰命名\nfunc MaheHump(s string) string {\n\twords := strings.Split(s, \"-\")\n\n\tfor i := 1; i < len(words); i++ {\n\t\twords[i] = strings.Title(words[i])\n\t}\n\n\treturn strings.Join(words, \"\")\n}\n\n// HumpToUnderscore 将驼峰命名转换为下划线分割模式\nfunc HumpToUnderscore(s string) string {\n\tvar result strings.Builder\n\n\tfor i, char := range s {\n\t\tif i > 0 && char >= 'A' && char <= 'Z' {\n\t\t\t// 在大写字母前添加下划线\n\t\t\tresult.WriteRune('_')\n\t\t\tresult.WriteRune(char - 'A' + 'a') // 转小写\n\t\t} else {\n\t\t\tresult.WriteRune(char)\n\t\t}\n\t}\n\n\treturn strings.ToLower(result.String())\n}\n\n// RandomString 随机字符串\nfunc RandomString(n int) string {\n\tvar letters = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\")\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = letters[RandomInt(0, len(letters))]\n\t}\n\treturn string(b)\n}\n\nfunc RandomInt(min, max int) int {\n\treturn min + rand.Intn(max-min)\n}\n\n// BuildTree 用于构建一个树形结构\nfunc BuildTree[T common.TreeNode[T]](nodes []T) []T {\n\tnodeMap := make(map[int]T)\n\t// 创建一个基本map\n\tfor i := range nodes {\n\t\tnodeMap[nodes[i].GetID()] = nodes[i]\n\t}\n\n\tfor i := range nodes {\n\t\tif nodes[i].GetParentID() != 0 {\n\t\t\tparent := nodeMap[nodes[i].GetParentID()]\n\t\t\tparent.SetChildren(nodes[i])\n\t\t}\n\t}\n\n\tvar rootNodes []T\n\n\tfor i := range nodeMap {\n\t\tif nodeMap[i].GetParentID() == 0 {\n\t\t\trootNodes = append(rootNodes, nodeMap[i])\n\t\t}\n\t}\n\treturn rootNodes\n}\n"
  },
  {
    "path": "server/utils/hash.go",
    "content": "package utils\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"golang.org/x/crypto/bcrypt\"\n)\n\n// BcryptHash 使用 bcrypt 对密码进行加密\nfunc BcryptHash(password string) string {\n\tbytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)\n\treturn string(bytes)\n}\n\n// BcryptCheck 对比明文密码和数据库的哈希值\nfunc BcryptCheck(password, hash string) bool {\n\terr := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))\n\treturn err == nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: MD5V\n//@description: md5加密\n//@param: str []byte\n//@return: string\n\nfunc MD5V(str []byte, b ...byte) string {\n\th := md5.New()\n\th.Write(str)\n\treturn hex.EncodeToString(h.Sum(b))\n}\n"
  },
  {
    "path": "server/utils/human_duration.go",
    "content": "package utils\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc ParseDuration(d string) (time.Duration, error) {\n\td = strings.TrimSpace(d)\n\tdr, err := time.ParseDuration(d)\n\tif err == nil {\n\t\treturn dr, nil\n\t}\n\tif strings.Contains(d, \"d\") {\n\t\tindex := strings.Index(d, \"d\")\n\n\t\thour, _ := strconv.Atoi(d[:index])\n\t\tdr = time.Hour * 24 * time.Duration(hour)\n\t\tndr, err := time.ParseDuration(d[index+1:])\n\t\tif err != nil {\n\t\t\treturn dr, nil\n\t\t}\n\t\treturn dr + ndr, nil\n\t}\n\n\tdv, err := strconv.ParseInt(d, 10, 64)\n\treturn time.Duration(dv), err\n}\n"
  },
  {
    "path": "server/utils/human_duration_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestParseDuration(t *testing.T) {\n\ttype args struct {\n\t\td string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    time.Duration\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"5h20m\",\n\t\t\targs:    args{\"5h20m\"},\n\t\t\twant:    time.Hour*5 + 20*time.Minute,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"1d5h20m\",\n\t\t\targs:    args{\"1d5h20m\"},\n\t\t\twant:    24*time.Hour + time.Hour*5 + 20*time.Minute,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"1d\",\n\t\t\targs:    args{\"1d\"},\n\t\t\twant:    24 * time.Hour,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseDuration(tt.args.d)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseDuration() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"ParseDuration() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/utils/json.go",
    "content": "package utils\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n)\n\nfunc GetJSONKeys(jsonStr string) (keys []string, err error) {\n\t// 使用json.Decoder，以便在解析过程中记录键的顺序\n\tdec := json.NewDecoder(strings.NewReader(jsonStr))\n\tt, err := dec.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// 确保数据是一个对象\n\tif t != json.Delim('{') {\n\t\treturn nil, err\n\t}\n\tfor dec.More() {\n\t\tt, err = dec.Token()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkeys = append(keys, t.(string))\n\n\t\t// 解析值\n\t\tvar value interface{}\n\t\terr = dec.Decode(&value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn keys, nil\n}\n"
  },
  {
    "path": "server/utils/json_test.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestGetJSONKeys(t *testing.T) {\n\tvar jsonStr = `\n\t{\n\t\t\"Name\": \"test\",\n\t\t\"TableName\": \"test\",\n\t\t\"TemplateID\": \"test\",\n\t\t\"TemplateInfo\": \"test\",\n\t\t\"Limit\": 0\n}`\n\tkeys, err := GetJSONKeys(jsonStr)\n\tif err != nil {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\t\treturn\n\t}\n\tif len(keys) != 5 {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\t\treturn\n\t}\n\tif keys[0] != \"Name\" {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\n\t\treturn\n\t}\n\tif keys[1] != \"TableName\" {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\n\t\treturn\n\t}\n\tif keys[2] != \"TemplateID\" {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\n\t\treturn\n\t}\n\tif keys[3] != \"TemplateInfo\" {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\n\t\treturn\n\t}\n\tif keys[4] != \"Limit\" {\n\t\tt.Errorf(\"GetJSONKeys failed\" + err.Error())\n\n\t\treturn\n\t}\n\n\tfmt.Println(keys)\n}\n"
  },
  {
    "path": "server/utils/jwt.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tjwt \"github.com/golang-jwt/jwt/v5\"\n)\n\ntype JWT struct {\n\tSigningKey []byte\n}\n\nvar (\n\tTokenValid            = errors.New(\"未知错误\")\n\tTokenExpired          = errors.New(\"token已过期\")\n\tTokenNotValidYet      = errors.New(\"token尚未激活\")\n\tTokenMalformed        = errors.New(\"这不是一个token\")\n\tTokenSignatureInvalid = errors.New(\"无效签名\")\n\tTokenInvalid          = errors.New(\"无法处理此token\")\n)\n\nfunc NewJWT() *JWT {\n\treturn &JWT{\n\t\t[]byte(global.GVA_CONFIG.JWT.SigningKey),\n\t}\n}\n\nfunc (j *JWT) CreateClaims(baseClaims request.BaseClaims) request.CustomClaims {\n\tbf, _ := ParseDuration(global.GVA_CONFIG.JWT.BufferTime)\n\tep, _ := ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)\n\tclaims := request.CustomClaims{\n\t\tBaseClaims: baseClaims,\n\t\tBufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失\n\t\tRegisteredClaims: jwt.RegisteredClaims{\n\t\t\tAudience:  jwt.ClaimStrings{\"GVA\"},                   // 受众\n\t\t\tNotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间\n\t\t\tExpiresAt: jwt.NewNumericDate(time.Now().Add(ep)),    // 过期时间 7天  配置文件\n\t\t\tIssuer:    global.GVA_CONFIG.JWT.Issuer,              // 签名的发行者\n\t\t},\n\t}\n\treturn claims\n}\n\n// CreateToken 创建一个token\nfunc (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\treturn token.SignedString(j.SigningKey)\n}\n\n// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题\nfunc (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) {\n\tv, err, _ := global.GVA_Concurrency_Control.Do(\"JWT:\"+oldToken, func() (interface{}, error) {\n\t\treturn j.CreateToken(claims)\n\t})\n\treturn v.(string), err\n}\n\n// ParseToken 解析 token\nfunc (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {\n\ttoken, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {\n\t\treturn j.SigningKey, nil\n\t})\n\n\tif err != nil {\n\t\tswitch {\n\t\tcase errors.Is(err, jwt.ErrTokenExpired):\n\t\t\treturn nil, TokenExpired\n\t\tcase errors.Is(err, jwt.ErrTokenMalformed):\n\t\t\treturn nil, TokenMalformed\n\t\tcase errors.Is(err, jwt.ErrTokenSignatureInvalid):\n\t\t\treturn nil, TokenSignatureInvalid\n\t\tcase errors.Is(err, jwt.ErrTokenNotValidYet):\n\t\t\treturn nil, TokenNotValidYet\n\t\tdefault:\n\t\t\treturn nil, TokenInvalid\n\t\t}\n\t}\n\tif token != nil {\n\t\tif claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {\n\t\t\treturn claims, nil\n\t\t}\n\t}\n\treturn nil, TokenValid\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: SetRedisJWT\n//@description: jwt存入redis并设置过期时间\n//@param: jwt string, userName string\n//@return: err error\n\nfunc SetRedisJWT(jwt string, userName string) (err error) {\n\t// 此处过期时间等于jwt过期时间\n\tdr, err := ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttimer := dr\n\terr = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err()\n\treturn err\n}\n"
  },
  {
    "path": "server/utils/plugin/plugin.go",
    "content": "package plugin\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\nconst (\n\tOnlyFuncName = \"Plugin\"\n)\n\n// Plugin 插件模式接口化\ntype Plugin interface {\n\t// Register 注册路由\n\tRegister(group *gin.RouterGroup)\n\n\t// RouterPath 用户返回注册路由\n\tRouterPath() string\n}\n"
  },
  {
    "path": "server/utils/plugin/v2/plugin.go",
    "content": "package plugin\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Plugin 插件模式接口化v2\ntype Plugin interface {\n\t// Register 注册路由\n\tRegister(group *gin.Engine)\n}\n"
  },
  {
    "path": "server/utils/plugin/v2/registry.go",
    "content": "package plugin\n\nimport \"sync\"\n\nvar (\n\tregistryMu sync.RWMutex\n\tregistry   []Plugin\n)\n\n// Register records a plugin for auto initialization.\nfunc Register(p Plugin) {\n\tif p == nil {\n\t\treturn\n\t}\n\tregistryMu.Lock()\n\tregistry = append(registry, p)\n\tregistryMu.Unlock()\n}\n\n// Registered returns a snapshot of all registered plugins.\nfunc Registered() []Plugin {\n\tregistryMu.RLock()\n\tdefer registryMu.RUnlock()\n\tout := make([]Plugin, len(registry))\n\tcopy(out, registry)\n\treturn out\n}\n"
  },
  {
    "path": "server/utils/request/http.go",
    "content": "package request\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nfunc HttpRequest(\n\turlStr string,\n\tmethod string,\n\theaders map[string]string,\n\tparams map[string]string,\n\tdata any) (*http.Response, error) {\n\t// 创建URL\n\tu, err := url.Parse(urlStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 添加查询参数\n\tquery := u.Query()\n\tfor k, v := range params {\n\t\tquery.Set(k, v)\n\t}\n\tu.RawQuery = query.Encode()\n\n\t// 将数据编码为JSON\n\tbuf := new(bytes.Buffer)\n\tif data != nil {\n\t\tb, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbuf = bytes.NewBuffer(b)\n\t}\n\n\t// 创建请求\n\treq, err := http.NewRequest(method, u.String(), buf)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor k, v := range headers {\n\t\treq.Header.Set(k, v)\n\t}\n\n\tif data != nil {\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t}\n\n\t// 发送请求\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 返回响应，让调用者处理\n\treturn resp, nil\n}\n"
  },
  {
    "path": "server/utils/server.go",
    "content": "package utils\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/v3/cpu\"\n\t\"github.com/shirou/gopsutil/v3/disk\"\n\t\"github.com/shirou/gopsutil/v3/mem\"\n)\n\nconst (\n\tB  = 1\n\tKB = 1024 * B\n\tMB = 1024 * KB\n\tGB = 1024 * MB\n)\n\ntype Server struct {\n\tOs   Os   `json:\"os\"`\n\tCpu  Cpu  `json:\"cpu\"`\n\tRam  Ram  `json:\"ram\"`\n\tDisk []Disk `json:\"disk\"`\n}\n\ntype Os struct {\n\tGOOS         string `json:\"goos\"`\n\tNumCPU       int    `json:\"numCpu\"`\n\tCompiler     string `json:\"compiler\"`\n\tGoVersion    string `json:\"goVersion\"`\n\tNumGoroutine int    `json:\"numGoroutine\"`\n}\n\ntype Cpu struct {\n\tCpus  []float64 `json:\"cpus\"`\n\tCores int       `json:\"cores\"`\n}\n\ntype Ram struct {\n\tUsedMB      int `json:\"usedMb\"`\n\tTotalMB     int `json:\"totalMb\"`\n\tUsedPercent int `json:\"usedPercent\"`\n}\n\ntype Disk struct {\n\tMountPoint  string `json:\"mountPoint\"`\n\tUsedMB      int `json:\"usedMb\"`\n\tUsedGB      int `json:\"usedGb\"`\n\tTotalMB     int `json:\"totalMb\"`\n\tTotalGB     int `json:\"totalGb\"`\n\tUsedPercent int `json:\"usedPercent\"`\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: InitCPU\n//@description: OS信息\n//@return: o Os, err error\n\nfunc InitOS() (o Os) {\n\to.GOOS = runtime.GOOS\n\to.NumCPU = runtime.NumCPU()\n\to.Compiler = runtime.Compiler\n\to.GoVersion = runtime.Version()\n\to.NumGoroutine = runtime.NumGoroutine()\n\treturn o\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: InitCPU\n//@description: CPU信息\n//@return: c Cpu, err error\n\nfunc InitCPU() (c Cpu, err error) {\n\tif cores, err := cpu.Counts(false); err != nil {\n\t\treturn c, err\n\t} else {\n\t\tc.Cores = cores\n\t}\n\tif cpus, err := cpu.Percent(time.Duration(200)*time.Millisecond, true); err != nil {\n\t\treturn c, err\n\t} else {\n\t\tc.Cpus = cpus\n\t}\n\treturn c, nil\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: InitRAM\n//@description: RAM信息\n//@return: r Ram, err error\n\nfunc InitRAM() (r Ram, err error) {\n\tif u, err := mem.VirtualMemory(); err != nil {\n\t\treturn r, err\n\t} else {\n\t\tr.UsedMB = int(u.Used) / MB\n\t\tr.TotalMB = int(u.Total) / MB\n\t\tr.UsedPercent = int(u.UsedPercent)\n\t}\n\treturn r, nil\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@function: InitDisk\n//@description: 硬盘信息\n//@return: d Disk, err error\n\nfunc InitDisk() (d []Disk, err error) {\n\tfor i := range global.GVA_CONFIG.DiskList {\n\t\tmp := global.GVA_CONFIG.DiskList[i].MountPoint\n\t\tif u, err := disk.Usage(mp); err != nil {\n\t\t\treturn d, err\n\t\t} else {\n\t\t\td = append(d, Disk{\n\t\t\t\tMountPoint:  mp,\n\t\t\t\tUsedMB:      int(u.Used) / MB,\n\t\t\t\tUsedGB:      int(u.Used) / GB,\n\t\t\t\tTotalMB:     int(u.Total) / MB,\n\t\t\t\tTotalGB:     int(u.Total) / GB,\n\t\t\t\tUsedPercent: int(u.UsedPercent),\n\t\t\t})\n\t\t}\n\t}\n\treturn d, nil\n}\n"
  },
  {
    "path": "server/utils/stacktrace/stacktrace.go",
    "content": "package stacktrace\n\nimport (\n    \"regexp\"\n    \"strconv\"\n    \"strings\"\n)\n\n// Frame 表示一次栈帧解析结果\ntype Frame struct {\n    File string\n    Line int\n    Func string\n}\n\nvar fileLineRe = regexp.MustCompile(`\\s*(.+\\.go):(\\d+)\\s*$`)\n\n// FindFinalCaller 从 zap 的 entry.Stack 文本中，解析“最终业务调用方”的文件与行号\n// 策略：自顶向下解析，优先选择第一条项目代码帧，过滤第三方库/标准库/框架中间件\nfunc FindFinalCaller(stack string) (Frame, bool) {\n    if stack == \"\" {\n        return Frame{}, false\n    }\n    lines := strings.Split(stack, \"\\n\")\n    var currFunc string\n    for i := 0; i < len(lines); i++ {\n        line := strings.TrimSpace(lines[i])\n        if line == \"\" {\n            continue\n        }\n        if m := fileLineRe.FindStringSubmatch(line); m != nil {\n            file := m[1]\n            ln, _ := strconv.Atoi(m[2])\n            if shouldSkip(file) {\n                // 跳过此帧，同时重置函数名以避免错误配对\n                currFunc = \"\"\n                continue\n            }\n            return Frame{File: file, Line: ln, Func: currFunc}, true\n        }\n        // 记录函数名行，下一行通常是文件:行\n        currFunc = line\n    }\n    return Frame{}, false\n}\n\nfunc shouldSkip(file string) bool {\n    // 第三方库与 Go 模块缓存\n    if strings.Contains(file, \"/go/pkg/mod/\") {\n        return true\n    }\n    if strings.Contains(file, \"/go.uber.org/\") {\n        return true\n    }\n    if strings.Contains(file, \"/gorm.io/\") {\n        return true\n    }\n    // 标准库\n    if strings.Contains(file, \"/go/go\") && strings.Contains(file, \"/src/\") { // e.g. /Users/name/go/go1.24.2/src/net/http/server.go\n        return true\n    }\n    // 框架内不需要作为最终调用方的路径\n    if strings.Contains(file, \"/server/core/zap.go\") {\n        return true\n    }\n    if strings.Contains(file, \"/server/core/\") {\n        return true\n    }\n    if strings.Contains(file, \"/server/utils/errorhook/\") {\n        return true\n    }\n    if strings.Contains(file, \"/server/middleware/\") {\n        return true\n    }\n    if strings.Contains(file, \"/server/router/\") {\n        return true\n    }\n    return false\n}"
  },
  {
    "path": "server/utils/system_events.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n)\n\n// SystemEvents 定义系统级事件处理\ntype SystemEvents struct {\n\treloadHandlers []func() error\n\tmu             sync.RWMutex\n}\n\n// 全局事件管理器\nvar GlobalSystemEvents = &SystemEvents{}\n\n// RegisterReloadHandler 注册系统重载处理函数\nfunc (e *SystemEvents) RegisterReloadHandler(handler func() error) {\n\te.mu.Lock()\n\tdefer e.mu.Unlock()\n\te.reloadHandlers = append(e.reloadHandlers, handler)\n}\n\n// TriggerReload 触发所有注册的重载处理函数\nfunc (e *SystemEvents) TriggerReload() error {\n\te.mu.RLock()\n\tdefer e.mu.RUnlock()\n\t\n\tfor _, handler := range e.reloadHandlers {\n\t\tif err := handler(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/utils/timer/timed_task.go",
    "content": "package timer\n\nimport (\n\t\"github.com/robfig/cron/v3\"\n\t\"sync\"\n)\n\ntype Timer interface {\n\t// 寻找所有Cron\n\tFindCronList() map[string]*taskManager\n\t// 添加Task 方法形式以秒的形式加入\n\tAddTaskByFuncWithSecond(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) // 添加Task Func以秒的形式加入\n\t// 添加Task 接口形式以秒的形式加入\n\tAddTaskByJobWithSeconds(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error)\n\t// 通过函数的方法添加任务\n\tAddTaskByFunc(cronName string, spec string, task func(), taskName string, option ...cron.Option) (cron.EntryID, error)\n\t// 通过接口的方法添加任务 要实现一个带有 Run方法的接口触发\n\tAddTaskByJob(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error)\n\t// 获取对应taskName的cron 可能会为空\n\tFindCron(cronName string) (*taskManager, bool)\n\t// 指定cron开始执行\n\tStartCron(cronName string)\n\t// 指定cron停止执行\n\tStopCron(cronName string)\n\t// 查找指定cron下的指定task\n\tFindTask(cronName string, taskName string) (*task, bool)\n\t// 根据id删除指定cron下的指定task\n\tRemoveTask(cronName string, id int)\n\t// 根据taskName删除指定cron下的指定task\n\tRemoveTaskByName(cronName string, taskName string)\n\t// 清理掉指定cronName\n\tClear(cronName string)\n\t// 停止所有的cron\n\tClose()\n}\n\ntype task struct {\n\tEntryID  cron.EntryID\n\tSpec     string\n\tTaskName string\n}\n\ntype taskManager struct {\n\tcorn  *cron.Cron\n\ttasks map[cron.EntryID]*task\n}\n\n// timer 定时任务管理\ntype timer struct {\n\tcronList map[string]*taskManager\n\tsync.Mutex\n}\n\n// AddTaskByFunc 通过函数的方法添加任务\nfunc (t *timer) AddTaskByFunc(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tif _, ok := t.cronList[cronName]; !ok {\n\t\ttasks := make(map[cron.EntryID]*task)\n\t\tt.cronList[cronName] = &taskManager{\n\t\t\tcorn:  cron.New(option...),\n\t\t\ttasks: tasks,\n\t\t}\n\t}\n\tid, err := t.cronList[cronName].corn.AddFunc(spec, fun)\n\tt.cronList[cronName].corn.Start()\n\tt.cronList[cronName].tasks[id] = &task{\n\t\tEntryID:  id,\n\t\tSpec:     spec,\n\t\tTaskName: taskName,\n\t}\n\treturn id, err\n}\n\n// AddTaskByFuncWithSecond 通过函数的方法使用WithSeconds添加任务\nfunc (t *timer) AddTaskByFuncWithSecond(cronName string, spec string, fun func(), taskName string, option ...cron.Option) (cron.EntryID, error) {\n\tt.Lock()\n\tdefer t.Unlock()\n\toption = append(option, cron.WithSeconds())\n\tif _, ok := t.cronList[cronName]; !ok {\n\t\ttasks := make(map[cron.EntryID]*task)\n\t\tt.cronList[cronName] = &taskManager{\n\t\t\tcorn:  cron.New(option...),\n\t\t\ttasks: tasks,\n\t\t}\n\t}\n\tid, err := t.cronList[cronName].corn.AddFunc(spec, fun)\n\tt.cronList[cronName].corn.Start()\n\tt.cronList[cronName].tasks[id] = &task{\n\t\tEntryID:  id,\n\t\tSpec:     spec,\n\t\tTaskName: taskName,\n\t}\n\treturn id, err\n}\n\n// AddTaskByJob 通过接口的方法添加任务\nfunc (t *timer) AddTaskByJob(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tif _, ok := t.cronList[cronName]; !ok {\n\t\ttasks := make(map[cron.EntryID]*task)\n\t\tt.cronList[cronName] = &taskManager{\n\t\t\tcorn:  cron.New(option...),\n\t\t\ttasks: tasks,\n\t\t}\n\t}\n\tid, err := t.cronList[cronName].corn.AddJob(spec, job)\n\tt.cronList[cronName].corn.Start()\n\tt.cronList[cronName].tasks[id] = &task{\n\t\tEntryID:  id,\n\t\tSpec:     spec,\n\t\tTaskName: taskName,\n\t}\n\treturn id, err\n}\n\n// AddTaskByJobWithSeconds 通过接口的方法添加任务\nfunc (t *timer) AddTaskByJobWithSeconds(cronName string, spec string, job interface{ Run() }, taskName string, option ...cron.Option) (cron.EntryID, error) {\n\tt.Lock()\n\tdefer t.Unlock()\n\toption = append(option, cron.WithSeconds())\n\tif _, ok := t.cronList[cronName]; !ok {\n\t\ttasks := make(map[cron.EntryID]*task)\n\t\tt.cronList[cronName] = &taskManager{\n\t\t\tcorn:  cron.New(option...),\n\t\t\ttasks: tasks,\n\t\t}\n\t}\n\tid, err := t.cronList[cronName].corn.AddJob(spec, job)\n\tt.cronList[cronName].corn.Start()\n\tt.cronList[cronName].tasks[id] = &task{\n\t\tEntryID:  id,\n\t\tSpec:     spec,\n\t\tTaskName: taskName,\n\t}\n\treturn id, err\n}\n\n// FindCron 获取对应cronName的cron 可能会为空\nfunc (t *timer) FindCron(cronName string) (*taskManager, bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tv, ok := t.cronList[cronName]\n\treturn v, ok\n}\n\n// FindTask 获取对应cronName的cron 可能会为空\nfunc (t *timer) FindTask(cronName string, taskName string) (*task, bool) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tv, ok := t.cronList[cronName]\n\tif !ok {\n\t\treturn nil, ok\n\t}\n\tfor _, t2 := range v.tasks {\n\t\tif t2.TaskName == taskName {\n\t\t\treturn t2, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\n// FindCronList 获取所有的任务列表\nfunc (t *timer) FindCronList() map[string]*taskManager {\n\tt.Lock()\n\tdefer t.Unlock()\n\treturn t.cronList\n}\n\n// StartCron 开始任务\nfunc (t *timer) StartCron(cronName string) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tif v, ok := t.cronList[cronName]; ok {\n\t\tv.corn.Start()\n\t}\n}\n\n// StopCron 停止任务\nfunc (t *timer) StopCron(cronName string) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tif v, ok := t.cronList[cronName]; ok {\n\t\tv.corn.Stop()\n\t}\n}\n\n// RemoveTask 从cronName 删除指定任务\nfunc (t *timer) RemoveTask(cronName string, id int) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tif v, ok := t.cronList[cronName]; ok {\n\t\tv.corn.Remove(cron.EntryID(id))\n\t\tdelete(v.tasks, cron.EntryID(id))\n\t}\n}\n\n// RemoveTaskByName 从cronName 使用taskName 删除指定任务\nfunc (t *timer) RemoveTaskByName(cronName string, taskName string) {\n\tfTask, ok := t.FindTask(cronName, taskName)\n\tif !ok {\n\t\treturn\n\t}\n\tt.RemoveTask(cronName, int(fTask.EntryID))\n}\n\n// Clear 清除任务\nfunc (t *timer) Clear(cronName string) {\n\tt.Lock()\n\tdefer t.Unlock()\n\tif v, ok := t.cronList[cronName]; ok {\n\t\tv.corn.Stop()\n\t\tdelete(t.cronList, cronName)\n\t}\n}\n\n// Close 释放资源\nfunc (t *timer) Close() {\n\tt.Lock()\n\tdefer t.Unlock()\n\tfor _, v := range t.cronList {\n\t\tv.corn.Stop()\n\t}\n}\n\nfunc NewTimerTask() Timer {\n\treturn &timer{cronList: make(map[string]*taskManager)}\n}\n"
  },
  {
    "path": "server/utils/timer/timed_task_test.go",
    "content": "package timer\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar job = mockJob{}\n\ntype mockJob struct{}\n\nfunc (job mockJob) Run() {\n\tmockFunc()\n}\n\nfunc mockFunc() {\n\ttime.Sleep(time.Second)\n\tfmt.Println(\"1s...\")\n}\n\nfunc TestNewTimerTask(t *testing.T) {\n\ttm := NewTimerTask()\n\t_tm := tm.(*timer)\n\n\t{\n\t\t_, err := tm.AddTaskByFunc(\"func\", \"@every 1s\", mockFunc, \"测试mockfunc\")\n\t\tassert.Nil(t, err)\n\t\t_, ok := _tm.cronList[\"func\"]\n\t\tif !ok {\n\t\t\tt.Error(\"no find func\")\n\t\t}\n\t}\n\n\t{\n\t\t_, err := tm.AddTaskByJob(\"job\", \"@every 1s\", job, \"测试job mockfunc\")\n\t\tassert.Nil(t, err)\n\t\t_, ok := _tm.cronList[\"job\"]\n\t\tif !ok {\n\t\t\tt.Error(\"no find job\")\n\t\t}\n\t}\n\n\t{\n\t\t_, ok := tm.FindCron(\"func\")\n\t\tif !ok {\n\t\t\tt.Error(\"no find func\")\n\t\t}\n\t\t_, ok = tm.FindCron(\"job\")\n\t\tif !ok {\n\t\t\tt.Error(\"no find job\")\n\t\t}\n\t\t_, ok = tm.FindCron(\"none\")\n\t\tif ok {\n\t\t\tt.Error(\"find none\")\n\t\t}\n\t}\n\t{\n\t\ttm.Clear(\"func\")\n\t\t_, ok := tm.FindCron(\"func\")\n\t\tif ok {\n\t\t\tt.Error(\"find func\")\n\t\t}\n\t}\n\t{\n\t\ta := tm.FindCronList()\n\t\tb, c := tm.FindCron(\"job\")\n\t\tfmt.Println(a, b, c)\n\t}\n}\n"
  },
  {
    "path": "server/utils/upload/aliyun_oss.go",
    "content": "package upload\n\nimport (\n\t\"errors\"\n\t\"mime/multipart\"\n\t\"time\"\n\n\t\"github.com/aliyun/aliyun-oss-go-sdk/oss\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\ntype AliyunOSS struct{}\n\nfunc (*AliyunOSS) UploadFile(file *multipart.FileHeader) (string, string, error) {\n\tbucket, err := NewBucket()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function AliyunOSS.NewBucket() Failed\", zap.Any(\"err\", err.Error()))\n\t\treturn \"\", \"\", errors.New(\"function AliyunOSS.NewBucket() Failed, err:\" + err.Error())\n\t}\n\n\t// 读取本地文件。\n\tf, openError := file.Open()\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() Failed\", zap.Any(\"err\", openError.Error()))\n\t\treturn \"\", \"\", errors.New(\"function file.Open() Failed, err:\" + openError.Error())\n\t}\n\tdefer f.Close() // 创建文件 defer 关闭\n\t// 上传阿里云路径 文件名格式 自己可以改 建议保证唯一性\n\t// yunFileTmpPath := filepath.Join(\"uploads\", time.Now().Format(\"2006-01-02\")) + \"/\" + file.Filename\n\tyunFileTmpPath := global.GVA_CONFIG.AliyunOSS.BasePath + \"/\" + \"uploads\" + \"/\" + time.Now().Format(\"2006-01-02\") + \"/\" + file.Filename\n\n\t// 上传文件流。\n\terr = bucket.PutObject(yunFileTmpPath, f)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function formUploader.Put() Failed\", zap.Any(\"err\", err.Error()))\n\t\treturn \"\", \"\", errors.New(\"function formUploader.Put() Failed, err:\" + err.Error())\n\t}\n\n\treturn global.GVA_CONFIG.AliyunOSS.BucketUrl + \"/\" + yunFileTmpPath, yunFileTmpPath, nil\n}\n\nfunc (*AliyunOSS) DeleteFile(key string) error {\n\tbucket, err := NewBucket()\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function AliyunOSS.NewBucket() Failed\", zap.Any(\"err\", err.Error()))\n\t\treturn errors.New(\"function AliyunOSS.NewBucket() Failed, err:\" + err.Error())\n\t}\n\n\t// 删除单个文件。objectName表示删除OSS文件时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。\n\t// 如需删除文件夹，请将objectName设置为对应的文件夹名称。如果文件夹非空，则需要将文件夹下的所有object删除后才能删除该文件夹。\n\terr = bucket.DeleteObject(key)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function bucketManager.Delete() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn errors.New(\"function bucketManager.Delete() failed, err:\" + err.Error())\n\t}\n\n\treturn nil\n}\n\nfunc NewBucket() (*oss.Bucket, error) {\n\t// 创建OSSClient实例。\n\tclient, err := oss.New(global.GVA_CONFIG.AliyunOSS.Endpoint, global.GVA_CONFIG.AliyunOSS.AccessKeyId, global.GVA_CONFIG.AliyunOSS.AccessKeySecret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 获取存储空间。\n\tbucket, err := client.Bucket(global.GVA_CONFIG.AliyunOSS.BucketName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn bucket, nil\n}\n"
  },
  {
    "path": "server/utils/upload/aws_s3.go",
    "content": "package upload\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/feature/s3/manager\"\n\t\"github.com/aws/aws-sdk-go-v2/service/s3\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\ntype AwsS3 struct{}\n\n//@author: [WqyJh](https://github.com/WqyJh)\n//@object: *AwsS3\n//@function: UploadFile\n//@description: Upload file to Aws S3 using aws-sdk-go-v2. See https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/s3-example-basic-bucket-operations.html\n//@param: file *multipart.FileHeader\n//@return: string, string, error\n\nfunc (*AwsS3) UploadFile(file *multipart.FileHeader) (string, string, error) {\n\tclient := newS3Client()\n\tuploader := manager.NewUploader(client)\n\n\tfileKey := fmt.Sprintf(\"%d%s\", time.Now().Unix(), file.Filename)\n\tfilename := global.GVA_CONFIG.AwsS3.PathPrefix + \"/\" + fileKey\n\tf, openError := file.Open()\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() failed\", zap.Any(\"err\", openError.Error()))\n\t\treturn \"\", \"\", errors.New(\"function file.Open() failed, err:\" + openError.Error())\n\t}\n\tdefer f.Close() // 创建文件 defer 关闭\n\n\t_, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{\n\t\tBucket:      aws.String(global.GVA_CONFIG.AwsS3.Bucket),\n\t\tKey:         aws.String(filename),\n\t\tBody:        f,\n\t\tContentType: aws.String(file.Header.Get(\"Content-Type\")),\n\t})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function uploader.Upload() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn \"\", \"\", err\n\t}\n\n\treturn global.GVA_CONFIG.AwsS3.BaseURL + \"/\" + filename, fileKey, nil\n}\n\n//@author: [WqyJh](https://github.com/WqyJh)\n//@object: *AwsS3\n//@function: DeleteFile\n//@description: Delete file from Aws S3 using aws-sdk-go-v2. See https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/s3-example-basic-bucket-operations.html\n//@param: key string\n//@return: error\n\nfunc (*AwsS3) DeleteFile(key string) error {\n\tclient := newS3Client()\n\tfilename := global.GVA_CONFIG.AwsS3.PathPrefix + \"/\" + key\n\tbucket := global.GVA_CONFIG.AwsS3.Bucket\n\n\t_, err := client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{\n\t\tBucket: aws.String(bucket),\n\t\tKey:    aws.String(filename),\n\t})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function client.DeleteObject() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn errors.New(\"function client.DeleteObject() failed, err:\" + err.Error())\n\t}\n\n\twaiter := s3.NewObjectNotExistsWaiter(client)\n\t_ = waiter.Wait(context.TODO(), &s3.HeadObjectInput{\n\t\tBucket: aws.String(bucket),\n\t\tKey:    aws.String(filename),\n\t}, 30*time.Second)\n\n\treturn nil\n}\n\n// newS3Client creates an S3 v2 client with static credentials and optional custom endpoint.\n// minio在这里设置Endpoint地址,可以兼容\nfunc newS3Client() *s3.Client {\n\tcfg := global.GVA_CONFIG.AwsS3\n\n\tawsCfg, _ := config.LoadDefaultConfig(context.TODO(),\n\t\tconfig.WithRegion(cfg.Region),\n\t\tconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(\n\t\t\tcfg.SecretID,\n\t\t\tcfg.SecretKey,\n\t\t\t\"\",\n\t\t)),\n\t)\n\n\treturn s3.NewFromConfig(awsCfg, func(o *s3.Options) {\n\t\tif cfg.Endpoint != \"\" {\n\t\t\tendpoint := cfg.Endpoint\n\t\t\tif !strings.HasPrefix(endpoint, \"http://\") && !strings.HasPrefix(endpoint, \"https://\") {\n\t\t\t\tif cfg.DisableSSL {\n\t\t\t\t\tendpoint = \"http://\" + endpoint\n\t\t\t\t} else {\n\t\t\t\t\tendpoint = \"https://\" + endpoint\n\t\t\t\t}\n\t\t\t}\n\t\t\to.BaseEndpoint = aws.String(endpoint)\n\t\t}\n\t\to.UsePathStyle = cfg.S3ForcePathStyle\n\t})\n}\n"
  },
  {
    "path": "server/utils/upload/cloudflare_r2.go",
    "content": "package upload\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/feature/s3/manager\"\n\t\"github.com/aws/aws-sdk-go-v2/service/s3\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"go.uber.org/zap\"\n)\n\ntype CloudflareR2 struct{}\n\nfunc (c *CloudflareR2) UploadFile(file *multipart.FileHeader) (fileUrl string, fileName string, err error) {\n\tclient := c.newR2Client()\n\tuploader := manager.NewUploader(client)\n\n\tfileKey := fmt.Sprintf(\"%d_%s\", time.Now().Unix(), file.Filename)\n\tfileName = fmt.Sprintf(\"%s/%s\", global.GVA_CONFIG.CloudflareR2.Path, fileKey)\n\tf, openError := file.Open()\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() failed\", zap.Any(\"err\", openError.Error()))\n\t\treturn \"\", \"\", errors.New(\"function file.Open() failed, err:\" + openError.Error())\n\t}\n\tdefer f.Close() // 创建文件 defer 关闭\n\n\t_, err = uploader.Upload(context.TODO(), &s3.PutObjectInput{\n\t\tBucket: aws.String(global.GVA_CONFIG.CloudflareR2.Bucket),\n\t\tKey:    aws.String(fileName),\n\t\tBody:   f,\n\t})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function uploader.Upload() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn \"\", \"\", err\n\t}\n\n\treturn fmt.Sprintf(\"%s/%s\", global.GVA_CONFIG.CloudflareR2.BaseURL, fileName), fileKey, nil\n}\n\nfunc (c *CloudflareR2) DeleteFile(key string) error {\n\tclient := c.newR2Client()\n\tfilename := global.GVA_CONFIG.CloudflareR2.Path + \"/\" + key\n\tbucket := global.GVA_CONFIG.CloudflareR2.Bucket\n\n\t_, err := client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{\n\t\tBucket: aws.String(bucket),\n\t\tKey:    aws.String(filename),\n\t})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function client.DeleteObject() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn errors.New(\"function client.DeleteObject() failed, err:\" + err.Error())\n\t}\n\n\twaiter := s3.NewObjectNotExistsWaiter(client)\n\t_ = waiter.Wait(context.TODO(), &s3.HeadObjectInput{\n\t\tBucket: aws.String(bucket),\n\t\tKey:    aws.String(filename),\n\t}, 30*time.Second)\n\n\treturn nil\n}\n\nfunc (*CloudflareR2) newR2Client() *s3.Client {\n\tendpoint := fmt.Sprintf(\"https://%s.r2.cloudflarestorage.com\", global.GVA_CONFIG.CloudflareR2.AccountID)\n\n\tcfg, _ := config.LoadDefaultConfig(context.TODO(),\n\t\tconfig.WithRegion(\"auto\"),\n\t\tconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(\n\t\t\tglobal.GVA_CONFIG.CloudflareR2.AccessKeyID,\n\t\t\tglobal.GVA_CONFIG.CloudflareR2.SecretAccessKey,\n\t\t\t\"\",\n\t\t)),\n\t)\n\n\treturn s3.NewFromConfig(cfg, func(o *s3.Options) {\n\t\to.BaseEndpoint = aws.String(endpoint)\n\t})\n}\n"
  },
  {
    "path": "server/utils/upload/local.go",
    "content": "package upload\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"go.uber.org/zap\"\n)\n\nvar mu sync.Mutex\n\ntype Local struct{}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [ccfish86](https://github.com/ccfish86)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@object: *Local\n//@function: UploadFile\n//@description: 上传文件\n//@param: file *multipart.FileHeader\n//@return: string, string, error\n\nfunc (*Local) UploadFile(file *multipart.FileHeader) (string, string, error) {\n\t// 读取文件后缀\n\text := filepath.Ext(file.Filename)\n\t// 读取文件名并加密\n\tname := strings.TrimSuffix(file.Filename, ext)\n\tname = utils.MD5V([]byte(name))\n\t// 拼接新文件名\n\tfilename := name + \"_\" + time.Now().Format(\"20060102150405\") + ext\n\t// 尝试创建此路径\n\tmkdirErr := os.MkdirAll(global.GVA_CONFIG.Local.StorePath, os.ModePerm)\n\tif mkdirErr != nil {\n\t\tglobal.GVA_LOG.Error(\"function os.MkdirAll() failed\", zap.Any(\"err\", mkdirErr.Error()))\n\t\treturn \"\", \"\", errors.New(\"function os.MkdirAll() failed, err:\" + mkdirErr.Error())\n\t}\n\t// 拼接路径和文件名\n\tp := global.GVA_CONFIG.Local.StorePath + \"/\" + filename\n\tfilepath := global.GVA_CONFIG.Local.Path + \"/\" + filename\n\n\tf, openError := file.Open() // 读取文件\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() failed\", zap.Any(\"err\", openError.Error()))\n\t\treturn \"\", \"\", errors.New(\"function file.Open() failed, err:\" + openError.Error())\n\t}\n\tdefer f.Close() // 创建文件 defer 关闭\n\n\tout, createErr := os.Create(p)\n\tif createErr != nil {\n\t\tglobal.GVA_LOG.Error(\"function os.Create() failed\", zap.Any(\"err\", createErr.Error()))\n\n\t\treturn \"\", \"\", errors.New(\"function os.Create() failed, err:\" + createErr.Error())\n\t}\n\tdefer out.Close() // 创建文件 defer 关闭\n\n\t_, copyErr := io.Copy(out, f) // 传输（拷贝）文件\n\tif copyErr != nil {\n\t\tglobal.GVA_LOG.Error(\"function io.Copy() failed\", zap.Any(\"err\", copyErr.Error()))\n\t\treturn \"\", \"\", errors.New(\"function io.Copy() failed, err:\" + copyErr.Error())\n\t}\n\treturn filepath, filename, nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [ccfish86](https://github.com/ccfish86)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@object: *Local\n//@function: DeleteFile\n//@description: 删除文件\n//@param: key string\n//@return: error\n\nfunc (*Local) DeleteFile(key string) error {\n\t// 检查 key 是否为空\n\tif key == \"\" {\n\t\treturn errors.New(\"key不能为空\")\n\t}\n\n\t// 验证 key 是否包含非法字符或尝试访问存储路径之外的文件\n\tif strings.Contains(key, \"..\") || strings.ContainsAny(key, `\\/:*?\"<>|`) {\n\t\treturn errors.New(\"非法的key\")\n\t}\n\n\tp := filepath.Join(global.GVA_CONFIG.Local.StorePath, key)\n\n\t// 检查文件是否存在\n\tif _, err := os.Stat(p); os.IsNotExist(err) {\n\t\treturn errors.New(\"文件不存在\")\n\t}\n\n\t// 使用文件锁防止并发删除\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\terr := os.Remove(p)\n\tif err != nil {\n\t\treturn errors.New(\"文件删除失败: \" + err.Error())\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "server/utils/upload/minio_oss.go",
    "content": "package upload\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"mime\"\n\t\"mime/multipart\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/utils\"\n\t\"github.com/minio/minio-go/v7\"\n\t\"github.com/minio/minio-go/v7/pkg/credentials\"\n\t\"go.uber.org/zap\"\n)\n\nvar MinioClient *Minio // 优化性能，但是不支持动态配置\n\ntype Minio struct {\n\tClient *minio.Client\n\tbucket string\n}\n\nfunc GetMinio(endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (*Minio, error) {\n\tif MinioClient != nil {\n\t\treturn MinioClient, nil\n\t}\n\t// Initialize minio client object.\n\tminioClient, err := minio.New(endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, \"\"),\n\t\tSecure: useSSL, // Set to true if using https\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// 尝试创建bucket\n\terr = minioClient.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})\n\tif err != nil {\n\t\t// Check to see if we already own this bucket (which happens if you run this twice)\n\t\texists, errBucketExists := minioClient.BucketExists(context.Background(), bucketName)\n\t\tif errBucketExists == nil && exists {\n\t\t\t// log.Printf(\"We already own %s\\n\", bucketName)\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tMinioClient = &Minio{Client: minioClient, bucket: bucketName}\n\treturn MinioClient, nil\n}\n\nfunc (m *Minio) UploadFile(file *multipart.FileHeader) (filePathres, key string, uploadErr error) {\n\tf, openError := file.Open()\n\t// mutipart.File to os.File\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() Failed\", zap.Any(\"err\", openError.Error()))\n\t\treturn \"\", \"\", errors.New(\"function file.Open() Failed, err:\" + openError.Error())\n\t}\n\n\tfilecontent := bytes.Buffer{}\n\t_, err := io.Copy(&filecontent, f)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"读取文件失败\", zap.Any(\"err\", err.Error()))\n\t\treturn \"\", \"\", errors.New(\"读取文件失败, err:\" + err.Error())\n\t}\n\tf.Close() // 创建文件 defer 关闭\n\n\t// 对文件名进行加密存储\n\text := filepath.Ext(file.Filename)\n\tfilename := utils.MD5V([]byte(strings.TrimSuffix(file.Filename, ext))) + ext\n\tif global.GVA_CONFIG.Minio.BasePath == \"\" {\n\t\tfilePathres = \"uploads\" + \"/\" + time.Now().Format(\"2006-01-02\") + \"/\" + filename\n\t} else {\n\t\tfilePathres = global.GVA_CONFIG.Minio.BasePath + \"/\" + time.Now().Format(\"2006-01-02\") + \"/\" + filename\n\t}\n\n\t// 根据文件扩展名检测 MIME 类型\n\tcontentType := mime.TypeByExtension(ext)\n\tif contentType == \"\" {\n\t\tcontentType = \"application/octet-stream\"\n\t}\n\n\t// 设置超时10分钟\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)\n\tdefer cancel()\n\n\t// Upload the file with PutObject   大文件自动切换为分片上传\n\tinfo, err := m.Client.PutObject(ctx, global.GVA_CONFIG.Minio.BucketName, filePathres, &filecontent, file.Size, minio.PutObjectOptions{ContentType: contentType})\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"上传文件到minio失败\", zap.Any(\"err\", err.Error()))\n\t\treturn \"\", \"\", errors.New(\"上传文件到minio失败, err:\" + err.Error())\n\t}\n\treturn global.GVA_CONFIG.Minio.BucketUrl + \"/\" + info.Key, filePathres, nil\n}\n\nfunc (m *Minio) DeleteFile(key string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\t// Delete the object from MinIO\n\terr := m.Client.RemoveObject(ctx, m.bucket, key, minio.RemoveObjectOptions{})\n\treturn err\n}\n"
  },
  {
    "path": "server/utils/upload/obs.go",
    "content": "package upload\n\nimport (\n\t\"mime/multipart\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs\"\n\t\"github.com/pkg/errors\"\n)\n\nvar HuaWeiObs = new(Obs)\n\ntype Obs struct{}\n\nfunc NewHuaWeiObsClient() (client *obs.ObsClient, err error) {\n\treturn obs.New(global.GVA_CONFIG.HuaWeiObs.AccessKey, global.GVA_CONFIG.HuaWeiObs.SecretKey, global.GVA_CONFIG.HuaWeiObs.Endpoint)\n}\n\nfunc (o *Obs) UploadFile(file *multipart.FileHeader) (string, string, error) {\n\t// var open multipart.File\n\topen, err := file.Open()\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\tdefer open.Close()\n\tfilename := file.Filename\n\tinput := &obs.PutObjectInput{\n\t\tPutObjectBasicInput: obs.PutObjectBasicInput{\n\t\t\tObjectOperationInput: obs.ObjectOperationInput{\n\t\t\t\tBucket: global.GVA_CONFIG.HuaWeiObs.Bucket,\n\t\t\t\tKey:    filename,\n\t\t\t},\n\t\t\tHttpHeader: obs.HttpHeader{\n\t\t\t\tContentType: file.Header.Get(\"content-type\"),\n\t\t\t},\n\t\t},\n\t\tBody: open,\n\t}\n\n\tvar client *obs.ObsClient\n\tclient, err = NewHuaWeiObsClient()\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"获取华为对象存储对象失败!\")\n\t}\n\n\t_, err = client.PutObject(input)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"文件上传失败!\")\n\t}\n\tfilepath := global.GVA_CONFIG.HuaWeiObs.Path + \"/\" + filename\n\treturn filepath, filename, err\n}\n\nfunc (o *Obs) DeleteFile(key string) error {\n\tclient, err := NewHuaWeiObsClient()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"获取华为对象存储对象失败!\")\n\t}\n\tinput := &obs.DeleteObjectInput{\n\t\tBucket: global.GVA_CONFIG.HuaWeiObs.Bucket,\n\t\tKey:    key,\n\t}\n\tvar output *obs.DeleteObjectOutput\n\toutput, err = client.DeleteObject(input)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"删除对象(%s)失败!, output: %v\", key, output)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/utils/upload/qiniu.go",
    "content": "package upload\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/qiniu/go-sdk/v7/auth/qbox\"\n\t\"github.com/qiniu/go-sdk/v7/storage\"\n\t\"go.uber.org/zap\"\n)\n\ntype Qiniu struct{}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [ccfish86](https://github.com/ccfish86)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@object: *Qiniu\n//@function: UploadFile\n//@description: 上传文件\n//@param: file *multipart.FileHeader\n//@return: string, string, error\n\nfunc (*Qiniu) UploadFile(file *multipart.FileHeader) (string, string, error) {\n\tputPolicy := storage.PutPolicy{Scope: global.GVA_CONFIG.Qiniu.Bucket}\n\tmac := qbox.NewMac(global.GVA_CONFIG.Qiniu.AccessKey, global.GVA_CONFIG.Qiniu.SecretKey)\n\tupToken := putPolicy.UploadToken(mac)\n\tcfg := qiniuConfig()\n\tformUploader := storage.NewFormUploader(cfg)\n\tret := storage.PutRet{}\n\tputExtra := storage.PutExtra{Params: map[string]string{\"x:name\": \"github logo\"}}\n\n\tf, openError := file.Open()\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() failed\", zap.Any(\"err\", openError.Error()))\n\n\t\treturn \"\", \"\", errors.New(\"function file.Open() failed, err:\" + openError.Error())\n\t}\n\tdefer f.Close()                                                  // 创建文件 defer 关闭\n\tfileKey := fmt.Sprintf(\"%d%s\", time.Now().Unix(), file.Filename) // 文件名格式 自己可以改 建议保证唯一性\n\tputErr := formUploader.Put(context.Background(), &ret, upToken, fileKey, f, file.Size, &putExtra)\n\tif putErr != nil {\n\t\tglobal.GVA_LOG.Error(\"function formUploader.Put() failed\", zap.Any(\"err\", putErr.Error()))\n\t\treturn \"\", \"\", errors.New(\"function formUploader.Put() failed, err:\" + putErr.Error())\n\t}\n\treturn global.GVA_CONFIG.Qiniu.ImgPath + \"/\" + ret.Key, ret.Key, nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@author: [ccfish86](https://github.com/ccfish86)\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@object: *Qiniu\n//@function: DeleteFile\n//@description: 删除文件\n//@param: key string\n//@return: error\n\nfunc (*Qiniu) DeleteFile(key string) error {\n\tmac := qbox.NewMac(global.GVA_CONFIG.Qiniu.AccessKey, global.GVA_CONFIG.Qiniu.SecretKey)\n\tcfg := qiniuConfig()\n\tbucketManager := storage.NewBucketManager(mac, cfg)\n\tif err := bucketManager.Delete(global.GVA_CONFIG.Qiniu.Bucket, key); err != nil {\n\t\tglobal.GVA_LOG.Error(\"function bucketManager.Delete() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn errors.New(\"function bucketManager.Delete() failed, err:\" + err.Error())\n\t}\n\treturn nil\n}\n\n//@author: [SliverHorn](https://github.com/SliverHorn)\n//@object: *Qiniu\n//@function: qiniuConfig\n//@description: 根据配置文件进行返回七牛云的配置\n//@return: *storage.Config\n\nfunc qiniuConfig() *storage.Config {\n\tcfg := storage.Config{\n\t\tUseHTTPS:      global.GVA_CONFIG.Qiniu.UseHTTPS,\n\t\tUseCdnDomains: global.GVA_CONFIG.Qiniu.UseCdnDomains,\n\t}\n\tswitch global.GVA_CONFIG.Qiniu.Zone { // 根据配置文件进行初始化空间对应的机房\n\tcase \"ZoneHuadong\":\n\t\tcfg.Zone = &storage.ZoneHuadong\n\tcase \"ZoneHuabei\":\n\t\tcfg.Zone = &storage.ZoneHuabei\n\tcase \"ZoneHuanan\":\n\t\tcfg.Zone = &storage.ZoneHuanan\n\tcase \"ZoneBeimei\":\n\t\tcfg.Zone = &storage.ZoneBeimei\n\tcase \"ZoneXinjiapo\":\n\t\tcfg.Zone = &storage.ZoneXinjiapo\n\t}\n\treturn &cfg\n}\n"
  },
  {
    "path": "server/utils/upload/tencent_cos.go",
    "content": "package upload\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\n\t\"github.com/tencentyun/cos-go-sdk-v5\"\n\t\"go.uber.org/zap\"\n)\n\ntype TencentCOS struct{}\n\n// UploadFile upload file to COS\nfunc (*TencentCOS) UploadFile(file *multipart.FileHeader) (string, string, error) {\n\tclient := NewClient()\n\tf, openError := file.Open()\n\tif openError != nil {\n\t\tglobal.GVA_LOG.Error(\"function file.Open() failed\", zap.Any(\"err\", openError.Error()))\n\t\treturn \"\", \"\", errors.New(\"function file.Open() failed, err:\" + openError.Error())\n\t}\n\tdefer f.Close() // 创建文件 defer 关闭\n\tfileKey := fmt.Sprintf(\"%d%s\", time.Now().Unix(), file.Filename)\n\n\t_, err := client.Object.Put(context.Background(), global.GVA_CONFIG.TencentCOS.PathPrefix+\"/\"+fileKey, f, nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn global.GVA_CONFIG.TencentCOS.BaseURL + \"/\" + global.GVA_CONFIG.TencentCOS.PathPrefix + \"/\" + fileKey, fileKey, nil\n}\n\n// DeleteFile delete file form COS\nfunc (*TencentCOS) DeleteFile(key string) error {\n\tclient := NewClient()\n\tname := global.GVA_CONFIG.TencentCOS.PathPrefix + \"/\" + key\n\t_, err := client.Object.Delete(context.Background(), name)\n\tif err != nil {\n\t\tglobal.GVA_LOG.Error(\"function bucketManager.Delete() failed\", zap.Any(\"err\", err.Error()))\n\t\treturn errors.New(\"function bucketManager.Delete() failed, err:\" + err.Error())\n\t}\n\treturn nil\n}\n\n// NewClient init COS client\nfunc NewClient() *cos.Client {\n\turlStr, _ := url.Parse(\"https://\" + global.GVA_CONFIG.TencentCOS.Bucket + \".cos.\" + global.GVA_CONFIG.TencentCOS.Region + \".myqcloud.com\")\n\tbaseURL := &cos.BaseURL{BucketURL: urlStr}\n\tclient := cos.NewClient(baseURL, &http.Client{\n\t\tTransport: &cos.AuthorizationTransport{\n\t\t\tSecretID:  global.GVA_CONFIG.TencentCOS.SecretID,\n\t\t\tSecretKey: global.GVA_CONFIG.TencentCOS.SecretKey,\n\t\t},\n\t})\n\treturn client\n}\n"
  },
  {
    "path": "server/utils/upload/upload.go",
    "content": "package upload\n\nimport (\n\t\"mime/multipart\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n)\n\n// OSS 对象存储接口\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [ccfish86](https://github.com/ccfish86)\ntype OSS interface {\n\tUploadFile(file *multipart.FileHeader) (string, string, error)\n\tDeleteFile(key string) error\n}\n\n// NewOss OSS的实例化方法\n// Author [SliverHorn](https://github.com/SliverHorn)\n// Author [ccfish86](https://github.com/ccfish86)\nfunc NewOss() OSS {\n\tswitch global.GVA_CONFIG.System.OssType {\n\tcase \"local\":\n\t\treturn &Local{}\n\tcase \"qiniu\":\n\t\treturn &Qiniu{}\n\tcase \"tencent-cos\":\n\t\treturn &TencentCOS{}\n\tcase \"aliyun-oss\":\n\t\treturn &AliyunOSS{}\n\tcase \"huawei-obs\":\n\t\treturn HuaWeiObs\n\tcase \"aws-s3\":\n\t\treturn &AwsS3{}\n\tcase \"cloudflare-r2\":\n\t\treturn &CloudflareR2{}\n\tcase \"minio\":\n\t\tminioClient, err := GetMinio(global.GVA_CONFIG.Minio.Endpoint, global.GVA_CONFIG.Minio.AccessKeyId, global.GVA_CONFIG.Minio.AccessKeySecret, global.GVA_CONFIG.Minio.BucketName, global.GVA_CONFIG.Minio.UseSSL)\n\t\tif err != nil {\n\t\t\tglobal.GVA_LOG.Warn(\"你配置了使用minio，但是初始化失败，请检查minio可用性或安全配置: \" + err.Error())\n\t\t\tpanic(\"minio初始化失败\") // 建议这样做，用户自己配置了minio，如果报错了还要把服务开起来，使用起来也很危险\n\t\t}\n\t\treturn minioClient\n\tdefault:\n\t\treturn &Local{}\n\t}\n}\n"
  },
  {
    "path": "server/utils/validator.go",
    "content": "package utils\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Rules map[string][]string\n\ntype RulesMap map[string]Rules\n\nvar CustomizeMap = make(map[string]Rules)\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: RegisterRule\n//@description: 注册自定义规则方案建议在路由初始化层即注册\n//@param: key string, rule Rules\n//@return: err error\n\nfunc RegisterRule(key string, rule Rules) (err error) {\n\tif CustomizeMap[key] != nil {\n\t\treturn errors.New(key + \"已注册,无法重复注册\")\n\t} else {\n\t\tCustomizeMap[key] = rule\n\t\treturn nil\n\t}\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: NotEmpty\n//@description: 非空 不能为其对应类型的0值\n//@return: string\n\nfunc NotEmpty() string {\n\treturn \"notEmpty\"\n}\n\n// @author: [zooqkl](https://github.com/zooqkl)\n// @function: RegexpMatch\n// @description: 正则校验 校验输入项是否满足正则表达式\n// @param:  rule string\n// @return: string\n\nfunc RegexpMatch(rule string) string {\n\treturn \"regexp=\" + rule\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Lt\n//@description: 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较\n//@param: mark string\n//@return: string\n\nfunc Lt(mark string) string {\n\treturn \"lt=\" + mark\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Le\n//@description: 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较\n//@param: mark string\n//@return: string\n\nfunc Le(mark string) string {\n\treturn \"le=\" + mark\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Eq\n//@description: 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较\n//@param: mark string\n//@return: string\n\nfunc Eq(mark string) string {\n\treturn \"eq=\" + mark\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Ne\n//@description: 不等于入参(!=)  如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较\n//@param: mark string\n//@return: string\n\nfunc Ne(mark string) string {\n\treturn \"ne=\" + mark\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Ge\n//@description: 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较\n//@param: mark string\n//@return: string\n\nfunc Ge(mark string) string {\n\treturn \"ge=\" + mark\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Gt\n//@description: 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较\n//@param: mark string\n//@return: string\n\nfunc Gt(mark string) string {\n\treturn \"gt=\" + mark\n}\n\n//\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: Verify\n//@description: 校验方法\n//@param: st interface{}, roleMap Rules(入参实例，规则map)\n//@return: err error\n\nfunc Verify(st interface{}, roleMap Rules) (err error) {\n\tcompareMap := map[string]bool{\n\t\t\"lt\": true,\n\t\t\"le\": true,\n\t\t\"eq\": true,\n\t\t\"ne\": true,\n\t\t\"ge\": true,\n\t\t\"gt\": true,\n\t}\n\n\ttyp := reflect.TypeOf(st)\n\tval := reflect.ValueOf(st) // 获取reflect.Type类型\n\n\tkd := val.Kind() // 获取到st对应的类别\n\tif kd != reflect.Struct {\n\t\treturn errors.New(\"expect struct\")\n\t}\n\tnum := val.NumField()\n\t// 遍历结构体的所有字段\n\tfor i := 0; i < num; i++ {\n\t\ttagVal := typ.Field(i)\n\t\tval := val.Field(i)\n\t\tif tagVal.Type.Kind() == reflect.Struct {\n\t\t\tif err = Verify(val.Interface(), roleMap); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif len(roleMap[tagVal.Name]) > 0 {\n\t\t\tfor _, v := range roleMap[tagVal.Name] {\n\t\t\t\tswitch {\n\t\t\t\tcase v == \"notEmpty\":\n\t\t\t\t\tif isBlank(val) {\n\t\t\t\t\t\treturn errors.New(tagVal.Name + \"值不能为空\")\n\t\t\t\t\t}\n\t\t\t\tcase strings.Split(v, \"=\")[0] == \"regexp\":\n\t\t\t\t\tif !regexpMatch(strings.Split(v, \"=\")[1], val.String()) {\n\t\t\t\t\t\treturn errors.New(tagVal.Name + \"格式校验不通过\")\n\t\t\t\t\t}\n\t\t\t\tcase compareMap[strings.Split(v, \"=\")[0]]:\n\t\t\t\t\tif !compareVerify(val, v) {\n\t\t\t\t\t\treturn errors.New(tagVal.Name + \"长度或值不在合法范围,\" + v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: compareVerify\n//@description: 长度和数字的校验方法 根据类型自动校验\n//@param: value reflect.Value, VerifyStr string\n//@return: bool\n\nfunc compareVerify(value reflect.Value, VerifyStr string) bool {\n\tswitch value.Kind() {\n\tcase reflect.String:\n\t\treturn compare(len([]rune(value.String())), VerifyStr)\n\tcase reflect.Slice, reflect.Array:\n\t\treturn compare(value.Len(), VerifyStr)\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\treturn compare(value.Uint(), VerifyStr)\n\tcase reflect.Float32, reflect.Float64:\n\t\treturn compare(value.Float(), VerifyStr)\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\treturn compare(value.Int(), VerifyStr)\n\tdefault:\n\t\treturn false\n\t}\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: isBlank\n//@description: 非空校验\n//@param: value reflect.Value\n//@return: bool\n\nfunc isBlank(value reflect.Value) bool {\n\tswitch value.Kind() {\n\tcase reflect.String, reflect.Slice:\n\t\treturn value.Len() == 0\n\tcase reflect.Bool:\n\t\treturn !value.Bool()\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\treturn value.Int() == 0\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\treturn value.Uint() == 0\n\tcase reflect.Float32, reflect.Float64:\n\t\treturn value.Float() == 0\n\tcase reflect.Interface, reflect.Ptr:\n\t\treturn value.IsNil()\n\t}\n\treturn reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())\n}\n\n//@author: [piexlmax](https://github.com/piexlmax)\n//@function: compare\n//@description: 比较函数\n//@param: value interface{}, VerifyStr string\n//@return: bool\n\nfunc compare(value interface{}, VerifyStr string) bool {\n\tVerifyStrArr := strings.Split(VerifyStr, \"=\")\n\tval := reflect.ValueOf(value)\n\tswitch val.Kind() {\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:\n\t\tVInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64)\n\t\tif VErr != nil {\n\t\t\treturn false\n\t\t}\n\t\tswitch {\n\t\tcase VerifyStrArr[0] == \"lt\":\n\t\t\treturn val.Int() < VInt\n\t\tcase VerifyStrArr[0] == \"le\":\n\t\t\treturn val.Int() <= VInt\n\t\tcase VerifyStrArr[0] == \"eq\":\n\t\t\treturn val.Int() == VInt\n\t\tcase VerifyStrArr[0] == \"ne\":\n\t\t\treturn val.Int() != VInt\n\t\tcase VerifyStrArr[0] == \"ge\":\n\t\t\treturn val.Int() >= VInt\n\t\tcase VerifyStrArr[0] == \"gt\":\n\t\t\treturn val.Int() > VInt\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:\n\t\tVInt, VErr := strconv.Atoi(VerifyStrArr[1])\n\t\tif VErr != nil {\n\t\t\treturn false\n\t\t}\n\t\tswitch {\n\t\tcase VerifyStrArr[0] == \"lt\":\n\t\t\treturn val.Uint() < uint64(VInt)\n\t\tcase VerifyStrArr[0] == \"le\":\n\t\t\treturn val.Uint() <= uint64(VInt)\n\t\tcase VerifyStrArr[0] == \"eq\":\n\t\t\treturn val.Uint() == uint64(VInt)\n\t\tcase VerifyStrArr[0] == \"ne\":\n\t\t\treturn val.Uint() != uint64(VInt)\n\t\tcase VerifyStrArr[0] == \"ge\":\n\t\t\treturn val.Uint() >= uint64(VInt)\n\t\tcase VerifyStrArr[0] == \"gt\":\n\t\t\treturn val.Uint() > uint64(VInt)\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\tcase reflect.Float32, reflect.Float64:\n\t\tVFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64)\n\t\tif VErr != nil {\n\t\t\treturn false\n\t\t}\n\t\tswitch {\n\t\tcase VerifyStrArr[0] == \"lt\":\n\t\t\treturn val.Float() < VFloat\n\t\tcase VerifyStrArr[0] == \"le\":\n\t\t\treturn val.Float() <= VFloat\n\t\tcase VerifyStrArr[0] == \"eq\":\n\t\t\treturn val.Float() == VFloat\n\t\tcase VerifyStrArr[0] == \"ne\":\n\t\t\treturn val.Float() != VFloat\n\t\tcase VerifyStrArr[0] == \"ge\":\n\t\t\treturn val.Float() >= VFloat\n\t\tcase VerifyStrArr[0] == \"gt\":\n\t\t\treturn val.Float() > VFloat\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc regexpMatch(rule, matchStr string) bool {\n\treturn regexp.MustCompile(rule).MatchString(matchStr)\n}\n"
  },
  {
    "path": "server/utils/validator_test.go",
    "content": "package utils\n\nimport (\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/common/request\"\n\t\"testing\"\n)\n\ntype PageInfoTest struct {\n\tPageInfo request.PageInfo\n\tName     string\n}\n\nfunc TestVerify(t *testing.T) {\n\tPageInfoVerify := Rules{\"Page\": {NotEmpty()}, \"PageSize\": {NotEmpty()}, \"Name\": {NotEmpty()}}\n\tvar testInfo PageInfoTest\n\ttestInfo.Name = \"test\"\n\ttestInfo.PageInfo.Page = 0\n\ttestInfo.PageInfo.PageSize = 0\n\terr := Verify(testInfo, PageInfoVerify)\n\tif err == nil {\n\t\tt.Error(\"校验失败，未能捕捉0值\")\n\t}\n\ttestInfo.Name = \"\"\n\ttestInfo.PageInfo.Page = 1\n\ttestInfo.PageInfo.PageSize = 10\n\terr = Verify(testInfo, PageInfoVerify)\n\tif err == nil {\n\t\tt.Error(\"校验失败，未能正常检测name为空\")\n\t}\n\ttestInfo.Name = \"test\"\n\ttestInfo.PageInfo.Page = 1\n\ttestInfo.PageInfo.PageSize = 10\n\terr = Verify(testInfo, PageInfoVerify)\n\tif err != nil {\n\t\tt.Error(\"校验失败，未能正常通过检测\")\n\t}\n}\n"
  },
  {
    "path": "server/utils/verify.go",
    "content": "package utils\n\nvar (\n\tIdVerify               = Rules{\"ID\": []string{NotEmpty()}}\n\tApiVerify              = Rules{\"Path\": {NotEmpty()}, \"Description\": {NotEmpty()}, \"ApiGroup\": {NotEmpty()}, \"Method\": {NotEmpty()}}\n\tMenuVerify             = Rules{\"Path\": {NotEmpty()}, \"Name\": {NotEmpty()}, \"Component\": {NotEmpty()}, \"Sort\": {Ge(\"0\")}}\n\tMenuMetaVerify         = Rules{\"Title\": {NotEmpty()}}\n\tLoginVerify            = Rules{\"Username\": {NotEmpty()}, \"Password\": {NotEmpty()}}\n\tRegisterVerify         = Rules{\"Username\": {NotEmpty()}, \"NickName\": {NotEmpty()}, \"Password\": {NotEmpty()}, \"AuthorityId\": {NotEmpty()}}\n\tPageInfoVerify         = Rules{\"Page\": {NotEmpty()}, \"PageSize\": {NotEmpty()}}\n\tCustomerVerify         = Rules{\"CustomerName\": {NotEmpty()}, \"CustomerPhoneData\": {NotEmpty()}}\n\tAutoCodeVerify         = Rules{\"Abbreviation\": {NotEmpty()}, \"StructName\": {NotEmpty()}, \"PackageName\": {NotEmpty()}}\n\tAutoPackageVerify      = Rules{\"PackageName\": {NotEmpty()}}\n\tAuthorityVerify        = Rules{\"AuthorityId\": {NotEmpty()}, \"AuthorityName\": {NotEmpty()}}\n\tAuthorityIdVerify      = Rules{\"AuthorityId\": {NotEmpty()}}\n\tOldAuthorityVerify     = Rules{\"OldAuthorityId\": {NotEmpty()}}\n\tChangePasswordVerify   = Rules{\"Password\": {NotEmpty()}, \"NewPassword\": {NotEmpty()}}\n\tSetUserAuthorityVerify = Rules{\"AuthorityId\": {NotEmpty()}}\n)\n"
  },
  {
    "path": "server/utils/zip.go",
    "content": "package utils\n\nimport (\n\t\"archive/zip\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// 解压\nfunc Unzip(zipFile string, destDir string) ([]string, error) {\n\tzipReader, err := zip.OpenReader(zipFile)\n\tvar paths []string\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\tdefer zipReader.Close()\n\n\tfor _, f := range zipReader.File {\n\t\tif strings.Contains(f.Name, \"..\") {\n\t\t\treturn []string{}, fmt.Errorf(\"%s 文件名不合法\", f.Name)\n\t\t}\n\t\tfpath := filepath.Join(destDir, f.Name)\n\t\tpaths = append(paths, fpath)\n\t\tif f.FileInfo().IsDir() {\n\t\t\tos.MkdirAll(fpath, os.ModePerm)\n\t\t} else {\n\t\t\tif err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {\n\t\t\t\treturn []string{}, err\n\t\t\t}\n\n\t\t\tinFile, err := f.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn []string{}, err\n\t\t\t}\n\t\t\tdefer inFile.Close()\n\n\t\t\toutFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())\n\t\t\tif err != nil {\n\t\t\t\treturn []string{}, err\n\t\t\t}\n\t\t\tdefer outFile.Close()\n\n\t\t\t_, err = io.Copy(outFile, inFile)\n\t\t\tif err != nil {\n\t\t\t\treturn []string{}, err\n\t\t\t}\n\t\t}\n\t}\n\treturn paths, nil\n}\n"
  },
  {
    "path": "web/.docker-compose/nginx/conf.d/my.conf",
    "content": "server {\n    listen       8080;\n    server_name localhost;\n\n    #charset koi8-r;\n    #access_log  logs/host.access.log  main;\n\n    location / {\n        root /usr/share/nginx/html;\n        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';\n        try_files $uri $uri/ /index.html;\n    }\n\n    location /api {\n        proxy_set_header Host $http_host;\n        proxy_set_header  X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        rewrite ^/api/(.*)$ /$1 break;  #重写\n        proxy_pass http://177.7.0.12:8888; # 设置代理服务器的协议和地址\n     }\n\n    location /api/swagger/index.html {\n        proxy_pass http://127.0.0.1:8888/swagger/index.html;\n     }\n }"
  },
  {
    "path": "web/.docker-compose/nginx/conf.d/nginx.conf",
    "content": "server {\n    listen  80;\n    server_name localhost;\n\n    #charset koi8-r;\n    #access_log  logs/host.access.log  main;\n\n    location / {\n        root /usr/share/nginx/html/dist;\n        add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';\n        try_files $uri $uri/ /index.html;\n    }\n\n    location /api {\n        proxy_set_header Host $http_host;\n        proxy_set_header  X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        rewrite ^/api/(.*)$ /$1 break;  #重写\n        proxy_pass http://127.0.0.1:8888; # 设置代理服务器的协议和地址\n     }\n    location  /form-generator {\n        proxy_set_header Host $http_host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_pass http://127.0.0.1:8888;\n    }\n    location /api/swagger/index.html {\n        proxy_pass http://127.0.0.1:8888/swagger/index.html;\n     }\n }"
  },
  {
    "path": "web/.dockerignore",
    "content": "node_modules/"
  },
  {
    "path": "web/.gitignore",
    "content": "node_modules/*\npackage-lock.json\nyarn.lock\nbun.lockb\nconfig.yaml"
  },
  {
    "path": "web/.prettierrc",
    "content": "{\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"none\",\n  \"bracketSpacing\": true,\n  \"arrowParens\": \"always\",\n  \"vueIndentScriptAndStyle\": true,\n  \"endOfLine\": \"lf\"\n}\n"
  },
  {
    "path": "web/Dockerfile",
    "content": "# 如果需要用 cicd ，请设置环境变量：\n# variables:\n#     DOCKER_BUILDKIT: 1\n\nFROM node:20-slim AS base\nENV PNPM_HOME=\"/pnpm\"\nENV PATH=\"$PNPM_HOME:$PATH\"\nRUN corepack enable\nCOPY . /app\nWORKDIR /app\n\n\nFROM base AS prod-deps\nRUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod\n\nFROM base AS build\nCOPY --from=prod-deps /app/node_modules /app/node_modules\nRUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install && pnpm run build\n\n\nFROM nginx:alpine\nLABEL MAINTAINER=\"bypanghu@163.com\"\nCOPY --from=base  /app/.docker-compose/nginx/conf.d/my.conf /etc/nginx/conf.d/my.conf\nCOPY --from=build  /app/dist /usr/share/nginx/html\nRUN ls -al /usr/share/nginx/html\n"
  },
  {
    "path": "web/README.md",
    "content": "# gin-vue-admin web\n\n## Project setup\n\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n\n```\nnpm run build\n```\n\n### Run your tests\n\n```\nnpm run test\n```\n\n### Lints and fixes files\n\n```\nnpm run lint\n```\n\n整理代码结构\n\n```lua\nweb\n ├── babel.config.js\n ├── Dockerfile\n ├── favicon.ico\n ├── index.html                 -- 主页面\n ├── limit.js                   -- 助手代码\n ├── package.json               -- 包管理器代码\n ├── src                        -- 源代码\n │   ├── api                    -- api 组\n │   ├── App.vue                -- 主页面\n │   ├── assets                 -- 静态资源\n │   ├── components             -- 全局组件\n │   ├── core                   -- gva 组件包\n │   │   ├── config.js          -- gva网站配置文件\n │   │   ├── gin-vue-admin.js   -- 注册欢迎文件\n │   │   └── global.js          -- 统一导入文件\n │   ├── directive              -- v-auth 注册文件\n │   ├── main.js                -- 主文件\n │   ├── permission.js          -- 路由中间件\n │   ├── pinia                  -- pinia 状态管理器，取代vuex\n │   │   ├── index.js           -- 入口文件\n │   │   └── modules            -- modules\n │   │       ├── dictionary.js\n │   │       ├── router.js\n │   │       └── user.js\n │   ├── router                 -- 路由声明文件\n │   │   └── index.js\n │   ├── style                  -- 全局样式\n │   │   ├── base.scss\n │   │   ├── basics.scss\n │   │   ├── element_visiable.scss  -- 此处可以全局覆盖 element-plus 样式\n │   │   ├── iconfont.css           -- 顶部几个icon的样式文件\n │   │   ├── main.scss\n │   │   ├── mobile.scss\n │   │   └── newLogin.scss\n │   ├── utils                  -- 方法包库\n │   │   ├── asyncRouter.js     -- 动态路由相关\n │   │   ├── bus.js             -- 全局mitt声明文件\n │   │   ├── date.js            -- 日期相关\n │   │   ├── dictionary.js      -- 获取字典方法\n │   │   ├── downloadImg.js     -- 下载图片方法\n │   │   ├── format.js          -- 格式整理相关\n │   │   ├── image.js           -- 图片相关方法\n │   │   ├── page.js            -- 设置页面标题\n │   │   ├── request.js         -- 请求\n │   │   └── stringFun.js       -- 字符串文件\n |   ├── view -- 主要view代码\n |   |   ├── about -- 关于我们\n |   |   ├── dashboard -- 面板\n |   |   ├── error -- 错误\n |   |   ├── example --上传案例\n |   |   ├── iconList -- icon列表\n |   |   ├── init -- 初始化数据\n |   |   |   ├── index -- 新版本\n |   |   |   ├── init -- 旧版本\n |   |   ├── layout  --  layout约束页面\n |   |   |   ├── aside\n |   |   |   ├── bottomInfo     -- bottomInfo\n |   |   |   ├── screenfull     -- 全屏设置\n |   |   |   ├── setting        -- 系统设置\n |   |   |   └── index.vue      -- base 约束\n |   |   ├── login              --登录\n |   |   ├── person             --个人中心\n |   |   ├── superAdmin         -- 超级管理员操作\n |   |   ├── system             -- 系统检测页面\n |   |   ├── systemTools        -- 系统配置相关页面\n |   |   └── routerHolder.vue   -- page 入口页面\n ├── vite.config.js             -- vite 配置文件\n └── yarn.lock\n\n```\n"
  },
  {
    "path": "web/babel.config.js",
    "content": "module.exports = {\n  presets: [],\n  plugins: []\n}\n"
  },
  {
    "path": "web/eslint.config.mjs",
    "content": "import js from '@eslint/js'\nimport pluginVue from 'eslint-plugin-vue'\nimport globals from 'globals'\n\nexport default [\n  js.configs.recommended,\n  ...pluginVue.configs['flat/essential'],\n  {\n    name: 'app/files-to-lint',\n    files: ['**/*.{js,mjs,jsx,vue}'],\n    languageOptions: {\n      ecmaVersion: 'latest',\n      sourceType: 'module',\n      globals: globals.node\n    },\n    rules: {\n      'vue/max-attributes-per-line': 0,\n      'vue/no-v-model-argument': 0,\n      'vue/multi-word-component-names': 'off',\n      'no-lone-blocks': 'off',\n      'no-extend-native': 'off',\n      'no-unused-vars': ['error', { argsIgnorePattern: '^_' }]\n    }\n  },\n  {\n    name: 'app/files-to-ignore',\n    ignores: ['**/dist/**', '**/build/*.js', '**/src/assets/**', '**/public/**']\n  }\n]\n"
  },
  {
    "path": "web/index.html",
    "content": "<!doctype html>\n<html lang=\"zh-cn\" class=\"transition-colors\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n    <meta\n      content=\"Gin,Vue,Admin.Gin-Vue-Admin,GVA,gin-vue-admin,后台管理框架,vue后台管理框架,gin-vue-admin文档,gin-vue-admin首页,gin-vue-admin\"\n      name=\"keywords\"\n    />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <title></title>\n    <style>\n      .transition-colors {\n        transition-property: color, background-color, border-color,\n          text-decoration-color, fill, stroke;\n        transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n        transition-duration: 150ms;\n      }\n      body {\n        margin: 0;\n        --64f90c3645474bd5: #409eff;\n      }\n      #gva-loading-box {\n        position: relative;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        height: 100vh;\n        width: 100vw;\n      }\n      #loading-text {\n        position: absolute;\n        bottom: calc(50% - 100px);\n        left: 0;\n        width: 100%;\n        text-align: center;\n        color: #666;\n        font-size: 14px;\n      }\n      #loading {\n        position: absolute;\n        top: calc(50% - 20px);\n        left: calc(50% - 20px);\n      }\n      @keyframes loader {\n        0% {\n          left: -100px;\n        }\n        100% {\n          left: 110%;\n        }\n      }\n      #box {\n        width: 50px;\n        height: 50px;\n        background: var(--64f90c3645474bd5);\n        animation: animate 0.5s linear infinite;\n        position: absolute;\n        top: 0;\n        left: 0;\n        border-radius: 3px;\n      }\n      @keyframes animate {\n        17% {\n          border-bottom-right-radius: 3px;\n        }\n        25% {\n          transform: translateY(9px) rotate(22.5deg);\n        }\n        50% {\n          transform: translateY(18px) scale(1, 0.9) rotate(45deg);\n          border-bottom-right-radius: 40px;\n        }\n        75% {\n          transform: translateY(9px) rotate(67.5deg);\n        }\n        100% {\n          transform: translateY(0) rotate(90deg);\n        }\n      }\n      #shadow {\n        width: 50px;\n        height: 5px;\n        background: #000;\n        opacity: 0.1;\n        position: absolute;\n        top: 59px;\n        left: 0;\n        border-radius: 50%;\n        animation: shadow 0.5s linear infinite;\n      }\n      .dark #shadow {\n        background: #fff;\n      }\n      @keyframes shadow {\n        50% {\n          transform: scale(1.2, 1);\n        }\n      }\n    </style>\n  </head>\n\n  <body>\n    <div id=\"gva-loading-box\">\n      <div id=\"loading\">\n        <div id=\"shadow\"></div>\n        <div id=\"box\"></div>\n      </div>\n      <div id=\"loading-text\">系统正在加载中，请稍候...</div>\n    </div>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"./src/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "web/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@/*\": [\"src/*\"]\n    }\n  },\n  \"exclude\": [\"node_modules\", \"dist\"],\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "web/limit.js",
    "content": "// 运行项目前通过node执行此脚本 （此脚本与 node_modules 目录同级）\nimport fs from 'fs'\nimport path from 'path'\nconst wfPath = path.resolve(__dirname, './node_modules/.bin')\n\nfs.readdir(wfPath, (err, files) => {\n  if (err) {\n    console.log(err)\n  } else {\n    if (files.length !== 0) {\n      files.forEach((item) => {\n        if (item.split('.')[1] === 'cmd') {\n          replaceStr(`${wfPath}/${item}`, /\"%_prog%\"/, '%_prog%')\n        }\n      })\n    }\n  }\n})\n\n// 参数：[文件路径、 需要修改的字符串、修改后的字符串] (替换对应文件内字符串的公共函数)\nfunction replaceStr(filePath, sourceRegx, targetSrt) {\n  fs.readFile(filePath, (err, data) => {\n    if (err) {\n      console.log(err)\n    } else {\n      let str = data.toString()\n      str = str.replace(sourceRegx, targetSrt)\n      fs.writeFile(filePath, str, (err) => {\n        if (err) {\n          console.log(err)\n        } else {\n          console.log('\\x1B[42m%s\\x1B[0m', '文件修改成功')\n        }\n      })\n    }\n  })\n}\n"
  },
  {
    "path": "web/openDocument.js",
    "content": "/*\n此文件受版权保护，未经授权禁止修改！如果您尚未获得授权，请通过微信(shouzi_1994)联系我们以购买授权。在未授权状态下，只需保留此代码，不会影响任何正常使用。\n     未经授权的商用使用可能会被我们的资产搜索引擎爬取，并可能导致后续索赔。索赔金额将不低于高级授权费的十倍。请您遵守版权法律法规，尊重知识产权。\n*/\n\nimport child_process from 'child_process'\n\nvar url = 'https://www.gin-vue-admin.com'\nvar cmd = ''\nswitch (process.platform) {\n  case 'win32':\n    cmd = 'start'\n    child_process.exec(cmd + ' ' + url)\n    break\n\n  case 'darwin':\n    cmd = 'open'\n    child_process.exec(cmd + ' ' + url)\n    break\n}\n"
  },
  {
    "path": "web/package.json",
    "content": "{\n  \"name\": \"gin-vue-admin\",\n  \"version\": \"2.9.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"node openDocument.js && vite --host --mode development\",\n    \"serve\": \"node openDocument.js && vite --host --mode development\",\n    \"build\": \"vite build --mode production\",\n    \"limit-build\": \"npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build\",\n    \"preview\": \"vite preview\",\n    \"fix-memory-limit\": \"cross-env LIMIT=4096 increase-memory-limit\"\n  },\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@element-plus/icons-vue\": \"^2.3.1\",\n    \"@form-create/designer\": \"^3.2.6\",\n    \"@form-create/element-ui\": \"^3.2.10\",\n    \"@iconify/vue\": \"^5.0.0\",\n    \"@unocss/transformer-directives\": \"^66.4.2\",\n    \"@vue-office/docx\": \"^1.6.2\",\n    \"@vue-office/excel\": \"^1.7.11\",\n    \"@vue-office/pdf\": \"^2.0.2\",\n    \"@vueuse/core\": \"^11.0.3\",\n    \"@vueuse/integrations\": \"^12.0.0\",\n    \"@wangeditor/editor\": \"^5.1.23\",\n    \"@wangeditor/editor-for-vue\": \"^5.1.12\",\n    \"ace-builds\": \"^1.36.4\",\n    \"axios\": \"1.8.2\",\n    \"chokidar\": \"^4.0.0\",\n    \"core-js\": \"^3.38.1\",\n    \"echarts\": \"5.5.1\",\n    \"element-plus\": \"^2.10.2\",\n    \"highlight.js\": \"^11.10.0\",\n    \"install\": \"^0.13.0\",\n    \"marked\": \"14.1.1\",\n    \"marked-highlight\": \"^2.1.4\",\n    \"mitt\": \"^3.0.1\",\n    \"npm\": \"^11.3.0\",\n    \"nprogress\": \"^0.2.0\",\n    \"path\": \"^0.12.7\",\n    \"pinia\": \"^2.2.2\",\n    \"qs\": \"^6.13.0\",\n    \"screenfull\": \"^6.0.2\",\n    \"sortablejs\": \"^1.15.3\",\n    \"spark-md5\": \"^3.0.2\",\n    \"universal-cookie\": \"^7\",\n    \"vform3-builds\": \"^3.0.10\",\n    \"vite-auto-import-svg\": \"^2.1.0\",\n    \"vue\": \"^3.5.7\",\n    \"vue-cropper\": \"^1.1.4\",\n    \"vue-echarts\": \"^7.0.3\",\n    \"vue-qr\": \"^4.0.9\",\n    \"vue-router\": \"^4.4.3\",\n    \"vue3-ace-editor\": \"^2.2.4\",\n    \"vue3-sfc-loader\": \"^0.9.5\",\n    \"vuedraggable\": \"^4.1.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/eslint-parser\": \"^7.25.1\",\n    \"@eslint/js\": \"^8.56.0\",\n    \"@unocss/extractor-svelte\": \"^66.4.2\",\n    \"@unocss/preset-wind3\": \"^66.4.2\",\n    \"@unocss/vite\": \"^66.5.0\",\n    \"@vitejs/plugin-legacy\": \"^6.0.0\",\n    \"@vitejs/plugin-vue\": \"^5.0.3\",\n    \"@vue/cli-plugin-babel\": \"~5.0.8\",\n    \"@vue/cli-plugin-eslint\": \"~5.0.8\",\n    \"@vue/cli-plugin-router\": \"~5.0.8\",\n    \"@vue/cli-plugin-vuex\": \"~5.0.8\",\n    \"@vue/cli-service\": \"~5.0.8\",\n    \"@vue/compiler-sfc\": \"^3.5.1\",\n    \"autoprefixer\": \"^10.4.20\",\n    \"babel-plugin-import\": \"^1.13.8\",\n    \"chalk\": \"^5.3.0\",\n    \"dotenv\": \"^16.4.5\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-plugin-vue\": \"^9.19.2\",\n    \"globals\": \"^16.3.0\",\n    \"sass\": \"^1.78.0\",\n    \"terser\": \"^5.31.6\",\n    \"vite\": \"^6.2.3\",\n    \"vite-check-multiple-dom\": \"0.2.1\",\n    \"vite-plugin-banner\": \"^0.8.0\",\n    \"vite-plugin-importer\": \"^0.2.5\",\n    \"vite-plugin-vue-devtools\": \"^7.0.16\"\n  }\n}\n"
  },
  {
    "path": "web/src/App.vue",
    "content": "<template>\n  <div\n    id=\"app\"\n    class=\"bg-gray-50 text-slate-700 !dark:text-slate-500 dark:bg-slate-800\"\n  >\n    <el-config-provider :locale=\"zhCn\" :size=\"appStore.config.global_size\">\n      <router-view />\n      <Application />\n    </el-config-provider>\n  </div>\n</template>\n\n<script setup>\n  import zhCn from 'element-plus/dist/locale/zh-cn.mjs'\n  import Application from '@/components/application/index.vue'\n  import { useAppStore } from '@/pinia'\n\n  const appStore = useAppStore()\n  defineOptions({\n    name: 'App'\n  })\n</script>\n<style lang=\"scss\">\n  // 引入初始化样式\n  #app {\n    height: 100vh;\n    overflow: hidden;\n    font-weight: 400 !important;\n  }\n\n  .el-button {\n    font-weight: 400 !important;\n  }\n\n  .gva-body-h {\n    min-height: calc(100% - 3rem);\n  }\n\n  .gva-container {\n    height: calc(100% - 2.5rem);\n  }\n\n  .gva-container2 {\n    height: calc(100% - 4.5rem);\n  }\n</style>\n"
  },
  {
    "path": "web/src/api/api.js",
    "content": "import service from '@/utils/request'\n\n// @Tags api\n// @Summary 分页获取角色列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body modelInterface.PageInfo true \"分页获取用户列表\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /api/getApiList [post]\n// {\n//  page     int\n//\tpageSize int\n// }\nexport const getApiList = (data) => {\n  return service({\n    url: '/api/getApiList',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags Api\n// @Summary 创建基础api\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateApiParams true \"创建api\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /api/createApi [post]\nexport const createApi = (data) => {\n  return service({\n    url: '/api/createApi',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags menu\n// @Summary 根据id获取菜单\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.GetById true \"根据id获取菜单\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /menu/getApiById [post]\nexport const getApiById = (data) => {\n  return service({\n    url: '/api/getApiById',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags Api\n// @Summary 更新api\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateApiParams true \"更新api\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /api/updateApi [post]\nexport const updateApi = (data) => {\n  return service({\n    url: '/api/updateApi',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags Api\n// @Summary 更新api\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateApiParams true \"更新api\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /api/setAuthApi [post]\nexport const setAuthApi = (data) => {\n  return service({\n    url: '/api/setAuthApi',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags Api\n// @Summary 获取所有的Api 不分页\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /api/getAllApis [post]\nexport const getAllApis = (data) => {\n  return service({\n    url: '/api/getAllApis',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags Api\n// @Summary 删除指定api\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body dbModel.Api true \"删除api\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /api/deleteApi [post]\nexport const deleteApi = (data) => {\n  return service({\n    url: '/api/deleteApi',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysApi\n// @Summary 删除选中Api\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"ID\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /api/deleteApisByIds [delete]\nexport const deleteApisByIds = (data) => {\n  return service({\n    url: '/api/deleteApisByIds',\n    method: 'delete',\n    data\n  })\n}\n\n// FreshCasbin\n// @Tags      SysApi\n// @Summary   刷新casbin缓存\n// @accept    application/json\n// @Produce   application/json\n// @Success   200   {object}  response.Response{msg=string}  \"刷新成功\"\n// @Router    /api/freshCasbin [get]\nexport const freshCasbin = () => {\n  return service({\n    url: '/api/freshCasbin',\n    method: 'get'\n  })\n}\n\nexport const syncApi = () => {\n  return service({\n    url: '/api/syncApi',\n    method: 'get'\n  })\n}\n\nexport const getApiGroups = () => {\n  return service({\n    url: '/api/getApiGroups',\n    method: 'get'\n  })\n}\n\nexport const ignoreApi = (data) => {\n  return service({\n    url: '/api/ignoreApi',\n    method: 'post',\n    data\n  })\n}\n\nexport const enterSyncApi = (data) => {\n  return service({\n    url: '/api/enterSyncApi',\n    method: 'post',\n    data\n  })\n}\n\n/**\n * 获取拥有指定API权限的角色ID列表\n * @param {string} path API路径\n * @param {string} method 请求方法\n * @returns {Promise<number[]>} 角色ID数组\n */\nexport const getApiRoles = (path, method) => {\n  return service({\n    url: '/api/getApiRoles',\n    method: 'get',\n    params: { path, method }\n  })\n}\n\n/**\n * 全量覆盖某API关联的角色列表\n * @param {Object} data\n * @param {string} data.path API路径\n * @param {string} data.method 请求方法\n * @param {number[]} data.authorityIds 角色ID列表\n * @returns {Promise}\n */\nexport const setApiRoles = (data) => {\n  return service({\n    url: '/api/setApiRoles',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/attachmentCategory.js",
    "content": "import service from '@/utils/request'\n// 分类列表\nexport const getCategoryList = () => {\n    return service({\n        url: '/attachmentCategory/getCategoryList',\n        method: 'get',\n    })\n}\n\n// 添加/编辑分类\nexport const addCategory = (data) => {\n    return service({\n        url: '/attachmentCategory/addCategory',\n        method: 'post',\n        data\n    })\n}\n\n// 删除分类\nexport const deleteCategory = (data) => {\n    return service({\n        url: '/attachmentCategory/deleteCategory',\n        method: 'post',\n        data\n    })\n}\n"
  },
  {
    "path": "web/src/api/authority.js",
    "content": "import service from '@/utils/request'\n// @Router /authority/getAuthorityList [post]\nexport const getAuthorityList = (data) => {\n  return service({\n    url: '/authority/getAuthorityList',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 删除角色\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body {authorityId uint} true \"删除角色\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /authority/deleteAuthority [post]\nexport const deleteAuthority = (data) => {\n  return service({\n    url: '/authority/deleteAuthority',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 创建角色\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateAuthorityPatams true \"创建角色\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /authority/createAuthority [post]\nexport const createAuthority = (data) => {\n  return service({\n    url: '/authority/createAuthority',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags authority\n// @Summary 拷贝角色\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateAuthorityPatams true \"拷贝角色\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"拷贝成功\"}\"\n// @Router /authority/copyAuthority [post]\nexport const copyAuthority = (data) => {\n  return service({\n    url: '/authority/copyAuthority',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 设置角色资源权限\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body sysModel.SysAuthority true \"设置角色资源权限\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"设置成功\"}\"\n// @Router /authority/setDataAuthority [post]\nexport const setDataAuthority = (data) => {\n  return service({\n    url: '/authority/setDataAuthority',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 修改角色\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysAuthority true \"修改角色\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"设置成功\"}\"\n// @Router /authority/setDataAuthority [post]\nexport const updateAuthority = (data) => {\n  return service({\n    url: '/authority/updateAuthority',\n    method: 'put',\n    data\n  })\n}\n\n/**\n * 获取拥有指定角色的用户ID列表\n * @param {number} authorityId 角色ID\n * @returns {Promise<number[]>} 用户ID数组\n */\nexport const getUsersByAuthorityId = (authorityId) => {\n  return service({\n    url: '/authority/getUsersByAuthority',\n    method: 'get',\n    params: { authorityId }\n  })\n}\n\n/**\n * 全量覆盖某角色关联的用户列表\n * @param {Object} data\n * @param {number} data.authorityId 角色ID\n * @param {number[]} data.userIds 用户ID列表\n * @returns {Promise}\n */\nexport const setRoleUsers = (data) => {\n  return service({\n    url: '/authority/setRoleUsers',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/authorityBtn.js",
    "content": "import service from '@/utils/request'\n\nexport const getAuthorityBtnApi = (data) => {\n  return service({\n    url: '/authorityBtn/getAuthorityBtn',\n    method: 'post',\n    data\n  })\n}\n\nexport const setAuthorityBtnApi = (data) => {\n  return service({\n    url: '/authorityBtn/setAuthorityBtn',\n    method: 'post',\n    data\n  })\n}\n\nexport const canRemoveAuthorityBtnApi = (params) => {\n  return service({\n    url: '/authorityBtn/canRemoveAuthorityBtn',\n    method: 'post',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/autoCode.js",
    "content": "import service from '@/utils/request'\n\nexport const preview = (data) => {\n  return service({\n    url: '/autoCode/preview',\n    method: 'post',\n    data\n  })\n}\n\nexport const createTemp = (data) => {\n  return service({\n    url: '/autoCode/createTemp',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysApi\n// @Summary 获取当前所有数据库\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /autoCode/getDatabase [get]\nexport const getDB = (params) => {\n  return service({\n    url: '/autoCode/getDB',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysApi\n// @Summary 获取当前数据库所有表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /autoCode/getTables [get]\nexport const getTable = (params) => {\n  return service({\n    url: '/autoCode/getTables',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysApi\n// @Summary 获取当前数据库所有表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /autoCode/getColumn [get]\nexport const getColumn = (params) => {\n  return service({\n    url: '/autoCode/getColumn',\n    method: 'get',\n    params\n  })\n}\n\nexport const getSysHistory = (data) => {\n  return service({\n    url: '/autoCode/getSysHistory',\n    method: 'post',\n    data\n  })\n}\n\nexport const rollback = (data) => {\n  return service({\n    url: '/autoCode/rollback',\n    method: 'post',\n    data\n  })\n}\n\nexport const getMeta = (data) => {\n  return service({\n    url: '/autoCode/getMeta',\n    method: 'post',\n    data\n  })\n}\n\nexport const delSysHistory = (data) => {\n  return service({\n    url: '/autoCode/delSysHistory',\n    method: 'post',\n    data\n  })\n}\n\nexport const createPackageApi = (data) => {\n  return service({\n    url: '/autoCode/createPackage',\n    method: 'post',\n    data\n  })\n}\n\nexport const getPackageApi = () => {\n  return service({\n    url: '/autoCode/getPackage',\n    method: 'post'\n  })\n}\n\nexport const deletePackageApi = (data) => {\n  return service({\n    url: '/autoCode/delPackage',\n    method: 'post',\n    data\n  })\n}\n\nexport const getTemplatesApi = () => {\n  return service({\n    url: '/autoCode/getTemplates',\n    method: 'get'\n  })\n}\n\nexport const installPlug = (data) => {\n  return service({\n    url: '/autoCode/installPlug',\n    method: 'post',\n    data\n  })\n}\n\nexport const pubPlug = (params) => {\n  return service({\n    url: '/autoCode/pubPlug',\n    method: 'post',\n    params\n  })\n}\n\nexport const llmAuto = (data) => {\n  return service({\n    url: '/autoCode/llmAuto',\n    method: 'post',\n    data: { ...data },\n    timeout: 1000 * 60 * 10,\n    loadingOption: {\n      lock: true,\n      fullscreen: true,\n      text: `小淼正在思考，请稍候...`\n    }\n  })\n}\n\nexport const addFunc = (data) => {\n  return service({\n    url: '/autoCode/addFunc',\n    method: 'post',\n    data\n  })\n}\n\nexport const initMenu = (data) => {\n  return service({\n    url: '/autoCode/initMenu',\n    method: 'post',\n    data\n  })\n}\n\nexport const initAPI = (data) => {\n  return service({\n    url: '/autoCode/initAPI',\n    method: 'post',\n    data\n  })\n}\n\nexport const initDictionary = (data) => {\n  return service({\n    url: '/autoCode/initDictionary',\n    method: 'post',\n    data\n  })\n}\n\nexport const mcp = (data) => {\n  return service({\n    url: '/autoCode/mcp',\n    method: 'post',\n    data\n  })\n}\n\n\nexport const mcpList = (data) => {\n  return service({\n    url: '/autoCode/mcpList',\n    method: 'post',\n    data\n  })\n}\n\n\nexport const mcpTest = (data) => {\n  return service({\n    url: '/autoCode/mcpTest',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysApi\n// @Summary 获取插件列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /autoCode/getPluginList [get]\nexport const getPluginList = (params) => {\n  return service({\n    url: '/autoCode/getPluginList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysApi\n// @Summary 删除插件\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /autoCode/removePlugin [post]\nexport const removePlugin = (params) => {\n  return service({\n    url: '/autoCode/removePlugin',\n    method: 'post',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/breakpoint.js",
    "content": "import service from '@/utils/request'\n// @Summary 设置角色资源权限\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body sysModel.SysAuthority true \"设置角色资源权限\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"设置成功\"}\"\n// @Router /authority/setDataAuthority [post]\n\nexport const findFile = (params) => {\n  return service({\n    url: '/fileUploadAndDownload/findFile',\n    method: 'get',\n    params\n  })\n}\n\nexport const breakpointContinue = (data) => {\n  return service({\n    url: '/fileUploadAndDownload/breakpointContinue',\n    method: 'post',\n    donNotShowLoading: true,\n    headers: { 'Content-Type': 'multipart/form-data' },\n    data\n  })\n}\n\nexport const breakpointContinueFinish = (params) => {\n  return service({\n    url: '/fileUploadAndDownload/breakpointContinueFinish',\n    method: 'post',\n    params\n  })\n}\n\nexport const removeChunk = (data, params) => {\n  return service({\n    url: '/fileUploadAndDownload/removeChunk',\n    method: 'post',\n    data,\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/casbin.js",
    "content": "import service from '@/utils/request'\n// @Tags authority\n// @Summary 更改角色api权限\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateAuthorityPatams true \"更改角色api权限\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /casbin/UpdateCasbin [post]\nexport const UpdateCasbin = (data) => {\n  return service({\n    url: '/casbin/updateCasbin',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags casbin\n// @Summary 获取权限列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.CreateAuthorityPatams true \"获取权限列表\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /casbin/getPolicyPathByAuthorityId [post]\nexport const getPolicyPathByAuthorityId = (data) => {\n  return service({\n    url: '/casbin/getPolicyPathByAuthorityId',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/customer.js",
    "content": "import service from '@/utils/request'\n// @Tags SysApi\n// @Summary 删除客户\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body dbModel.ExaCustomer true \"删除客户\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /customer/customer [post]\nexport const createExaCustomer = (data) => {\n  return service({\n    url: '/customer/customer',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysApi\n// @Summary 更新客户信息\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body dbModel.ExaCustomer true \"更新客户信息\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /customer/customer [put]\nexport const updateExaCustomer = (data) => {\n  return service({\n    url: '/customer/customer',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags SysApi\n// @Summary 创建客户\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body dbModel.ExaCustomer true \"创建客户\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /customer/customer [delete]\nexport const deleteExaCustomer = (data) => {\n  return service({\n    url: '/customer/customer',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysApi\n// @Summary 获取单一客户信息\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body dbModel.ExaCustomer true \"获取单一客户信息\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /customer/customer [get]\nexport const getExaCustomer = (params) => {\n  return service({\n    url: '/customer/customer',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysApi\n// @Summary 获取权限客户列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body modelInterface.PageInfo true \"获取权限客户列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /customer/customerList [get]\nexport const getExaCustomerList = (params) => {\n  return service({\n    url: '/customer/customerList',\n    method: 'get',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/email.js",
    "content": "import service from '@/utils/request'\n// @Tags email\n// @Summary 发送测试邮件\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"返回成功\"}\"\n// @Router /email/emailTest [post]\nexport const emailTest = (data) => {\n  return service({\n    url: '/email/emailTest',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/exportTemplate.js",
    "content": "import service from '@/utils/request'\n\n// @Tags SysExportTemplate\n// @Summary 创建导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysExportTemplate true \"创建导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /sysExportTemplate/createSysExportTemplate [post]\nexport const createSysExportTemplate = (data) => {\n  return service({\n    url: '/sysExportTemplate/createSysExportTemplate',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysExportTemplate\n// @Summary 删除导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysExportTemplate true \"删除导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysExportTemplate/deleteSysExportTemplate [delete]\nexport const deleteSysExportTemplate = (data) => {\n  return service({\n    url: '/sysExportTemplate/deleteSysExportTemplate',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysExportTemplate\n// @Summary 批量删除导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysExportTemplate/deleteSysExportTemplate [delete]\nexport const deleteSysExportTemplateByIds = (data) => {\n  return service({\n    url: '/sysExportTemplate/deleteSysExportTemplateByIds',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysExportTemplate\n// @Summary 更新导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysExportTemplate true \"更新导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /sysExportTemplate/updateSysExportTemplate [put]\nexport const updateSysExportTemplate = (data) => {\n  return service({\n    url: '/sysExportTemplate/updateSysExportTemplate',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags SysExportTemplate\n// @Summary 用id查询导出模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query model.SysExportTemplate true \"用id查询导出模板\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysExportTemplate/findSysExportTemplate [get]\nexport const findSysExportTemplate = (params) => {\n  return service({\n    url: '/sysExportTemplate/findSysExportTemplate',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysExportTemplate\n// @Summary 分页获取导出模板列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取导出模板列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysExportTemplate/getSysExportTemplateList [get]\nexport const getSysExportTemplateList = (params) => {\n  return service({\n    url: '/sysExportTemplate/getSysExportTemplateList',\n    method: 'get',\n    params\n  })\n}\n\n\n// ExportExcel 导出表格token\n// @Tags SysExportTemplate\n// @Summary 导出表格\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/exportExcel [get]\nexport const exportExcel = (params) => {\n  return service({\n    url: '/sysExportTemplate/exportExcel',\n    method: 'get',\n    params\n  })\n}\n\n// ExportTemplate 导出表格模板\n// @Tags SysExportTemplate\n// @Summary 导出表格模板\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/exportTemplate [get]\nexport const exportTemplate = (params) => {\n  return service({\n    url: '/sysExportTemplate/exportTemplate',\n    method: 'get',\n    params\n  })\n}\n\n// PreviewSQL 预览最终生成的SQL\n// @Tags SysExportTemplate\n// @Summary 预览最终生成的SQL\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Router /sysExportTemplate/previewSQL [get]\n// @Param templateID query string true  \"导出模板ID\"\n// @Param params     query string false \"查询参数编码字符串，参考 ExportExcel 组件\"\nexport const previewSQL = (params) => {\n  return service({\n    url: '/sysExportTemplate/previewSQL',\n    method: 'get',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/fileUploadAndDownload.js",
    "content": "import service from '@/utils/request'\n// @Tags FileUploadAndDownload\n// @Summary 分页文件列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body modelInterface.PageInfo true \"分页获取文件户列表\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /fileUploadAndDownload/getFileList [post]\nexport const getFileList = (data) => {\n  return service({\n    url: '/fileUploadAndDownload/getFileList',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags FileUploadAndDownload\n// @Summary 删除文件\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Param data body dbModel.FileUploadAndDownload true \"传入文件里面id即可\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"返回成功\"}\"\n// @Router /fileUploadAndDownload/deleteFile [post]\nexport const deleteFile = (data) => {\n  return service({\n    url: '/fileUploadAndDownload/deleteFile',\n    method: 'post',\n    data\n  })\n}\n\n/**\n * 编辑文件名或者备注\n * @param data\n * @returns {*}\n */\nexport const editFileName = (data) => {\n  return service({\n    url: '/fileUploadAndDownload/editFileName',\n    method: 'post',\n    data\n  })\n}\n\n/**\n * 导入URL\n * @param data\n * @returns {*}\n */\nexport const importURL = (data) => {\n  return service({\n    url: '/fileUploadAndDownload/importURL',\n    method: 'post',\n    data\n  })\n}\n\n\n// 上传文件 暂时用于头像上传\nexport const uploadFile = (data) => {\n  return service({\n    url: \"/fileUploadAndDownload/upload\",\n    method: \"post\",\n    data,\n  });\n};"
  },
  {
    "path": "web/src/api/github.js",
    "content": "import axios from 'axios'\n\nconst service = axios.create()\n\nexport function Commits(page) {\n  return service({\n    url:\n      'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' +\n      page,\n    method: 'get'\n  })\n}\n\nexport function Members() {\n  return service({\n    url: 'https://api.github.com/orgs/FLIPPED-AURORA/members',\n    method: 'get'\n  })\n}\n"
  },
  {
    "path": "web/src/api/initdb.js",
    "content": "import service from '@/utils/request'\n// @Tags InitDB\n// @Summary 初始化用户数据库\n// @Produce  application/json\n// @Param data body request.InitDB true \"初始化数据库参数\"\n// @Success 200 {string} string \"{\"code\":0,\"data\":{},\"msg\":\"自动创建数据库成功\"}\"\n// @Router /init/initdb [post]\nexport const initDB = (data) => {\n  return service({\n    url: '/init/initdb',\n    method: 'post',\n    data,\n    donNotShowLoading: true\n  })\n}\n\n// @Tags CheckDB\n// @Summary 初始化用户数据库\n// @Produce  application/json\n// @Success 200 {string} string \"{\"code\":0,\"data\":{},\"msg\":\"探测完成\"}\"\n// @Router /init/checkdb [post]\nexport const checkDB = () => {\n  return service({\n    url: '/init/checkdb',\n    method: 'post'\n  })\n}\n"
  },
  {
    "path": "web/src/api/jwt.js",
    "content": "import service from '@/utils/request'\n// @Tags jwt\n// @Summary jwt加入黑名单\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"拉黑成功\"}\"\n// @Router /jwt/jsonInBlacklist [post]\nexport const jsonInBlacklist = () => {\n  return service({\n    url: '/jwt/jsonInBlacklist',\n    method: 'post'\n  })\n}\n"
  },
  {
    "path": "web/src/api/menu.js",
    "content": "import service from '@/utils/request'\n// @Summary 用户登录 获取动态路由\n// @Produce  application/json\n// @Param 可以什么都不填 调一下即可\n// @Router /menu/getMenu [post]\nexport const asyncMenu = () => {\n  return service({\n    url: '/menu/getMenu',\n    method: 'post'\n  })\n}\n\n// @Summary 获取menu列表\n// @Produce  application/json\n// @Param {\n//  page     int\n//\tpageSize int\n// }\n// @Router /menu/getMenuList [post]\nexport const getMenuList = (data) => {\n  return service({\n    url: '/menu/getMenuList',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 新增基础menu\n// @Produce  application/json\n// @Param menu Object\n// @Router /menu/getMenuList [post]\nexport const addBaseMenu = (data) => {\n  return service({\n    url: '/menu/addBaseMenu',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 获取基础路由列表\n// @Produce  application/json\n// @Param 可以什么都不填 调一下即可\n// @Router /menu/getBaseMenuTree [post]\nexport const getBaseMenuTree = () => {\n  return service({\n    url: '/menu/getBaseMenuTree',\n    method: 'post'\n  })\n}\n\n// @Summary 添加用户menu关联关系\n// @Produce  application/json\n// @Param menus Object authorityId string\n// @Router /menu/getMenuList [post]\nexport const addMenuAuthority = (data) => {\n  return service({\n    url: '/menu/addMenuAuthority',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 获取用户menu关联关系\n// @Produce  application/json\n// @Param authorityId string\n// @Router /menu/getMenuAuthority [post]\nexport const getMenuAuthority = (data) => {\n  return service({\n    url: '/menu/getMenuAuthority',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 删除menu\n// @Produce  application/json\n// @Param ID float64\n// @Router /menu/deleteBaseMenu [post]\nexport const deleteBaseMenu = (data) => {\n  return service({\n    url: '/menu/deleteBaseMenu',\n    method: 'post',\n    data\n  })\n}\n\n// @Summary 修改menu列表\n// @Produce  application/json\n// @Param menu Object\n// @Router /menu/updateBaseMenu [post]\nexport const updateBaseMenu = (data) => {\n  return service({\n    url: '/menu/updateBaseMenu',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags menu\n// @Summary 根据id获取菜单\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.GetById true \"根据id获取菜单\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /menu/getBaseMenuById [post]\nexport const getBaseMenuById = (data) => {\n  return service({\n    url: '/menu/getBaseMenuById',\n    method: 'post',\n    data\n  })\n}\n\n/**\n * 获取拥有指定菜单的角色ID列表\n * @param {number} menuId 菜单ID\n * @returns {Promise<number[]>} 角色ID数组\n */\nexport const getMenuRoles = (menuId) => {\n  return service({\n    url: '/menu/getMenuRoles',\n    method: 'get',\n    params: { menuId }\n  })\n}\n\n/**\n * 全量覆盖某菜单关联的角色列表\n * @param {Object} data\n * @param {number} data.menuId 菜单ID\n * @param {number[]} data.authorityIds 角色ID列表\n * @returns {Promise}\n */\nexport const setMenuRoles = (data) => {\n  return service({\n    url: '/menu/setMenuRoles',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/plugin/api.js",
    "content": "import service from '@/utils/request'\n\nexport const getShopPluginList = (params) => {\n  return service({\n    baseURL: \"plugin\",\n    url: '/shopPlugin/getShopPluginList',\n    method: 'get',\n    params\n  })\n}"
  },
  {
    "path": "web/src/api/skills.js",
    "content": "import service from '@/utils/request'\n\nexport const getSkillTools = () => {\n  return service({\n    url: '/skills/getTools',\n    method: 'get'\n  })\n}\n\nexport const getSkillList = (data) => {\n  return service({\n    url: '/skills/getSkillList',\n    method: 'post',\n    data\n  })\n}\n\nexport const getSkillDetail = (data) => {\n  return service({\n    url: '/skills/getSkillDetail',\n    method: 'post',\n    data\n  })\n}\n\nexport const saveSkill = (data) => {\n  return service({\n    url: '/skills/saveSkill',\n    method: 'post',\n    data\n  })\n}\n\nexport const deleteSkill = (data) => {\n  return service({\n    url: '/skills/deleteSkill',\n    method: 'post',\n    data\n  })\n}\n\nexport const createSkillScript = (data) => {\n  return service({\n    url: '/skills/createScript',\n    method: 'post',\n    data\n  })\n}\n\nexport const getSkillScript = (data) => {\n  return service({\n    url: '/skills/getScript',\n    method: 'post',\n    data\n  })\n}\n\nexport const saveSkillScript = (data) => {\n  return service({\n    url: '/skills/saveScript',\n    method: 'post',\n    data\n  })\n}\n\nexport const createSkillResource = (data) => {\n  return service({\n    url: '/skills/createResource',\n    method: 'post',\n    data\n  })\n}\n\nexport const getSkillResource = (data) => {\n  return service({\n    url: '/skills/getResource',\n    method: 'post',\n    data\n  })\n}\n\nexport const saveSkillResource = (data) => {\n  return service({\n    url: '/skills/saveResource',\n    method: 'post',\n    data\n  })\n}\n\nexport const createSkillReference = (data) => {\n  return service({\n    url: '/skills/createReference',\n    method: 'post',\n    data\n  })\n}\n\nexport const getSkillReference = (data) => {\n  return service({\n    url: '/skills/getReference',\n    method: 'post',\n    data\n  })\n}\n\nexport const saveSkillReference = (data) => {\n  return service({\n    url: '/skills/saveReference',\n    method: 'post',\n    data\n  })\n}\n\nexport const createSkillTemplate = (data) => {\n  return service({\n    url: '/skills/createTemplate',\n    method: 'post',\n    data\n  })\n}\n\nexport const getSkillTemplate = (data) => {\n  return service({\n    url: '/skills/getTemplate',\n    method: 'post',\n    data\n  })\n}\n\nexport const saveSkillTemplate = (data) => {\n  return service({\n    url: '/skills/saveTemplate',\n    method: 'post',\n    data\n  })\n}\n\nexport const getGlobalConstraint = (data) => {\n  return service({\n    url: '/skills/getGlobalConstraint',\n    method: 'post',\n    data\n  })\n}\n\nexport const saveGlobalConstraint = (data) => {\n  return service({\n    url: '/skills/saveGlobalConstraint',\n    method: 'post',\n    data\n  })\n}\n\nexport const packageSkill = (data) => {\n  return service({\n    url: '/skills/packageSkill',\n    method: 'post',\n    data,\n    responseType: 'blob'\n  })\n}\n\nexport const downloadOnlineSkill = (data) => {\n  return service({\n    url: '/skills/downloadOnlineSkill',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/sysApiToken.js",
    "content": "import service from '@/utils/request'\n\nexport const createApiToken = (data) => {\n  return service({\n    url: '/sysApiToken/createApiToken',\n    method: 'post',\n    data\n  })\n}\n\nexport const getApiTokenList = (data) => {\n  return service({\n    url: '/sysApiToken/getApiTokenList',\n    method: 'post',\n    data\n  })\n}\n\nexport const deleteApiToken = (data) => {\n  return service({\n    url: '/sysApiToken/deleteApiToken',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/sysDictionary.js",
    "content": "import service from '@/utils/request'\n// @Tags SysDictionary\n// @Summary 创建SysDictionary\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionary true \"创建SysDictionary\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionary/createSysDictionary [post]\nexport const createSysDictionary = (data) => {\n  return service({\n    url: '/sysDictionary/createSysDictionary',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysDictionary\n// @Summary 删除SysDictionary\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionary true \"删除SysDictionary\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysDictionary/deleteSysDictionary [delete]\nexport const deleteSysDictionary = (data) => {\n  return service({\n    url: '/sysDictionary/deleteSysDictionary',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysDictionary\n// @Summary 更新SysDictionary\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionary true \"更新SysDictionary\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /sysDictionary/updateSysDictionary [put]\nexport const updateSysDictionary = (data) => {\n  return service({\n    url: '/sysDictionary/updateSysDictionary',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags SysDictionary\n// @Summary 用id查询SysDictionary\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionary true \"用id查询SysDictionary\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysDictionary/findSysDictionary [get]\nexport const findSysDictionary = (params) => {\n  return service({\n    url: '/sysDictionary/findSysDictionary',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionary\n// @Summary 分页获取SysDictionary列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.PageInfo true \"分页获取SysDictionary列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionary/getSysDictionaryList [get]\nexport const getSysDictionaryList = (params) => {\n  return service({\n    url: '/sysDictionary/getSysDictionaryList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionary\n// @Summary 导出字典JSON（包含字典详情）\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query model.SysDictionary true \"字典ID\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"导出成功\"}\"\n// @Router /sysDictionary/exportSysDictionary [get]\nexport const exportSysDictionary = (params) => {\n  return service({\n    url: '/sysDictionary/exportSysDictionary',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionary\n// @Summary 导入字典JSON（包含字典详情）\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body object true \"字典JSON数据\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"导入成功\"}\"\n// @Router /sysDictionary/importSysDictionary [post]\nexport const importSysDictionary = (data) => {\n  return service({\n    url: '/sysDictionary/importSysDictionary',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/sysDictionaryDetail.js",
    "content": "import service from '@/utils/request'\n// @Tags SysDictionaryDetail\n// @Summary 创建SysDictionaryDetail\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionaryDetail true \"创建SysDictionaryDetail\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionaryDetail/createSysDictionaryDetail [post]\nexport const createSysDictionaryDetail = (data) => {\n  return service({\n    url: '/sysDictionaryDetail/createSysDictionaryDetail',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 删除SysDictionaryDetail\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionaryDetail true \"删除SysDictionaryDetail\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysDictionaryDetail/deleteSysDictionaryDetail [delete]\nexport const deleteSysDictionaryDetail = (data) => {\n  return service({\n    url: '/sysDictionaryDetail/deleteSysDictionaryDetail',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 更新SysDictionaryDetail\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionaryDetail true \"更新SysDictionaryDetail\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /sysDictionaryDetail/updateSysDictionaryDetail [put]\nexport const updateSysDictionaryDetail = (data) => {\n  return service({\n    url: '/sysDictionaryDetail/updateSysDictionaryDetail',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 用id查询SysDictionaryDetail\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysDictionaryDetail true \"用id查询SysDictionaryDetail\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysDictionaryDetail/findSysDictionaryDetail [get]\nexport const findSysDictionaryDetail = (params) => {\n  return service({\n    url: '/sysDictionaryDetail/findSysDictionaryDetail',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 分页获取SysDictionaryDetail列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.PageInfo true \"分页获取SysDictionaryDetail列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionaryDetail/getSysDictionaryDetailList [get]\nexport const getSysDictionaryDetailList = (params) => {\n  return service({\n    url: '/sysDictionaryDetail/getSysDictionaryDetailList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 获取层级字典详情树形结构（根据字典ID）\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param sysDictionaryID query string true \"字典ID\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionaryDetail/getDictionaryTreeList [get]\nexport const getDictionaryTreeList = (params) => {\n  return service({\n    url: '/sysDictionaryDetail/getDictionaryTreeList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 获取层级字典详情树形结构（根据字典类型）\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param dictType query string true \"字典类型\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionaryDetail/getDictionaryTreeListByType [get]\nexport const getDictionaryTreeListByType = (params) => {\n  return service({\n    url: '/sysDictionaryDetail/getDictionaryTreeListByType',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 根据父级ID获取字典详情\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param parentID query string true \"父级ID\"\n// @Param includeChildren query boolean false \"是否包含子级\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionaryDetail/getDictionaryDetailsByParent [get]\nexport const getDictionaryDetailsByParent = (params) => {\n  return service({\n    url: '/sysDictionaryDetail/getDictionaryDetailsByParent',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysDictionaryDetail\n// @Summary 获取字典详情的完整路径\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param ID query string true \"字典详情ID\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysDictionaryDetail/getDictionaryPath [get]\nexport const getDictionaryPath = (params) => {\n  return service({\n    url: '/sysDictionaryDetail/getDictionaryPath',\n    method: 'get',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/sysLoginLog.js",
    "content": "import service from '@/utils/request'\n\nexport const deleteLoginLog = (data) => {\n  return service({\n    url: '/sysLoginLog/deleteLoginLog',\n    method: 'delete',\n    data\n  })\n}\n\nexport const deleteLoginLogByIds = (data) => {\n  return service({\n    url: '/sysLoginLog/deleteLoginLogByIds',\n    method: 'delete',\n    data\n  })\n}\n\nexport const getLoginLogList = (params) => {\n  return service({\n    url: '/sysLoginLog/getLoginLogList',\n    method: 'get',\n    params\n  })\n}\n\nexport const findLoginLog = (params) => {\n    return service({\n        url: '/sysLoginLog/findLoginLog',\n        method: 'get',\n        params\n    })\n}\n"
  },
  {
    "path": "web/src/api/sysOperationRecord.js",
    "content": "import service from '@/utils/request'\n// @Tags SysOperationRecord\n// @Summary 删除SysOperationRecord\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysOperationRecord true \"删除SysOperationRecord\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysOperationRecord/deleteSysOperationRecord [delete]\nexport const deleteSysOperationRecord = (data) => {\n  return service({\n    url: '/sysOperationRecord/deleteSysOperationRecord',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysOperationRecord\n// @Summary 删除SysOperationRecord\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"删除SysOperationRecord\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysOperationRecord/deleteSysOperationRecord [delete]\nexport const deleteSysOperationRecordByIds = (data) => {\n  return service({\n    url: '/sysOperationRecord/deleteSysOperationRecordByIds',\n    method: 'delete',\n    data\n  })\n}\n\n// @Tags SysOperationRecord\n// @Summary 分页获取SysOperationRecord列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.PageInfo true \"分页获取SysOperationRecord列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysOperationRecord/getSysOperationRecordList [get]\nexport const getSysOperationRecordList = (params) => {\n  return service({\n    url: '/sysOperationRecord/getSysOperationRecordList',\n    method: 'get',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/sysParams.js",
    "content": "import service from '@/utils/request'\n// @Tags SysParams\n// @Summary 创建参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysParams true \"创建参数\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /sysParams/createSysParams [post]\nexport const createSysParams = (data) => {\n  return service({\n    url: '/sysParams/createSysParams',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysParams\n// @Summary 删除参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysParams true \"删除参数\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysParams/deleteSysParams [delete]\nexport const deleteSysParams = (params) => {\n  return service({\n    url: '/sysParams/deleteSysParams',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags SysParams\n// @Summary 批量删除参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除参数\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysParams/deleteSysParams [delete]\nexport const deleteSysParamsByIds = (params) => {\n  return service({\n    url: '/sysParams/deleteSysParamsByIds',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags SysParams\n// @Summary 更新参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysParams true \"更新参数\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /sysParams/updateSysParams [put]\nexport const updateSysParams = (data) => {\n  return service({\n    url: '/sysParams/updateSysParams',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags SysParams\n// @Summary 用id查询参数\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query model.SysParams true \"用id查询参数\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysParams/findSysParams [get]\nexport const findSysParams = (params) => {\n  return service({\n    url: '/sysParams/findSysParams',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysParams\n// @Summary 分页获取参数列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取参数列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysParams/getSysParamsList [get]\nexport const getSysParamsList = (params) => {\n  return service({\n    url: '/sysParams/getSysParamsList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysParams\n// @Summary 不需要鉴权的参数接口\n// @accept application/json\n// @Produce application/json\n// @Param data query systemReq.SysParamsSearch true \"分页获取参数列表\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /sysParams/getSysParam [get]\nexport const getSysParam = (params) => {\n  return service({\n    url: '/sysParams/getSysParam',\n    method: 'get',\n    params\n  })\n}\n"
  },
  {
    "path": "web/src/api/system/sysError.js",
    "content": "import service from '@/utils/request'\n// @Tags SysError\n// @Summary 创建错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.SysError true \"创建错误日志\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /sysError/createSysError [post]\nexport const createSysError = (data) => {\n  return service({\n    url: '/sysError/createSysError',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysError\n// @Summary 删除错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.SysError true \"删除错误日志\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysError/deleteSysError [delete]\nexport const deleteSysError = (params) => {\n  return service({\n    url: '/sysError/deleteSysError',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags SysError\n// @Summary 批量删除错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除错误日志\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysError/deleteSysError [delete]\nexport const deleteSysErrorByIds = (params) => {\n  return service({\n    url: '/sysError/deleteSysErrorByIds',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags SysError\n// @Summary 更新错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.SysError true \"更新错误日志\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /sysError/updateSysError [put]\nexport const updateSysError = (data) => {\n  return service({\n    url: '/sysError/updateSysError',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags SysError\n// @Summary 用id查询错误日志\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query model.SysError true \"用id查询错误日志\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysError/findSysError [get]\nexport const findSysError = (params) => {\n  return service({\n    url: '/sysError/findSysError',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysError\n// @Summary 分页获取错误日志列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取错误日志列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysError/getSysErrorList [get]\nexport const getSysErrorList = (params) => {\n  return service({\n    url: '/sysError/getSysErrorList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysError\n// @Summary 不需要鉴权的错误日志接口\n// @Accept application/json\n// @Produce application/json\n// @Param data query systemReq.SysErrorSearch true \"分页获取错误日志列表\"\n// @Success 200 {object} response.Response{data=object,msg=string} \"获取成功\"\n// @Router /sysError/getSysErrorPublic [get]\nexport const getSysErrorPublic = () => {\n  return service({\n    url: '/sysError/getSysErrorPublic',\n    method: 'get',\n  })\n}\n\n// @Tags SysError\n// @Summary 触发错误处理（异步）\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param id query string true \"错误日志ID\"\n// @Success 200 {string} string \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"处理已提交\\\"}\"\n// @Router /sysError/getSysErrorSolution [get]\nexport const getSysErrorSolution = (params) => {\n  return service({\n    url: '/sysError/getSysErrorSolution',\n    method: 'get',\n    params\n  })\n}"
  },
  {
    "path": "web/src/api/system.js",
    "content": "import service from '@/utils/request'\n// @Tags systrm\n// @Summary 获取配置文件内容\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"返回成功\"}\"\n// @Router /system/getSystemConfig [post]\nexport const getSystemConfig = () => {\n  return service({\n    url: '/system/getSystemConfig',\n    method: 'post'\n  })\n}\n\n// @Tags system\n// @Summary 设置配置文件内容\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Param data body sysModel.System true\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"返回成功\"}\"\n// @Router /system/setSystemConfig [post]\nexport const setSystemConfig = (data) => {\n  return service({\n    url: '/system/setSystemConfig',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags system\n// @Summary 获取服务器运行状态\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"返回成功\"}\"\n// @Router /system/getServerInfo [post]\nexport const getSystemState = () => {\n  return service({\n    url: '/system/getServerInfo',\n    method: 'post',\n    donNotShowLoading: true\n  })\n}\n\n/**\n * 重载服务\n * @param data\n * @returns {*}\n */\nexport const reloadSystem = (data) => {\n  return service({\n    url: '/system/reloadSystem',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/api/user.js",
    "content": "import service from '@/utils/request'\n// @Summary 用户登录\n// @Produce  application/json\n// @Param data body {username:\"string\",password:\"string\"}\n// @Router /base/login [post]\nexport const login = (data) => {\n  return service({\n    url: '/base/login',\n    method: 'post',\n    data: data\n  })\n}\n\n// @Summary 获取验证码\n// @Produce  application/json\n// @Param data body {username:\"string\",password:\"string\"}\n// @Router /base/captcha [post]\nexport const captcha = () => {\n  return service({\n    url: '/base/captcha',\n    method: 'post'\n  })\n}\n\n// @Summary 用户注册\n// @Produce  application/json\n// @Param data body {username:\"string\",password:\"string\"}\n// @Router /base/resige [post]\nexport const register = (data) => {\n  return service({\n    url: '/user/admin_register',\n    method: 'post',\n    data: data\n  })\n}\n\n// @Summary 修改密码\n// @Produce  application/json\n// @Param data body {username:\"string\",password:\"string\",newPassword:\"string\"}\n// @Router /user/changePassword [post]\nexport const changePassword = (data) => {\n  return service({\n    url: '/user/changePassword',\n    method: 'post',\n    data: data\n  })\n}\n\n// @Tags User\n// @Summary 分页获取用户列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body modelInterface.PageInfo true \"分页获取用户列表\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /user/getUserList [post]\nexport const getUserList = (data) => {\n  return service({\n    url: '/user/getUserList',\n    method: 'post',\n    data: data\n  })\n}\n\n// @Tags User\n// @Summary 设置用户权限\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.SetUserAuth true \"设置用户权限\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"修改成功\"}\"\n// @Router /user/setUserAuthority [post]\nexport const setUserAuthority = (data) => {\n  return service({\n    url: '/user/setUserAuthority',\n    method: 'post',\n    data: data\n  })\n}\n\n// @Tags SysUser\n// @Summary 删除用户\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.SetUserAuth true \"删除用户\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"修改成功\"}\"\n// @Router /user/deleteUser [delete]\nexport const deleteUser = (data) => {\n  return service({\n    url: '/user/deleteUser',\n    method: 'delete',\n    data: data\n  })\n}\n\n// @Tags SysUser\n// @Summary 设置用户信息\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysUser true \"设置用户信息\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"修改成功\"}\"\n// @Router /user/setUserInfo [put]\nexport const setUserInfo = (data) => {\n  return service({\n    url: '/user/setUserInfo',\n    method: 'put',\n    data: data\n  })\n}\n\n// @Tags SysUser\n// @Summary 设置用户信息\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysUser true \"设置用户信息\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"修改成功\"}\"\n// @Router /user/setSelfInfo [put]\nexport const setSelfInfo = (data) => {\n  return service({\n    url: '/user/setSelfInfo',\n    method: 'put',\n    data: data\n  })\n}\n\n// @Tags SysUser\n// @Summary 设置自身界面配置\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.SysUser true \"设置自身界面配置\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"修改成功\"}\"\n// @Router /user/setSelfSetting [put]\nexport const setSelfSetting = (data) => {\n  return service({\n    url: '/user/setSelfSetting',\n    method: 'put',\n    data: data\n  })\n}\n\n// @Tags User\n// @Summary 设置用户权限\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body api.setUserAuthorities true \"设置用户权限\"\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"修改成功\"}\"\n// @Router /user/setUserAuthorities [post]\nexport const setUserAuthorities = (data) => {\n  return service({\n    url: '/user/setUserAuthorities',\n    method: 'post',\n    data: data\n  })\n}\n\n// @Tags User\n// @Summary 获取用户信息\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} json \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /user/getUserInfo [get]\nexport const getUserInfo = () => {\n  return service({\n    url: '/user/getUserInfo',\n    method: 'get'\n  })\n}\n\nexport const resetPassword = (data) => {\n  return service({\n    url: '/user/resetPassword',\n    method: 'post',\n    data: data\n  })\n}\n"
  },
  {
    "path": "web/src/api/version.js",
    "content": "import service from '@/utils/request'\n\n// @Tags SysVersion\n// @Summary 删除版本管理\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body model.SysVersion true \"删除版本管理\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysVersion/deleteSysVersion [delete]\nexport const deleteSysVersion = (params) => {\n  return service({\n    url: '/sysVersion/deleteSysVersion',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags SysVersion\n// @Summary 批量删除版本管理\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除版本管理\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /sysVersion/deleteSysVersion [delete]\nexport const deleteSysVersionByIds = (params) => {\n  return service({\n    url: '/sysVersion/deleteSysVersionByIds',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags SysVersion\n// @Summary 用id查询版本管理\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query model.SysVersion true \"用id查询版本管理\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /sysVersion/findSysVersion [get]\nexport const findSysVersion = (params) => {\n  return service({\n    url: '/sysVersion/findSysVersion',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysVersion\n// @Summary 分页获取版本管理列表\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取版本管理列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /sysVersion/getSysVersionList [get]\nexport const getSysVersionList = (params) => {\n  return service({\n    url: '/sysVersion/getSysVersionList',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags SysVersion\n// @Summary 导出版本数据\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body object true \"导出版本数据\"\n// @Success 200 {string} string \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"导出成功\\\"}\"\n// @Router /sysVersion/exportVersion [post]\nexport const exportVersion = (data) => {\n  return service({\n    url: '/sysVersion/exportVersion',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags SysVersion\n// @Summary 下载版本JSON数据\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param ID query string true \"版本ID\"\n// @Success 200 {string} string \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"下载成功\\\"}\"\n// @Router /sysVersion/downloadVersionJson [get]\nexport const downloadVersionJson = (params) => {\n  return service({\n    url: '/sysVersion/downloadVersionJson',\n    method: 'get',\n    params,\n    responseType: 'blob'\n  })\n}\n\n// @Tags SysVersion\n// @Summary 导入版本数据\n// @Security ApiKeyAuth\n// @Accept application/json\n// @Produce application/json\n// @Param data body object true \"版本JSON数据\"\n// @Success 200 {string} string \"{\\\"success\\\":true,\\\"data\\\":{},\\\"msg\\\":\\\"导入成功\\\"}\"\n// @Router /sysVersion/importVersion [post]\nexport const importVersion = (data) => {\n  return service({\n    url: '/sysVersion/importVersion',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/components/application/index.vue",
    "content": "<template>\n  <error-preview v-if=\"showError\" :error-data=\"errorInfo\" @close=\"handleClose\" @confirm=\"handleConfirm\" />\n</template>\n\n<script setup>\nimport { ref, onUnmounted } from 'vue'\nimport { emitter } from '@/utils/bus'\nimport ErrorPreview from '@/components/errorPreview/index.vue'\n\nconst showError = ref(false)\nconst errorInfo = ref(null)\nlet cb = null\n\nconst showErrorDialog = (data) => {\n  // 这玩意同时只允许存在一个\n  if(showError.value) return\n\n  errorInfo.value = data\n  showError.value = true\n  cb = data?.fn || null\n}\n\nconst handleClose = () => {\n  showError.value = false\n  errorInfo.value = null\n  cb = null\n}\n\nconst handleConfirm = (code) => {\n  cb && cb(code)\n  handleClose()\n}\n\nemitter.on('show-error', showErrorDialog)\n\nonUnmounted(() => {\n  emitter.off('show-error', showErrorDialog)\n})\n</script>\n"
  },
  {
    "path": "web/src/components/arrayCtrl/arrayCtrl.vue",
    "content": "<template>\n  <div class=\"flex gap-2\">\n    <el-tag\n      v-for=\"tag in modelValue\"\n      :key=\"tag\"\n      :closable=\"editable\"\n      :disable-transitions=\"false\"\n      @close=\"handleClose(tag)\"\n    >\n      {{ tag }}\n    </el-tag>\n    <template v-if=\"editable\">\n      <el-input\n        v-if=\"inputVisible\"\n        ref=\"InputRef\"\n        v-model=\"inputValue\"\n        class=\"w-20\"\n        size=\"small\"\n        @keyup.enter=\"handleInputConfirm\"\n        @blur=\"handleInputConfirm\"\n      />\n      <el-button v-else class=\"button-new-tag\" size=\"small\" @click=\"showInput\">\n        + 新增\n      </el-button>\n    </template>\n  </div>\n</template>\n\n<script setup>\n  defineOptions({\n    name: 'ArrayCtrl'\n  })\n\n  import { nextTick, ref } from 'vue'\n\n  const inputValue = ref('')\n  const inputVisible = ref(false)\n  const InputRef = ref(null)\n\n  const modelValue = defineModel()\n\n  defineProps({\n    editable: {\n      type: Boolean,\n      default: () => false\n    }\n  })\n\n  const handleClose = (tag) => {\n    modelValue.value.splice(modelValue.value.indexOf(tag), 1)\n  }\n\n  const showInput = () => {\n    inputVisible.value = true\n    nextTick(() => {\n      InputRef.value?.input?.focus()\n    })\n  }\n\n  const handleInputConfirm = () => {\n    if (inputValue.value) {\n      modelValue.value.push(inputValue.value)\n    }\n    inputVisible.value = false\n    inputValue.value = ''\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/bottomInfo/bottomInfo.vue",
    "content": "<!--\n此文件受版权保护，未经授权禁止修改！如果您尚未获得授权，请通过微信(shouzi_1994)联系我们以购买授权。在未授权状态下，只需保留此代码，不会影响任何正常使用。\n     未经授权的商用使用可能会被我们的资产搜索引擎爬取，并可能导致后续索赔。索赔金额将不低于高级授权费的十倍。请您遵守版权法律法规，尊重知识产权。\n -->\n<template>\n  <div\n    class=\"flex flex-col md:flex-row gap-2 items-center text-sm text-slate-700 dark:text-slate-500 justify-center py-2\"\n  >\n    <div class=\"text-center\">\n      <span class=\"mr-1\">Powered by</span>\n      <span>\n        <a\n          class=\"font-bold text-active\"\n          href=\"https://github.com/flipped-aurora/gin-vue-admin\"\n          >Gin-Vue-Admin</a\n        >\n      </span>\n    </div>\n    <slot />\n    <div class=\"text-center\">\n      <span class=\"mr-1\">Copyright</span>\n      <span>\n        <a\n          class=\"font-bold text-active\"\n          href=\"https://github.com/flipped-aurora\"\n          >flipped-aurora团队</a\n        >\n      </span>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  defineOptions({\n    name: 'BottomInfo'\n  })\n\n  console.log(\n    `%c powered by %c flipped-aurorae %c`,\n    'background:#0081ff; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',\n    'background:#354855; padding: 1px 5px; border-radius: 0 3px 3px 0; color: #fff; font-weight: bold;',\n    'background:transparent'\n  )\n</script>\n"
  },
  {
    "path": "web/src/components/charts/index.vue",
    "content": "<template>\n  <VCharts\n    v-if=\"renderChart\"\n    :option=\"options\"\n    :autoresize=\"autoResize\"\n    :style=\"{ width, height }\"\n  />\n</template>\n\n<script setup>\n  import { ref, nextTick } from 'vue'\n  import VCharts from 'vue-echarts'\n  import { useWindowResize } from '@/hooks/use-windows-resize'\n\n  defineProps({\n    options: {\n      type: Object,\n      default() {\n        return {}\n      }\n    },\n    autoResize: {\n      type: Boolean,\n      default: true\n    },\n    width: {\n      type: String,\n      default: '100%'\n    },\n    height: {\n      type: String,\n      default: '100%'\n    }\n  })\n  const renderChart = ref(false)\n  nextTick(() => {\n    renderChart.value = true\n  })\n  useWindowResize(() => {\n    renderChart.value = false\n    nextTick(() => {\n      renderChart.value = true\n    })\n  })\n</script>\n\n<style scoped lang=\"less\"></style>\n"
  },
  {
    "path": "web/src/components/commandMenu/index.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"30%\"\n    class=\"overlay\"\n    :show-close=\"false\"\n  >\n    <template #header>\n      <input\n        v-model=\"searchInput\"\n        class=\"quick-input\"\n        placeholder=\"请输入你需要快捷到达的功能\"\n      />\n    </template>\n\n    <div v-for=\"(option, index) in options\" :key=\"index\">\n      <div v-if=\"option.children.length\" class=\"quick-title\">\n        {{ option.label }}\n      </div>\n      <div\n        v-for=\"(item, key) in option.children\"\n        :key=\"index + '-' + key\"\n        class=\"quick-item\"\n        @click=\"item.func\"\n      >\n        {{ item.label }}\n      </div>\n    </div>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click=\"close\">关闭</el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup>\n  import { reactive, ref, watch } from 'vue'\n  import { useRouter } from 'vue-router'\n  import { useRouterStore } from '@/pinia/modules/router'\n  import { useAppStore, useUserStore } from '@/pinia'\n  defineOptions({\n    name: 'CommandMenu'\n  })\n  const appStore = useAppStore()\n  const userStore = useUserStore()\n\n  const router = useRouter()\n  const route = useRouter()\n  const routerStore = useRouterStore()\n  const dialogVisible = ref(false)\n  const searchInput = ref('')\n  const options = reactive([])\n  const deepMenus = (menus) => {\n    const arr = []\n    menus?.forEach((menu) => {\n      if (menu.children && menu.children.length > 0) {\n        arr.push(...deepMenus(menu.children))\n      } else {\n        if (\n          menu.meta.title &&\n          menu.meta.title.indexOf(searchInput.value) > -1\n        ) {\n          arr.push({\n            label: menu.meta.title,\n            func: () => changeRouter(menu)\n          })\n        }\n      }\n    })\n    return arr\n  }\n\n  const addQuickMenu = () => {\n    const option = {\n      label: '跳转',\n      children: []\n    }\n    const menus = deepMenus(routerStore.asyncRouters[0]?.children || [])\n    option.children.push(...menus)\n    options.push(option)\n  }\n\n  const addQuickOption = () => {\n    const option = {\n      label: '操作',\n      children: []\n    }\n    const quickArr = [\n      {\n        label: '亮色主题',\n        func: () => changeMode(false)\n      },\n      {\n        label: '暗色主题',\n        func: () => changeMode(true)\n      },\n      {\n        label: '退出登录',\n        func: () => userStore.LoginOut()\n      }\n    ]\n    option.children.push(\n      ...quickArr.filter((item) => item.label.indexOf(searchInput.value) > -1)\n    )\n    options.push(option)\n  }\n\n  addQuickMenu()\n  addQuickOption()\n\n  const open = () => {\n    dialogVisible.value = true\n  }\n\n  const changeRouter = (e) => {\n    const index = e.name\n    const query = {}\n    const params = {}\n    routerStore.routeMap[index]?.parameters &&\n      routerStore.routeMap[index]?.parameters.forEach((item) => {\n        if (item.type === 'query') {\n          query[item.key] = item.value\n        } else {\n          params[item.key] = item.value\n        }\n      })\n    if (index === route.name) return\n    if (e.name.indexOf('http://') > -1 || e.name.indexOf('https://') > -1) {\n      window.open(e.name)\n    } else {\n      router.push({ name: index, query, params })\n    }\n    dialogVisible.value = false\n  }\n\n  const changeMode = (darkMode) => {\n    appStore.toggleTheme(darkMode)\n  }\n\n  const close = () => {\n    dialogVisible.value = false\n  }\n\n  defineExpose({ open })\n\n  watch(searchInput, () => {\n    options.length = 0\n    addQuickMenu()\n    addQuickOption()\n  })\n</script>\n\n<style lang=\"scss\">\n  .overlay {\n    border-radius: 4px;\n    .el-dialog__header {\n      padding: 0 !important;\n      margin-right: 0 !important;\n    }\n    .el-dialog__body {\n      padding: 12px !important;\n      height: 50vh;\n      overflow: auto !important;\n    }\n    .quick-title {\n      margin-top: 8px;\n      font-size: 12px;\n      font-weight: 600;\n      color: #666;\n    }\n    .quick-input {\n      @apply bg-gray-50 dark:bg-gray-800;\n      color: #666;\n      border-radius: 4px 4px 0 0;\n      border: none;\n      padding: 12px 16px;\n      box-sizing: border-box;\n      width: 100%;\n      font-size: 16px;\n      border-bottom: 1px solid #ddd;\n    }\n    .quick-item {\n      font-size: 14px;\n      padding: 8px;\n      margin: 4px 0;\n      &:hover {\n        @apply bg-gray-200 dark:bg-slate-500;\n        cursor: pointer;\n        border-radius: 4px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/components/customPic/index.vue",
    "content": "<template>\n  <span class=\"headerAvatar\">\n    <template v-if=\"picType === 'avatar'\">\n      <el-avatar v-if=\"userStore.userInfo.headerImg\" :size=\"30\" :src=\"avatar\" />\n      <el-avatar v-else :size=\"30\" :src=\"noAvatar\" />\n    </template>\n    <template v-if=\"picType === 'img'\">\n      <img v-if=\"userStore.userInfo.headerImg\" :src=\"avatar\" class=\"avatar\" />\n      <img v-else :src=\"noAvatar\" class=\"avatar\" />\n    </template>\n    <template v-if=\"picType === 'file'\">\n      <el-image\n        :src=\"file\"\n        class=\"file\"\n        :preview-src-list=\"previewSrcList\"\n        :preview-teleported=\"true\"\n      />\n    </template>\n  </span>\n</template>\n\n<script setup>\n  import noAvatarPng from '@/assets/noBody.png'\n  import { useUserStore } from '@/pinia/modules/user'\n  import { computed, ref } from 'vue'\n\n  defineOptions({\n    name: 'CustomPic'\n  })\n\n  const props = defineProps({\n    picType: {\n      type: String,\n      required: false,\n      default: 'avatar'\n    },\n    picSrc: {\n      type: String,\n      required: false,\n      default: ''\n    },\n    preview: {\n      type: Boolean,\n      default: false\n    }\n  })\n\n  const path = ref(import.meta.env.VITE_BASE_API + '/')\n  const noAvatar = ref(noAvatarPng)\n\n  const userStore = useUserStore()\n\n  const avatar = computed(() => {\n    if (props.picSrc === '') {\n      if (\n        userStore.userInfo.headerImg !== '' &&\n        userStore.userInfo.headerImg.slice(0, 4) === 'http'\n      ) {\n        return userStore.userInfo.headerImg\n      }\n      return path.value + userStore.userInfo.headerImg\n    } else {\n      if (props.picSrc !== '' && props.picSrc.slice(0, 4) === 'http') {\n        return props.picSrc\n      }\n      return path.value + props.picSrc\n    }\n  })\n  const file = computed(() => {\n    if (props.picSrc && props.picSrc.slice(0, 4) !== 'http') {\n      return path.value + props.picSrc\n    }\n    return props.picSrc\n  })\n  const previewSrcList = computed(() => (props.preview ? [file.value] : []))\n</script>\n\n<style scoped>\n  .headerAvatar {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    margin-right: 8px;\n  }\n  .file {\n    width: 80px;\n    height: 80px;\n    position: relative;\n  }\n</style>\n"
  },
  {
    "path": "web/src/components/errorPreview/index.vue",
    "content": "<template>\n  <div \n    class=\"fixed inset-0 bg-black/40 dark:bg-black/60 flex items-center justify-center z-[999]\"\n    @click.self=\"closeModal\"\n  >\n    <div class=\"bg-white dark:bg-gray-800 rounded-xl shadow-dialog dark:shadow-lg w-full max-w-md mx-4 transform transition-all duration-300 ease-in-out border border-transparent dark:border-gray-700\">\n      <!-- 弹窗头部 -->\n      <div class=\"p-5 border-b border-gray-100 dark:border-gray-700 flex justify-between items-center\">\n        <h3 class=\"text-lg font-semibold text-gray-800 dark:text-gray-100\">{{ displayData.title }}</h3>\n        <div class=\"text-gray-400 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-200 transition-colors cursor-pointer\" @click=\"closeModal\">\n          <close class=\"h-6 w-6\" />\n        </div>\n      </div>\n      \n      <!-- 弹窗内容 -->\n      <div class=\"p-6 pt-0\">\n        <!-- 错误类型 -->\n        <div class=\"mb-4\">\n          <div class=\"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2\">错误类型</div>\n          <div class=\"flex items-center gap-2\">\n            <lock v-if=\"displayData.icon === 'lock'\" :class=\"['w-5 h-5', displayData.color]\" />\n            <warn v-if=\"displayData.icon === 'warn'\" :class=\"['w-5 h-5', displayData.color]\" />\n            <server v-if=\"displayData.icon === 'server'\" :class=\"['w-5 h-5', displayData.color]\" />\n            <span class=\"font-medium text-gray-800 dark:text-gray-100\">{{ displayData.type }}</span>\n          </div>\n        </div>\n        \n        <!-- 具体错误 -->\n        <div class=\"mb-6\">\n          <div class=\"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2\">具体错误</div>\n          <div class=\"bg-gray-100 dark:bg-gray-900/40 rounded-lg p-3 text-sm text-gray-700 dark:text-gray-200 leading-relaxed\">\n            {{ displayData.message }}\n          </div>\n        </div>\n        \n        <!-- 提示信息 -->\n        <div v-if=\"displayData.tips\">\n          <div class=\"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase mb-2\">提示</div>\n          <div class=\"flex items-center gap-2\">\n            <idea class=\"text-blue-500 dark:text-blue-400 w-5 h-5\" />\n            <p class=\"text-sm text-gray-600 dark:text-gray-300\">{{ displayData.tips }}</p>\n          </div>\n        </div>\n      </div>\n      \n      <!-- 弹窗底部 -->\n      <div class=\"py-2 px-4 border-t border-gray-100 dark:border-gray-700 flex justify-end\">\n        <div class=\"px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white dark:text-gray-100 rounded-lg hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors font-medium text-sm shadow-sm cursor-pointer\" @click=\"handleConfirm\">\n          确定\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { computed } from 'vue';\n\nconst props = defineProps({\n  errorData: {\n    type: Object,\n    required: true\n  }\n});\n\nconst emits = defineEmits(['close', 'confirm']);\n\nconst presetErrors = {\n  500: {\n    title: '检测到接口错误',\n    type: '服务器发生内部错误',\n    icon: 'server',\n    color: 'text-red-500 dark:text-red-400',\n    tips: '此类错误内容常见于后台panic，请先查看后台日志，如果影响您正常使用可强制登出清理缓存'\n  },\n  404: {\n    title: '资源未找到',\n    type: 'Not Found',\n    icon: 'warn',\n    color: 'text-orange-500 dark:text-orange-400',\n    tips: '此类错误多为接口未注册（或未重启）或者请求路径（方法）与api路径（方法）不符--如果为自动化代码请检查是否存在空格'\n  },\n  401: {\n    title: '身份认证失败',\n    type: '身份令牌无效',\n    icon: 'lock',\n    color: 'text-purple-500 dark:text-purple-400',\n    tips: '您的身份认证已过期或无效，请重新登录。'\n  },\n  'network': {\n    title: '网络错误',\n    type: 'Network Error',\n    icon: 'fa-wifi-slash',\n    color: 'text-gray-500 dark:text-gray-400',\n    tips: '无法连接到服务器，请检查您的网络连接。'\n  }\n};\n\nconst displayData = computed(() => {\n  const preset = presetErrors[props.errorData.code];\n  if (preset) {\n    return {\n      ...preset,\n      message: props.errorData.message || '没有提供额外信息。'\n    };\n  }\n\n  return {\n    title: '未知错误',\n    type: '检测到请求错误',\n    icon: 'fa-question-circle',\n    color: 'text-gray-400 dark:text-gray-300',\n    message: props.errorData.message || '发生了一个未知错误。',\n    tips: '请检查控制台获取更多信息。'\n  };\n});\n\nconst closeModal = () => {\n   emits('close')\n};\n\nconst handleConfirm = () => {\n  emits('confirm', props.errorData.code);\n  closeModal();\n};\n</script>\n"
  },
  {
    "path": "web/src/components/exportExcel/exportExcel.vue",
    "content": "<template>\n  <el-button type=\"primary\" icon=\"download\" @click=\"exportExcelFunc\"\n    >导出</el-button\n  >\n</template>\n\n<script setup>\n\nimport { exportExcel } from '@/api/exportTemplate'\n\n  const props = defineProps({\n    filterDeleted: {\n      type: Boolean,\n      default: true\n    },\n    templateId: {\n      type: String,\n      required: true\n    },\n    condition: {\n      type: Object,\n      default: () => ({})\n    },\n    limit: {\n      type: Number,\n      default: 0\n    },\n    offset: {\n      type: Number,\n      default: 0\n    },\n    order: {\n      type: String,\n      default: ''\n    }\n  })\n\n  import { ElMessage } from 'element-plus'\n\n  const exportExcelFunc = async () => {\n    if (props.templateId === '') {\n      ElMessage.error('组件未设置模板ID')\n      return\n    }\n    let baseUrl = import.meta.env.VITE_BASE_API\n    if (baseUrl === \"/\"){\n      baseUrl = \"\"\n    }\n    const paramsCopy = JSON.parse(JSON.stringify(props.condition))\n\n    if (props.filterDeleted) {\n      paramsCopy.filterDeleted = 'true'\n    }\n\n    if (props.limit) {\n      paramsCopy.limit = props.limit\n    }\n    if (props.offset) {\n      paramsCopy.offset = props.offset\n    }\n    if (props.order) {\n      paramsCopy.order = props.order\n    }\n    const params = Object.entries(paramsCopy)\n      .map(\n        ([key, value]) =>\n          `${encodeURIComponent(key)}=${encodeURIComponent(value)}`\n      )\n      .join('&')\n\n    const res = await exportExcel({\n      templateID: props.templateId,\n      params\n    })\n\n    if(res.code === 0){\n      ElMessage.success('创建导出任务成功，开始下载')\n      const url = `${baseUrl}${res.data}`\n      window.open(url, '_blank')\n    }\n\n\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/exportExcel/exportTemplate.vue",
    "content": "<template>\n  <el-button type=\"primary\" icon=\"download\" @click=\"exportTemplateFunc\"\n    >下载模板</el-button\n  >\n</template>\n\n<script setup>\n  import { ElMessage } from 'element-plus'\n  import {exportTemplate} from \"@/api/exportTemplate\";\n\n  const props = defineProps({\n    templateId: {\n      type: String,\n      required: true\n    }\n  })\n\n\n  const exportTemplateFunc = async () => {\n    if (props.templateId === '') {\n      ElMessage.error('组件未设置模板ID')\n      return\n    }\n    let baseUrl = import.meta.env.VITE_BASE_API\n    if (baseUrl === \"/\"){\n      baseUrl = \"\"\n    }\n\n    const res = await exportTemplate({\n      templateID: props.templateId\n    })\n\n    if(res.code === 0){\n      ElMessage.success('创建导出任务成功，开始下载')\n      const url = `${baseUrl}${res.data}`\n      window.open(url, '_blank')\n    }\n\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/exportExcel/importExcel.vue",
    "content": "<template>\n  <el-upload\n    :action=\"url\"\n    :show-file-list=\"false\"\n    :on-success=\"handleSuccess\"\n    :multiple=\"false\"\n    :headers=\"{'x-token': token}\"\n  >\n    <el-button type=\"primary\" icon=\"upload\" class=\"ml-3\"> 导入 </el-button>\n  </el-upload>\n</template>\n\n<script setup>\n  import { ElMessage } from 'element-plus'\n  import { useUserStore } from \"@/pinia\";\n\n  let baseUrl = import.meta.env.VITE_BASE_API\n  if (baseUrl === \"/\"){\n    baseUrl = \"\"\n  }\n\n  const props = defineProps({\n    templateId: {\n      type: String,\n      required: true\n    }\n  })\n\n  const userStore = useUserStore()\n\n  const token = userStore.token\n\n  const emit = defineEmits(['on-success'])\n\n  const url = `${baseUrl}/sysExportTemplate/importExcel?templateID=${props.templateId}`\n\n  const handleSuccess = (res) => {\n    if (res.code === 0) {\n      ElMessage.success('导入成功')\n      emit('on-success')\n    } else {\n      ElMessage.error(res.msg)\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/logo/index.vue",
    "content": "<script setup>\nimport { ref, watchEffect } from 'vue';\nimport { useAppStore } from '@/pinia/modules/app.js';\nimport { storeToRefs } from 'pinia';\n\nconst props = defineProps({\n  // logo 尺寸，单位为:rem\n  size: {\n    type: Number,\n    default: 2\n  }\n})\n\nconst darkLogoPath = \"/logo.png\";  // 系统没有暗黑模式logo，如果需要暗黑模式logo请自行修改文件路径。\nconst lightLogoPath = \"/logo.png\";\n\nconst appStore = useAppStore();\nconst { isDark } = storeToRefs(appStore);\n\nconst logoSrc = ref('');\nconst showTextPlaceholder = ref(false);\n\n// 检查图片是否存在\nfunction checkImageExists(url) {\n  return new Promise((resolve) => {\n    const tryToLoad = new Image();\n    tryToLoad.onload = () => resolve(true);\n    tryToLoad.onerror = () => resolve(false);\n    tryToLoad.src = url;\n  });\n}\n\nwatchEffect(async () => {\n  showTextPlaceholder.value = false; // 重置占位符状态\n\n  // 暗色模式直接 load，可以省一次亮色的 load\n  if (isDark.value && await checkImageExists(darkLogoPath)) {\n    logoSrc.value = darkLogoPath;\n    return;\n  }\n\n  if (await checkImageExists(lightLogoPath)) {\n    logoSrc.value = lightLogoPath;\n    return\n  }\n\n  // 到这里就包没有提供两种 logo 了\n  showTextPlaceholder.value = true;\n  console.error(\n    '错误: 在公共目录中找不到logo.png（或logo-dark.png）。'\n  );\n  console.warn(\n    '解决方案: 请在您的公共目录(/public)中放置logo.png和/或logo-dark.png文件，或确保路径正确。'\n  );\n});\n\n// 直接用 16px 作为默认的基准大小\nconst SPACING = 16\nfunction getSize() {\n  return {\n    width: `${props.size * SPACING}px`,\n    height: `${props.size * SPACING}px`,\n  }\n}\n</script>\n\n<template>\n  <img v-if=\"!showTextPlaceholder && logoSrc\" :src=\"logoSrc\" :alt=\"$GIN_VUE_ADMIN.appName\" class=\"object-contain\"\n    :style=\"{\n      ...getSize()\n    }\" :class=\"{\n      'filter invert-[90%] hue-rotate-180 brightness-110':\n        isDark && logoSrc === '/logo.png',\n    }\" />\n  <div v-else-if=\"showTextPlaceholder\"\n    class=\"rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-gray-700 dark:text-gray-200 font-bold text-xs\"\n    :style=\"{\n      ...getSize()\n    }\">\n    GVA\n  </div>\n</template>\n"
  },
  {
    "path": "web/src/components/office/docx.vue",
    "content": "<template>\n  <vue-office-docx :src=\"docx\" @rendered=\"rendered\" />\n</template>\n<script>\n  export default {\n    name: 'Docx'\n  }\n</script>\n<script setup>\n  import { ref, watch } from 'vue'\n\n  // 引入VueOfficeDocx组件\n  import VueOfficeDocx from '@vue-office/docx'\n  // 引入相关样式\n  import '@vue-office/docx/lib/index.css'\n\n  const model = defineModel({\n    type: String\n  })\n\n  const docx = ref(null)\n  watch(\n    () => model,\n    (value) => {\n      docx.value = value\n    },\n    { immediate: true }\n  )\n  const rendered = () => {}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "web/src/components/office/excel.vue",
    "content": "<template>\n  <VueOfficeExcel\n    :src=\"excel\"\n    @rendered=\"renderedHandler\"\n    @error=\"errorHandler\"\n    style=\"height: 100vh; width: 100vh\"\n  />\n</template>\n<script>\n  export default {\n    name: 'Excel'\n  }\n</script>\n<script setup>\n  //引入VueOfficeExcel组件\n  import VueOfficeExcel from '@vue-office/excel'\n  //引入相关样式\n  import '@vue-office/excel/lib/index.css'\n  import { ref, watch } from 'vue'\n\n  const props = defineProps({\n    modelValue: {\n      type: String,\n      default: () => ''\n    }\n  })\n  const excel = ref('')\n  watch(\n    () => props.modelValue,\n    (val) => (excel.value = val),\n    { immediate: true }\n  )\n  const renderedHandler = () => {}\n  const errorHandler = () => {}\n</script>\n<style></style>\n"
  },
  {
    "path": "web/src/components/office/index.vue",
    "content": "<template>\n  <div class=\"border border-solid border-gray-100 h-full w-full\">\n    <el-row>\n      <div v-if=\"ext === 'docx'\">\n        <Docx v-model=\"fullFileURL\" />\n      </div>\n      <div v-else-if=\"ext === 'pdf'\">\n        <Pdf v-model=\"fullFileURL\" />\n      </div>\n      <div v-else-if=\"ext === 'xlsx'\">\n        <Excel v-model=\"fullFileURL\" />\n      </div>\n      <div v-else-if=\"ext === 'image'\">\n        <el-image :src=\"fullFileURL\" lazy />\n      </div>\n    </el-row>\n  </div>\n</template>\n<script>\n  export default {\n    name: 'Office'\n  }\n</script>\n<script setup>\n  import { ref, watch, computed } from 'vue'\n  import Docx from '@/components/office/docx.vue'\n  import Pdf from '@/components/office/pdf.vue'\n  import Excel from '@/components/office/excel.vue'\n\n  const path = ref(import.meta.env.VITE_BASE_API)\n\n  const model = defineModel({ type: String })\n\n  const fileUrl = ref('')\n  const ext = ref('')\n  watch(\n    () => model,\n    (val) => {\n      fileUrl.value = val\n      const fileExt = val.split('.')[1] || ''\n      const image = ['png', 'jpg', 'jpeg', 'gif']\n      ext.value = image.includes(fileExt?.toLowerCase()) ? 'image' : fileExt\n    },\n    { immediate: true }\n  )\n  const fullFileURL = computed(() => {\n    return path.value + '/' + fileUrl.value\n  })\n</script>\n"
  },
  {
    "path": "web/src/components/office/pdf.vue",
    "content": "<template>\n  <vue-office-pdf\n    :src=\"pdf\"\n    @rendered=\"renderedHandler\"\n    @error=\"errorHandler\"\n  />\n</template>\n<script>\n  export default {\n    name: 'Pdf'\n  }\n</script>\n<script setup>\n  import { ref, watch } from 'vue'\n\n  //引入VueOfficeDocx组件\n  import VueOfficePdf from '@vue-office/pdf'\n  //引入相关样式\n  import '@vue-office/docx/lib/index.css'\n  console.log('pdf===>')\n  const props = defineProps({\n    modelValue: {\n      type: String,\n      default: () => ''\n    }\n  })\n  const pdf = ref(null)\n  watch(\n    () => props.modelValue,\n    (val) => (pdf.value = val),\n    { immediate: true }\n  )\n  const renderedHandler = () => {\n    console.log('pdf 加载成功')\n  }\n  const errorHandler = () => {\n    console.log('pdf 错误')\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/richtext/rich-edit.vue",
    "content": "<template>\n  <div class=\"richtext-wrapper border border-solid border-gray-100 h-full z-10\">\n    <Toolbar\n      :editor=\"editorRef\"\n      :default-config=\"toolbarConfig\"\n      mode=\"default\"\n    />\n    <Editor\n      v-model=\"valueHtml\"\n      class=\"overflow-y-hidden mt-0.5\"\n      style=\"height: 18rem\"\n      :default-config=\"editorConfig\"\n      mode=\"default\"\n      @onCreated=\"handleCreated\"\n      @onChange=\"change\"\n    />\n  </div>\n</template>\n\n<script setup>\n  import '@wangeditor/editor/dist/css/style.css' // 引入 css\n\n  const basePath = import.meta.env.VITE_BASE_API\n\n  import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'\n  import { Editor, Toolbar } from '@wangeditor/editor-for-vue'\n\n  import { ElMessage } from 'element-plus'\n  import { getUrl } from '@/utils/image'\n  import { useUserStore } from '@/pinia/modules/user'\n\n  const emits = defineEmits(['change', 'update:modelValue'])\n\n  const change = (editor) => {\n    emits('change', editor)\n    emits('update:modelValue', valueHtml.value)\n  }\n\n  const userStore = useUserStore()\n  const props = defineProps({\n    modelValue: {\n      type: String,\n      default: ''\n    }\n  })\n\n  const editorRef = shallowRef()\n  const valueHtml = ref('')\n\n  const toolbarConfig = {}\n  const editorConfig = {\n    placeholder: '请输入内容...',\n    MENU_CONF: {}\n  }\n  editorConfig.MENU_CONF['uploadImage'] = {\n    fieldName: 'file',\n    server: basePath + '/fileUploadAndDownload/upload?noSave=1',\n    headers: {\n      'x-token': userStore.token,\n    },\n    customInsert(res, insertFn) {\n      if (res.code === 0) {\n        const urlPath = getUrl(res.data.file.url)\n        insertFn(urlPath, res.data.file.name)\n        return\n      }\n      ElMessage.error(res.msg)\n    }\n  }\n\n  // 组件销毁时，也及时销毁编辑器\n  onBeforeUnmount(() => {\n    const editor = editorRef.value\n    if (editor == null) return\n    editor.destroy()\n  })\n\n  const handleCreated = (editor) => {\n    editorRef.value = editor\n    valueHtml.value = props.modelValue\n  }\n\n  watch(\n    () => props.modelValue,\n    () => {\n      valueHtml.value = props.modelValue\n    }\n  )\n</script>\n\n<style scoped lang=\"scss\">\n  .richtext-wrapper {\n    :deep(.w-e-text-container [data-slate-editor]) {\n      h1 {\n        font-size: 2em;\n        font-weight: 700;\n      }\n\n      h2 {\n        font-size: 1.5em;\n        font-weight: 700;\n      }\n\n      h3 {\n        font-size: 1.17em;\n        font-weight: 700;\n      }\n\n      h4 {\n        font-size: 1em;\n        font-weight: 700;\n      }\n\n      h5 {\n        font-size: 0.83em;\n        font-weight: 700;\n      }\n\n      h6 {\n        font-size: 0.67em;\n        font-weight: 700;\n      }\n\n      ul,\n      ol {\n        margin: 1em 0;\n        padding-left: 2em;\n      }\n\n      ul {\n        list-style-type: disc;\n      }\n\n      ol {\n        list-style-type: decimal;\n      }\n\n      li {\n        margin: 0.25em 0;\n      }\n\n      a {\n        color: var(--el-color-primary, #409eff);\n        text-decoration: underline;\n      }\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ul ul) {\n      list-style-type: circle;\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ul ul ul) {\n      list-style-type: square;\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ol ol) {\n      list-style-type: lower-alpha;\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ol ol ol) {\n      list-style-type: lower-roman;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/components/richtext/rich-view.vue",
    "content": "<template>\n  <div class=\"richtext-wrapper border border-solid border-gray-100 h-full\">\n    <Editor\n      v-model=\"valueHtml\"\n      class=\"overflow-y-hidden mt-0.5\"\n      :default-config=\"editorConfig\"\n      mode=\"default\"\n      @onCreated=\"handleCreated\"\n      @onChange=\"change\"\n    />\n  </div>\n</template>\n<script setup>\n  import '@wangeditor/editor/dist/css/style.css' // 引入 css\n\n  import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'\n  import { Editor } from '@wangeditor/editor-for-vue'\n\n  const emits = defineEmits(['change', 'update:modelValue'])\n  const editorConfig = ref({\n    readOnly: true\n  })\n  const change = (editor) => {\n    emits('change', editor)\n    emits('update:modelValue', valueHtml.value)\n  }\n\n  const props = defineProps({\n    modelValue: {\n      type: String,\n      default: ''\n    }\n  })\n\n  const editorRef = shallowRef()\n  const valueHtml = ref('')\n\n  // 组件销毁时，也及时销毁编辑器\n  onBeforeUnmount(() => {\n    const editor = editorRef.value\n    if (editor == null) return\n    editor.destroy()\n  })\n\n  const handleCreated = (editor) => {\n    editorRef.value = editor\n    valueHtml.value = props.modelValue\n  }\n\n  watch(\n    () => props.modelValue,\n    () => {\n      valueHtml.value = props.modelValue\n    }\n  )\n</script>\n\n<style scoped lang=\"scss\">\n  .richtext-wrapper {\n    :deep(.w-e-text-container [data-slate-editor]) {\n      h1 {\n        font-size: 2em;\n        font-weight: 700;\n      }\n\n      h2 {\n        font-size: 1.5em;\n        font-weight: 700;\n      }\n\n      h3 {\n        font-size: 1.17em;\n        font-weight: 700;\n      }\n\n      h4 {\n        font-size: 1em;\n        font-weight: 700;\n      }\n\n      h5 {\n        font-size: 0.83em;\n        font-weight: 700;\n      }\n\n      h6 {\n        font-size: 0.67em;\n        font-weight: 700;\n      }\n\n      ul,\n      ol {\n        margin: 1em 0;\n        padding-left: 2em;\n      }\n\n      ul {\n        list-style-type: disc;\n      }\n\n      ol {\n        list-style-type: decimal;\n      }\n\n      li {\n        margin: 0.25em 0;\n      }\n\n      a {\n        color: var(--el-color-primary, #409eff);\n        text-decoration: underline;\n      }\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ul ul) {\n      list-style-type: circle;\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ul ul ul) {\n      list-style-type: square;\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ol ol) {\n      list-style-type: lower-alpha;\n    }\n\n    :deep(.w-e-text-container [data-slate-editor] ol ol ol) {\n      list-style-type: lower-roman;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/components/selectFile/selectFile.vue",
    "content": "<template>\n  <div>\n    <el-upload\n      v-model:file-list=\"fileList\"\n      multiple\n      :action=\"`${getBaseUrl()}/fileUploadAndDownload/upload?noSave=1`\"\n      :on-error=\"uploadError\"\n      :on-success=\"uploadSuccess\"\n      :on-remove=\"uploadRemove\"\n      :show-file-list=\"true\"\n      :limit=\"limit\"\n      :accept=\"accept\"\n      class=\"upload-btn\"\n      :headers=\"{'x-token': token}\"\n    >\n      <el-button type=\"primary\"> 上传文件 </el-button>\n    </el-upload>\n  </div>\n</template>\n\n<script setup>\n  import { ref } from 'vue'\n  import { ElMessage } from 'element-plus'\n  import { getBaseUrl } from '@/utils/format'\n  import { useUserStore } from \"@/pinia\";\n\n  defineOptions({\n    name: 'UploadCommon'\n  })\n\n  defineProps({\n    limit: {\n      type: Number,\n      default: 3\n    },\n    accept: {\n      type: String,\n      default: ''\n    }\n  })\n\n  const userStore = useUserStore()\n\n  const token = userStore.token\n\n  const fullscreenLoading = ref(false)\n\n  const model = defineModel({ type: Array })\n\n  const fileList = ref(model.value || [])\n\n  const emits = defineEmits(['on-success', 'on-error'])\n\n  const uploadSuccess = (res) => {\n    const { data, code } = res\n    if (code !== 0) {\n      ElMessage({\n        type: 'error',\n        message: '上传失败' + res.msg\n      })\n      fileList.value.pop()\n      return\n    }\n    model.value.push({\n      name: data.file.name,\n      url: data.file.url\n    })\n    emits('on-success', res)\n  }\n\n  const uploadRemove = (file) => {\n    const index = model.value.indexOf(file)\n    if (index > -1) {\n      model.value.splice(index, 1)\n      fileList.value = model.value\n    }\n  }\n\n  const uploadError = (err) => {\n    ElMessage({\n      type: 'error',\n      message: '上传失败'\n    })\n    fullscreenLoading.value = false\n    emits('on-error', err)\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/selectImage/selectComponent.vue",
    "content": "<template>\n  <div\n      class=\"w-40 h-40 relative rounded border border-dashed border-gray-300 cursor-pointer group\"\n      :class=\"rounded ? 'rounded-full' : ''\"\n  >\n    <div class=\"w-full h-full overflow-hidden\" :class=\"rounded ? 'rounded-full' : ''\">\n      <el-icon\n          v-if=\"isVideoExt(model || '')\"\n          :size=\"32\"\n          class=\"absolute top-[calc(50%-16px)] left-[calc(50%-16px)]\"\n      >\n        <VideoPlay />\n      </el-icon>\n      <video\n          v-if=\"isVideoExt(model || '')\"\n          class=\"w-full h-full object-cover\"\n          muted\n          preload=\"metadata\"\n      >\n        <source :src=\"getUrl(model) + '#t=1'\" />\n      </video>\n\n      <el-image\n          v-if=\"model && !isVideoExt(model)\"\n          class=\"w-full h-full\"\n          :src=\"imgUrl\"\n          :preview-src-list=\"srcList\"\n          fit=\"cover\"\n      />\n      <div\n          v-else\n          class=\"text-gray-600 group-hover:bg-gray-200 group-hover:opacity-60 w-full h-full flex justify-center items-center\"\n          @click=\"chooseItem\"\n      >\n        <el-icon>\n          <plus />\n        </el-icon>\n        上传\n      </div>\n    </div>\n    <!-- 删除按钮在外层容器中 -->\n    <div\n        v-if=\"model\"\n        class=\"right-0 top-0 hidden text-gray-400 group-hover:flex justify-center items-center absolute z-10\"\n        @click=\"deleteItem\"\n    >\n      <el-icon :size=\"24\">\n        <CircleCloseFilled />\n      </el-icon>\n    </div>\n  </div>\n</template>\n<script setup>\n  import { getUrl, isVideoExt } from '@/utils/image'\n  import { CircleCloseFilled, Plus } from '@element-plus/icons-vue'\n  import { computed } from 'vue'\n\n  const props = defineProps({\n    model: {\n      default: '',\n      type: String\n    },\n    rounded: {\n      default: false,\n      type: Boolean\n    }\n  })\n\n  const emits = defineEmits(['chooseItem', 'deleteItem'])\n\n  const chooseItem = () => {\n    emits('chooseItem')\n  }\n\n  const deleteItem = () => {\n    emits('deleteItem')\n  }\n\n  const imgUrl = computed(() => {\n    return getUrl(props.model)\n  })\n\n  const srcList = computed(() => {\n    return imgUrl.value ? [imgUrl.value] : []\n  })\n</script>\n"
  },
  {
    "path": "web/src/components/selectImage/selectImage.vue",
    "content": "<template>\n  <div>\n    <selectComponent :rounded=\"rounded\" v-if=\"!props.multiple\" :model=\"model\" @chooseItem=\"openChooseImg\" @deleteItem=\"openChooseImg\" />\n    <div v-else class=\"w-full gap-4 flex flex-wrap\">\n      <draggable \n        v-model=\"model\" \n        class=\"flex flex-wrap gap-4\" \n        item-key=\"url\" \n        ghost-class=\"ghost-item\" \n        handle=\".drag-handle\"\n        animation=\"300\"\n        @start=\"onDragStart\"\n        @end=\"onDragEnd\"\n      >\n        <template #item=\"{element, index}\">\n          <div class=\"relative group\">\n            <div class=\"drag-handle absolute left-2 top-2 w-8 h-8 flex items-center justify-center cursor-move z-10 opacity-0 group-hover:opacity-100 rounded-full\">\n              <el-icon :size=\"18\"><Menu /></el-icon>\n            </div>\n            <selectComponent :rounded=\"rounded\" :model=\"element\" @chooseItem=\"openChooseImg\"\n                           @deleteItem=\"deleteImg(index)\"\n            />\n          </div>\n        </template>\n      </draggable>\n      <selectComponent :rounded=\"rounded\" v-if=\"model?.length < props.maxUpdateCount || props.maxUpdateCount === 0\"\n                       @chooseItem=\"openChooseImg\" @deleteItem=\"openChooseImg\"\n      />\n    </div>\n\n    <el-drawer v-model=\"drawer\" title=\"媒体库 | 点击“文件名”可以编辑，选择的类别即是上传的类别\" :size=\"880\">\n      <div class=\"flex\">\n        <div class=\"w-64\" style=\"border-right: solid 1px var(--el-border-color);\">\n          <el-scrollbar style=\"height: calc(100vh - 110px)\">\n            <el-tree\n                :data=\"categories\"\n                node-key=\"id\"\n                :props=\"defaultProps\"\n                @node-click=\"handleNodeClick\"\n                default-expand-all\n            >\n              <template #default=\"{ node, data }\">\n                <div class=\"w-36\" :class=\"search.classId === data.ID ? 'text-blue-500 font-bold' : ''\">{{ data.name }}\n                </div>\n                <el-dropdown>\n                  <el-icon class=\"ml-3 text-right\" v-if=\"data.ID > 0\"><MoreFilled /></el-icon>\n                  <el-icon class=\"ml-3 text-right mt-1\" v-else><Plus /></el-icon>\n                  <template #dropdown>\n                    <el-dropdown-menu>\n                      <el-dropdown-item @click=\"addCategoryFun(data)\">添加分类</el-dropdown-item>\n                      <el-dropdown-item @click=\"editCategory(data)\" v-if=\"data.ID > 0\">编辑分类</el-dropdown-item>\n                      <el-dropdown-item @click=\"deleteCategoryFun(data.ID)\" v-if=\"data.ID > 0\">删除分类</el-dropdown-item>\n                    </el-dropdown-menu>\n                  </template>\n                </el-dropdown>\n              </template>\n            </el-tree>\n          </el-scrollbar>\n        </div>\n        <div class=\"ml-4 w-[605px]\">\n          <div class=\"gva-btn-list gap-2\">\n            <el-input v-model.trim=\"search.keyword\" class=\"w-96\" placeholder=\"请输入文件名或备注\" clearable />\n            <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\"></el-button>\n          </div>\n          <div class=\"gva-btn-list gap-2\">\n            <el-button @click=\"useSelectedImages\" type=\"danger\" :disabled=\"selectedImages.length === 0\" :icon=\"ArrowLeftBold\">选定</el-button>\n            <upload-common :image-common=\"imageCommon\" :classId=\"search.classId\" @on-success=\"onSuccess\" />\n            <cropper-image :classId=\"search.classId\" @on-success=\"onSuccess\" />\n            <QRCodeUpload :classId=\"search.classId\" @on-success=\"onSuccess\" />\n            <upload-image :image-url=\"imageUrl\" :file-size=\"2048\" :max-w-h=\"1080\" :classId=\"search.classId\" @on-success=\"onSuccess\" />\n          </div>\n          <div class=\"flex flex-wrap gap-4\">\n            <div v-for=\"(item,key) in picList\" :key=\"key\" class=\"w-40\">\n              <div class=\"w-40 h-40 border rounded overflow-hidden border-dashed border-gray-300 cursor-pointer relative group\">\n                <el-image :key=\"key\" :src=\"getUrl(item.url)\" fit=\"cover\" class=\"w-full h-full relative\" @click=\"toggleImageSelection(item)\" :class=\"{ selected: isSelected(item) }\">\n                  <template #error>\n                    <el-icon v-if=\"isVideoExt(item.url || '')\" :size=\"32\" class=\"absolute top-[calc(50%-16px)] left-[calc(50%-16px)]\">\n                      <VideoPlay />\n                    </el-icon>\n                    <video v-if=\"isVideoExt(item.url || '')\"\n                           class=\"w-full h-full object-cover\"\n                           muted\n                           preload=\"metadata\"\n                           @click=\"toggleImageSelection(item)\"\n                           :class=\"{ selected: isSelected(item) }\"\n                    >\n                      <source :src=\"getUrl(item.url) + '#t=1'\">\n                      您的浏览器不支持视频播放\n                    </video>\n                    <div v-else class=\"w-full h-full object-cover flex items-center justify-center\">\n                      <el-icon :size=\"32\">\n                        <icon-picture />\n                      </el-icon>\n                    </div>\n                  </template>\n                </el-image>\n                <div class=\"absolute -right-1 top-1 w-8 h-8 group-hover:inline-block hidden\" @click=\"deleteCheck(item)\">\n                  <el-icon :size=\"18\">\n                    <CloseBold />\n                  </el-icon>\n                </div>\n              </div>\n              <div class=\"overflow-hidden text-nowrap overflow-ellipsis text-center w-full cursor-pointer\" @click=\"editFileNameFunc(item)\">\n                {{ item.name }}\n              </div>\n            </div>\n          </div>\n          <el-pagination\n              :current-page=\"page\"\n              :page-size=\"pageSize\"\n              :total=\"total\"\n              class=\"justify-center\"\n              layout=\"total, prev, pager, next, jumper\"\n              @current-change=\"handleCurrentChange\"\n              @size-change=\"handleSizeChange\"\n          />\n        </div>\n      </div>\n    </el-drawer>\n\n\n    <!-- 添加分类弹窗 -->\n    <el-dialog v-model=\"categoryDialogVisible\" @close=\"closeAddCategoryDialog\" width=\"520\"\n               :title=\"(categoryFormData.ID === 0 ? '添加' : '编辑') + '分类'\"\n               draggable\n    >\n      <el-form ref=\"categoryForm\" :rules=\"rules\" :model=\"categoryFormData\" label-width=\"80px\">\n        <el-form-item label=\"上级分类\">\n          <el-tree-select\n              v-model=\"categoryFormData.pid\"\n              :data=\"categories\"\n              check-strictly\n              :props=\"defaultProps\"\n              :render-after-expand=\"false\"\n              style=\"width: 240px\"\n          />\n        </el-form-item>\n        <el-form-item label=\"分类名称\" prop=\"name\">\n          <el-input v-model.trim=\"categoryFormData.name\" placeholder=\"分类名称\"></el-input>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"closeAddCategoryDialog\">取消</el-button>\n        <el-button type=\"primary\" @click=\"confirmAddCategory\">确定</el-button>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\nimport { getUrl, isVideoExt } from '@/utils/image'\nimport { ref } from 'vue'\nimport { getFileList, editFileName, deleteFile } from '@/api/fileUploadAndDownload'\nimport UploadImage from '@/components/upload/image.vue'\nimport UploadCommon from '@/components/upload/common.vue'\nimport WarningBar from '@/components/warningBar/warningBar.vue'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport {\n  ArrowLeftBold,\n  CloseBold,\n  Menu,\n  MoreFilled,\n  Picture as IconPicture,\n  Plus,\n  VideoPlay\n} from '@element-plus/icons-vue'\nimport selectComponent from '@/components/selectImage/selectComponent.vue'\nimport { addCategory, deleteCategory, getCategoryList } from '@/api/attachmentCategory'\nimport CropperImage from \"@/components/upload/cropper.vue\";\nimport QRCodeUpload from \"@/components/upload/QR-code.vue\";\nimport draggable from 'vuedraggable'\n\nconst imageUrl = ref('')\nconst imageCommon = ref('')\n\nconst search = ref({\n  keyword: null,\n  classId: 0\n})\nconst page = ref(1)\nconst total = ref(0)\nconst pageSize = ref(20)\n\nconst model = defineModel({ type: [String, Array] })\n\nconst props = defineProps({\n  multiple: {\n    type: Boolean,\n    default: false\n  },\n  fileType: {\n    type: String,\n    default: ''\n  },\n  maxUpdateCount: {\n    type: Number,\n    default: 0\n  },\n  rounded: {\n    type: Boolean,\n    default: false\n  }\n})\n\nconst deleteImg = (index) => {\n  model.value.splice(index, 1)\n}\n\nconst handleSizeChange = (val) => {\n  pageSize.value = val\n  getImageList()\n}\n\nconst handleCurrentChange = (val) => {\n  page.value = val\n  getImageList()\n}\n\nconst onSubmit = () => {\n  search.value.classId = 0\n  page.value = 1\n  getImageList()\n}\n\nconst editFileNameFunc = async(row) => {\n  ElMessageBox.prompt('请输入文件名或者备注', '编辑', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    inputPattern: /\\S/,\n    inputErrorMessage: '不能为空',\n    inputValue: row.name\n  }).then(async({ value }) => {\n    row.name = value\n    const res = await editFileName(row)\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '编辑成功!'\n      })\n      await getImageList()\n    }\n  }).catch(() => {\n    ElMessage({\n      type: 'info',\n      message: '取消修改'\n    })\n  })\n}\n\nconst drawer = ref(false)\nconst picList = ref([])\n\nconst imageTypeList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg']\nconst videoTypeList = ['mp4', 'avi', 'rmvb', 'rm', 'asf', 'divx', 'mpg', 'mpeg', 'mpe', 'wmv', 'mkv', 'vob']\n\nconst listObj = {\n  image: imageTypeList,\n  video: videoTypeList\n}\n\nconst chooseImg = (url) => {\n  if (props.fileType) {\n    const typeSuccess = listObj[props.fileType].some(item => {\n      if (url?.toLowerCase().includes(item)) {\n        return true\n      }\n    })\n    if (!typeSuccess) {\n      ElMessage({\n        type: 'error',\n        message: '当前类型不支持使用'\n      })\n      return\n    }\n  }\n  //if (props.multiple) {\n  //  model.value.push(url)\n  //} else {\n  model.value = url\n  //}\n  drawer.value = false\n}\n\nconst openChooseImg = async() => {\n  if (model.value && !props.multiple) {\n    model.value = ''\n    return\n  }\n  await getImageList()\n  await fetchCategories()\n  drawer.value = true\n}\n\nconst getImageList = async() => {\n  const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })\n  if (res.code === 0) {\n    picList.value = res.data.list\n    total.value = res.data.total\n    page.value = res.data.page\n    pageSize.value = res.data.pageSize\n  }\n}\n\nconst deleteCheck = (item) => {\n  ElMessageBox.confirm('是否删除该文件', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(async() => {\n    const res = await deleteFile(item)\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '删除成功!'\n      })\n      await getImageList()\n    }\n  }).catch(() => {\n    ElMessage({\n      type: 'info',\n      message: '已取消删除'\n    })\n  })\n}\n\nconst defaultProps = {\n  children: 'children',\n  label: 'name',\n  value: 'ID'\n}\n\nconst categories = ref([])\nconst fetchCategories = async() => {\n  const res = await getCategoryList()\n  let data = {\n    name: '全部分类',\n    ID: 0,\n    pid: 0,\n    children:[]\n  }\n  if (res.code === 0) {\n    categories.value = res.data || []\n    categories.value.unshift(data)\n  }\n}\n\nconst handleNodeClick = (node) => {\n  search.value.keyword = null\n  search.value.classId = node.ID\n  page.value = 1\n  getImageList()\n}\n\nconst onSuccess = () => {\n  search.value.keyword = null\n  page.value = 1\n  getImageList()\n}\n\nconst categoryDialogVisible = ref(false)\nconst categoryFormData = ref({\n  ID: 0,\n  pid: 0,\n  name: ''\n})\n\nconst categoryForm = ref(null)\nconst rules = ref({\n  name: [\n    { required: true, message: '请输入分类名称', trigger: 'blur' },\n    { max: 20, message: '最多20位字符', trigger: 'blur' }\n  ]\n})\n\nconst addCategoryFun = (category) => {\n  categoryDialogVisible.value = true\n  categoryFormData.value.ID = 0\n  categoryFormData.value.pid = category.ID\n}\n\nconst editCategory = (category) => {\n  categoryFormData.value = {\n    ID: category.ID,\n    pid: category.pid,\n    name: category.name\n  }\n  categoryDialogVisible.value = true\n}\n\nconst deleteCategoryFun = async(id) => {\n  const res = await deleteCategory({ id: id })\n  if (res.code === 0) {\n    ElMessage.success({ type: 'success', message: '删除成功' })\n    await fetchCategories()\n  }\n}\n\nconst confirmAddCategory = async() => {\n  categoryForm.value.validate(async valid => {\n    if (valid) {\n      const res = await addCategory(categoryFormData.value)\n      if (res.code === 0) {\n        ElMessage({ type: 'success', message: '操作成功' })\n        await fetchCategories()\n        closeAddCategoryDialog()\n      }\n    }\n  })\n}\n\nconst closeAddCategoryDialog = () => {\n  categoryDialogVisible.value = false\n  categoryFormData.value = {\n    ID: 0,\n    pid: 0,\n    name: ''\n  }\n}\n\nconst selectedImages = ref([])\n\nconst toggleImageSelection = (item) => {\n  if (props.multiple === false) {\n    chooseImg(item.url)\n    return\n  }\n  const index = selectedImages.value.findIndex(img => img.ID === item.ID)\n  if (index > -1) {\n    selectedImages.value.splice(index, 1)\n  } else {\n    selectedImages.value.push(item)\n  }\n}\n\nconst isSelected = (item) => {\n  return selectedImages.value.some(img => img.ID === item.ID)\n}\n\nconst useSelectedImages = () => {\n  selectedImages.value.forEach((item) => {\n    model.value.push(item.url)\n  })\n  drawer.value = false\n  selectedImages.value = []\n}\n\nconst onDragStart = () => {\n  // 拖拽开始时的处理\n  document.body.style.cursor = 'grabbing'\n}\n\nconst onDragEnd = () => {\n  // 拖拽结束时的处理\n  document.body.style.cursor = 'default'\n  // 确保model是数组类型\n  if (!Array.isArray(model.value)) {\n    model.value = []\n  }\n}\n\n</script>\n<style scoped>\n.selected {\n  border: 3px solid #409eff;\n}\n\n.selected:before {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  top: 0;\n  border: 10px solid #409eff;\n}\n\n.selected:after {\n  content: \"\";\n  width: 9px;\n  height: 14px;\n  position: absolute;\n  left: 6px;\n  top: 0;\n  border: 3px solid #fff;\n  border-top-color: transparent;\n  border-left-color: transparent;\n  transform: rotate(45deg);\n}\n\n.ghost-item {\n  opacity: 0.5;\n  background: #c8ebfb;\n  border: 1px dashed #409eff;\n}\n\n.drag-handle {\n  background-color: rgba(64, 158, 255, 0.1);\n  border-radius: 4px;\n  transition: opacity 0.3s;\n}\n\n.drag-handle:hover {\n  background-color: rgba(64, 158, 255, 0.2);\n}\n</style>\n"
  },
  {
    "path": "web/src/components/svgIcon/svgIcon.vue",
    "content": "<template>\n  <template v-if=\"localIcon\">\n    <svg aria-hidden=\"true\" width=\"1em\" height=\"1em\" v-bind=\"bindAttrs\">\n      <use :xlink:href=\"'#' + localIcon\" rel=\"external nofollow\" />\n    </svg>\n  </template>\n  <template v-else-if=\"icon\">\n    <Icon :icon=\"icon\" v-bind=\"bindAttrs\" />\n  </template>\n</template>\n\n<script setup>\nimport { computed, useAttrs } from 'vue';\nimport { Icon } from '@iconify/vue'\n\n/**\n * 使用示例：\n * 本地图标（所有可用的本地图标见控制台输出）：\n * <SvgIcon localIcon=\"lock\" class=\"text-red-500 text-3xl\" />\n * \n * 在线图标（相关查询网站：https://icones.js.org/ 或是：https://icon-sets.iconify.design/）：\n * <SvgIcon icon=\"mingcute:love-fill\" class=\"text-red-500 text-3xl\" />\n */\ndefineProps({\n  // 通过 symbol id 使用本地注册的 svg 图标\n  localIcon: {\n    type: String,\n    required: false,\n    default: ''\n  },\n  // Iconify 图标名称, 例如: 'mdi:home'\n  icon: {\n    type: String,\n    required: false,\n    default: ''\n  }\n})\nconst attrs = useAttrs();\n\nconst bindAttrs = computed(() => ({\n  class: (attrs.class) || '',\n  style: (attrs.style) || ''\n}))\n</script>\n"
  },
  {
    "path": "web/src/components/upload/QR-code.vue",
    "content": "<template>\n  <div>\n    <el-button type=\"primary\" icon=\"iphone\" @click=\"createQrCode\"> 扫码上传</el-button>\n  </div>\n\n  <el-dialog v-model=\"dialogVisible\" title=\"扫码上传\" width=\"320px\" :show-close=\"false\" append-to-body :close-on-click-modal=\"false\"\n             draggable\n  >\n    <div class=\"m-2\">\n      <vue-qr :logoSrc=\"logoSrc\"\n              :size=\"291\"\n              :margin=\"0\"\n              :autoColor=\"true\"\n              :dotScale=\"1\"\n              :text=\"codeUrl\"\n              colorDark=\"green\"\n              colorLight=\"white\"\n              ref=\"qrcode\"\n      />\n    </div>\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"dialogVisible = false\">取 消</el-button>\n        <el-button type=\"primary\" @click=\"onFinished\">完成上传</el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup>\nimport logoSrc from '@/assets/logo.png'\nimport vueQr from 'vue-qr/src/packages/vue-qr.vue'\nimport { ref } from 'vue'\nimport { useUserStore } from '@/pinia/modules/user'\n\ndefineOptions({\n  name: 'QRCodeUpload'\n})\n\nconst emit = defineEmits(['on-success'])\n\nconst props = defineProps({\n  classId: {\n    type: Number,\n    default: 0\n  }\n})\n\nconst dialogVisible = ref(false)\nconst userStore = useUserStore()\nconst codeUrl = ref('')\n\nconst createQrCode = () => {\n  const local = window.location\n  codeUrl.value = local.protocol + '//' + local.host + '/#/scanUpload?id=' + props.classId + '&token=' + userStore.token + '&t=' + Date.now()\n  dialogVisible.value = true\n  console.log(codeUrl.value)\n}\n\nconst onFinished = () => {\n  dialogVisible.value = false\n  codeUrl.value = ''\n  emit('on-success', '')\n}\n</script>\n"
  },
  {
    "path": "web/src/components/upload/common.vue",
    "content": "<template>\n  <div>\n    <el-upload\n      :action=\"`${getBaseUrl()}/fileUploadAndDownload/upload`\"\n      :before-upload=\"checkFile\"\n      :on-error=\"uploadError\"\n      :on-success=\"uploadSuccess\"\n      :show-file-list=\"false\"\n      :data=\"{'classId': props.classId}\"\n      :headers=\"{'x-token': token}\"\n      multiple\n      class=\"upload-btn\"\n    >\n      <el-button type=\"primary\" :icon=\"Upload\">普通上传</el-button>\n    </el-upload>\n  </div>\n</template>\n\n<script setup>\n  import { ref } from 'vue'\n  import { ElMessage } from 'element-plus'\n  import { isVideoMime, isImageMime } from '@/utils/image'\n  import { getBaseUrl } from '@/utils/format'\n  import { Upload } from \"@element-plus/icons-vue\";\n  import { useUserStore } from \"@/pinia\";\n\n  defineOptions({\n    name: 'UploadCommon'\n  })\n\n  const userStore = useUserStore()\n\n  const token = userStore.token\n\n  const props = defineProps({\n    classId: {\n      type: Number,\n      default: 0\n    }\n  })\n\n  const emit = defineEmits(['on-success'])\n\n  const fullscreenLoading = ref(false)\n\n  const checkFile = (file) => {\n    fullscreenLoading.value = true\n    const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo 应支持在项目中设置\n    const isLt5M = file.size / 1024 / 1024 < 5 // 5MB, @todo 应支持项目中设置\n    const isVideo = isVideoMime(file.type)\n    const isImage = isImageMime(file.type)\n    let pass = true\n    if (!isVideo && !isImage) {\n      ElMessage.error(\n        '上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!'\n      )\n      fullscreenLoading.value = false\n      pass = false\n    }\n    if (!isLt5M && isVideo) {\n      ElMessage.error('上传视频大小不能超过 5MB')\n      fullscreenLoading.value = false\n      pass = false\n    }\n    if (!isLt500K && isImage) {\n      ElMessage.error('未压缩的上传图片大小不能超过 500KB，请使用压缩上传')\n      fullscreenLoading.value = false\n      pass = false\n    }\n\n    console.log('upload file check result: ', pass)\n\n    return pass\n  }\n\n  const uploadSuccess = (res) => {\n    const { data } = res\n    if (data.file) {\n      emit('on-success', data.file.url)\n    }\n  }\n\n  const uploadError = () => {\n    ElMessage({\n      type: 'error',\n      message: '上传失败'\n    })\n    fullscreenLoading.value = false\n  }\n</script>\n"
  },
  {
    "path": "web/src/components/upload/cropper.vue",
    "content": "<template>\n  <el-upload\n      ref=\"uploadRef\"\n      :action=\"`${getBaseUrl()}/fileUploadAndDownload/upload`\"\n      accept=\"image/*\"\n      :show-file-list=\"false\"\n      :auto-upload=\"false\"\n      :data=\"{'classId': props.classId}\"\n      :on-success=\"handleImageSuccess\"\n      :on-change=\"handleFileChange\"\n      :headers=\"{'x-token': token}\"\n  >\n    <el-button type=\"primary\" icon=\"crop\"> 裁剪上传</el-button>\n  </el-upload>\n\n  <el-dialog v-model=\"dialogVisible\" title=\"图片裁剪\" width=\"1200px\" append-to-body @close=\"dialogVisible = false\" :close-on-click-modal=\"false\" draggable>\n    <div class=\"flex gap-[30px] h-[600px]\">\n      <!-- 左侧编辑区 -->\n      <div class=\"flex flex-col flex-1\">\n        <div class=\"flex-1 bg-[#f8f8f8] rounded-lg overflow-hidden\">\n          <VueCropper\n              ref=\"cropperRef\"\n              :img=\"imgSrc\"\n              outputType=\"jpeg\"\n              :autoCrop=\"true\"\n              :autoCropWidth=\"cropWidth\"\n              :autoCropHeight=\"cropHeight\"\n              :fixedBox=\"false\"\n              :fixed=\"fixedRatio\"\n              :fixedNumber=\"fixedNumber\"\n              :centerBox=\"true\"\n              :canMoveBox=\"true\"\n              :full=\"false\"\n              :maxImgSize=\"1200\"\n              :original=\"true\"\n              @realTime=\"handleRealTime\"\n          ></VueCropper>\n        </div>\n\n        <!-- 工具栏 -->\n        <div class=\"mt-[20px] flex items-center p-[10px] bg-white rounded-lg shadow-[0_2px_12px_rgba(0,0,0,0.1)]\">\n          <el-button-group>\n            <el-tooltip content=\"向左旋转\">\n              <el-button @click=\"rotate(-90)\" :icon=\"RefreshLeft\" />\n            </el-tooltip>\n            <el-tooltip content=\"向右旋转\">\n              <el-button @click=\"rotate(90)\" :icon=\"RefreshRight\" />\n            </el-tooltip>\n            <el-button :icon=\"Plus\" @click=\"changeScale(1)\"></el-button>\n            <el-button :icon=\"Minus\" @click=\"changeScale(-1)\"></el-button>\n          </el-button-group>\n\n\n          <el-select v-model=\"currentRatio\" placeholder=\"选择比例\" class=\"w-32 ml-4\" @change=\"onCurrentRatio\">\n            <el-option v-for=\"(item, index) in ratioOptions\" :key=\"index\" :label=\"item.label\" :value=\"index\" />\n          </el-select>\n        </div>\n      </div>\n\n      <!-- 右侧预览区 -->\n      <div class=\"w-[340px]\">\n        <div class=\"bg-white p-5 rounded-lg shadow-[0_2px_12px_rgba(0,0,0,0.1)]\">\n          <div class=\"mb-[15px] text-gray-600\">裁剪预览</div>\n          <div class=\"bg-white p-5 rounded-lg shadow-[0_2px_12px_rgba(0,0,0,0.1)]\"\n               :style=\"{'width': previews.w + 'px', 'height': previews.h + 'px'}\"\n          >\n            <div class=\"w-full h-full relative overflow-hidden\">\n              <img :src=\"previews.url\" :style=\"previews.img\" alt=\"\" class=\"max-w-none absolute transition-all duration-300 ease-in-out image-render-pixelated origin-[0_0]\" />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"dialogVisible = false\">取 消</el-button>\n        <el-button type=\"primary\" @click=\"handleUpload\" :loading=\"uploading\"> {{ uploading ? '上传中...' : '上 传' }}\n        </el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup>\nimport { ref, getCurrentInstance } from 'vue'\nimport { ElMessage } from 'element-plus'\nimport { RefreshLeft, RefreshRight, Plus, Minus } from '@element-plus/icons-vue'\nimport 'vue-cropper/dist/index.css'\nimport { VueCropper } from 'vue-cropper'\nimport { getBaseUrl } from '@/utils/format'\nimport { useUserStore } from \"@/pinia\";\n\ndefineOptions({\n  name: 'CropperImage'\n})\n\nconst emit = defineEmits(['on-success'])\n\nconst props = defineProps({\n  classId: {\n    type: Number,\n    default: 0\n  }\n})\n\nconst uploadRef = ref(null)\n// 响应式数据\nconst dialogVisible = ref(false)\nconst imgSrc = ref('')\nconst cropperRef = ref(null)\nconst { proxy } = getCurrentInstance()\nconst previews = ref({})\nconst uploading = ref(false)\n\n// 缩放控制\nconst changeScale = (value) => {\n  proxy.$refs.cropperRef.changeScale(value)\n}\n\n// 比例预设\nconst ratioOptions = ref([\n  { label: '1:1', value: [1, 1] },\n  { label: '16:9', value: [16, 9] },\n  { label: '9:16', value: [9, 16] },\n  { label: '4:3', value: [4, 3] },\n  { label: '自由比例', value: [] }\n])\n\nconst fixedNumber = ref([1, 1])\nconst cropWidth = ref(300)\nconst cropHeight = ref(300)\n\nconst fixedRatio = ref(false)\nconst currentRatio = ref(4)\nconst onCurrentRatio = () => {\n  fixedNumber.value = ratioOptions.value[currentRatio.value].value\n  switch (currentRatio.value) {\n    case 0:\n      cropWidth.value = 300\n      cropHeight.value = 300\n      fixedRatio.value = true\n      break\n    case 1:\n      cropWidth.value = 300\n      cropHeight.value = 300 * 9 / 16\n      fixedRatio.value = true\n      break\n    case 2:\n      cropWidth.value = 300 * 9 / 16\n      cropHeight.value = 300\n      fixedRatio.value = true\n      break\n    case 3:\n      cropWidth.value = 300\n      cropHeight.value = 300 * 3 / 4\n      fixedRatio.value = true\n      break\n    default:\n      cropWidth.value = 300\n      cropHeight.value = 300\n      fixedRatio.value = false\n  }\n}\n\n// 文件处理\nconst handleFileChange = (file) => {\n  const isImage = file.raw.type.includes('image')\n  if (!isImage) {\n    ElMessage.error('请选择图片文件')\n    return\n  }\n\n  if (file.raw.size / 1024 / 1024 > 8) {\n    ElMessage.error('文件大小不能超过8MB!')\n    return false\n  }\n\n  const reader = new FileReader()\n  reader.onload = (e) => {\n    imgSrc.value = e.target.result\n    dialogVisible.value = true\n  }\n  reader.readAsDataURL(file.raw)\n}\n\n// 旋转控制\nconst rotate = (degree) => {\n  if (degree === -90) {\n    proxy.$refs.cropperRef.rotateLeft()\n  } else {\n    proxy.$refs.cropperRef.rotateRight()\n  }\n}\n\n// 实时预览\nconst handleRealTime = (data) => {\n  previews.value = data\n  //console.log(data)\n}\n\n// 上传处理\nconst handleUpload = () => {\n  uploading.value = true\n  proxy.$refs.cropperRef.getCropBlob((blob) => {\n    try {\n      const file = new File([blob], `${Date.now()}.jpg`, { type: 'image/jpeg' })\n      uploadRef.value.clearFiles()\n      uploadRef.value.handleStart(file)\n      uploadRef.value.submit()\n\n    } catch (error) {\n      uploading.value = false\n      ElMessage.error('上传失败: ' + error.message)\n    }\n  })\n}\n\nconst handleImageSuccess = (res) => {\n  const { data } = res\n  if (data) {\n    setTimeout(() => {\n      uploading.value = false\n      dialogVisible.value = false\n      previews.value = {}\n      ElMessage.success('上传成功')\n      emit('on-success', data.url)\n    }, 1000)\n  }\n}\n\n</script>\n\n<style scoped>\n:deep(.vue-cropper) {\n  background: transparent;\n}\n</style>\n"
  },
  {
    "path": "web/src/components/upload/image.vue",
    "content": "<template>\n  <div>\n    <el-upload\n      :action=\"`${getBaseUrl()}/fileUploadAndDownload/upload`\"\n      :show-file-list=\"false\"\n      :on-success=\"handleImageSuccess\"\n      :before-upload=\"beforeImageUpload\"\n      :multiple=\"false\"\n      :data=\"{'classId': props.classId}\"\n      :headers=\"{'x-token': token}\"\n    >\n      <el-button type=\"primary\" :icon=\"Upload\">压缩上传</el-button>\n    </el-upload>\n  </div>\n</template>\n\n<script setup>\n  import ImageCompress from '@/utils/image'\n  import { ElMessage } from 'element-plus'\n  import { getBaseUrl } from '@/utils/format'\n  import { Upload } from \"@element-plus/icons-vue\";\n  import { useUserStore } from \"@/pinia\";\n\n  defineOptions({\n    name: 'UploadImage'\n  })\n\n  const emit = defineEmits(['on-success'])\n  const props = defineProps({\n    imageUrl: {\n      type: String,\n      default: ''\n    },\n    fileSize: {\n      type: Number,\n      default: 2048 // 2M 超出后执行压缩\n    },\n    maxWH: {\n      type: Number,\n      default: 1920 // 图片长宽上限\n    },\n    classId: {\n      type: Number,\n      default: 0\n    }\n  })\n\n  const userStore = useUserStore()\n\n  const token = userStore.token\n\n  const beforeImageUpload = (file) => {\n    const isJPG = file.type?.toLowerCase() === 'image/jpeg'\n    const isPng = file.type?.toLowerCase() === 'image/png'\n    if (!isJPG && !isPng) {\n      ElMessage.error('上传头像图片只能是 jpg或png 格式!')\n      return false\n    }\n\n    const isRightSize = file.size / 1024 < props.fileSize\n    if (!isRightSize) {\n      // 压缩\n      const compress = new ImageCompress(file, props.fileSize, props.maxWH)\n      return compress.compress()\n    }\n    return isRightSize\n  }\n\n  const handleImageSuccess = (res) => {\n    const { data } = res\n    if (data.file) {\n      emit('on-success', data.file.url)\n    }\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .image-uploader {\n    border: 1px dashed #d9d9d9;\n    width: 180px;\n    border-radius: 6px;\n    cursor: pointer;\n    position: relative;\n    overflow: hidden;\n  }\n  .image-uploader {\n    border-color: #409eff;\n  }\n  .image-uploader-icon {\n    font-size: 28px;\n    color: #8c939d;\n    width: 178px;\n    height: 178px;\n    line-height: 178px;\n    text-align: center;\n  }\n  .image {\n    width: 178px;\n    height: 178px;\n    display: block;\n  }\n</style>\n"
  },
  {
    "path": "web/src/components/warningBar/warningBar.vue",
    "content": "<template>\n  <div\n    class=\"px-1.5 py-2 flex items-center rounded-sm mt-2 bg-amber-50 gap-2 mb-3 text-amber-500 dark:bg-amber-700 dark:text-gray-200\"\n    :class=\"href && 'cursor-pointer'\"\n    @click=\"open\"\n  >\n    <el-icon class=\"text-xl\">\n      <warning-filled />\n    </el-icon>\n    <span>\n      {{ title }}\n    </span>\n  </div>\n</template>\n<script setup>\n  import { WarningFilled } from '@element-plus/icons-vue'\n  const prop = defineProps({\n    title: {\n      type: String,\n      default: ''\n    },\n    href: {\n      type: String,\n      default: ''\n    }\n  })\n\n  const open = () => {\n    if (prop.href) {\n      window.open(prop.href)\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/core/config.js",
    "content": "/**\n * 网站配置文件\n */\nimport packageInfo from '../../package.json'\n\nconst greenText = (text) => `\\x1b[32m${text}\\x1b[0m`\n\nexport const config = {\n  appName: 'Gin-Vue-Admin',\n  showViteLogo: true,\n  keepAliveTabs: false,\n  logs: []\n}\n\nexport const viteLogo = (env) => {\n  if (config.showViteLogo) {\n    console.log(\n      greenText(\n        `> 欢迎使用Gin-Vue-Admin，开源地址：https://github.com/flipped-aurora/gin-vue-admin`\n      )\n    )\n    console.log(greenText(`> 当前版本:v${packageInfo.version}`))\n    console.log(greenText(`> 加群方式:微信：shouzi_1994 QQ群：470239250`))\n    console.log(\n      greenText(`> 项目地址：https://github.com/flipped-aurora/gin-vue-admin`)\n    )\n    console.log(greenText(`> 插件市场:https://plugin.gin-vue-admin.com`))\n    console.log(\n      greenText(`> GVA讨论社区:https://support.qq.com/products/371961`)\n    )\n    console.log(\n      greenText(\n        `> 默认自动化文档地址:http://127.0.0.1:${env.VITE_SERVER_PORT}/swagger/index.html`\n      )\n    )\n    console.log(\n      greenText(`> 默认前端文件运行地址:http://127.0.0.1:${env.VITE_CLI_PORT}`)\n    )\n    console.log(\n      greenText(\n        `--------------------------------------版权声明--------------------------------------`\n      )\n    )\n    console.log(greenText(`** 版权所有方：flipped-aurora开源团队 **`))\n    console.log(greenText(`** 版权持有公司：北京翻转极光科技有限责任公司 **`))\n    console.log(\n      greenText(\n        `** 剔除授权标识需购买商用授权：https://plugin.gin-vue-admin.com/license **`\n      )\n    )\n    console.log('\\n')\n  }\n}\n\nexport default config\n"
  },
  {
    "path": "web/src/core/error-handel.js",
    "content": "import { createSysError } from '@/api/system/sysError'\n\nfunction sendErrorTip(errorInfo) {\n  setTimeout(() => {\n    const errorData = {\n      form: errorInfo.type,\n      info: `${errorInfo.message}\\nStack: ${errorInfo.stack}${errorInfo.component ? `\\nComponent: ${errorInfo.component.name || 'Unknown'}` : ''}${errorInfo.vueInfo ? `\\nVue Info: ${errorInfo.vueInfo}` : ''}${errorInfo.source ? `\\nSource: ${errorInfo.source}:${errorInfo.lineno}:${errorInfo.colno}` : ''}`,\n      level: 'error',\n      solution: null\n    }\n    \n    createSysError(errorData).catch(apiErr => {\n      console.error('Failed to create error record:', apiErr)\n    })\n  }, 0)\n}\n  \n  window.addEventListener('unhandledrejection', (event) => {\n    sendErrorTip({\n      type: '前端',\n      message: `错误信息: ${event.reason}`,\n      stack: `调用栈: ${event.reason?.stack || '没有调用栈信息'}`,\n    });\n  });\n"
  },
  {
    "path": "web/src/core/gin-vue-admin.js",
    "content": "/*\n * gin-vue-admin web框架组\n *\n * */\n// 加载网站配置文件夹\nimport { register } from './global'\nimport packageInfo from '../../package.json'\n\nexport default {\n  install: (app) => {\n    register(app)\n    console.log(`\n       欢迎使用 Gin-Vue-Admin\n       当前版本:v${packageInfo.version}\n       加群方式:微信：shouzi_1994 QQ群：622360840\n       项目地址：https://github.com/flipped-aurora/gin-vue-admin\n       插件市场:https://plugin.gin-vue-admin.com\n       GVA讨论社区:https://support.qq.com/products/371961\n       默认自动化文档地址:http://127.0.0.1:${import.meta.env.VITE_SERVER_PORT}/swagger/index.html\n       默认前端文件运行地址:http://127.0.0.1:${import.meta.env.VITE_CLI_PORT}\n       如果项目让您获得了收益，希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/coffee/index.html\n       --------------------------------------版权声明--------------------------------------\n       ** 版权所有方：flipped-aurora开源团队 **\n       ** 版权持有公司：北京翻转极光科技有限责任公司 **\n       ** 剔除授权标识需购买商用授权：https://plugin.gin-vue-admin.com/license **\n       ** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**\n    `)\n  }\n}\n"
  },
  {
    "path": "web/src/core/global.js",
    "content": "import config from './config'\nimport { h } from 'vue'\n\n// 统一导入el-icon图标\nimport * as ElIconModules from '@element-plus/icons-vue'\nimport svgIcon from '@/components/svgIcon/svgIcon.vue'\n// 导入转换图标名称的函数\n\nconst createIconComponent = (name) => ({\n  name: 'SvgIcon',\n  render() {\n    return h(svgIcon, {\n      localIcon: name\n    })\n  }\n})\n\nconst registerIcons = async (app) => {\n  const iconModules = import.meta.glob('@/assets/icons/**/*.svg') // 系统目录 svg 图标\n  const pluginIconModules = import.meta.glob(\n    '@/plugin/**/assets/icons/**/*.svg'\n  ) // 插件目录 svg 图标\n  const mergedIconModules = Object.assign({}, iconModules, pluginIconModules) // 合并所有 svg 图标\n  let allKeys = []\n  for (const path in mergedIconModules) {\n    let pluginName = ''\n    if (path.startsWith('/src/plugin/')) {\n      pluginName = `${path.split('/')[3]}-`\n    }\n    const iconName = path\n      .split('/')\n      .pop()\n      .replace(/\\.svg$/, '')\n    // 如果iconName带空格则不加入到图标库中并且提示名称不合法\n    if (iconName.indexOf(' ') !== -1) {\n      console.error(`icon ${iconName}.svg includes whitespace in ${path}`)\n      continue\n    }\n    const key = `${pluginName}${iconName}`\n    const iconComponent = createIconComponent(key)\n    config.logs.push({\n      key: key,\n      label: key\n    })\n    app.component(key, iconComponent)\n\n    // 开发模式下列出所有 svg 图标，方便开发者直接查找复制使用\n    allKeys.push(key)\n  }\n\n  import.meta.env.MODE == 'development' &&\n    console.log(`所有可用的本地图标: ${allKeys.join(', ')}`)\n}\n\nexport const register = (app) => {\n  // 统一注册el-icon图标\n  for (const iconName in ElIconModules) {\n    app.component(iconName, ElIconModules[iconName])\n  }\n  app.component('SvgIcon', svgIcon)\n  registerIcons(app)\n  app.config.globalProperties.$GIN_VUE_ADMIN = config\n}\n"
  },
  {
    "path": "web/src/directive/auth.js",
    "content": "// 权限按钮展示指令\nimport { useUserStore } from '@/pinia/modules/user'\nexport default {\n  install: (app) => {\n    const userStore = useUserStore()\n    app.directive('auth', {\n      // 当被绑定的元素插入到 DOM 中时……\n      mounted: function (el, binding) {\n        const userInfo = userStore.userInfo\n        if (!binding.value){\n          el.parentNode.removeChild(el)\n          return\n        }\n        const waitUse = binding.value.toString().split(',')\n        let flag = waitUse.some((item) => Number(item) === userInfo.authorityId)\n        if (binding.modifiers.not) {\n          flag = !flag\n        }\n        if (!flag) {\n          el.parentNode.removeChild(el)\n        }\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "web/src/directive/clickOutSide.js",
    "content": "export default {\n  install: (app) => {\n    app.directive('click-outside', {\n      mounted(el, binding) {\n        const handler = (e) => {\n          // 如果绑定的元素包含事件目标，或元素已经被移除，则不触发\n          if (!el || el.contains(e.target) || e.target === el) return\n          // 支持函数或对象 { handler: fn, exclude: [el1, el2], capture: true }\n          const value = binding.value\n          if (value && typeof value === 'object') {\n            if (\n              value.exclude &&\n              value.exclude.some(\n                (ex) => ex && ex.contains && ex.contains(e.target)\n              )\n            )\n              return\n            if (typeof value.handler === 'function') value.handler(e)\n          } else if (typeof value === 'function') {\n            value(e)\n          }\n        }\n\n        // 存到 el 上，便于解绑\n        el.__clickOutsideHandler__ = handler\n\n        // 延迟注册，避免 mounted 时触发（比如当点击就是触发绑定动作时）\n        setTimeout(() => {\n          document.addEventListener('mousedown', handler)\n          document.addEventListener('touchstart', handler)\n        }, 0)\n      },\n      unmounted(el) {\n        const h = el.__clickOutsideHandler__\n        if (h) {\n          document.removeEventListener('mousedown', h)\n          document.removeEventListener('touchstart', h)\n          delete el.__clickOutsideHandler__\n        }\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "web/src/hooks/charts.js",
    "content": "// 本组件参考 arco-pro 的实现\n// https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/chart-option.ts\n\nimport { computed } from 'vue'\nimport { useAppStore } from '@/pinia'\n\nexport default function useChartOption(sourceOption) {\n  const appStore = useAppStore()\n  const isDark = computed(() => {\n    return appStore.isDark\n  })\n  const chartOption = computed(() => {\n    return sourceOption(isDark.value)\n  })\n  return {\n    chartOption\n  }\n}\n"
  },
  {
    "path": "web/src/hooks/responsive.js",
    "content": "// 本组件参考 arco-pro 的实现\n// https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/responsive.ts\n\nimport { onMounted, onBeforeMount, onBeforeUnmount } from 'vue'\nimport { useDebounceFn } from '@vueuse/core'\nimport { useAppStore } from '@/pinia'\nimport { addEventListen, removeEventListen } from '@/utils/event'\n\nconst WIDTH = 992\n\nfunction queryDevice() {\n  const rect = document.body.getBoundingClientRect()\n  return rect.width - 1 < WIDTH\n}\n\nexport default function useResponsive(immediate) {\n  const appStore = useAppStore()\n  function resizeHandler() {\n    if (!document.hidden) {\n      const isMobile = queryDevice()\n      appStore.toggleDevice(isMobile ? 'mobile' : 'desktop')\n      // appStore.toggleDevice(isMobile);\n    }\n  }\n  const debounceFn = useDebounceFn(resizeHandler, 100)\n  onMounted(() => {\n    if (immediate) debounceFn()\n  })\n  onBeforeMount(() => {\n    addEventListen(window, 'resize', debounceFn)\n  })\n  onBeforeUnmount(() => {\n    removeEventListen(window, 'resize', debounceFn)\n  })\n}\n"
  },
  {
    "path": "web/src/hooks/use-windows-resize.js",
    "content": "// 监听 window 的 resize 事件，返回当前窗口的宽高\nimport { shallowRef } from 'vue'\nimport { tryOnMounted, useEventListener } from '@vueuse/core'\n\nconst width = shallowRef(0)\nconst height = shallowRef(0)\n\nexport const useWindowResize = (cb) => {\n  const onResize = () => {\n    width.value = window.innerWidth\n    height.value = window.innerHeight\n    if (cb && typeof cb === 'function') {\n      cb(width.value, height.value)\n    }\n  }\n\n  tryOnMounted(onResize)\n  useEventListener('resize', onResize, { passive: true })\n  return {\n    width,\n    height\n  }\n}\n"
  },
  {
    "path": "web/src/main.js",
    "content": "import './style/element_visiable.scss'\nimport 'element-plus/theme-chalk/dark/css-vars.css'\nimport 'uno.css'\nimport { createApp } from 'vue'\nimport ElementPlus from 'element-plus'\nimport { setupVueRootValidator } from 'vite-check-multiple-dom/client';\n\nimport 'element-plus/dist/index.css'\n// 引入gin-vue-admin前端初始化相关内容\nimport './core/gin-vue-admin'\n// 引入封装的router\nimport router from '@/router/index'\nimport '@/permission'\nimport run from '@/core/gin-vue-admin.js'\nimport auth from '@/directive/auth'\nimport clickOutSide from '@/directive/clickOutSide'\nimport { store } from '@/pinia'\nimport App from './App.vue'\nimport '@/core/error-handel'\n\nconst app = createApp(App)\n\napp.config.productionTip = false\n\nsetupVueRootValidator(app, {\n    lang: 'zh'\n  })\n\napp\n  .use(run)\n  .use(ElementPlus)\n  .use(store)\n  .use(auth)\n  .use(clickOutSide)\n  .use(router)\n  .mount('#app')\nexport default app\n"
  },
  {
    "path": "web/src/pathInfo.json",
    "content": "{\n  \"/src/view/about/index.vue\": \"About\",\n  \"/src/view/dashboard/components/banner.vue\": \"Banner\",\n  \"/src/view/dashboard/components/card.vue\": \"Card\",\n  \"/src/view/dashboard/components/charts-content-numbers.vue\": \"ChartsContentNumbers\",\n  \"/src/view/dashboard/components/charts-people-numbers.vue\": \"ChartsPeopleNumbers\",\n  \"/src/view/dashboard/components/charts.vue\": \"Charts\",\n  \"/src/view/dashboard/components/notice.vue\": \"Notice\",\n  \"/src/view/dashboard/components/pluginTable.vue\": \"PluginTable\",\n  \"/src/view/dashboard/components/quickLinks.vue\": \"QuickLinks\",\n  \"/src/view/dashboard/components/table.vue\": \"Table\",\n  \"/src/view/dashboard/components/wiki.vue\": \"Wiki\",\n  \"/src/view/dashboard/index.vue\": \"Dashboard\",\n  \"/src/view/error/index.vue\": \"Error\",\n  \"/src/view/error/reload.vue\": \"Reload\",\n  \"/src/view/example/breakpoint/breakpoint.vue\": \"BreakPoint\",\n  \"/src/view/example/customer/customer.vue\": \"Customer\",\n  \"/src/view/example/index.vue\": \"Example\",\n  \"/src/view/example/upload/scanUpload.vue\": \"scanUpload\",\n  \"/src/view/example/upload/upload.vue\": \"Upload\",\n  \"/src/view/init/index.vue\": \"Init\",\n  \"/src/view/layout/aside/asideComponent/asyncSubmenu.vue\": \"AsyncSubmenu\",\n  \"/src/view/layout/aside/asideComponent/index.vue\": \"AsideComponent\",\n  \"/src/view/layout/aside/asideComponent/menuItem.vue\": \"MenuItem\",\n  \"/src/view/layout/aside/combinationMode.vue\": \"GvaAside\",\n  \"/src/view/layout/aside/headMode.vue\": \"GvaAside\",\n  \"/src/view/layout/aside/index.vue\": \"Index\",\n  \"/src/view/layout/aside/normalMode.vue\": \"GvaAside\",\n  \"/src/view/layout/aside/sidebarMode.vue\": \"SidebarMode\",\n  \"/src/view/layout/header/index.vue\": \"Index\",\n  \"/src/view/layout/header/tools.vue\": \"Tools\",\n  \"/src/view/layout/iframe.vue\": \"GvaLayoutIframe\",\n  \"/src/view/layout/index.vue\": \"GvaLayout\",\n  \"/src/view/layout/screenfull/index.vue\": \"Screenfull\",\n  \"/src/view/layout/search/search.vue\": \"BtnBox\",\n  \"/src/view/layout/setting/components/layoutModeCard.vue\": \"LayoutModeCard\",\n  \"/src/view/layout/setting/components/settingItem.vue\": \"SettingItem\",\n  \"/src/view/layout/setting/components/themeColorPicker.vue\": \"ThemeColorPicker\",\n  \"/src/view/layout/setting/components/themeModeSelector.vue\": \"ThemeModeSelector\",\n  \"/src/view/layout/setting/index.vue\": \"GvaSetting\",\n  \"/src/view/layout/setting/modules/appearance/index.vue\": \"AppearanceSettings\",\n  \"/src/view/layout/setting/modules/general/index.vue\": \"GeneralSettings\",\n  \"/src/view/layout/setting/modules/layout/index.vue\": \"LayoutSettings\",\n  \"/src/view/layout/tabs/index.vue\": \"HistoryComponent\",\n  \"/src/view/login/index.vue\": \"Login\",\n  \"/src/view/person/person.vue\": \"Person\",\n  \"/src/view/routerHolder.vue\": \"RouterHolder\",\n  \"/src/view/superAdmin/api/api.vue\": \"Api\",\n  \"/src/view/superAdmin/authority/authority.vue\": \"Authority\",\n  \"/src/view/superAdmin/authority/components/apis.vue\": \"Apis\",\n  \"/src/view/superAdmin/authority/components/datas.vue\": \"Datas\",\n  \"/src/view/superAdmin/authority/components/menus.vue\": \"Menus\",\n  \"/src/view/superAdmin/dictionary/sysDictionary.vue\": \"SysDictionary\",\n  \"/src/view/superAdmin/dictionary/sysDictionaryDetail.vue\": \"SysDictionaryDetail\",\n  \"/src/view/superAdmin/index.vue\": \"SuperAdmin\",\n  \"/src/view/superAdmin/menu/components/components-cascader.vue\": \"ComponentsCascader\",\n  \"/src/view/superAdmin/menu/icon.vue\": \"Icon\",\n  \"/src/view/superAdmin/menu/menu.vue\": \"Menus\",\n  \"/src/view/superAdmin/operation/sysOperationRecord.vue\": \"SysOperationRecord\",\n  \"/src/view/superAdmin/params/sysParams.vue\": \"SysParams\",\n  \"/src/view/superAdmin/user/user.vue\": \"User\",\n  \"/src/view/system/state.vue\": \"State\",\n  \"/src/view/systemTools/apiToken/index.vue\": \"Index\",\n  \"/src/view/systemTools/autoCode/component/fieldDialog.vue\": \"FieldDialog\",\n  \"/src/view/systemTools/autoCode/component/previewCodeDialog.vue\": \"PreviewCodeDialog\",\n  \"/src/view/systemTools/autoCode/index.vue\": \"AutoCode\",\n  \"/src/view/systemTools/autoCode/mcp.vue\": \"MCP\",\n  \"/src/view/systemTools/autoCode/mcpTest.vue\": \"MCPTest\",\n  \"/src/view/systemTools/autoCode/picture.vue\": \"Picture\",\n  \"/src/view/systemTools/autoCodeAdmin/index.vue\": \"AutoCodeAdmin\",\n  \"/src/view/systemTools/autoPkg/autoPkg.vue\": \"AutoPkg\",\n  \"/src/view/systemTools/exportTemplate/exportTemplate.vue\": \"ExportTemplate\",\n  \"/src/view/systemTools/formCreate/index.vue\": \"FormGenerator\",\n  \"/src/view/systemTools/index.vue\": \"System\",\n  \"/src/view/systemTools/installPlugin/index.vue\": \"Index\",\n  \"/src/view/systemTools/loginLog/index.vue\": \"Index\",\n  \"/src/view/systemTools/pubPlug/pubPlug.vue\": \"PubPlug\",\n  \"/src/view/systemTools/skills/index.vue\": \"Skills\",\n  \"/src/view/systemTools/sysError/sysError.vue\": \"SysError\",\n  \"/src/view/systemTools/system/system.vue\": \"Config\",\n  \"/src/view/systemTools/version/version.vue\": \"SysVersion\",\n  \"/src/plugin/announcement/form/info.vue\": \"InfoForm\",\n  \"/src/plugin/announcement/view/info.vue\": \"Info\",\n  \"/src/plugin/email/view/index.vue\": \"Email\"\n}"
  },
  {
    "path": "web/src/permission.js",
    "content": "import { useUserStore } from '@/pinia/modules/user'\nimport { useRouterStore } from '@/pinia/modules/router'\nimport getPageTitle from '@/utils/page'\nimport router from '@/router'\nimport Nprogress from 'nprogress'\nimport 'nprogress/nprogress.css'\n\n// 配置 NProgress\nNprogress.configure({\n  showSpinner: false,\n  ease: 'ease',\n  speed: 500\n})\n\n// 白名单路由\nconst WHITE_LIST = ['Login', 'Init']\n\nfunction isExternalUrl(val) {\n  return typeof val === 'string' && /^(https?:)?\\/\\//.test(val)\n}\n\n// 工具函数：统一路径归一化\nfunction normalizeAbsolutePath(p) {\n  const s = '/' + String(p || '')\n  return s.replace(/\\/+/g, '/')\n}\n\nfunction normalizeRelativePath(p) {\n  return String(p || '').replace(/^\\/+/, '')\n}\n\n// 安全注册：仅在路由名未存在时注册顶级路由\nfunction addTopLevelIfAbsent(r) {\n  if (!router.hasRoute(r.name)) {\n    router.addRoute(r)\n  }\n}\n\n// 将 n 级菜单扁平化为：\n// - 常规：一级 layout + 二级页面组件\n// - 若某节点 meta.defaultMenu === true：该节点为顶级（不包裹在 layout 下），其子节点作为该顶级的二级页面组件\nfunction addRouteByChildren(route, segments = [], parentName = null) {\n  // 跳过外链根节点\n  if (isExternalUrl(route?.path) || isExternalUrl(route?.name) || isExternalUrl(route?.component)) {\n    return\n  }\n\n  // 顶层 layout 仅用于承载，不参与路径拼接\n  if (route?.name === 'layout') {\n    route.children?.forEach((child) => addRouteByChildren(child, [], null))\n    return\n  }\n\n  // 如果标记为 defaultMenu，则该路由应作为顶级路由（不包裹在 layout 下）\n  if (route?.meta?.defaultMenu === true && parentName === null) {\n    const fullPath = [...segments, route.path].filter(Boolean).join('/')\n    const children = route.children ? [...route.children] : []\n    const newRoute = { ...route, path: fullPath }\n    delete newRoute.children\n    delete newRoute.parent\n    // 顶级路由使用绝对路径\n    newRoute.path = normalizeAbsolutePath(newRoute.path)\n\n    // 若已存在同名路由则整体跳过（之前应已处理过其子节点）\n    if (router.hasRoute(newRoute.name)) return\n    addTopLevelIfAbsent(newRoute)\n\n    // 若该 defaultMenu 节点仍有子节点，继续递归处理其子节点（挂载到该顶级路由下）\n    if (children.length) {\n      // 重置片段，使其成为顶级下的二级相对路径\n      children.forEach((child) => addRouteByChildren(child, [], newRoute.name))\n    }\n    return\n  }\n\n  // 还有子节点，继续向下收集路径片段（忽略外链片段）\n  if (route?.children && route.children.length) {\n    if(!parentName){\n      const firstChild = route.children[0]\n      if (firstChild) {\n         const fullParentPath = [...segments, route.path].filter(Boolean).join('/')\n         const redirectPath = normalizeRelativePath(\n           [fullParentPath, firstChild.path].filter(Boolean).join('/')\n         )\n         const parentRoute = {\n           path: normalizeRelativePath(fullParentPath),\n           name: route.name, // 保留父级名称，以便 defaultRouter 可以指向它\n           meta: route.meta,\n           redirect: \"/layout/\" + redirectPath,\n         }\n         router.addRoute('layout', parentRoute)\n       }\n    }\n    const nextSegments = isExternalUrl(route.path) ? segments : [...segments, route.path]\n    route.children.forEach((child) => addRouteByChildren(child, nextSegments, parentName))\n    return\n  }\n\n  // 叶子节点：注册为其父（defaultMenu 顶级或 layout）的二级子路由\n  const fullPath = [...segments, route.path].filter(Boolean).join('/')\n  const newRoute = { ...route, path: fullPath }\n  delete newRoute.children\n  delete newRoute.parent\n  // 子路由使用相对路径，避免 /layout/layout/... 的问题\n  newRoute.path = normalizeRelativePath(newRoute.path)\n\n  if (parentName) {\n    // 挂载到 defaultMenu 顶级路由下\n    router.addRoute(parentName, newRoute)\n  } else {\n    // 常规：挂载到 layout 下\n    router.addRoute('layout', newRoute)\n  }\n}\n\n// 处理路由加载\nconst setupRouter = async (userStore) => {\n  try {\n    const routerStore = useRouterStore()\n    await Promise.all([routerStore.SetAsyncRouter(), userStore.GetUserInfo()])\n\n    // 确保先注册父级 layout\n    const baseRouters = routerStore.asyncRouters || []\n    const layoutRoute = baseRouters[0]\n    if (layoutRoute?.name === 'layout' && !router.hasRoute('layout')) {\n      const bareLayout = { ...layoutRoute, children: [] }\n      router.addRoute(bareLayout)\n    }\n\n    // 扁平化：将 layout.children 与其余顶层异步路由一并作为二级子路由注册到 layout 下\n    const toRegister = []\n    if (layoutRoute?.children?.length) {\n      toRegister.push(...layoutRoute.children)\n    }\n    if (baseRouters.length > 1) {\n      baseRouters.slice(1).forEach((r) => {\n        if (r?.name !== 'layout') toRegister.push(r)\n      })\n    }\n  toRegister.forEach((r) => addRouteByChildren(r, [], null))\n    return true\n  } catch (error) {\n    console.error('Setup router failed:', error)\n    return false\n  }\n}\n\n// 移除加载动画\nconst removeLoading = () => {\n  const element = document.getElementById('gva-loading-box')\n  element?.remove()\n}\n\n\n// 路由守卫\nrouter.beforeEach(async (to, from) => {\n  const userStore = useUserStore()\n  const routerStore = useRouterStore()\n  const token = userStore.token\n\n  Nprogress.start()\n\n  // 处理元数据和缓存\n  to.meta.matched = [...to.matched]\n  await routerStore.handleKeepAlive(to)\n  // 设置页面标题\n  document.title = getPageTitle(to.meta.title, to)\n  if (to.meta.client) {\n    return true\n  }\n\n  // 白名单路由处理\n  if (WHITE_LIST.includes(to.name)) {\n    if (token) {\n      if(!routerStore.asyncRouterFlag){\n        await setupRouter(userStore)\n      }\n      if(userStore.userInfo.authority.defaultRouter){\n        return { name: userStore.userInfo.authority.defaultRouter }\n      }\n    }\n    return  true\n  }\n\n  // 需要登录的路由处理\n  if (token) {\n    // 处理需要跳转到首页的情况\n    if (sessionStorage.getItem('needToHome') === 'true') {\n      sessionStorage.removeItem('needToHome')\n      return { path: '/' }\n    }\n\n    // 处理异步路由\n    if (!routerStore.asyncRouterFlag && !WHITE_LIST.includes(from.name)) {\n      await setupRouter(userStore)\n      return to\n    }\n\n    return to.matched.length ? true : { path: '/layout/404' }\n  }\n\n  // 未登录跳转登录页\n  return {\n    name: 'Login',\n    query: {\n      redirect: to.fullPath\n    }\n  }\n})\n\n// 路由加载完成\nrouter.afterEach(() => {\n  document.querySelector('.main-cont.main-right')?.scrollTo(0, 0)\n  Nprogress.done()\n})\n\n// 路由错误处理\nrouter.onError((error) => {\n  console.error('Router error:', error)\n  Nprogress.remove()\n})\n\n// 移除初始加载动画\nremoveLoading()\n"
  },
  {
    "path": "web/src/pinia/index.js",
    "content": "import { createPinia } from 'pinia'\nimport { useAppStore } from '@/pinia/modules/app'\nimport { useUserStore } from '@/pinia/modules/user'\nimport { useDictionaryStore } from '@/pinia/modules/dictionary'\n\nconst store = createPinia()\n\nexport { store, useAppStore, useUserStore, useDictionaryStore }\n"
  },
  {
    "path": "web/src/pinia/modules/app.js",
    "content": "import { defineStore } from 'pinia'\nimport { ref, watchEffect, reactive } from 'vue'\nimport { setBodyPrimaryColor } from '@/utils/format'\nimport { useDark, usePreferredDark } from '@vueuse/core'\n\nexport const useAppStore = defineStore('app', () => {\n  const device = ref('')\n  const drawerSize = ref('')\n  const operateMinWith = ref('240')\n  const config = reactive({\n    weakness: false,\n    grey: false,\n    primaryColor: '#3b82f6',\n    showTabs: true,\n    darkMode: 'auto',\n    layout_side_width: 256,\n    layout_side_collapsed_width: 80,\n    layout_side_item_height: 48,\n    show_watermark: true,\n    side_mode: 'normal',\n    // 页面过渡动画配置\n    transition_type: 'slide',\n    global_size: 'default'\n  })\n\n  const isDark = useDark({\n    selector: 'html',\n    attribute: 'class',\n    valueDark: 'dark',\n    valueLight: 'light'\n  })\n\n  const preferredDark = usePreferredDark()\n\n  const toggleTheme = (darkMode) => {\n    isDark.value = darkMode\n  }\n\n  const toggleWeakness = (e) => {\n    config.weakness = e\n  }\n\n  const toggleGrey = (e) => {\n    config.grey = e\n  }\n\n  const togglePrimaryColor = (e) => {\n    config.primaryColor = e\n  }\n\n  const toggleTabs = (e) => {\n    config.showTabs = e\n  }\n\n  const toggleDevice = (e) => {\n    if (e === 'mobile') {\n      drawerSize.value = '100%'\n      operateMinWith.value = '80'\n    } else {\n      drawerSize.value = '800'\n      operateMinWith.value = '240'\n    }\n    device.value = e\n  }\n\n  const toggleDarkMode = (e) => {\n    config.darkMode = e\n  }\n\n  // 监听系统主题变化\n  watchEffect(() => {\n    if (config.darkMode === 'auto') {\n      isDark.value = preferredDark.value\n      return\n    }\n    isDark.value = config.darkMode === 'dark'\n  })\n\n  const toggleConfigSideWidth = (e) => {\n    config.layout_side_width = e\n  }\n\n  const toggleConfigSideCollapsedWidth = (e) => {\n    config.layout_side_collapsed_width = e\n  }\n\n  const toggleConfigSideItemHeight = (e) => {\n    config.layout_side_item_height = e\n  }\n\n  const toggleConfigWatermark = (e) => {\n    config.show_watermark = e\n  }\n\n  const toggleSideMode = (e) => {\n    config.side_mode = e\n  }\n\n  const toggleTransition = (e) => {\n    config.transition_type = e\n  }\n\n  const toggleGlobalSize = (e) => {\n    config.global_size = e\n  }\n\n  const baseCoinfg = {\n    weakness: false,\n    grey: false,\n    primaryColor: '#3b82f6',\n    showTabs: true,\n    darkMode: 'auto',\n    layout_side_width: 256,\n    layout_side_collapsed_width: 80,\n    layout_side_item_height: 48,\n    show_watermark: true,\n    side_mode: 'normal',\n    // 页面过渡动画配置\n    transition_type: 'slide',\n    global_size: 'default'\n  }\n\n  const resetConfig = () => {\n    for (let baseCoinfgKey in baseCoinfg) {\n      config[baseCoinfgKey] = baseCoinfg[baseCoinfgKey]\n    }\n  }\n\n  // 监听色弱模式和灰色模式\n  watchEffect(() => {\n    document.documentElement.classList.toggle('html-weakenss', config.weakness)\n    document.documentElement.classList.toggle('html-grey', config.grey)\n  })\n\n  // 监听主题色\n  watchEffect(() => {\n    setBodyPrimaryColor(config.primaryColor, isDark.value ? 'dark' : 'light')\n  })\n\n  return {\n    isDark,\n    device,\n    drawerSize,\n    operateMinWith,\n    config,\n    toggleTheme,\n    toggleDevice,\n    toggleWeakness,\n    toggleGrey,\n    togglePrimaryColor,\n    toggleTabs,\n    toggleDarkMode,\n    toggleConfigSideWidth,\n    toggleConfigSideCollapsedWidth,\n    toggleConfigSideItemHeight,\n    toggleConfigWatermark,\n    toggleSideMode,\n    toggleTransition,\n    resetConfig,\n    toggleGlobalSize\n  }\n})\n"
  },
  {
    "path": "web/src/pinia/modules/dictionary.js",
    "content": "import { findSysDictionary } from '@/api/sysDictionary'\nimport { getDictionaryTreeListByType } from '@/api/sysDictionaryDetail'\n\nimport { defineStore } from 'pinia'\nimport { ref } from 'vue'\n\nexport const useDictionaryStore = defineStore('dictionary', () => {\n  const dictionaryMap = ref({})\n\n  const setDictionaryMap = (dictionaryRes) => {\n    dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes }\n  }\n\n  // 过滤树形数据的深度\n  const filterTreeByDepth = (items, currentDepth, targetDepth) => {\n    if (targetDepth === 0) {\n      // depth=0 返回全部数据\n      return items\n    }\n\n    if (currentDepth >= targetDepth) {\n      // 达到目标深度，移除children\n      return items.map((item) => ({\n        label: item.label,\n        value: item.value,\n        extend: item.extend\n      }))\n    }\n\n    // 递归处理子项\n    return items.map((item) => ({\n      label: item.label,\n      value: item.value,\n      extend: item.extend,\n      children: item.children\n        ? filterTreeByDepth(item.children, currentDepth + 1, targetDepth)\n        : undefined\n    }))\n  }\n\n  // 将树形结构扁平化为数组（用于兼容原有的平铺格式）\n  const flattenTree = (items) => {\n    const result = []\n\n    const traverse = (nodes) => {\n      nodes.forEach((item) => {\n        result.push({\n          label: item.label,\n          value: item.value,\n          extend: item.extend\n        })\n\n        if (item.children && item.children.length > 0) {\n          traverse(item.children)\n        }\n      })\n    }\n\n    traverse(items)\n    return result\n  }\n\n  // 标准化树形数据，确保每个节点都包含标准的字段格式\n  const normalizeTreeData = (items) => {\n    return items.map((item) => ({\n      label: item.label,\n      value: item.value,\n      extend: item.extend,\n      children:\n        item.children && item.children.length > 0\n          ? normalizeTreeData(item.children)\n          : undefined\n    }))\n  }\n\n  // 根据value和depth查找指定节点并返回其children\n  const findNodeByValue = (\n    items,\n    targetValue,\n    currentDepth = 1,\n    maxDepth = 0\n  ) => {\n    for (const item of items) {\n      // 如果找到目标value的节点\n      if (item.value === targetValue) {\n        // 如果maxDepth为0，返回所有children\n        if (maxDepth === 0) {\n          return item.children ? normalizeTreeData(item.children) : []\n        }\n        // 否则根据depth限制返回children\n        if (item.children && item.children.length > 0) {\n          return filterTreeByDepth(item.children, 1, maxDepth)\n        }\n        return []\n      }\n\n      // 如果当前深度小于最大深度，继续在children中查找\n      if (\n        item.children &&\n        item.children.length > 0 &&\n        (maxDepth === 0 || currentDepth < maxDepth)\n      ) {\n        const result = findNodeByValue(\n          item.children,\n          targetValue,\n          currentDepth + 1,\n          maxDepth\n        )\n        if (result !== null) {\n          return result\n        }\n      }\n    }\n    return null\n  }\n\n  const getDictionary = async (type, depth = 0, value = null) => {\n    // 如果传入了value参数，则查找指定节点的children\n    if (value !== null) {\n      // 构建缓存key，包含value和depth信息\n      const cacheKey = `${type}_value_${value}_depth_${depth}`\n\n      if (\n        dictionaryMap.value[cacheKey] &&\n        dictionaryMap.value[cacheKey].length\n      ) {\n        return dictionaryMap.value[cacheKey]\n      }\n\n      try {\n        // 获取完整的树形结构数据\n        const treeRes = await getDictionaryTreeListByType({ type })\n        if (\n          treeRes.code === 0 &&\n          treeRes.data &&\n          treeRes.data.list &&\n          treeRes.data.list.length > 0\n        ) {\n          // 查找指定value的节点并返回其children\n          const targetNodeChildren = findNodeByValue(\n            treeRes.data.list,\n            value,\n            1,\n            depth\n          )\n\n          if (targetNodeChildren !== null) {\n            let resultData\n            if (depth === 0) {\n              // depth=0 时返回完整的children树形结构\n              resultData = targetNodeChildren\n            } else {\n              // 其他depth值：扁平化children数据\n              resultData = flattenTree(targetNodeChildren)\n            }\n\n            const dictionaryRes = {}\n            dictionaryRes[cacheKey] = resultData\n            setDictionaryMap(dictionaryRes)\n            return dictionaryMap.value[cacheKey]\n          } else {\n            // 如果没找到指定value的节点，返回空数组\n            return []\n          }\n        }\n      } catch (error) {\n        console.error('根据value获取字典数据失败:', error)\n        return []\n      }\n    }\n\n    // 原有的逻辑：不传value参数时的处理\n    // 构建缓存key，包含depth信息\n    const cacheKey = depth === 0 ? `${type}_tree` : `${type}_depth_${depth}`\n\n    if (dictionaryMap.value[cacheKey] && dictionaryMap.value[cacheKey].length) {\n      return dictionaryMap.value[cacheKey]\n    } else {\n      try {\n        // 首先尝试获取树形结构数据\n        const treeRes = await getDictionaryTreeListByType({ type })\n        if (\n          treeRes.code === 0 &&\n          treeRes.data &&\n          treeRes.data.list &&\n          treeRes.data.list.length > 0\n        ) {\n          // 使用树形结构数据\n          const treeData = treeRes.data.list\n\n          let resultData\n          if (depth === 0) {\n            // depth=0 时返回完整的树形结构，但要确保字段格式标准化\n            resultData = normalizeTreeData(treeData)\n          } else {\n            // 其他depth值：根据depth参数过滤数据，然后扁平化\n            const filteredData = filterTreeByDepth(treeData, 1, depth)\n            resultData = flattenTree(filteredData)\n          }\n\n          const dictionaryRes = {}\n          dictionaryRes[cacheKey] = resultData\n          setDictionaryMap(dictionaryRes)\n          return dictionaryMap.value[cacheKey]\n        } else {\n          // 如果没有树形数据，回退到原有的平铺方式\n          const res = await findSysDictionary({ type })\n          if (res.code === 0) {\n            const dictionaryRes = {}\n            const dict = []\n            res.data.resysDictionary.sysDictionaryDetails &&\n              res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {\n                dict.push({\n                  label: item.label,\n                  value: item.value,\n                  extend: item.extend\n                })\n              })\n            dictionaryRes[cacheKey] = dict\n            setDictionaryMap(dictionaryRes)\n            return dictionaryMap.value[cacheKey]\n          }\n        }\n      } catch (error) {\n        console.error('获取字典数据失败:', error)\n        // 发生错误时回退到原有方式\n        const res = await findSysDictionary({ type })\n        if (res.code === 0) {\n          const dictionaryRes = {}\n          const dict = []\n          res.data.resysDictionary.sysDictionaryDetails &&\n            res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {\n              dict.push({\n                label: item.label,\n                value: item.value,\n                extend: item.extend\n              })\n            })\n          dictionaryRes[cacheKey] = dict\n          setDictionaryMap(dictionaryRes)\n          return dictionaryMap.value[cacheKey]\n        }\n      }\n    }\n  }\n\n  return {\n    dictionaryMap,\n    setDictionaryMap,\n    getDictionary\n  }\n})\n"
  },
  {
    "path": "web/src/pinia/modules/params.js",
    "content": "import { getSysParam } from '@/api/sysParams'\nimport { defineStore } from 'pinia'\nimport { ref } from 'vue'\n\nexport const useParamsStore = defineStore('params', () => {\n    const paramsMap = ref({})\n\n    const setParamsMap = (paramsRes) => {\n        paramsMap.value = { ...paramsMap.value, ...paramsRes }\n    }\n\n    const getParams = async(key) => {\n        if (paramsMap.value[key] && paramsMap.value[key].length) {\n            return paramsMap.value[key]\n        } else {\n            const res = await getSysParam({ key })\n            if (res.code === 0) {\n                const paramsRes = {}\n                paramsRes[key] = res.data.value\n                setParamsMap(paramsRes)\n                return paramsMap.value[key]\n            }\n        }\n    }\n\n    return {\n        paramsMap,\n        setParamsMap,\n        getParams\n    }\n})\n"
  },
  {
    "path": "web/src/pinia/modules/router.js",
    "content": "import { asyncRouterHandle } from '@/utils/asyncRouter'\nimport { emitter } from '@/utils/bus.js'\nimport { asyncMenu } from '@/api/menu'\nimport { defineStore } from 'pinia'\nimport { ref, watchEffect } from 'vue'\nimport pathInfo from '@/pathInfo.json'\nimport {useRoute} from \"vue-router\";\nimport {config} from \"@/core/config.js\";\n\nconst notLayoutRouterArr = []\nconst keepAliveRoutersArr = []\nconst nameMap = {}\n\nconst formatRouter = (routes, routeMap, parent) => {\n  routes &&\n    routes.forEach((item) => {\n      item.parent = parent\n      item.meta.btns = item.btns\n      item.meta.hidden = item.hidden\n      if (item.meta.defaultMenu === true) {\n        if (!parent) {\n          item = { ...item, path: `/${item.path}` }\n          notLayoutRouterArr.push(item)\n        }\n      }\n      routeMap[item.name] = item\n      if (item.children && item.children.length > 0) {\n        formatRouter(item.children, routeMap, item)\n      }\n    })\n}\n\nconst KeepAliveFilter = (routes) => {\n  routes &&\n    routes.forEach((item) => {\n      // 子菜单中有 keep-alive 的，父菜单也必须 keep-alive，否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。\n      if (\n        (item.children && item.children.some((ch) => ch.meta.keepAlive)) ||\n        item.meta.keepAlive\n      ) {\n        const path = item.meta.path\n        keepAliveRoutersArr.push(pathInfo[path])\n        nameMap[item.name] = pathInfo[path]\n      }\n      if (item.children && item.children.length > 0) {\n        KeepAliveFilter(item.children)\n      }\n    })\n}\n\nexport const useRouterStore = defineStore('router', () => {\n  const keepAliveRouters = ref([])\n  const asyncRouterFlag = ref(0)\n  const setKeepAliveRouters = (history) => {\n    const keepArrTemp = []\n    \n    // 1. 首先添加原有的keepAlive配置\n    keepArrTemp.push(...keepAliveRoutersArr)\n    if (config.keepAliveTabs) {\n      history.forEach((item) => {\n        // 2. 为所有history中的路由强制启用keep-alive\n        // 通过routeMap获取路由信息，然后通过pathInfo获取组件名\n        const routeInfo = routeMap[item.name]\n        if (routeInfo && routeInfo.meta && routeInfo.meta.path) {\n          const componentName = pathInfo[routeInfo.meta.path]\n          if (componentName) {\n            keepArrTemp.push(componentName)\n          }\n        }\n        \n        // 3. 如果子路由在tabs中打开，父路由也需要keepAlive\n        if (nameMap[item.name]) {\n          keepArrTemp.push(nameMap[item.name])\n        }\n      })\n    }\n    keepAliveRouters.value = Array.from(new Set(keepArrTemp))\n  }\n\n  // 处理组件缓存\n  const handleKeepAlive = async (to) => {\n    if (!to.matched.some((item) => item.meta.keepAlive)) return\n\n    if (to.matched?.length > 2) {\n      for (let i = 1; i < to.matched.length; i++) {\n        const element = to.matched[i - 1]\n\n        if (element.name === 'layout') {\n          to.matched.splice(i, 1)\n          await handleKeepAlive(to)\n          continue\n        }\n\n        if (typeof element.components.default === 'function') {\n          await element.components.default()\n          await handleKeepAlive(to)\n        }\n      }\n    }\n  }\n\n\n  const route = useRoute()\n\n  emitter.on('setKeepAlive', setKeepAliveRouters)\n\n  const asyncRouters = ref([])\n\n  const topMenu = ref([])\n\n  const leftMenu = ref([])\n\n  const menuMap = {}\n\n  const topActive = ref('')\n\n  const setLeftMenu = (name) => {\n    sessionStorage.setItem('topActive', name)\n    topActive.value = name\n    leftMenu.value = []\n    if (menuMap[name]?.children) {\n      leftMenu.value = menuMap[name].children\n    }\n    return menuMap[name]?.children\n  }\n\n  const findTopActive = (menuMap, routeName) => {\n    for (let topName in menuMap) {\n      const topItem = menuMap[topName];\n      if (topItem.children?.some(item => item.name === routeName)) {\n        return topName;\n      }\n      const foundName = findTopActive(topItem.children || {}, routeName);\n      if (foundName) {\n        return topName;\n      }\n    }\n    return null;\n  };\n\n  watchEffect(() => {\n    let topActive = sessionStorage.getItem('topActive')\n    // 初始化菜单内容，防止重复添加\n    topMenu.value = [];\n    asyncRouters.value[0]?.children.forEach((item) => {\n      if (item.hidden) return\n      menuMap[item.name] = item\n      topMenu.value.push({ ...item, children: [] })\n    })\n    if (!topActive || topActive === 'undefined' || topActive === 'null') {\n      topActive = findTopActive(menuMap, route.name);\n    }\n    setLeftMenu(topActive)\n  })\n\n  const routeMap = {}\n  // 从后台获取动态路由\n  const SetAsyncRouter = async () => {\n    asyncRouterFlag.value++\n    const baseRouter = [\n      {\n        path: '/layout',\n        name: 'layout',\n        component: 'view/layout/index.vue',\n        meta: {\n          title: '底层layout'\n        },\n        children: []\n      }\n    ]\n    const asyncRouterRes = await asyncMenu()\n    const asyncRouter = asyncRouterRes.data.menus\n    asyncRouter &&\n      asyncRouter.push({\n        path: 'reload',\n        name: 'Reload',\n        hidden: true,\n        meta: {\n          title: '',\n          closeTab: true\n        },\n        component: 'view/error/reload.vue'\n      })\n    formatRouter(asyncRouter, routeMap)\n    baseRouter[0].children = asyncRouter\n    if (notLayoutRouterArr.length !== 0) {\n      baseRouter.push(...notLayoutRouterArr)\n    }\n    asyncRouterHandle(baseRouter)\n    KeepAliveFilter(asyncRouter)\n    asyncRouters.value = baseRouter\n    return true\n  }\n\n  return {\n    topActive,\n    setLeftMenu,\n    topMenu,\n    leftMenu,\n    asyncRouters,\n    keepAliveRouters,\n    asyncRouterFlag,\n    SetAsyncRouter,\n    routeMap,\n    handleKeepAlive\n  }\n})\n"
  },
  {
    "path": "web/src/pinia/modules/user.js",
    "content": "import { login, getUserInfo } from '@/api/user'\nimport { jsonInBlacklist } from '@/api/jwt'\nimport router from '@/router/index'\nimport { ElLoading, ElMessage } from 'element-plus'\nimport { defineStore } from 'pinia'\nimport { ref, computed } from 'vue'\nimport { useRouterStore } from './router'\nimport { useCookies } from '@vueuse/integrations/useCookies'\nimport { useStorage } from '@vueuse/core'\n\nimport { useAppStore } from '@/pinia'\n\nexport const useUserStore = defineStore('user', () => {\n  const appStore = useAppStore()\n  const loadingInstance = ref(null)\n\n  const userInfo = ref({\n    uuid: '',\n    nickName: '',\n    headerImg: '',\n    authority: {}\n  })\n  const token = useStorage('token', '')\n  const xToken = useCookies()\n  const currentToken = computed(() => token.value || xToken.get('x-token') || '')\n\n  const setUserInfo = (val) => {\n    userInfo.value = val\n    if (val.originSetting) {\n      Object.keys(appStore.config).forEach((key) => {\n        if (val.originSetting[key] !== undefined) {\n          appStore.config[key] = val.originSetting[key]\n        }\n      })\n    }\n  }\n\n  const setToken = (val) => {\n    token.value = val\n    xToken.value = val\n  }\n\n  const NeedInit = async () => {\n    await ClearStorage()\n    await router.push({ name: 'Init', replace: true })\n  }\n\n  const ResetUserInfo = (value = {}) => {\n    userInfo.value = {\n      ...userInfo.value,\n      ...value\n    }\n  }\n  /* 获取用户信息*/\n  const GetUserInfo = async () => {\n    const res = await getUserInfo()\n    if (res.code === 0) {\n      setUserInfo(res.data.userInfo)\n    }\n    return res\n  }\n  /* 登录*/\n  const LoginIn = async (loginInfo) => {\n    try {\n      loadingInstance.value = ElLoading.service({\n        fullscreen: true,\n        text: '登录中，请稍候...'\n      })\n\n      const res = await login(loginInfo)\n\n      if (res.code !== 0) {\n        return false\n      }\n      // 登陆成功，设置用户信息和权限相关信息\n      setUserInfo(res.data.user)\n      setToken(res.data.token)\n\n      // 初始化路由信息\n      const routerStore = useRouterStore()\n      await routerStore.SetAsyncRouter()\n      const asyncRouters = routerStore.asyncRouters\n\n      // 注册到路由表里\n      asyncRouters.forEach((asyncRouter) => {\n        router.addRoute(asyncRouter)\n      })\n\n      if(router.currentRoute.value.query.redirect) {\n        await router.replace(router.currentRoute.value.query.redirect)\n        return true\n      }\n\n      if (!router.hasRoute(userInfo.value.authority.defaultRouter)) {\n        ElMessage.error('不存在可以登陆的首页，请联系管理员进行配置')\n      } else {\n        await router.replace({ name: userInfo.value.authority.defaultRouter })\n      }\n\n      const isWindows = /windows/i.test(navigator.userAgent)\n      window.localStorage.setItem('osType', isWindows ? 'WIN' : 'MAC')\n\n      // 全部操作均结束，关闭loading并返回\n      return true\n    } catch (error) {\n      console.error('LoginIn error:', error)\n      return false\n    } finally {\n      loadingInstance.value?.close()\n    }\n  }\n  /* 登出*/\n  const LoginOut = async () => {\n    const res = await jsonInBlacklist()\n\n    // 登出失败\n    if (res.code !== 0) {\n      return\n    }\n\n    await ClearStorage()\n\n    // 把路由定向到登录页，无需等待直接reload\n    router.push({ name: 'Login', replace: true })\n    window.location.reload()\n  }\n  /* 清理数据 */\n  const ClearStorage = async () => {\n    token.value = ''\n    // 使用remove方法正确删除cookie\n    xToken.remove()\n    sessionStorage.clear()\n    // 清理所有相关的localStorage项\n    localStorage.removeItem('originSetting')\n    localStorage.removeItem('token')\n  }\n\n  return {\n    userInfo,\n    token: currentToken,\n    NeedInit,\n    ResetUserInfo,\n    GetUserInfo,\n    LoginIn,\n    LoginOut,\n    setToken,\n    loadingInstance,\n    ClearStorage\n  }\n})\n"
  },
  {
    "path": "web/src/plugin/announcement/api/info.js",
    "content": "import service from '@/utils/request'\n\n// @Tags Info\n// @Summary 创建公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.Info true \"创建公告\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"创建成功\"}\"\n// @Router /info/createInfo [post]\nexport const createInfo = (data) => {\n  return service({\n    url: '/info/createInfo',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags Info\n// @Summary 删除公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.Info true \"删除公告\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /info/deleteInfo [delete]\nexport const deleteInfo = (params) => {\n  return service({\n    url: '/info/deleteInfo',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags Info\n// @Summary 批量删除公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body request.IdsReq true \"批量删除公告\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"删除成功\"}\"\n// @Router /info/deleteInfo [delete]\nexport const deleteInfoByIds = (params) => {\n  return service({\n    url: '/info/deleteInfoByIds',\n    method: 'delete',\n    params\n  })\n}\n\n// @Tags Info\n// @Summary 更新公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data body model.Info true \"更新公告\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"更新成功\"}\"\n// @Router /info/updateInfo [put]\nexport const updateInfo = (data) => {\n  return service({\n    url: '/info/updateInfo',\n    method: 'put',\n    data\n  })\n}\n\n// @Tags Info\n// @Summary 用id查询公告\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query model.Info true \"用id查询公告\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /info/findInfo [get]\nexport const findInfo = (params) => {\n  return service({\n    url: '/info/findInfo',\n    method: 'get',\n    params\n  })\n}\n\n// @Tags Info\n// @Summary 分页获取公告列表\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Param data query request.PageInfo true \"分页获取公告列表\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"获取成功\"}\"\n// @Router /info/getInfoList [get]\nexport const getInfoList = (params) => {\n  return service({\n    url: '/info/getInfoList',\n    method: 'get',\n    params\n  })\n}\n// @Tags Info\n// @Summary 获取数据源\n// @Security ApiKeyAuth\n// @accept application/json\n// @Produce application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"查询成功\"}\"\n// @Router /info/findInfoDataSource [get]\nexport const getInfoDataSource = () => {\n  return service({\n    url: '/info/getInfoDataSource',\n    method: 'get'\n  })\n}\n"
  },
  {
    "path": "web/src/plugin/announcement/form/info.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-form-box\">\n      <el-form\n        :model=\"formData\"\n        ref=\"elFormRef\"\n        label-position=\"right\"\n        :rules=\"rule\"\n        label-width=\"80px\"\n      >\n        <el-form-item label=\"标题:\" prop=\"title\">\n          <el-input\n            v-model=\"formData.title\"\n            :clearable=\"true\"\n            placeholder=\"请输入标题\"\n          />\n        </el-form-item>\n        <el-form-item label=\"内容:\" prop=\"content\">\n          <RichEdit v-model=\"formData.content\" />\n        </el-form-item>\n        <el-form-item label=\"作者:\" prop=\"userID\">\n          <el-select\n            v-model=\"formData.userID\"\n            placeholder=\"请选择作者\"\n            style=\"width: 100%\"\n            :clearable=\"true\"\n          >\n            <el-option\n              v-for=\"(item, key) in dataSource.userID\"\n              :key=\"key\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"附件:\" prop=\"attachments\">\n          <SelectFile v-model=\"formData.attachments\" />\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" @click=\"save\">保存</el-button>\n          <el-button type=\"primary\" @click=\"back\">返回</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getInfoDataSource,\n    createInfo,\n    updateInfo,\n    findInfo\n  } from '@/plugin/announcement/api/info'\n\n  defineOptions({\n    name: 'InfoForm'\n  })\n\n  // 自动获取字典\n  import { useRoute, useRouter } from 'vue-router'\n  import { ElMessage } from 'element-plus'\n  import { ref, reactive } from 'vue'\n  import SelectFile from '@/components/selectFile/selectFile.vue'\n  // 富文本组件\n  import RichEdit from '@/components/richtext/rich-edit.vue'\n\n  const route = useRoute()\n  const router = useRouter()\n\n  const type = ref('')\n  const formData = ref({\n    title: '',\n    content: '',\n    userID: undefined,\n    attachments: []\n  })\n  // 验证规则\n  const rule = reactive({})\n\n  const elFormRef = ref()\n  const dataSource = ref([])\n  const getDataSourceFunc = async () => {\n    const res = await getInfoDataSource()\n    if (res.code === 0) {\n      dataSource.value = res.data\n    }\n  }\n  getDataSourceFunc()\n\n  // 初始化方法\n  const init = async () => {\n    // 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例\n    if (route.query.id) {\n      const res = await findInfo({ ID: route.query.id })\n      if (res.code === 0) {\n        formData.value = res.data\n        type.value = 'update'\n      }\n    } else {\n      type.value = 'create'\n    }\n  }\n\n  init()\n  // 保存按钮\n  const save = async () => {\n    elFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      let res\n      switch (type.value) {\n        case 'create':\n          res = await createInfo(formData.value)\n          break\n        case 'update':\n          res = await updateInfo(formData.value)\n          break\n        default:\n          res = await createInfo(formData.value)\n          break\n      }\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '创建/更改成功'\n        })\n      }\n    })\n  }\n\n  // 返回按钮\n  const back = () => {\n    router.go(-1)\n  }\n</script>\n\n<style></style>\n"
  },
  {
    "path": "web/src/plugin/announcement/view/info.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form\n        ref=\"elSearchFormRef\"\n        :inline=\"true\"\n        :model=\"searchInfo\"\n        class=\"demo-form-inline\"\n        :rules=\"searchRule\"\n        @keyup.enter=\"onSubmit\"\n      >\n        <el-form-item label=\"创建日期\" prop=\"createdAt\">\n          <template #label>\n            <span>\n              创建日期\n              <el-tooltip\n                content=\"搜索范围是开始日期（包含）至结束日期（不包含）\"\n              >\n                <el-icon><QuestionFilled /></el-icon>\n              </el-tooltip>\n            </span>\n          </template>\n          <el-date-picker\n            v-model=\"searchInfo.startCreatedAt\"\n            type=\"datetime\"\n            placeholder=\"开始日期\"\n            :disabled-date=\"\n              (time) =>\n                searchInfo.endCreatedAt\n                  ? time.getTime() > searchInfo.endCreatedAt.getTime()\n                  : false\n            \"\n          />\n          —\n          <el-date-picker\n            v-model=\"searchInfo.endCreatedAt\"\n            type=\"datetime\"\n            placeholder=\"结束日期\"\n            :disabled-date=\"\n              (time) =>\n                searchInfo.startCreatedAt\n                  ? time.getTime() < searchInfo.startCreatedAt.getTime()\n                  : false\n            \"\n          />\n        </el-form-item>\n\n        <template v-if=\"showAllQuery\">\n          <!-- 将需要控制显示状态的查询条件添加到此范围内 -->\n        </template>\n\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">\n            查询\n          </el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\"> 重置 </el-button>\n          <el-button\n            v-if=\"!showAllQuery\"\n            link\n            type=\"primary\"\n            icon=\"arrow-down\"\n            @click=\"showAllQuery = true\"\n          >\n            展开\n          </el-button>\n          <el-button\n            v-else\n            link\n            type=\"primary\"\n            icon=\"arrow-up\"\n            @click=\"showAllQuery = false\"\n          >\n            收起\n          </el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDialog\">\n          新增\n        </el-button>\n        <el-button\n          icon=\"delete\"\n          style=\"margin-left: 10px\"\n          :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\"\n        >\n          删除\n        </el-button>\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :data=\"tableData\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n\n        <el-table-column align=\"left\" label=\"日期\" prop=\"CreatedAt\" width=\"180\">\n          <template #default=\"scope\">\n            {{ formatDate(scope.row.CreatedAt) }}\n          </template>\n        </el-table-column>\n\n        <el-table-column align=\"left\" label=\"标题\" prop=\"title\" width=\"120\" />\n        <el-table-column align=\"left\" label=\"作者\" prop=\"userID\" width=\"120\">\n          <template #default=\"scope\">\n            <span>{{\n              filterDataSource(dataSource.userID, scope.row.userID)\n            }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"附件\" prop=\"attachments\" width=\"200\">\n          <template #default=\"scope\">\n            <div class=\"file-list\">\n              <el-tag\n                v-for=\"file in scope.row.attachments\"\n                :key=\"file.uid\"\n                @click=\"downloadFile(file.url)\"\n              >\n                {{ file.name }}\n              </el-tag>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"操作\"\n          fixed=\"right\"\n          min-width=\"240\"\n        >\n          <template #default=\"scope\">\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              class=\"table-button\"\n              @click=\"updateInfoFunc(scope.row)\"\n            >\n              变更\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteRow(scope.row)\"\n            >\n              删除\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n    <el-drawer\n      v-model=\"dialogFormVisible\"\n      destroy-on-close\n      size=\"800\"\n      :show-close=\"false\"\n      :before-close=\"closeDialog\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{ type === 'create' ? '添加' : '修改' }}</span>\n          <div>\n            <el-button type=\"primary\" @click=\"enterDialog\"> 确 定 </el-button>\n            <el-button @click=\"closeDialog\"> 取 消 </el-button>\n          </div>\n        </div>\n      </template>\n\n      <el-form\n        ref=\"elFormRef\"\n        :model=\"formData\"\n        label-position=\"top\"\n        :rules=\"rule\"\n        label-width=\"80px\"\n      >\n        <el-form-item label=\"标题:\" prop=\"title\">\n          <el-input\n            v-model=\"formData.title\"\n            :clearable=\"true\"\n            placeholder=\"请输入标题\"\n          />\n        </el-form-item>\n        <el-form-item label=\"内容:\" prop=\"content\">\n          <RichEdit v-model=\"formData.content\" />\n        </el-form-item>\n        <el-form-item label=\"作者:\" prop=\"userID\">\n          <el-select\n            v-model=\"formData.userID\"\n            placeholder=\"请选择作者\"\n            style=\"width: 100%\"\n            :clearable=\"true\"\n          >\n            <el-option\n              v-for=\"(item, key) in dataSource.userID\"\n              :key=\"key\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"附件:\" prop=\"attachments\">\n          <SelectFile v-model=\"formData.attachments\" />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getInfoDataSource,\n    createInfo,\n    deleteInfo,\n    deleteInfoByIds,\n    updateInfo,\n    findInfo,\n    getInfoList\n  } from '@/plugin/announcement/api/info'\n  import { getUrl } from '@/utils/image'\n  // 富文本组件\n  import RichEdit from '@/components/richtext/rich-edit.vue'\n  // 文件选择组件\n  import SelectFile from '@/components/selectFile/selectFile.vue'\n\n  // 全量引入格式化工具 请按需保留\n  import { formatDate, filterDataSource } from '@/utils/format'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { ref, reactive } from 'vue'\n\n  defineOptions({\n    name: 'Info'\n  })\n\n  // 控制更多查询条件显示/隐藏状态\n  const showAllQuery = ref(false)\n\n  // 自动化生成的字典（可能为空）以及字段\n  const formData = ref({\n    title: '',\n    content: '',\n    userID: undefined,\n    attachments: []\n  })\n  const dataSource = ref([])\n  const getDataSourceFunc = async () => {\n    const res = await getInfoDataSource()\n    if (res.code === 0) {\n      dataSource.value = res.data\n    }\n  }\n  getDataSourceFunc()\n\n  // 验证规则\n  const rule = reactive({})\n\n  const searchRule = reactive({\n    createdAt: [\n      {\n        validator: (rule, value, callback) => {\n          if (\n            searchInfo.value.startCreatedAt &&\n            !searchInfo.value.endCreatedAt\n          ) {\n            callback(new Error('请填写结束日期'))\n          } else if (\n            !searchInfo.value.startCreatedAt &&\n            searchInfo.value.endCreatedAt\n          ) {\n            callback(new Error('请填写开始日期'))\n          } else if (\n            searchInfo.value.startCreatedAt &&\n            searchInfo.value.endCreatedAt &&\n            (searchInfo.value.startCreatedAt.getTime() ===\n              searchInfo.value.endCreatedAt.getTime() ||\n              searchInfo.value.startCreatedAt.getTime() >\n                searchInfo.value.endCreatedAt.getTime())\n          ) {\n            callback(new Error('开始日期应当早于结束日期'))\n          } else {\n            callback()\n          }\n        },\n        trigger: 'change'\n      }\n    ]\n  })\n\n  const elFormRef = ref()\n  const elSearchFormRef = ref()\n\n  // =========== 表格控制部分 ===========\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const searchInfo = ref({})\n\n  // 重置\n  const onReset = () => {\n    searchInfo.value = {}\n    getTableData()\n  }\n\n  // 搜索\n  const onSubmit = () => {\n    elSearchFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      page.value = 1\n      getTableData()\n    })\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  // 修改页面容量\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getInfoList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  // ============== 表格控制部分结束 ===============\n\n  // 获取需要的字典 可能为空 按需保留\n  const setOptions = async () => {}\n\n  // 获取需要的字典 可能为空 按需保留\n  setOptions()\n\n  // 多选数据\n  const multipleSelection = ref([])\n  // 多选\n  const handleSelectionChange = (val) => {\n    multipleSelection.value = val\n  }\n\n  // 删除行\n  const deleteRow = (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(() => {\n      deleteInfoFunc(row)\n    })\n  }\n\n  // 多选删除\n  const onDelete = async () => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const IDs = []\n      if (multipleSelection.value.length === 0) {\n        ElMessage({\n          type: 'warning',\n          message: '请选择要删除的数据'\n        })\n        return\n      }\n      multipleSelection.value &&\n        multipleSelection.value.map((item) => {\n          IDs.push(item.ID)\n        })\n      const res = await deleteInfoByIds({ IDs })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === IDs.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n\n  // 行为控制标记（弹窗内部需要增还是改）\n  const type = ref('')\n\n  // 更新行\n  const updateInfoFunc = async (row) => {\n    const res = await findInfo({ ID: row.ID })\n    type.value = 'update'\n    if (res.code === 0) {\n      formData.value = res.data\n      dialogFormVisible.value = true\n    }\n  }\n\n  // 删除行\n  const deleteInfoFunc = async (row) => {\n    const res = await deleteInfo({ ID: row.ID })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '删除成功'\n      })\n      if (tableData.value.length === 1 && page.value > 1) {\n        page.value--\n      }\n      getTableData()\n    }\n  }\n\n  // 弹窗控制标记\n  const dialogFormVisible = ref(false)\n\n  // 打开弹窗\n  const openDialog = () => {\n    type.value = 'create'\n    dialogFormVisible.value = true\n  }\n\n  // 关闭弹窗\n  const closeDialog = () => {\n    dialogFormVisible.value = false\n    formData.value = {\n      title: '',\n      content: '',\n      userID: undefined,\n      attachments: []\n    }\n  }\n  // 弹窗确定\n  const enterDialog = async () => {\n    elFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      let res\n      switch (type.value) {\n        case 'create':\n          res = await createInfo(formData.value)\n          break\n        case 'update':\n          res = await updateInfo(formData.value)\n          break\n        default:\n          res = await createInfo(formData.value)\n          break\n      }\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '创建/更改成功'\n        })\n        closeDialog()\n        getTableData()\n      }\n    })\n  }\n\n  const downloadFile = (url) => {\n    window.open(getUrl(url), '_blank')\n  }\n</script>\n\n<style>\n  .file-list {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 4px;\n  }\n\n  .fileBtn {\n    margin-bottom: 10px;\n  }\n\n  .fileBtn:last-child {\n    margin-bottom: 0;\n  }\n</style>\n"
  },
  {
    "path": "web/src/plugin/email/api/email.js",
    "content": "import service from '@/utils/request'\n// @Tags System\n// @Summary 发送测试邮件\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}\"\n// @Router /email/emailTest [post]\nexport const emailTest = (data) => {\n  return service({\n    url: '/email/emailTest',\n    method: 'post',\n    data\n  })\n}\n\n// @Tags System\n// @Summary 发送邮件\n// @Security ApiKeyAuth\n// @Produce  application/json\n// @Param data body email_response.Email true \"发送邮件必须的参数\"\n// @Success 200 {string} string \"{\"success\":true,\"data\":{},\"msg\":\"发送成功\"}\"\n// @Router /email/sendEmail [post]\nexport const sendEmail = (data) => {\n  return service({\n    url: '/email/sendEmail',\n    method: 'post',\n    data\n  })\n}\n"
  },
  {
    "path": "web/src/plugin/email/view/index.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      title=\"需要提前配置email配置文件，为防止不必要的垃圾邮件，在线体验功能不开放此功能体验。\"\n    />\n    <div class=\"gva-form-box\">\n      <el-form\n        ref=\"emailForm\"\n        label-position=\"right\"\n        label-width=\"80px\"\n        :model=\"form\"\n      >\n        <el-form-item label=\"目标邮箱\">\n          <el-input v-model=\"form.to\" />\n        </el-form-item>\n        <el-form-item label=\"邮件\">\n          <el-input v-model=\"form.subject\" />\n        </el-form-item>\n        <el-form-item label=\"邮件内容\">\n          <el-input v-model=\"form.body\" type=\"textarea\" />\n        </el-form-item>\n        <el-form-item>\n          <el-button @click=\"sendTestEmail\">发送测试邮件</el-button>\n          <el-button @click=\"sendEmail\">发送邮件</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { emailTest } from '@/plugin/email/api/email.js'\n  import { ElMessage } from 'element-plus'\n  import { reactive, ref } from 'vue'\n\n  defineOptions({\n    name: 'Email'\n  })\n\n  const emailForm = ref(null)\n  const form = reactive({\n    to: '',\n    subject: '',\n    body: ''\n  })\n  const sendTestEmail = async () => {\n    const res = await emailTest()\n    if (res.code === 0) {\n      ElMessage.success('发送成功')\n    }\n  }\n\n  const sendEmail = async () => {\n    const res = await emailTest()\n    if (res.code === 0) {\n      ElMessage.success('发送成功,请查收')\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/router/index.js",
    "content": "import { createRouter, createWebHashHistory } from 'vue-router'\n\nconst routes = [\n  {\n    path: '/',\n    redirect: '/login'\n  },\n  {\n    path: '/init',\n    name: 'Init',\n    component: () => import('@/view/init/index.vue')\n  },\n  {\n    path: '/login',\n    name: 'Login',\n    component: () => import('@/view/login/index.vue')\n  },\n  {\n    path: '/scanUpload',\n    name: 'ScanUpload',\n    meta: {\n      title: '扫码上传',\n      client: true\n    },\n    component: () => import('@/view/example/upload/scanUpload.vue')\n  },\n  {\n    path: '/:catchAll(.*)',\n    meta: {\n      closeTab: true\n    },\n    component: () => import('@/view/error/index.vue')\n  },\n]\n\nconst router = createRouter({\n  history: createWebHashHistory(),\n  routes\n})\n\nexport default router\n"
  },
  {
    "path": "web/src/style/element/index.scss",
    "content": "@forward 'element-plus/theme-chalk/src/common/var.scss' with (\n  $colors: (\n    'white': #ffffff,\n    'black': #000000,\n    'primary': (\n      'base': #4d70ff\n    ),\n    'success': (\n      'base': #67c23a\n    ),\n    'warning': (\n      'base': #e6a23c\n    ),\n    'danger': (\n      'base': #f56c6c\n    ),\n    'error': (\n      'base': #f56c6c\n    ),\n    'info': (\n      'base': #909399\n    )\n  )\n);\n"
  },
  {
    "path": "web/src/style/element_visiable.scss",
    "content": "@use '@/style/main.scss';\n@use '@/style/reset';\n\n\n.el-button {\n  font-weight: 400;\n  border-radius: 2px;\n}\n\n.gva-pagination {\n  @apply flex justify-end;\n  .el-pagination__editor {\n    .el-input__inner {\n      @apply h-8;\n    }\n  }\n\n  .is-active {\n    @apply rounded text-white;\n    background: var(--el-color-primary);\n    color: #ffffff !important;\n  }\n}\n\n.el-drawer__header {\n  margin-bottom: 0 !important;\n  padding-top: 16px !important;\n  padding-bottom: 16px !important;\n  @apply border-0 border-b border-solid border-gray-200;\n}\n\n.el-form--inline {\n  .el-form-item {\n    & > .el-input,\n    .el-cascader,\n    .el-select,\n    .el-date-editor,\n    .el-autocomplete {\n      @apply w-52;\n    }\n  }\n}\n\n.el-dropdown {\n  @apply overflow-hidden;\n}\n\n.el-table {\n  tr {\n    th {\n      @apply dark:bg-slate-900;\n      .cell {\n        @apply leading-[36px] text-gray-700 dark:text-gray-200;\n      }\n    }\n  }\n  .el-table__row {\n    td {\n      @apply dark:bg-slate-900;\n      .cell {\n        @apply leading-[32px] text-gray-600 dark:text-gray-300;\n      }\n    }\n  }\n  tr {\n    th {\n      &.is-leaf {\n        @apply dark:bg-slate-900;\n      }\n    }\n  }\n}\n\n//   layout\n\n// table\n.el-pagination {\n  @apply mt-8;\n  .btn-prev,\n  .btn-next {\n    @apply border border-solid border-gray-300 dark:border-gray-700 rounded;\n  }\n  .el-pager {\n    li {\n      @apply border border-solid border-gray-300 dark:border-gray-600 rounded text-gray-600 text-sm mx-1;\n    }\n  }\n}\n.el-menu {\n  background-color: transparent !important;\n  li {\n    @apply my-1;\n  }\n}\n.el-menu--vertical {\n  .el-menu-item {\n    border-radius: 2px;\n    &.is-active {\n      background-color: var(--el-color-primary) !important;\n      color: #fff !important;\n    }\n  }\n}\n\n.el-sub-menu.el-sub-menu__hide-arrow {\n  height: 44px;\n}\n\n.el-tabs__header {\n  margin: 0 0 1px !important;\n}\n\n.el-sub-menu.is-active {\n  > .el-sub-menu__title {\n    color: var(--el-color-primary) !important;\n  }\n}\n\n.el-menu-item.is-active{\n  color: var(--el-color-primary)!important;\n}\n\n.el-sub-menu__title.el-tooltip__trigger,\n.el-menu-item .el-menu-tooltip__trigger {\n  justify-content: center;\n}\n\n.el-menu--horizontal .el-menu .el-sub-menu__title {\n  justify-content: flex-start;\n}\n\nhtml.dark {\n  /* 自定义深色背景颜色 */\n  --el-bg-color: rgb(30, 41, 59);\n  --el-bg-color-overlay: rgb(40, 51, 69);\n  --el-fill-color-light: rgb(15, 23, 42);\n  --el-fill-color: rgb(15, 23, 42);\n}\n"
  },
  {
    "path": "web/src/style/iconfont.css",
    "content": "@font-face {\n  font-family: 'gvaIcon';\n  src: url('data:font/ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTZJUyU8AAA14AAAAHEdERUYAKQARAAANWAAAAB5PUy8yPJpJTAAAAVgAAABgY21hcM0T0L4AAAHYAAABWmdhc3D//wADAAANUAAAAAhnbHlmRk3UvwAAA0wAAAbYaGVhZB/a5jgAAADcAAAANmhoZWEHngOFAAABFAAAACRobXR4DaoBrAAAAbgAAAAebG9jYQbMCGgAAAM0AAAAGG1heHABGgB+AAABOAAAACBuYW1lXoIBAgAACiQAAAKCcG9zdN15OnUAAAyoAAAAqAABAAAAAQAA+a916l8PPPUACwQAAAAAAN5YUSMAAAAA3lhRIwBL/8ADwAM1AAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAPAAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAALAHIABQAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZADA5mXmfQOA/4AAAAPcAIAAAAABAAAAAAAAAAAAAAAgAAEEAAAAAAAAAAQAAAAEAACLAIoAYAB1AHYASwBLAGAAAAAAAAMAAAADAAAAHAABAAAAAABUAAMAAQAAABwABAA4AAAACgAIAAIAAuZm5mrmduZ9//8AAOZl5mrmdeZ7//8ZnhmbGZEZjQABAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYAigEcAbgCUAK6AxoDbAACAIsAIANsAswAEQAjAAAlIicBJjQ3ATYeAQYHCQEeAQYhIicBJjQ3ATYeAQYHCQEeAQYDSw0J/qsLCwFVChsSAgr+xAE8CgIV/qkNCP6qCgoBVgkbEgIK/sUBOwoCFCAJATULGQsBNQoCExwI/uL+4ggbFAkBNQsZCwE1CgITHAj+4v7iCRoUAAAAAAIAigAgA2sCzAARACIAAAE0JwEmDgEWFwkBDgEWMjcBNiUBJg4BFhcJAQ4BFjI3ATY0AiAL/qsJHBECCQE8/sQJAhQZCQFVCwFA/qsKGxICCgE8/sQKAhUZCQFVCwF1DQsBNQoCExwI/uL+4gkaFAkBNQskATUKAhMcCP7i/uIJGhQJATULGQADAGD/wAOgAzUATABcAGwAAAE1NCcmJyYiBwYHBh0BDgEdARQWOwEyNj0BNCYrATU0NzY3NjIXFhcWHQEjIgYdARQWOwEGBwYHLgEjIgYUFjMyNjc2NzY3PgE9ATQmBRUUBisBIiY9ATQ2OwEyFgUUBisBIiY9ATQ2OwEyFhUDYDAvT1O+U08vMBslLB9VHi0tHiAoJkFDnENBJiggHi0tHhUPJC5SChwRHCQkHBEeCHJAMxAfKiX9kAYFVQUGBgVVBQYCVQYFVQUGBgVVBQYByQxgUlAuMDAuUFJgDAQqG6seLCweqx4tCk5DQScnJydBQ04KLR6rHiwrGiAGDxElNiUSEAc1KkUBKx6rGyhFqwQGBgSrBQYGsAQGBgSrBQYGBQAABAB1//UDjQMLABsANwBSAHEAABMyNj0BFxYyNjQvATMyNjQmKwEiBwYHBh0BFBYFIgYdAScmIgYUHwEjIgYUFjsBMjc2NzY9ATYmJQc1NCYiBh0BFBcWFxY7ATI2NCYrATc2NCYGATQ1FSYnJisBIgYUFjsBBwYUFjI/ARUUFjI2PQEnJpUNE7wJHRMKvIcMFBQM1ggCDAgCFALiDRPJCRoTCcmJDBQUDNYIAg8CAwES/gbJExkUAggKBAbWDBQUDInJCRMXAgEHCwQG2AwUFAyJvAkSHgi8ExoTAgEB9RQMibwIEhkKvBMZFAIGDAQI1gwU6hQMickJExoJyRMZFAIICgQG2AwUIsmHDBQUDNYIAg8CAxQZE8kKGRMBAcABAQIOAwMUGRO8ChkTCbyHDBQUDNYFBAAABAB2//cDjgMMABoANQBRAG0AAAEjIgYUFjsBMjc2NzY9ATQmIgYdAScmIgYUFwEzMjY0JisBIgcGBwYdARQWMjY9ARcWMjY0JyUmJyYrASIGFBY7AQcGFBYyPwEVFBYyNj0BLgE3FhcWOwEyNjQmKwE3NjQmIg8BNTQmIgYdAR4BATqJDRMTDdUJAg8CAhMaE7cKGRQKAjeJDRMTDdUJAg8CAhMaE8gJHhIK/i8HCgQH1w0TEw2JyQoTHQnIFBkTAQKoBwoEBtYNExMNibwKFBkKvBMZFAICAhoUGRMCBwoEBtYNExMNib4KExoK/iAUGRMCBwoEB9UNExMNickIEhkK8w8CAhMZFMgKGRMJyYkNExMN1QIJzQ8CAhMZFLsKGhMKvIkNExMN1QMIAAAAAAUAS//LA7UDNQAUACkAKgA3AEQAAAEiBwYHBhQXFhcWMjc2NzY0JyYnJgMiJyYnJjQ3Njc2MhcWFxYUBwYHBgMjFB4BMj4BNC4BIg4BFyIGHQEUFjI2PQE0JgIAd2ZiOzs7O2Jm7mZiOzs7O2Jmd2VXVDIzMzJUV8pXVDIzMzJUV2UrDBQWFAwMFBYUDCsNExMaExMDNTs7YmbuZmI7Ozs7YmbuZmI7O/zWMzJUV8pXVDIzMzJUV8pXVDIzAjULFAwMFBYUDAwUgBQM6w0TEw3rDBQAAQBL/+ADwAMgAD0AAAEmBg8BLgEjIgcGBwYUFxYXFjMyPgE3Ni4BBgcOAiMiJyYnJjQ3Njc2MzIeARcnJg4BFh8BMj8BNj8BNCYDpgwXAxc5yXZyY184Ojo4X2NyWaB4HgULGhcFGWaJS2FUUTAwMTBRU2FIhGQbgA0WBw4NwgUIBAwDMQ0CsQMODFhmeDk3XmHiYV43OUV9UQ0XCQsMRWo6MC9PUr9TTy8wNmNBJQMOGhYDMwMBCAu6DRYAAAAAAgBg/8YDugMiAB4AMwAABSc+ATU0JyYnJiIHBgcGFBcWFxYzMjc2NxcWMjc2JiUiJyYnJjQ3Njc2MhcWFxYUBwYHBgOxviouNDFVV8lXVTIzMzJVV2RDPzwzvgkeCAcB/hxUSEYpKiopRkioSEYpKyspRkgCvjB9RGRYVDIzNDJVWMlXVTE0GBYqvgkJChuBKylGSKhIRikqKilGSKhIRikrAAAAABIA3gABAAAAAAAAABMAKAABAAAAAAABAAgATgABAAAAAAACAAcAZwABAAAAAAADAAgAgQABAAAAAAAEAAgAnAABAAAAAAAFAAsAvQABAAAAAAAGAAgA2wABAAAAAAAKACsBPAABAAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=')\n    format('truetype');\n  font-weight: 600;\n  font-style: normal;\n  font-display: swap;\n}\n.gvaIcon {\n  font-family: 'gvaIcon' !important;\n  font-size: 16px;\n  font-style: normal;\n  font-weight: 800;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.gvaIcon-arrow-double-left:before {\n  content: '\\e665';\n}\n\n.gvaIcon-arrow-double-right:before {\n  content: '\\e666';\n}\n\n.gvaIcon-fullscreen-shrink:before {\n  content: '\\e676';\n}\n.gvaIcon-customer-service:before {\n  content: '\\e66a';\n}\n\n.gvaIcon-fullscreen-expand:before {\n  content: '\\e675';\n}\n\n.gvaIcon-prompt:before {\n  content: '\\e67b';\n}\n\n.gvaIcon-refresh:before {\n  content: '\\e67c';\n}\n\n.gvaIcon-search:before {\n  content: '\\e67d';\n}\n"
  },
  {
    "path": "web/src/style/main.scss",
    "content": "@use '@/style/iconfont.css';\n@use \"./transition.scss\";\n\n.html-grey {\n  filter: grayscale(100%);\n}\n\n.html-weakenss {\n  filter: invert(80%);\n}\n\n.gva-table-box {\n  @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;\n  .el-table {\n    @apply border-x border-t border-b-0 rounded border-table-border border-solid -mx-[1px];\n  }\n}\n\n.gva-btn-list {\n  @apply mb-3 flex items-center flex-wrap gap-2;\n  .el-button+.el-button{\n    @apply ml-0 !important;\n  }\n  .el-upload{\n    .el-button{\n      @apply ml-0 !important;\n    }\n  }\n}\n\n#nprogress .bar {\n  background: #29d !important;\n}\n.gva-customer-icon {\n  @apply w-4 h-4;\n}\n\n::-webkit-scrollbar {\n  @apply hidden;\n}\n\n.gva-search-box {\n  @apply p-4 pb-0 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;\n}\n\n.gva-form-box {\n  @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;\n}\n\n.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {\n  background: var(--el-color-primary-bg) !important;\n}\n\n.el-dropdown {\n  outline: none;\n  * {\n    outline: none;\n  }\n}\n"
  },
  {
    "path": "web/src/style/reset.scss",
    "content": "/*\n1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)\n2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)\n2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color`\n*/\n\n*,\n::before,\n::after {\n  box-sizing: border-box; /* 1 */\n  border-width: 0; /* 2 */\n  border-style: solid; /* 2 */\n  border-color: var(--un-default-border-color, #e5e7eb); /* 2 */\n}\n\n/*\n1. Use a consistent sensible line-height in all browsers.\n2. Prevent adjustments of font size after orientation changes in iOS.\n3. Use a more readable tab size.\n4. Use the user's configured `sans` font-family by default.\n*/\n\nhtml {\n  line-height: 1.5; /* 1 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n  -moz-tab-size: 4; /* 3 */\n  tab-size: 4; /* 3 */\n  font-family:\n    ui-sans-serif,\n    system-ui,\n    -apple-system,\n    BlinkMacSystemFont,\n    'Segoe UI',\n    Roboto,\n    'Helvetica Neue',\n    Arial,\n    'Noto Sans',\n    sans-serif,\n    'Apple Color Emoji',\n    'Segoe UI Emoji',\n    'Segoe UI Symbol',\n    'Noto Color Emoji'; /* 4 */\n\n  // TODO: 在下一个大版本更新的时候需要改回正确的16px\n  font-size: 14px;\n}\n\n/*\n1. Remove the margin in all browsers.\n2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.\n*/\n\nbody {\n  margin: 0; /* 1 */\n  line-height: inherit; /* 2 */\n}\n\n/*\n1. Add the correct height in Firefox.\n2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)\n3. Ensure horizontal rules are visible by default.\n*/\n\nhr {\n  height: 0; /* 1 */\n  color: inherit; /* 2 */\n  border-top-width: 1px; /* 3 */\n}\n\n/*\nAdd the correct text decoration in Chrome, Edge, and Safari.\n*/\n\nabbr:where([title]) {\n  text-decoration: underline dotted;\n}\n\n/*\nRemove the default font size and weight for headings.\n*/\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-size: inherit;\n  font-weight: inherit;\n}\n\n/*\nReset links to optimize for opt-in styling instead of opt-out.\n*/\n\na {\n  color: inherit;\n  text-decoration: inherit;\n}\n\n/*\nAdd the correct font weight in Edge and Safari.\n*/\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/*\n1. Use the user's configured `mono` font family by default.\n2. Correct the odd `em` font sizing in all browsers.\n*/\n\ncode,\nkbd,\nsamp,\npre {\n  font-family:\n    ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/*\nAdd the correct font size in all browsers.\n*/\n\nsmall {\n  font-size: 80%;\n}\n\n/*\nPrevent `sub` and `sup` elements from affecting the line height in all browsers.\n*/\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/*\n1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)\n2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)\n3. Remove gaps between table borders by default.\n*/\n\ntable {\n  text-indent: 0; /* 1 */\n  border-color: inherit; /* 2 */\n  border-collapse: collapse; /* 3 */\n}\n\n/*\n1. Change the font styles in all browsers.\n2. Remove the margin in Firefox and Safari.\n3. Remove default padding in all browsers.\n*/\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit; /* 1 */\n  font-feature-settings: inherit; /* 1 */\n  font-variation-settings: inherit; /* 1 */\n  font-size: 100%; /* 1 */\n  font-weight: inherit; /* 1 */\n  line-height: inherit; /* 1 */\n  color: inherit; /* 1 */\n  margin: 0; /* 2 */\n  padding: 0; /* 3 */\n}\n\n/*\nRemove the inheritance of text transform in Edge and Firefox.\n*/\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n/*\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Remove default button styles.\n*/\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n  -webkit-appearance: button; /* 1 */\n  /* background-color: transparent;  */\n  background-image: none; /* 2 */\n}\n\n/*\nUse the modern Firefox focus style for all focusable elements.\n*/\n\n:-moz-focusring {\n  outline: auto;\n}\n\n/*\nRemove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)\n*/\n\n:-moz-ui-invalid {\n  box-shadow: none;\n}\n\n/*\nAdd the correct vertical alignment in Chrome and Firefox.\n*/\n\nprogress {\n  vertical-align: baseline;\n}\n\n/*\nCorrect the cursor style of increment and decrement buttons in Safari.\n*/\n\n::-webkit-inner-spin-button,\n::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/*\n1. Correct the odd appearance in Chrome and Safari.\n2. Correct the outline style in Safari.\n*/\n\n[type='search'] {\n  -webkit-appearance: textfield; /* 1 */\n  outline-offset: -2px; /* 2 */\n}\n\n/*\nRemove the inner padding in Chrome and Safari on macOS.\n*/\n\n::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/*\n1. Correct the inability to style clickable types in iOS and Safari.\n2. Change font properties to `inherit` in Safari.\n*/\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button; /* 1 */\n  font: inherit; /* 2 */\n}\n\n/*\nAdd the correct display in Chrome and Safari.\n*/\n\nsummary {\n  display: list-item;\n}\n\n/*\nRemoves the default spacing and border for appropriate elements.\n*/\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n  margin: 0;\n}\n\nfieldset {\n  margin: 0;\n  padding: 0;\n}\n\nlegend {\n  padding: 0;\n}\n\nol,\nul,\nmenu {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\n/*\nPrevent resizing textareas horizontally by default.\n*/\n\ntextarea {\n  resize: vertical;\n}\n\n/*\n1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)\n2. Set the default placeholder color to the user's configured gray 400 color.\n*/\n\ninput::placeholder,\ntextarea::placeholder {\n  opacity: 1; /* 1 */\n  color: #9ca3af; /* 2 */\n}\n\n/*\nSet the default cursor for buttons.\n*/\n\nbutton,\n[role='button'] {\n  cursor: pointer;\n}\n\n/*\nMake sure disabled buttons don't get the pointer cursor.\n*/\n:disabled {\n  cursor: default;\n}\n\n/*\n1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)\n2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)\n   This can trigger a poorly considered lint error in some tools but is included by design.\n*/\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n  display: block; /* 1 */\n  vertical-align: middle; /* 2 */\n}\n\n/*\nConstrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)\n*/\n\nimg,\nvideo {\n  max-width: 100%;\n  height: auto;\n}\n\n/* Make elements with the HTML hidden attribute stay hidden by default */\n[hidden] {\n  display: none;\n}\n"
  },
  {
    "path": "web/src/style/transition.scss",
    "content": "\n// 淡入淡出动画\n.fade-enter-active,\n.fade-leave-active {\n  transition: all 0.3s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n  transform: translateY(10px);\n}\n\n.header {\n  border-radius: 0 0 10px 10px;\n}\n\n.body {\n  height: calc(100% - 6rem);\n}\n\n@keyframes slideDown {\n  from {\n    transform: translateY(-20px);\n    opacity: 0;\n  }\n\n  to {\n    transform: translateY(0);\n    opacity: 1;\n  }\n}\n\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n  }\n}\n// 缩放动画\n.zoom-enter-active,\n.zoom-leave-active {\n  transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.zoom-enter-from,\n.zoom-leave-to {\n  opacity: 0;\n  transform: scale(0.95);\n}\n\n\n/* fade-slide */\n.slide-leave-active,\n.slide-enter-active {\n  transition: all 0.3s;\n}\n.slide-enter-from {\n  opacity: 0;\n  transform: translateX(-30px);\n}\n.slide-leave-to {\n  opacity: 0;\n  transform: translateX(30px);\n}\n"
  },
  {
    "path": "web/src/utils/asyncRouter.js",
    "content": "const viewModules = import.meta.glob('../view/**/*.vue')\nconst pluginModules = import.meta.glob('../plugin/**/*.vue')\n\nexport const asyncRouterHandle = (asyncRouter) => {\n  asyncRouter.forEach((item) => {\n    if (item.component && typeof item.component === 'string') {\n      item.meta.path = '/src/' + item.component\n      if (item.component.split('/')[0] === 'view') {\n        item.component = dynamicImport(viewModules, item.component)\n      } else if (item.component.split('/')[0] === 'plugin') {\n        item.component = dynamicImport(pluginModules, item.component)\n      }\n    }\n    if (item.children) {\n      asyncRouterHandle(item.children)\n    }\n  })\n}\n\nfunction dynamicImport(dynamicViewsModules, component) {\n  const keys = Object.keys(dynamicViewsModules)\n  const matchKeys = keys.filter((key) => {\n    const k = key.replace('../', '')\n    return k === component\n  })\n  const matchKey = matchKeys[0]\n\n  return dynamicViewsModules[matchKey]\n}\n"
  },
  {
    "path": "web/src/utils/btnAuth.js",
    "content": "import { useRoute } from 'vue-router'\nimport { reactive } from 'vue'\nexport const useBtnAuth = () => {\n  const route = useRoute()\n  return route.meta.btns || reactive({})\n}\n"
  },
  {
    "path": "web/src/utils/bus.js",
    "content": "// using ES6 modules\nimport mitt from 'mitt'\n\nexport const emitter = mitt()\n"
  },
  {
    "path": "web/src/utils/closeThisPage.js",
    "content": "import { emitter } from '@/utils/bus.js'\n\nexport const closeThisPage = () => {\n  emitter.emit('closeThisPage')\n}\n"
  },
  {
    "path": "web/src/utils/date.js",
    "content": "// 对Date的扩展，将 Date 转化为指定格式的String\n// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符，\n// 年(y)可以用 1-4 个占位符，毫秒(S)只能用 1 个占位符(是 1-3 位的数字)\n// (new Date()).Format(\"yyyy-MM-dd hh:mm:ss.S\") ==> 2006-07-02 08:09:04.423\n// (new Date()).Format(\"yyyy-M-d h:m:s.S\")      ==> 2006-7-2 8:9:4.18\n// eslint-disable-next-line no-extend-native\nDate.prototype.Format = function(fmt) {\n  const o = {\n    'M+': this.getMonth() + 1, // 月份\n    'd+': this.getDate(), // 日\n    'h+': this.getHours(), // 小时\n    'm+': this.getMinutes(), // 分\n    's+': this.getSeconds(), // 秒\n    'q+': Math.floor((this.getMonth() + 3) / 3), // 季度\n    'S': this.getMilliseconds() // 毫秒\n  }\n  const reg = /(y+)/\n  if (reg.test(fmt)) {\n    const t = reg.exec(fmt)[1]\n    fmt = fmt.replace(\n      t,\n      (this.getFullYear() + '').substring(4 - t.length)\n    )\n  }\n  for (let k in o) {\n    const regx = new RegExp('(' + k + ')')\n    if (regx.test(fmt)) {\n      const t = regx.exec(fmt)[1]\n      fmt = fmt.replace(\n        t,\n        t.length === 1 ? o[k] : ('00' + o[k]).substring(('' + o[k]).length)\n      )\n    }\n  }\n  return fmt\n}\n\nexport function formatTimeToStr(times, pattern) {\n  let d = new Date(times).Format('yyyy-MM-dd hh:mm:ss')\n  if (pattern) {\n    d = new Date(times).Format(pattern)\n  }\n  return d.toLocaleString()\n}\n"
  },
  {
    "path": "web/src/utils/dictionary.js",
    "content": "import { useDictionaryStore } from '@/pinia/modules/dictionary'\n\n/**\n * 生成字典缓存key\n * @param {string} type - 字典类型\n * @param {number} depth - 深度参数\n * @param {string|number|null} value - 指定节点的value\n * @returns {string} 缓存key\n */\nconst generateCacheKey = (type, depth, value) => {\n  if (value !== null && value !== undefined) {\n    return `${type}_value_${value}_depth_${depth}`\n  }\n  return depth === 0 ? `${type}_tree` : `${type}_depth_${depth}`\n}\n\n/**\n * 获取字典数据\n * @param {string} type - 字典类型，必填\n * @param {Object} options - 可选参数\n * @param {number} options.depth - 指定获取字典的深度，默认为0（完整树形结构）\n * @param {string|number|null} options.value - 指定节点的value，获取该节点的children，默认为null\n * @returns {Promise<Array>} 字典数据数组\n * @example\n * // 获取完整的字典树形结构\n * const dictTree = await getDict('user_status')\n *\n * // 获取指定深度的扁平化字典数据\n * const dictFlat = await getDict('user_status', {\n *  depth: 2\n * })\n *\n * // 获取指定节点的children\n * const children = await getDict('user_status', {\n *  value: 'active'\n * })\n */\nexport const getDict = async (\n  type,\n  options = {\n    depth: 0,\n    value: null\n  }\n) => {\n  // 参数验证\n  if (!type || typeof type !== 'string') {\n    console.warn('getDict: type参数必须是非空字符串')\n    return []\n  }\n\n  if (typeof options.depth !== 'number' || options.depth < 0) {\n    console.warn('getDict: depth参数必须是非负数')\n    options.depth = 0\n  }\n\n  try {\n    const dictionaryStore = useDictionaryStore()\n\n    // 调用store方法获取字典数据\n    await dictionaryStore.getDictionary(type, options.depth, options.value)\n\n    // 生成缓存key\n    const cacheKey = generateCacheKey(type, options.depth, options.value)\n\n    // 从缓存中获取数据\n    const result = dictionaryStore.dictionaryMap[cacheKey]\n\n    // 返回数据，确保返回数组\n    return Array.isArray(result) ? result : []\n  } catch (error) {\n    console.error('getDict: 获取字典数据失败', { type, options, error })\n    return []\n  }\n}\n\n//  字典文字展示方法\nexport const showDictLabel = (\n  dict,\n  code,\n  keyCode = 'value',\n  valueCode = 'label'\n) => {\n  if (!dict) {\n    return ''\n  }\n  const dictMap = {}\n  dict.forEach((item) => {\n    if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) {\n      dictMap[item[keyCode]] = item[valueCode]\n    }\n  })\n  return Reflect.has(dictMap, code) ? dictMap[code] : ''\n}\n"
  },
  {
    "path": "web/src/utils/doc.js",
    "content": "export const toDoc = (url) => {\n  window.open(url, '_blank')\n}\n"
  },
  {
    "path": "web/src/utils/downloadImg.js",
    "content": "export const downloadImage = (imgsrc, name) => {\n  // 下载图片地址和图片名\n  var image = new Image()\n  image.setAttribute('crossOrigin', 'anonymous')\n  image.onload = function () {\n    var canvas = document.createElement('canvas')\n    canvas.width = image.width\n    canvas.height = image.height\n    var context = canvas.getContext('2d')\n    context.drawImage(image, 0, 0, image.width, image.height)\n    var url = canvas.toDataURL('image/png') // 得到图片的base64编码数据\n\n    var a = document.createElement('a') // 生成一个a元素\n    var event = new MouseEvent('click') // 创建一个单击事件\n    a.download = name || 'photo' // 设置图片名称\n    a.href = url // 将生成的URL设置为a.href属性\n    a.dispatchEvent(event) // 触发a的单击事件\n  }\n  image.src = imgsrc\n}\n"
  },
  {
    "path": "web/src/utils/env.js",
    "content": "export const isDev = import.meta.env.DEV;\n\nexport const isProd = import.meta.env.PROD;\n"
  },
  {
    "path": "web/src/utils/event.js",
    "content": "export function addEventListen(target, event, handler, capture = false) {\n  if (\n    target.addEventListener &&\n    typeof target.addEventListener === 'function'\n  ) {\n    target.addEventListener(event, handler, capture)\n  }\n}\n\nexport function removeEventListen(target, event, handler, capture = false) {\n  if (\n    target.removeEventListener &&\n    typeof target.removeEventListener === 'function'\n  ) {\n    target.removeEventListener(event, handler, capture)\n  }\n}\n"
  },
  {
    "path": "web/src/utils/fmtRouterTitle.js",
    "content": "export const fmtTitle = (title, now) => {\n  const reg = /\\$\\{(.+?)\\}/\n  const reg_g = /\\$\\{(.+?)\\}/g\n  const result = title.match(reg_g)\n  if (result) {\n    result.forEach((item) => {\n      const key = item.match(reg)[1]\n      const value = now.params[key] || now.query[key]\n      title = title.replace(item, value)\n    })\n  }\n  return title\n}\n"
  },
  {
    "path": "web/src/utils/format.js",
    "content": "import { formatTimeToStr } from '@/utils/date'\nimport { getDict } from '@/utils/dictionary'\nimport { ref } from 'vue'\n\nexport const formatBoolean = (bool) => {\n  if (bool !== null) {\n    return bool ? '是' : '否'\n  } else {\n    return ''\n  }\n}\nexport const formatDate = (time) => {\n  if (time !== null && time !== '') {\n    var date = new Date(time)\n    return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')\n  } else {\n    return ''\n  }\n}\n\nexport const filterDict = (value, options) => {\n  // 递归查找函数\n  const findInOptions = (opts, targetValue) => {\n    if (!opts || !Array.isArray(opts)) return null\n    \n    for (const item of opts) {\n      if (item.value === targetValue) {\n        return item\n      }\n      \n      if (item.children && Array.isArray(item.children)) {\n        const found = findInOptions(item.children, targetValue)\n        if (found) return found\n      }\n    }\n    \n    return null\n  }\n  \n  const rowLabel = findInOptions(options, value)\n  return rowLabel && rowLabel.label\n}\n\nexport const filterDataSource = (dataSource, value) => {\n  // 递归查找函数\n  const findInDataSource = (data, targetValue) => {\n    if (!data || !Array.isArray(data)) return null\n    \n    for (const item of data) {\n      // 检查当前项是否匹配\n      if (item.value === targetValue) {\n        return item\n      }\n      \n      // 如果有children属性，递归查找\n      if (item.children && Array.isArray(item.children)) {\n        const found = findInDataSource(item.children, targetValue)\n        if (found) return found\n      }\n    }\n    \n    return null\n  }\n  \n  if (Array.isArray(value)) {\n    return value.map((item) => {\n      const rowLabel = findInDataSource(dataSource, item)\n      return rowLabel?.label\n    })\n  }\n  \n  const rowLabel = findInDataSource(dataSource, value)\n  return rowLabel?.label\n}\n\nexport const getDictFunc = async (type) => {\n  const dicts = await getDict(type)\n  return dicts\n}\n\nconst path =\n  import.meta.env.VITE_BASE_PATH + ':' + import.meta.env.VITE_SERVER_PORT + '/'\nexport const ReturnArrImg = (arr) => {\n  const imgArr = []\n  if (arr instanceof Array) {\n    // 如果是数组类型\n    for (const arrKey in arr) {\n      if (arr[arrKey].slice(0, 4) !== 'http') {\n        imgArr.push(path + arr[arrKey])\n      } else {\n        imgArr.push(arr[arrKey])\n      }\n    }\n  } else {\n    // 如果不是数组类型\n    if (arr?.slice(0, 4) !== 'http') {\n      imgArr.push(path + arr)\n    } else {\n      imgArr.push(arr)\n    }\n  }\n  return imgArr\n}\n\nexport const returnArrImg = ReturnArrImg\n\nexport const onDownloadFile = (url) => {\n  window.open(path + url)\n}\nconst colorToHex = (u) => {\n  let e = u.replace('#', '').match(/../g)\n  for (let t = 0; t < 3; t++) e[t] = parseInt(e[t], 16)\n  return e\n}\n\nconst hexToColor = (u, e, t) => {\n  let a = [u.toString(16), e.toString(16), t.toString(16)]\n  for (let n = 0; n < 3; n++) a[n].length === 1 && (a[n] = `0${a[n]}`)\n  return `#${a.join('')}`\n}\nconst generateAllColors = (u, e) => {\n  let t = colorToHex(u)\n  const target = [10, 10, 30]\n  for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e)\n  return hexToColor(t[0], t[1], t[2])\n}\n\nconst generateAllLightColors = (u, e) => {\n  let t = colorToHex(u)\n  const target = [240, 248, 255] // RGB for blue white color\n  for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e)\n  return hexToColor(t[0], t[1], t[2])\n}\n\nfunction addOpacityToColor(u, opacity) {\n  let t = colorToHex(u)\n  return `rgba(${t[0]}, ${t[1]}, ${t[2]}, ${opacity})`\n}\n\nexport const setBodyPrimaryColor = (primaryColor, darkMode) => {\n  let fmtColorFunc = generateAllColors\n  if (darkMode === 'light') {\n    fmtColorFunc = generateAllLightColors\n  }\n\n  document.documentElement.style.setProperty('--el-color-primary', primaryColor)\n  document.documentElement.style.setProperty(\n    '--el-color-primary-bg',\n    addOpacityToColor(primaryColor, 0.4)\n  )\n  for (let times = 1; times <= 2; times++) {\n    document.documentElement.style.setProperty(\n      `--el-color-primary-dark-${times}`,\n      fmtColorFunc(primaryColor, times / 10)\n    )\n  }\n  for (let times = 1; times <= 10; times++) {\n    document.documentElement.style.setProperty(\n      `--el-color-primary-light-${times}`,\n      fmtColorFunc(primaryColor, times / 10)\n    )\n  }\n  document.documentElement.style.setProperty(\n    `--el-menu-hover-bg-color`,\n    addOpacityToColor(primaryColor, 0.2)\n  )\n}\n\nconst baseUrl = ref(import.meta.env.VITE_BASE_API)\n\nexport const getBaseUrl = () => {\n  return baseUrl.value === '/' ? '' : baseUrl.value\n}\n\nexport const CreateUUID = () => {\n  let d = new Date().getTime()\n  if (window.performance && typeof window.performance.now === 'function') {\n    d += performance.now()\n  }\n  return '00000000-0000-0000-0000-000000000000'.replace(/0/g, (c) => {\n    const r = (d + Math.random() * 16) % 16 | 0 // d是随机种子\n    d = Math.floor(d / 16)\n    return (c === '0' ? r : (r & 0x3) | 0x8).toString(16)\n  })\n}\n"
  },
  {
    "path": "web/src/utils/image.js",
    "content": "export default class ImageCompress {\n  constructor(file, fileSize, maxWH = 1920) {\n    this.file = file\n    this.fileSize = fileSize\n    this.maxWH = maxWH // 最大长宽\n  }\n\n  compress() {\n    // 压缩\n    const fileType = this.file.type\n    const fileSize = this.file.size / 1024\n    return new Promise((resolve) => {\n      const reader = new FileReader()\n      reader.readAsDataURL(this.file)\n      reader.onload = () => {\n        const canvas = document.createElement('canvas')\n        const img = document.createElement('img')\n        img.src = reader.result\n        img.onload = () => {\n          const ctx = canvas.getContext('2d')\n          const _dWH = this.dWH(img.width, img.height, this.maxWH)\n          canvas.width = _dWH.width\n          canvas.height = _dWH.height\n\n          // 清空后, 重写画布\n          ctx.clearRect(0, 0, canvas.width, canvas.height)\n          ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\n\n          const newImgData = canvas.toDataURL(fileType, 0.9)\n\n          // 压缩宽高后的图像大小\n          const newImgSize = this.fileSizeKB(newImgData)\n\n          if (newImgSize > this.fileSize) {\n            console.log('图片尺寸太大!' + fileSize + ' >> ' + newImgSize)\n          }\n\n          const blob = this.dataURLtoBlob(newImgData, fileType)\n          const nfile = new File([blob], this.file.name)\n          resolve(nfile)\n        }\n      }\n    })\n  }\n\n  /**\n   * 长宽等比缩小\n   * 图像的一边(长或宽)为最大目标值\n   */\n  dWH(srcW, srcH, dMax) {\n    const defaults = {\n      width: srcW,\n      height: srcH\n    }\n    if (Math.max(srcW, srcH) > dMax) {\n      if (srcW > srcH) {\n        defaults.width = dMax\n        defaults.height = Math.round(srcH * (dMax / srcW))\n        return defaults\n      } else {\n        defaults.height = dMax\n        defaults.width = Math.round(srcW * (dMax / srcH))\n        return defaults\n      }\n    } else {\n      return defaults\n    }\n  }\n\n  fileSizeKB(dataURL) {\n    let sizeKB = 0\n    sizeKB = Math.round((dataURL.split(',')[1].length * 3) / 4 / 1024)\n    return sizeKB\n  }\n\n  /**\n   * 转为Blob\n   */\n  dataURLtoBlob(dataURL, fileType) {\n    const byteString = atob(dataURL.split(',')[1])\n    let mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]\n    const ab = new ArrayBuffer(byteString.length)\n    const ia = new Uint8Array(ab)\n    for (let i = 0; i < byteString.length; i++) {\n      ia[i] = byteString.charCodeAt(i)\n    }\n    if (fileType) {\n      mimeString = fileType\n    }\n    return new Blob([ab], { type: mimeString, lastModifiedDate: new Date() })\n  }\n}\n\nconst path = import.meta.env.VITE_FILE_API\nexport const getUrl = (url) => {\n  if (url && url.slice(0, 4) !== 'http') {\n    if (path === '/') {\n      return url\n    }\n    if (url.slice(0, 1) === '/') {\n      return path + url\n    }\n    return path + '/' + url\n  } else {\n    return url\n  }\n}\n\nconst VIDEO_EXTENSIONS = ['.mp4', '.mov', '.webm', '.ogg']\nconst VIDEO_MIME_TYPES = ['video/mp4', 'video/webm', 'video/ogg']\nconst IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml']\n\nexport const isVideoExt = (url) => {\n  const urlLower = url?.toLowerCase() || ''\n  return urlLower !== '' && VIDEO_EXTENSIONS.some(ext => urlLower.endsWith(ext))\n}\n\nexport const isVideoMime = (type) => {\n  const typeLower = type?.toLowerCase() || ''\n  return typeLower !== '' && VIDEO_MIME_TYPES.includes(typeLower)\n}\n\nexport const isImageMime = (type) => {\n  const typeLower = type?.toLowerCase() || ''\n  return typeLower !== '' && IMAGE_MIME_TYPES.includes(typeLower)\n}\n"
  },
  {
    "path": "web/src/utils/page.js",
    "content": "import { fmtTitle } from '@/utils/fmtRouterTitle'\nimport config from '@/core/config'\nexport default function getPageTitle(pageTitle, route) {\n  if (pageTitle) {\n    const title = fmtTitle(pageTitle, route)\n    return `${title} - ${config.appName}`\n  }\n  return `${config.appName}`\n}\n"
  },
  {
    "path": "web/src/utils/params.js",
    "content": "import { useParamsStore } from '@/pinia/modules/params'\n/*\n * 获取参数方法 使用示例 getParams('key').then(res)  或者 async函数下 const res = await getParams('key')\n *   const res = ref('')\n *   const fun = async () => {\n *       res.value = await getParams('test')\n *   }\n *   fun()\n */\nexport const getParams = async(key) => {\n    const paramsStore = useParamsStore()\n    await paramsStore.getParams(key)\n    return paramsStore.paramsMap[key]\n}\n"
  },
  {
    "path": "web/src/utils/request.js",
    "content": "import axios from 'axios' // 引入axios\nimport { useUserStore } from '@/pinia/modules/user'\nimport { ElLoading, ElMessage } from 'element-plus'\nimport { emitter } from '@/utils/bus'\nimport router from '@/router/index'\n\nconst service = axios.create({\n  timeout: 99999\n})\nlet activeAxios = 0\nlet timer\nlet loadingInstance\nlet isLoadingVisible = false\nlet forceCloseTimer\n\nconst showLoading = (\n  option = {\n    target: null\n  }\n) => {\n  const loadDom = document.getElementById('gva-base-load-dom')\n  activeAxios++\n\n  // 清除之前的定时器\n  if (timer) {\n    clearTimeout(timer)\n  }\n\n  // 清除强制关闭定时器\n  if (forceCloseTimer) {\n    clearTimeout(forceCloseTimer)\n  }\n\n  timer = setTimeout(() => {\n    // 再次检查activeAxios状态，防止竞态条件\n    if (activeAxios > 0 && !isLoadingVisible) {\n      if (!option.target) option.target = loadDom\n      loadingInstance = ElLoading.service(option)\n      isLoadingVisible = true\n\n      // 设置强制关闭定时器，防止loading永远不关闭（30秒超时）\n      forceCloseTimer = setTimeout(() => {\n        if (isLoadingVisible && loadingInstance) {\n          console.warn('Loading强制关闭：超时30秒')\n          loadingInstance.close()\n          isLoadingVisible = false\n          activeAxios = 0 // 重置计数器\n        }\n      }, 30000)\n    }\n  }, 400)\n}\n\nconst closeLoading = () => {\n  activeAxios--\n  if (activeAxios <= 0) {\n    activeAxios = 0 // 确保不会变成负数\n    clearTimeout(timer)\n\n    if (forceCloseTimer) {\n      clearTimeout(forceCloseTimer)\n      forceCloseTimer = null\n    }\n\n    if (isLoadingVisible && loadingInstance) {\n      loadingInstance.close()\n      isLoadingVisible = false\n    }\n    loadingInstance = null\n  }\n}\n\n// 全局重置loading状态的函数，用于异常情况\nconst resetLoading = () => {\n  activeAxios = 0\n  isLoadingVisible = false\n\n  if (timer) {\n    clearTimeout(timer)\n    timer = null\n  }\n\n  if (forceCloseTimer) {\n    clearTimeout(forceCloseTimer)\n    forceCloseTimer = null\n  }\n\n  if (loadingInstance) {\n    try {\n      loadingInstance.close()\n    } catch (e) {\n      console.warn('关闭loading时出错:', e)\n    }\n    loadingInstance = null\n  }\n}\n\n// http request 拦截器\nservice.interceptors.request.use(\n  (config) => {\n    if (!config.donNotShowLoading) {\n      showLoading(config.loadingOption)\n    }\n    config.baseURL = config.baseURL || import.meta.env.VITE_BASE_API\n    const userStore = useUserStore()\n    config.headers = {\n      'Content-Type': 'application/json',\n      'x-token': userStore.token,\n      'x-user-id': userStore.userInfo.ID,\n      ...config.headers\n    }\n    return config\n  },\n  (error) => {\n    if (!error.config.donNotShowLoading) {\n      closeLoading()\n    }\n    emitter.emit('show-error', {\n      code: 'request',\n      message: error.message || '请求发送失败'\n    })\n    return error\n  }\n)\n\nfunction getErrorMessage(error) {\n  // 优先级： 响应体中的 msg > statusText > 默认消息\n  return error.response?.data?.msg || error.response?.statusText || '请求失败'\n}\n\n// http response 拦截器\nservice.interceptors.response.use(\n  (response) => {\n    const userStore = useUserStore()\n    if (!response.config.donNotShowLoading) {\n      closeLoading()\n    }\n    if (response.headers['new-token']) {\n      userStore.setToken(response.headers['new-token'])\n    }\n    if (typeof response.data.code === 'undefined') {\n      return response\n    }\n    if (response.data.code === 0 || response.headers.success === 'true') {\n      if (response.headers.msg) {\n        response.data.msg = decodeURI(response.headers.msg)\n      }\n      return response.data\n    } else {\n      ElMessage({\n        showClose: true,\n        message: response.data.msg || decodeURI(response.headers.msg),\n        type: 'error'\n      })\n      return response.data.msg ? response.data : response\n    }\n  },\n  (error) => {\n    if (!error.config.donNotShowLoading) {\n      closeLoading()\n    }\n\n    if (!error.response) {\n      // 网络错误\n      resetLoading()\n      emitter.emit('show-error', {\n        code: 'network',\n        message: getErrorMessage(error)\n      })\n      return Promise.reject(error)\n    }\n\n    // HTTP 状态码错误\n    if (error.response.status === 401) {\n      emitter.emit('show-error', {\n        code: '401',\n        message: getErrorMessage(error),\n        fn: () => {\n          const userStore = useUserStore()\n          userStore.ClearStorage()\n          router.push({ name: 'Login', replace: true })\n        }\n      })\n      return Promise.reject(error)\n    }\n\n    emitter.emit('show-error', {\n      code: error.response.status,\n      message: getErrorMessage(error)\n    })\n    return Promise.reject(error)\n  }\n)\n\n// 监听页面卸载事件，确保loading被正确清理\nif (typeof window !== 'undefined') {\n  window.addEventListener('beforeunload', resetLoading)\n  window.addEventListener('unload', resetLoading)\n}\n\n// 导出service和resetLoading函数\nexport { resetLoading }\nexport default service\n"
  },
  {
    "path": "web/src/utils/stringFun.js",
    "content": "/* eslint-disable */\nexport const toUpperCase = (str) => {\n  if (str[0]) {\n    return str.replace(str[0], str[0].toUpperCase())\n  } else {\n    return ''\n  }\n}\n\nexport const toLowerCase = (str) => {\n  if (str[0]) {\n    return str.replace(str[0], str[0].toLowerCase())\n  } else {\n    return ''\n  }\n}\n\n// 驼峰转换下划线\nexport const toSQLLine = (str) => {\n  if (str === 'ID') return 'ID'\n  return str.replace(/([A-Z])/g, '_$1').toLowerCase()\n}\n\n// 下划线转换驼峰\nexport const toHump = (name) => {\n  return name.replace(/\\_(\\w)/g, function (all, letter) {\n    return letter.toUpperCase()\n  })\n}\n"
  },
  {
    "path": "web/src/view/about/index.vue",
    "content": "<template>\n  <div class=\"mt-2\">\n    <div class=\"flex flex-col md:flex-row gap-4\">\n      <div class=\"w-full md:w-1/2\">\n        <el-card class=\"min-w-96\">\n          <template #header>\n            <el-divider>gin-vue-admin</el-divider>\n          </template>\n          <div>\n            <div class=\"w-full flex items-center justify-center\">\n              <a href=\"https://github.com/flipped-aurora/gin-vue-admin\">\n                  <img\n                    class=\"org-img dom-center\"\n                    src=\"@/assets/logo.png\"\n                    alt=\"gin-vue-admin\"\n                  />\n                </a>\n            </div>\n            <div class=\"w-full flex items-center justify-around\">\n              <a href=\"https://github.com/flipped-aurora/gin-vue-admin\">\n                  <img\n                    class=\"dom-center\"\n                    src=\"https://img.shields.io/github/watchers/flipped-aurora/gin-vue-admin.svg?label=Watch\"\n                    alt=\"\"\n                  />\n                </a>\n                <a href=\"https://github.com/flipped-aurora/gin-vue-admin\">\n                  <img\n                    class=\"dom-center\"\n                    src=\"https://img.shields.io/github/stars/flipped-aurora/gin-vue-admin.svg?style=social\"\n                    alt=\"\"\n                  />\n                </a>\n                <a href=\"https://github.com/flipped-aurora/gin-vue-admin\">\n                  <img\n                    class=\"dom-center\"\n                    src=\"https://img.shields.io/github/forks/flipped-aurora/gin-vue-admin.svg?label=Fork\"\n                    alt=\"\"\n                  />\n                </a>\n            </div>\n          </div>\n        </el-card>\n        <el-card class=\"min-w-96 mt-5\">\n          <template #header>\n            <div>flipped-aurora团队</div>\n          </template>\n          <div>\n            <div class=\"w-full flex items-center justify-center\">\n                <a href=\"https://github.com/flipped-aurora\">\n                  <img\n                    class=\"org-img dom-center\"\n                    src=\"@/assets/flipped-aurora.png\"\n                    alt=\"flipped-aurora\"\n                  />\n                </a>\n              </div>\n            <div class=\"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4 mt-4\">\n              <div v-for=\"(item, index) in members\" :key=\"index\" class=\"min-h-10 flex items-center\">\n                <a :href=\"item.html_url\" class=\"flex items-center group\">\n                  <img class=\"w-8 h-8 rounded-full\" :src=\"item.avatar_url\" />\n                  <el-link\n                    class=\"text-blue-700 ml-2 text-lg font-bold font-sans break-all\"\n                    >{{ item.login }}</el-link\n                  >\n                </a>\n              </div>\n            </div>\n          </div>\n        </el-card>\n      </div>\n      <div class=\"w-full md:w-1/2\">\n        <el-card>\n          <template #header>\n            <div>提交记录</div>\n          </template>\n          <div class=\"h-[calc(100vh-300px)] overflow-y-auto\">\n            <el-timeline>\n              <el-timeline-item\n                v-for=\"(item, index) in dataTimeline\"\n                :key=\"index\"\n                :timestamp=\"item.from\"\n                placement=\"top\"\n              >\n                <el-card>\n                  <h4>{{ item.title }}</h4>\n                  <p>{{ item.message }}</p>\n                </el-card>\n              </el-timeline-item>\n            </el-timeline>\n          </div>\n         <div class=\"w-full flex items-center justify-center\">\n          <el-button class=\"load-more\" type=\"primary\" link @click=\"loadMore\">\n            Load more\n          </el-button>\n         </div>\n        </el-card>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { ref } from 'vue'\n  import { Commits, Members } from '@/api/github'\n  import { formatTimeToStr } from '@/utils/date'\n  const page = ref(0)\n\n  defineOptions({\n    name: 'About'\n  })\n\n  const loadMore = () => {\n    page.value++\n    loadCommits()\n  }\n\n  const dataTimeline = ref([])\n  const loadCommits = () => {\n    Commits(page.value).then(({ data }) => {\n      data.forEach((element) => {\n        if (element.commit.message) {\n          dataTimeline.value.push({\n            from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),\n            title: element.commit.author.name,\n            showDayAndMonth: true,\n            message: element.commit.message\n          })\n        }\n      })\n    })\n  }\n\n  const members = ref([])\n  const loadMembers = () => {\n    Members().then(({ data }) => {\n      members.value = data\n      members.value.sort()\n    })\n  }\n\n  loadCommits()\n  loadMembers()\n</script>\n\n<style scoped>\n  .avatar-img {\n    float: left;\n    height: 40px;\n    width: 40px;\n    border-radius: 50%;\n    -webkit-border-radius: 50%;\n    -moz-border-radius: 50%;\n    margin-top: 15px;\n  }\n\n  .org-img {\n    height: 150px;\n    width: 150px;\n  }\n\n  .dom-center {\n    margin-left: 50%;\n    transform: translateX(-50%);\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/banner.vue",
    "content": "<template>\n  <el-carousel class=\"-mt-2\">\n    <el-carousel-item\n      class=\"cursor-pointer lg:h-40\"\n      v-for=\"(item, index) in banners\"\n      :key=\"index\"\n      @click=\"openLink(item.link)\"\n    >\n      <el-image class=\"h-full w-full\" :src=\"item.img\" fit=\"fill\"></el-image>\n    </el-carousel-item>\n  </el-carousel>\n</template>\n\n<script setup>\n  import banner from '@/assets/banner.jpg'\n  import banner2 from '@/assets/banner2.jpg'\n\n  const openLink = (link) => {\n    window.open(link, '_blank')\n  }\n\n  const banners = [\n    {\n      img: banner,\n      link: 'https://plugin.gin-vue-admin.com/license'\n    },\n    {\n      img: banner2,\n      link: 'https://plugin.gin-vue-admin.com'\n    },\n    {\n      img: 'https://qmplusimg.henrongyi.top/gvaDemo/k8s.jpg',\n      link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42'\n    }\n  ]\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/card.vue",
    "content": "﻿<template>\n  <div\n    class=\"rounded-lg border border-black/10 bg-white text-black/80 dark:text-slate-400 dark:bg-slate-900 dark:text-white/80\"\n    :class=\"[customClass || '', withoutPadding ? 'p-0' : 'p-4']\"\n  >\n    <div v-if=\"title\" class=\"flex justify-between items-center\">\n      <div class=\"text-sm font-semibold tracking-tight text-black dark:text-white\">\n        {{ title }}\n      </div>\n      <div\n        v-if=\"showAction\"\n        class=\"text-xs text-black/60 dark:text-white/60 hover:text-active cursor-pointer\"\n      >\n        更多\n      </div>\n    </div>\n    <div :class=\"title ? 'mt-3' : ''\">\n      <slot />\n    </div>\n  </div>\n</template>\n\n<script setup>\n  defineProps({\n    title: {\n      type: String,\n      default: ''\n    },\n    showAction: {\n      type: Boolean,\n      default: false\n    },\n    customClass: {\n      type: String,\n      default: ''\n    },\n    withoutPadding: {\n      type: Boolean,\n      default: false\n    }\n  })\n</script>\n\n<style scoped lang=\"scss\"></style>\n\n"
  },
  {
    "path": "web/src/view/dashboard/components/charts-content-numbers.vue",
    "content": "<template>\n  <Chart :height=\"height\" :option=\"chartOption\" />\n</template>\n\n<script setup>\n  import Chart from '@/components/charts/index.vue'\n  import useChartOption from '@/hooks/charts'\n  import { graphic } from 'echarts'\n  import { computed, ref } from 'vue'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { config } = storeToRefs(appStore)\n  defineProps({\n    height: {\n      type: String,\n      default: '128px'\n    }\n  })\n  const axisTextColor = computed(() => {\n    return appStore.isDark ? 'rgba(255,255,255,0.70)' : 'rgba(0,0,0,0.70)'\n  })\n  const dotColor = computed(() => {\n    return appStore.isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)'\n  })\n  const graphicFactory = (side) => {\n    return {\n      type: 'text',\n      bottom: '8',\n      ...side,\n      style: {\n        text: '',\n        textAlign: 'center',\n        fill: axisTextColor.value,\n        fontSize: 12\n      }\n    }\n  }\n  const xAxis = ref([\n    '2024-1',\n    '2024-2',\n    '2024-3',\n    '2024-4',\n    '2024-5',\n    '2024-6',\n    '2024-7',\n    '2024-8'\n  ])\n  const chartsData = ref([12, 22, 32, 45, 32, 78, 89, 92])\n  const graphicElements = ref([\n    graphicFactory({ left: '5%' }),\n    graphicFactory({ right: 0 })\n  ])\n  const { chartOption } = useChartOption(() => {\n    return {\n      grid: {\n        left: '40',\n        right: '0',\n        top: '10',\n        bottom: '30'\n      },\n      xAxis: {\n        type: 'category',\n        offset: 2,\n        data: xAxis.value,\n        boundaryGap: false,\n        axisLabel: {\n          color: axisTextColor.value,\n          formatter(value, idx) {\n            if (idx === 0) return ''\n            if (idx === xAxis.value.length - 1) return ''\n            return `${value}`\n          }\n        },\n        axisLine: {\n          show: false\n        },\n        axisTick: {\n          show: false\n        },\n        splitLine: {\n          show: true,\n          interval: (idx) => {\n            if (idx === 0) return false\n            if (idx === xAxis.value.length - 1) return false\n            return true\n          },\n          lineStyle: {\n            color: dotColor.value\n          }\n        },\n        axisPointer: {\n          show: true,\n          lineStyle: {\n            color: `${config.value.primaryColor}FF`,\n            width: 2\n          }\n        }\n      },\n      yAxis: {\n        type: 'value',\n        axisLine: {\n          show: false\n        },\n        axisLabel: {\n          formatter(value, idx) {\n            if (idx === 0) return value\n            return `${value}k`\n          }\n        },\n        splitLine: {\n          show: true,\n          lineStyle: {\n            type: 'dashed',\n            color: dotColor.value\n          }\n        }\n      },\n      tooltip: {\n        trigger: 'axis',\n        formatter(params) {\n          const [firstElement] = params\n          return `<div>\n            <p class=\"tooltip-title\">${firstElement.axisValueLabel}</p>\n            <div class=\"content-panel\"><span>总内容量</span><span class=\"tooltip-value\">${(\n              Number(firstElement.value) * 10000\n            ).toLocaleString()}</span></div>\n          </div>`\n        },\n        className: 'echarts-tooltip-diy'\n      },\n      graphic: {\n        elements: graphicElements.value\n      },\n      series: [\n        {\n          data: chartsData.value,\n          type: 'line',\n          smooth: true,\n          // symbol: 'circle',\n          symbolSize: 12,\n          emphasis: {\n            focus: 'series',\n            itemStyle: {\n              borderWidth: 2\n            }\n          },\n          lineStyle: {\n            width: 3,\n            color: new graphic.LinearGradient(0, 0, 1, 0, [\n              {\n                offset: 0,\n                color: `${config.value.primaryColor}80`\n              },\n              {\n                offset: 0.5,\n                color: `${config.value.primaryColor}92`\n              },\n              {\n                offset: 1,\n                color: `${config.value.primaryColor}FF`\n              }\n            ])\n          },\n          showSymbol: false,\n          areaStyle: {\n            opacity: 0.8,\n            color: new graphic.LinearGradient(0, 0, 0, 1, [\n              {\n                offset: 0,\n                color: `${config.value.primaryColor}20`\n              },\n              {\n                offset: 1,\n                color: `${config.value.primaryColor}08`\n              }\n            ])\n          }\n        }\n      ]\n    }\n  })\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/charts-people-numbers.vue",
    "content": "<template>\n  <Chart :height=\"height\" :option=\"chartOption\" />\n</template>\n\n<script setup>\n  import Chart from '@/components/charts/index.vue'\n  import useChartOption from '@/hooks/charts'\n  import { graphic } from 'echarts'\n  import { ref } from 'vue'\n  import { storeToRefs } from 'pinia'\n  import { useAppStore } from '@/pinia'\n  const appStore = useAppStore()\n  const { config } = storeToRefs(appStore)\n\n  const prop = defineProps({\n    height: {\n      type: String,\n      default: '128px'\n    },\n    data: {\n      type: Array,\n      default: () => []\n    }\n  })\n  const graphicFactory = (side) => {\n    return {\n      type: 'text',\n      bottom: '8',\n      ...side,\n      style: {\n        text: '',\n        textAlign: 'center',\n        fill: appStore.isDark ? '#FFFFFF' : '#000000',\n        fontSize: 12\n      }\n    }\n  }\n  const graphicElements = ref([\n    graphicFactory({ left: '5%' }),\n    graphicFactory({ right: 0 })\n  ])\n  const { chartOption } = useChartOption(() => {\n    return {\n      grid: {\n        left: '40',\n        right: '0',\n        top: '10',\n        bottom: '30'\n      },\n      xAxis: {\n        type: 'category',\n        offset: 2,\n        show: false,\n        boundaryGap: false,\n        axisLine: {\n          show: false\n        },\n        axisTick: {\n          show: false\n        },\n        splitLine: {\n          show: false\n        }\n      },\n      yAxis: {\n        type: 'value',\n        show: false,\n        axisLine: {\n          show: false\n        },\n        axisLabel: {\n          show: false\n        },\n        splitLine: {\n          show: false\n        }\n      },\n      graphic: {\n        elements: graphicElements.value\n      },\n      series: [\n        {\n          data: prop.data,\n          type: 'line',\n          smooth: true,\n          symbolSize: 12,\n          emphasis: {\n            focus: 'series',\n            itemStyle: {\n              borderWidth: 2\n            }\n          },\n          lineStyle: {\n            width: 3,\n            color: new graphic.LinearGradient(0, 0, 1, 0, [\n              {\n                offset: 0,\n                color: `${config.value.primaryColor}32`\n              },\n              {\n                offset: 0.5,\n                color: `${config.value.primaryColor}64`\n              },\n              {\n                offset: 1,\n                color: `${config.value.primaryColor}FF`\n              }\n            ])\n          },\n          showSymbol: false,\n          areaStyle: {\n            opacity: 0.8,\n            color: new graphic.LinearGradient(0, 0, 0, 1, [\n              {\n                offset: 0,\n                color: `${config.value.primaryColor}20`\n              },\n              {\n                offset: 1,\n                color: `${config.value.primaryColor}08`\n              }\n            ])\n          }\n        }\n      ]\n    }\n  })\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/charts.vue",
    "content": "<template>\n  <div class=\"\">\n    <div class=\"flex items-center justify-between mb-2\">\n      <div v-if=\"title\" class=\"text-sm font-semibold tracking-tight text-black dark:text-white\">\n        {{ title }}\n      </div>\n      <slot v-else name=\"title\" />\n    </div>\n    <div class=\"w-full relative\">\n      <div v-if=\"type !== 4\">\n        <div class=\"mt-4 text-3xl font-mono text-black dark:text-white\">\n          <el-statistic :value=\"268500\" />\n        </div>\n        <div class=\"mt-2 text-xs font-mono text-black/60 dark:text-white/60\">\n          +80% <el-icon class=\"align-middle\"><TopRight /></el-icon>\n        </div>\n      </div>\n      <div class=\"absolute top-0 right-2 w-[50%] h-20\">\n        <charts-people-number v-if=\"type === 1\" :data=\"data[0]\" height=\"100%\" />\n        <charts-people-number v-if=\"type === 2\" :data=\"data[1]\" height=\"100%\" />\n        <charts-people-number v-if=\"type === 3\" :data=\"data[2]\" height=\"100%\" />\n      </div>\n      <charts-content-number v-if=\"type === 4\" height=\"14rem\" />\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import chartsPeopleNumber from './charts-people-numbers.vue'\n  import chartsContentNumber from './charts-content-numbers.vue'\n  defineProps({\n    type: {\n      type: Number,\n      default: 1\n    },\n    title: {\n      type: String,\n      default: ''\n    }\n  })\n\n  const data = [\n    [12, 22, 32, 45, 32, 78, 89, 92],\n    [1, 2, 43, 5, 67, 78, 89, 12],\n    [12, 22, 32, 45, 32, 78, 89, 92]\n  ]\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/index.js",
    "content": "import GvaBanner from './banner.vue'\nimport GvaCard from './card.vue'\nimport GvaChart from './charts.vue'\nimport GvaTable from './table.vue'\nimport GvaNotice from './notice.vue'\nimport GvaQuickLink from './quickLinks.vue'\nimport GvaWiki from './wiki.vue'\nimport GvaPluginTable from './pluginTable.vue'\n\nexport {\n  GvaBanner,\n  GvaCard,\n  GvaChart,\n  GvaTable,\n  GvaNotice,\n  GvaQuickLink,\n  GvaWiki,\n  GvaPluginTable\n}\n"
  },
  {
    "path": "web/src/view/dashboard/components/notice.vue",
    "content": "﻿<template>\n  <div class=\"space-y-3\">\n    <el-scrollbar max-height=\"320px\">\n      <div class=\"space-y-2 pr-1\">\n        <div\n          v-for=\"(item, index) in notices\"\n          :key=\"index\"\n          class=\"group rounded-lg border border-black/10 bg-white/70 p-3 transition-all duration-200 hover:-translate-y-0.5 hover:border-black/20 hover:shadow-sm dark:border-white/10 dark:bg-white/[0.02] dark:hover:border-white/20\"\n        >\n          <div class=\"flex items-start gap-3\">\n            <span class=\"mt-1.5 h-2.5 w-2.5 shrink-0 rounded-full\" :class=\"item.dotClass\" />\n            <div class=\"min-w-0 flex-1\">\n              <div class=\"flex items-center justify-between gap-2\">\n                <span class=\"rounded-md px-2 py-0.5 text-[11px] font-semibold leading-4\" :class=\"item.tagClass\">\n                  {{ item.typeTitle }}\n                </span>\n                <span class=\"shrink-0 text-[11px] text-black/45 dark:text-white/45\">{{ item.time }}</span>\n              </div>\n              <el-tooltip effect=\"light\" :content=\"item.title\" placement=\"top\">\n                <p class=\"mt-1.5 line-clamp-2 text-sm text-black/75 dark:text-white/75\">\n                  {{ item.title }}\n                </p>\n              </el-tooltip>\n            </div>\n          </div>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n\n<script setup>\n\n  const notices = [\n    {\n      typeTitle: '通知',\n      time: '今天',\n      title: '购买商业授权后可进入专属技术支持通道，加快问题排查和版本升级效率。',\n      dotClass: 'bg-cyan-500',\n      tagClass: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/40 dark:text-cyan-200'\n    },\n    {\n      typeTitle: '活动',\n      time: '2天前',\n      title: '插件市场正在进行限时优惠活动，授权用户可获得更低的插件采购成本。',\n      dotClass: 'bg-emerald-500',\n      tagClass: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-200'\n    },\n    {\n      typeTitle: '合规',\n      time: '3天前',\n      title: '未授权商用存在合规风险，建议团队尽快完成授权以保障项目持续交付。',\n      dotClass: 'bg-amber-500',\n      tagClass: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200'\n    },\n    {\n      typeTitle: '服务',\n      time: '5天前',\n      title: '授权用户可获得官方长期维护承诺，包含安全修复与关键版本升级支持。',\n      dotClass: 'bg-violet-500',\n      tagClass: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-200'\n    }\n  ]\n</script>\n\n<style scoped lang=\"scss\"></style>\n\n"
  },
  {
    "path": "web/src/view/dashboard/components/pluginTable.vue",
    "content": "<template>\n  <div>\n    <el-table :data=\"tableData\" stripe style=\"width: 100%\">\n      <el-table-column prop=\"name\" label=\"插件标题\" show-overflow-tooltip width=\"200\">\n        <template #default=\"{ row }\">\n          <a\n            class=\"text-black dark:text-white decoration-black/20 dark:decoration-white/20 hover:text-active\"\n            :href=\"`https://plugin.gin-vue-admin.com/details/${row.ID}`\"\n            target=\"_blank\"\n          >{{ row.name }}</a>\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"resume\" label=\"简介\" show-overflow-tooltip></el-table-column>\n      <el-table-column prop=\"money\" label=\"价格\" width=\"100\">\n        <template #default=\"{ row }\">\n          <span v-if=\"row.money === 0\">免费</span>\n          <span v-else>￥{{ row.money }}</span>\n        </template>\n      </el-table-column>\n    </el-table>\n     <div class=\"gva-pagination\">\n      <el-pagination\n        :current-page=\"page\"\n        :page-size=\"pageSize\"\n        :page-sizes=\"[5, 10, 20]\"\n        :total=\"total\"\n        layout=\"total, prev, pager, next\"\n        size=\"small\"\n        @current-change=\"handleCurrentChange\"\n        @size-change=\"handleSizeChange\"\n      />\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { getShopPluginList } from '@/api/plugin/api'\n  import { ref } from 'vue'\n\n  const tableData = ref([])\n  const page = ref(1)\n  const pageSize = ref(5)\n  const total = ref(0)\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const getTableData = async() => {\n    const res = await getShopPluginList({ page: page.value, pageSize: pageSize.value ,updatedAt: 1})\n    if (res.code === 0) {\n      tableData.value = res.data.list\n      total.value = res.data.total\n    }\n  }\n\n  getTableData()\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/quickLinks.vue",
    "content": "﻿<template>\n  <div class=\"h-full space-y-5\">\n    <div>\n      <div class=\"mb-2 text-xs tracking-wide text-black/55 dark:text-white/55\">常用入口</div>\n      <div class=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n        <button\n          v-for=\"(item, index) in shortcuts\"\n          :key=\"index\"\n          class=\"group flex w-full items-center gap-3 rounded-lg border border-black/10 bg-white/70 p-2.5 text-left transition-all duration-200 hover:border-[var(--el-color-primary)] hover:shadow-sm dark:border-white/10 dark:bg-white/[0.02]\"\n          type=\"button\"\n          @click=\"toPath(item)\"\n        >\n          <span\n            class=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-700 transition-colors group-hover:bg-[var(--el-color-primary)] group-hover:text-white dark:bg-slate-800 dark:text-slate-200\"\n          >\n            <el-icon><component :is=\"item.icon\" /></el-icon>\n          </span>\n          <span class=\"min-w-0 text-sm text-black/75 dark:text-white/75\">{{ item.title }}</span>\n        </button>\n      </div>\n    </div>\n\n    <div>\n      <div class=\"mb-2 text-xs tracking-wide text-black/55 dark:text-white/55\">常用外链</div>\n      <div class=\"space-y-2\">\n        <button\n          v-for=\"(item, index) in recentVisits\"\n          :key=\"index\"\n          class=\"flex w-full items-center justify-between rounded-lg border border-black/10 bg-white/70 px-3 py-2 text-left transition-all duration-200 hover:border-[var(--el-color-primary)] hover:shadow-sm dark:border-white/10 dark:bg-white/[0.02]\"\n          type=\"button\"\n          @click=\"openLink(item)\"\n        >\n          <span class=\"flex items-center gap-2 text-sm text-black/75 dark:text-white/75\">\n            <el-icon><component :is=\"item.icon\" /></el-icon>\n            {{ item.title }}\n          </span>\n          <span class=\"text-xs text-black/45 dark:text-white/45\">打开</span>\n        </button>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import {\n    Menu,\n    Link,\n    User,\n    Service,\n    Document,\n    Reading,\n    Files,\n    Memo\n  } from '@element-plus/icons-vue'\n  import { useRouter } from 'vue-router'\n\n  const router = useRouter()\n\n  const toPath = (item) => {\n    router.push({ name: item.path })\n  }\n\n  const openLink = (item) => {\n    window.open(item.path, '_blank', 'noopener,noreferrer')\n  }\n\n  const shortcuts = [\n    { icon: Menu, title: '菜单管理', path: 'menu' },\n    { icon: Link, title: 'API管理', path: 'api' },\n    { icon: Service, title: '角色管理', path: 'authority' },\n    { icon: User, title: '用户管理', path: 'user' },\n    { icon: Files, title: '自动化包', path: 'autoPkg' },\n    { icon: Memo, title: '自动代码', path: 'autoCode' }\n  ]\n\n  const recentVisits = [\n    { icon: Reading, title: '授权购买', path: 'https://plugin.gin-vue-admin.com/license' },\n    { icon: Document, title: '插件市场', path: 'https://plugin.gin-vue-admin.com/#/layout/home' },\n    { icon: Link, title: '项目仓库', path: 'https://github.com/flipped-aurora/gin-vue-admin' }\n  ]\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/table.vue",
    "content": "<template>\n  <div>\n    <el-table :data=\"tableData\" stripe style=\"width: 100%\">\n      <el-table-column prop=\"ranking\" label=\"排名\" width=\"80\" align=\"center\" />\n      <el-table-column prop=\"message\" label=\"更新内容\" show-overflow-tooltip />\n      <el-table-column prop=\"author\" label=\"提交人\" width=\"140\" />\n      <el-table-column prop=\"date\" label=\"时间\" width=\"180\" />\n    </el-table>\n  </div>\n</template>\n\n<script setup>\n  import { formatTimeToStr } from '@/utils/date'\n  import { ref, onMounted } from 'vue'\n  import axios from 'axios'\n\n  const service = axios.create()\n\n  const tableData = ref([])\n\n  const Commits =(page) => {\n   return service({\n    url:\n      'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' +\n      page,\n    method: 'get'\n  })\n}\n\n  const loadCommits = async () => {\n    const { data } = await Commits(1)\n    tableData.value = data.slice(0, 5).map((item, index) => {\n      return {\n        ranking: index + 1,\n        message: item.commit.message,\n        author: item.commit.author.name,\n        date: formatTimeToStr(item.commit.author.date, 'yyyy-MM-dd hh:mm:ss')\n      }\n    })\n  }\n\n  onMounted(() => {\n    loadCommits()\n  })\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/components/wiki.vue",
    "content": "<template>\n  <div class=\"grid grid-cols-2 gap-2\">\n    <a\n      v-for=\"item in wikis\"\n      :key=\"item.url\"\n      :href=\"item.url\"\n      class=\"text-sm text-black/70 dark:text-white/70 no-underline hover:text-[var(--el-color-primary)] dark:hover:text-white\"\n      target=\"_blank\"\n    >\n      {{ item.title }}\n    </a>\n  </div>\n</template>\n\n<script setup>\n  const wikis = [\n    {\n      title: 'Vue3',\n      url: 'https://v3.cn.vuejs.org/guide/introduction.html'\n    },\n    {\n      title: 'GIN 文档',\n      url: 'https://gin-gonic.com/'\n    },\n    {\n      title: 'GVA 文档',\n      url: 'https://www.gin-vue-admin.com/'\n    },\n    {\n      title: '插件市场',\n      url: 'https://plugin.gin-vue-admin.com/'\n    },\n    {\n      title: 'github 仓库',\n      url: 'https://github.com/flipped-aurora/gin-vue-admin'\n    }\n  ]\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/dashboard/index.vue",
    "content": "﻿<template>\n  <div class=\"h-full gva-container2 overflow-auto bg-slate-50/60 dark:bg-slate-900\">\n    <div class=\"space-y-4 p-4 lg:p-6\">\n      <section\n        class=\"relative overflow-hidden rounded-xl border border-slate-200/80 bg-white px-5 py-6 shadow-sm dark:border-slate-700 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900\"\n      >\n        \n        <div class=\"relative flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between\">\n          <div>\n            <p class=\"text-xs tracking-[0.2em] text-slate-500 dark:text-slate-400\">DASHBOARD</p>\n            <h1 class=\"mt-2 text-xl font-semibold text-slate-900 dark:text-slate-100 lg:text-2xl\">\n              欢迎回来，开始今天的Coding节奏\n            </h1>\n            <p class=\"mt-2 text-sm text-slate-600 dark:text-slate-300\">\n              {{ today }} · 已为你聚合核心业务数据、插件动态和系统公告\n            </p>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <el-button type=\"primary\" @click=\"goLicense\">购买商业授权</el-button>\n            <el-button @click=\"goPluginMarket\">插件市场</el-button>\n          </div>\n        </div>\n      </section>\n\n      <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n        <gva-card>\n          <gva-chart :type=\"1\" title=\"访问人数\" />\n        </gva-card>\n        <gva-card>\n          <gva-chart :type=\"2\" title=\"新增客户\" />\n        </gva-card>\n        <gva-card>\n          <gva-chart :type=\"3\" title=\"解决数量\" />\n        </gva-card>\n      </div>\n\n      <div class=\"grid grid-cols-1 items-stretch gap-4 xl:grid-cols-12\">\n        <div class=\"grid grid-cols-1 gap-4 content-start xl:col-span-8 xl:h-full\">\n          <gva-card title=\"内容数据\">\n            <gva-chart :type=\"4\" />\n          </gva-card>\n\n          <gva-card title=\"最新插件\">\n            <gva-plugin-table />\n          </gva-card>\n\n          <gva-card title=\"最新更新\">\n            <gva-table />\n          </gva-card>\n        </div>\n\n        <div class=\"flex flex-col gap-4 xl:col-span-4 xl:h-full\">\n          <gva-card title=\"快捷功能\" show-action custom-class=\"min-h-[300px]\">\n            <gva-quick-link />\n          </gva-card>\n          <gva-card title=\"公告\" show-action custom-class=\"min-h-[300px]\">\n            <gva-notice />\n          </gva-card>\n          <gva-card title=\"文档\" show-action custom-class=\"min-h-[120px]\">\n            <gva-wiki />\n          </gva-card>\n          <div\n            class=\"relative min-h-[200px] flex-1 overflow-hidden rounded-lg border border-slate-200 bg-slate-900 p-5 text-white shadow-sm dark:border-slate-700\"\n          >\n            \n            <div class=\"relative\">\n              <div class=\"inline-flex rounded-full bg-white/10 px-3 py-1 text-xs\">商业授权</div>\n              <h3 class=\"mt-3 text-lg font-semibold\">解锁完整商用支持与专属服务</h3>\n              <p class=\"mt-2 text-sm text-slate-200/90\">\n                购买授权后可获得专属支持通道、插件优惠与商用合规保障，帮助团队更稳定地推进项目交付。\n              </p>\n              <div class=\"mt-4 flex flex-wrap gap-2 text-xs\">\n                <span class=\"rounded-full bg-white/10 px-2.5 py-1\">专属技术支持</span>\n                <span class=\"rounded-full bg-white/10 px-2.5 py-1\">插件优惠权益</span>\n                <span class=\"rounded-full bg-white/10 px-2.5 py-1\">商用授权凭证</span>\n              </div>\n              <div class=\"mt-5 flex items-center gap-3\">\n                <el-button type=\"primary\" @click=\"goLicense\">立即购买</el-button>\n                <el-button link class=\"!text-cyan-300\" @click=\"goPluginMarket\">查看插件市场</el-button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { computed } from 'vue'\n  import {\n    GvaPluginTable,\n    GvaTable,\n    GvaChart,\n    GvaWiki,\n    GvaNotice,\n    GvaQuickLink,\n    GvaCard\n  } from './components'\n\n  const today = computed(() => {\n    try {\n      const d = new Date()\n      return d.toLocaleDateString('zh-CN', {\n        year: 'numeric',\n        month: '2-digit',\n        day: '2-digit'\n      })\n    } catch (e) {\n      return new Date().toISOString().slice(0, 10)\n    }\n  })\n\n  const goLicense = () => {\n    window.open('https://plugin.gin-vue-admin.com/license', '_blank', 'noopener,noreferrer')\n  }\n\n  const goPluginMarket = () => {\n    window.open('https://plugin.gin-vue-admin.com', '_blank', 'noopener,noreferrer')\n  }\n\n  defineOptions({\n    name: 'Dashboard'\n  })\n</script>\n\n<style lang=\"scss\" scoped></style>\n\n"
  },
  {
    "path": "web/src/view/error/index.vue",
    "content": "<template>\n  <div>\n    <div class=\"w-full h-screen bg-gray-50 flex items-center justify-center\">\n      <div class=\"flex flex-col items-center text-2xl gap-4\">\n        <img class=\"w-1/3\" src=\"../../assets/404.png\" />\n        <p class=\"text-lg\">页面被神秘力量吸走了</p>\n        <p class=\"text-lg\">\n          常见问题为当前此角色无当前路由，如果确定要使用本路由，请到角色管理进行分配\n        </p>\n        <p>\n          项目地址：<a\n            href=\"https://github.com/flipped-aurora/gin-vue-admin\"\n            target=\"_blank\"\n            class=\"text-blue-600\"\n            >https://github.com/flipped-aurora/gin-vue-admin</a\n          >\n        </p>\n        <el-button @click=\"toDashboard\">返回首页</el-button>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { useUserStore } from '@/pinia/modules/user'\n  import { useRouter } from 'vue-router'\n  import { emitter } from '@/utils/bus'\n\n  defineOptions({\n    name: 'Error'\n  })\n\n  const userStore = useUserStore()\n  const router = useRouter()\n  const toDashboard = () => {\n    try {\n      router.push({ name: userStore.userInfo.authority.defaultRouter })\n    } catch (error) {\n        emitter.emit('show-error', {\n        code: '401',\n        message: \"检测到其他用户修改了路由权限，请重新登录\",\n        fn: () => {\n          userStore.ClearStorage()\n          router.push({ name: 'Login', replace: true })\n        }\n      })\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/view/error/reload.vue",
    "content": "<template>\n  <div />\n</template>\n\n<script setup>\n  import { useRouter } from 'vue-router'\n\n  defineOptions({\n    name: 'Reload'\n  })\n\n  const router = useRouter()\n  router.go(-1)\n</script>\n"
  },
  {
    "path": "web/src/view/example/breakpoint/breakpoint.vue",
    "content": "<template>\n  <div class=\"break-point\">\n    <div class=\"gva-table-box\">\n      <el-divider content-position=\"left\">大文件上传</el-divider>\n      <form id=\"fromCont\" method=\"post\">\n        <!-- 新增按钮容器，使用 Flexbox 对齐按钮 -->\n        <div class=\"button-container\">\n          <div class=\"fileUpload\" @click=\"inputChange\">\n            <span class=\"takeFile\">选择文件</span>\n            <input\n              v-show=\"false\"\n              id=\"file\"\n              ref=\"FileInput\"\n              multiple=\"multiple\"\n              type=\"file\"\n              @change=\"choseFile\"\n            />\n          </div>\n          <el-button\n            :disabled=\"limitFileSize\"\n            type=\"primary\"\n            class=\"uploadBtn\"\n            @click=\"getFile\"\n          >上传文件</el-button>\n        </div>\n      </form>\n      <div class=\"el-upload__tip\">请上传不超过5MB的文件</div>\n      <div class=\"list\">\n        <transition name=\"list\" tag=\"p\">\n          <div v-if=\"file\" class=\"list-item\">\n            <el-icon>\n              <document />\n            </el-icon>\n            <span>{{ file.name }}</span>\n            <span class=\"percentage\">{{ percentage }}%</span>\n            <el-progress\n              :show-text=\"false\"\n              :text-inside=\"false\"\n              :stroke-width=\"2\"\n              :percentage=\"percentage\"\n            />\n          </div>\n        </transition>\n      </div>\n      <div class=\"tips\">\n        此版本为先行体验功能测试版，样式美化和性能优化正在进行中，上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport SparkMD5 from 'spark-md5'\nimport {\n  findFile,\n  breakpointContinueFinish,\n  removeChunk,\n  breakpointContinue\n} from '@/api/breakpoint'\nimport { ref, watch } from 'vue'\nimport { ElMessage } from 'element-plus'\n\ndefineOptions({\n  name: 'BreakPoint'\n})\n\nconst file = ref(null)\nconst fileMd5 = ref('')\nconst formDataList = ref([])\nconst waitUpLoad = ref([])\nconst waitNum = ref(NaN)\nconst limitFileSize = ref(false)\nconst percentage = ref(0)\nconst percentageFlage = ref(true)\n\n// 选中文件的函数\nconst choseFile = async (e) => {\n  // 点击选择文件后取消 直接return\n  if (!e.target.files.length) {\n    return\n  }\n  const fileR = new FileReader() // 创建一个reader用来读取文件流\n  const fileInput = e.target.files[0] // 获取当前文件\n  const maxSize = 5 * 1024 * 1024\n  file.value = fileInput // file 丢全局方便后面用 可以改进为func传参形式\n  percentage.value = 0\n  if (file.value.size < maxSize) {\n    fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer  主要为了保持跟后端的流一致\n    fileR.onload = async (e) => {\n      // 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中\n      const blob = e.target.result\n      const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 （md5用于检测文件一致性 这里不懂就打电话问我）\n      spark.append(blob) // 文件流丢进工具\n      fileMd5.value = spark.end() // 工具结束 产生一个a 总文件的md5\n      const FileSliceCap = 1 * 1024 * 1024 // 分片字节数\n      let start = 0 // 定义分片开始切的地方\n      let end = 0 // 每片结束切的地方a\n      let i = 0 // 第几片\n      formDataList.value = [] // 分片存储的一个池子 丢全局\n      while (end < file.value.size) {\n        // 当结尾数字大于文件总size的时候 结束切片\n        start = i * FileSliceCap // 计算每片开始位置\n        end = (i + 1) * FileSliceCap // 计算每片结束位置\n        var fileSlice = file.value.slice(start, end) // 开始切  file.slice 为 h5方法 对文件切片 参数为 起止字节数\n        const formData = new window.FormData() // 创建FormData用于存储传给后端的信息\n        formData.append('fileMd5', fileMd5.value) // 存储总文件的Md5 让后端知道自己是谁的切片\n        formData.append('file', fileSlice) // 当前的切片\n        formData.append('chunkNumber', i) // 当前是第几片\n        formData.append('fileName', file.value.name) // 当前文件的文件名 用于后端文件切片的命名  formData.appen 为 formData对象添加参数的方法\n        formDataList.value.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子\n        i++\n      }\n      const params = {\n        fileName: file.value.name,\n        fileMd5: fileMd5.value,\n        chunkTotal: formDataList.value.length\n      }\n      const res = await findFile(params)\n      // 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片\n      const finishList = res.data.file.ExaFileChunk // 上传成功的切片\n      const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 （文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能）\n      if (!IsFinish) {\n        // 当是断点续传时候\n        waitUpLoad.value = formDataList.value.filter((all) => {\n          return !(\n            finishList &&\n            finishList.some((fi) => fi.FileChunkNumber === all.key)\n          ) // 找出需要上传的切片\n        })\n      } else {\n        waitUpLoad.value = [] // 秒传则没有需要上传的切片\n        ElMessage.success('文件已秒传!')\n      }\n      waitNum.value = waitUpLoad.value.length // 记录长度用于百分比展示\n    }\n  } else {\n    limitFileSize.value = true\n    ElMessage('请上传小于5M文件!')\n  }\n}\n\nconst getFile = () => {\n  // 确定按钮\n  if (file.value === null) {\n    ElMessage('请先上传文件!')\n    return\n  }\n  // 检查文件上传进度\n  if (percentage.value === 100) {\n    ElMessage.success('上传已完成!')  // 添加提示消息\n    percentageFlage.value = false\n    return // 如果进度已完成，阻止继续执行后续代码\n  }\n  // 如果文件未上传完成，继续上传切片\n  sliceFile() // 上传切片\n}\n\nconst sliceFile = () => {\n  waitUpLoad.value &&\n  waitUpLoad.value.forEach((item) => {\n    // 需要上传的切片\n    item.formData.append('chunkTotal', formDataList.value.length) // 切片总数携带给后台 总有用的\n    const fileR = new FileReader() // 功能同上\n    const fileF = item.formData.get('file')\n    fileR.readAsArrayBuffer(fileF)\n    fileR.onload = (e) => {\n      const spark = new SparkMD5.ArrayBuffer()\n      spark.append(e.target.result)\n      item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性\n      upLoadFileSlice(item)\n    }\n  })\n}\n\nwatch(\n  () => waitNum.value,\n  () => {\n    percentage.value = Math.floor(\n      ((formDataList.value.length - waitNum.value) /\n        formDataList.value.length) *\n      100\n    )\n  }\n)\n\nconst upLoadFileSlice = async (item) => {\n  // 切片上传\n  const fileRe = await breakpointContinue(item.formData)\n  if (fileRe.code !== 0) {\n    return\n  }\n  waitNum.value-- // 百分数增加\n  if (waitNum.value === 0) {\n    // 切片传完以后 合成文件\n    const params = {\n      fileName: file.value.name,\n      fileMd5: fileMd5.value\n    }\n    const res = await breakpointContinueFinish(params)\n    if (res.code === 0) {\n      // 合成文件过后 删除缓存切片\n      const params = {\n        fileName: file.value.name,\n        fileMd5: fileMd5.value,\n        filePath: res.data.filePath\n      }\n      ElMessage.success('上传成功')\n      await removeChunk(params)\n    }\n  }\n}\n\nconst FileInput = ref(null)\nconst inputChange = () => {\n  FileInput.value.dispatchEvent(new MouseEvent('click'))\n}\n</script>\n\n<style lang=\"scss\" scoped>\nh3 {\n  margin: 40px 0 0;\n}\nul {\n  list-style-type: none;\n  padding: 0;\n}\nli {\n  display: inline-block;\n  margin: 0 10px;\n}\na {\n  color: #42b983;\n}\n#fromCont {\n  display: inline-block;\n}\n\n.gva-table-box {\n  display: block;\n}\n\n.button-container {\n  display: flex;\n  align-items: center;\n}\n\n.fileUpload,\n.uploadBtn {\n  width: 90px;\n  height: 35px;\n  line-height: 35px;\n  font-size: 14px;\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  border-radius: 5px;\n  cursor: pointer;\n}\n\n.fileUpload {\n  padding: 0 15px;\n  background-color: #007bff;\n  color: #ffffff;\n  font-weight: 500;\n  transition: all 0.3s ease-in-out;\n  margin-right: 5px;\n}\n\n.uploadBtn {\n  background-color: #007bff;\n  color: #fff;\n  margin-left: 10px;\n}\n\n.fileUpload:hover {\n  background-color: #0056b3;\n}\n\n.uploadBtn:hover {\n  background-color: #0056b3;\n}\n\n\n.fileUpload:active,\n.uploadBtn:active {\n  transform: translateY(2px);\n}\n\n.fileUpload input {\n  position: relative;\n  font-size: 100px;\n  right: 0;\n  top: 0;\n  opacity: 0;\n  cursor: pointer;\n  width: 100%;\n  height: 100%;\n}\n\n\n\n.fileName {\n  display: inline-block;\n  vertical-align: top;\n  margin: 6px 15px 0 15px;\n}\n.tips {\n  margin-top: 30px;\n  font-size: 14px;\n  font-weight: 400;\n  color: #606266;\n}\n.el-divider {\n  margin: 0 0 30px 0;\n}\n\n.list {\n  margin-top: 15px;\n}\n.list-item {\n  display: block;\n  margin-right: 10px;\n  color: #606266;\n  line-height: 25px;\n  margin-bottom: 5px;\n  width: 40%;\n  .percentage {\n    float: right;\n  }\n}\n.list-enter-active,\n.list-leave-active {\n  transition: all 1s;\n}\n.list-enter, .list-leave-to\n  /* .list-leave-active for below version 2.1.8 */ {\n  opacity: 0;\n  transform: translateY(-30px);\n}\n</style>"
  },
  {
    "path": "web/src/view/example/customer/customer.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      title=\"在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示\"\n    />\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDrawer\"\n          >新增</el-button\n        >\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        :data=\"tableData\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        row-key=\"ID\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column align=\"left\" label=\"接入日期\" width=\"180\">\n          <template #default=\"scope\">\n            <span>{{ formatDate(scope.row.CreatedAt) }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"姓名\"\n          prop=\"customerName\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"电话\"\n          prop=\"customerPhoneData\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"接入人ID\"\n          prop=\"sysUserId\"\n          width=\"120\"\n        />\n        <el-table-column align=\"left\" label=\"操作\" min-width=\"160\">\n          <template #default=\"scope\">\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              @click=\"updateCustomer(scope.row)\"\n              >变更</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteCustomer(scope.row)\"\n              >删除</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n    <el-drawer\n      v-model=\"drawerFormVisible\"\n      :before-close=\"closeDrawer\"\n      :show-close=\"false\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">客户</span>\n          <div>\n            <el-button @click=\"closeDrawer\">取 消</el-button>\n            <el-button type=\"primary\" @click=\"enterDrawer\">确 定</el-button>\n          </div>\n        </div>\n      </template>\n      <el-form :inline=\"true\" :model=\"form\" label-width=\"80px\">\n        <el-form-item label=\"客户名\">\n          <el-input v-model=\"form.customerName\" autocomplete=\"off\" />\n        </el-form-item>\n        <el-form-item label=\"客户电话\">\n          <el-input v-model=\"form.customerPhoneData\" autocomplete=\"off\" />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    createExaCustomer,\n    updateExaCustomer,\n    deleteExaCustomer,\n    getExaCustomer,\n    getExaCustomerList\n  } from '@/api/customer'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { ref } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { formatDate } from '@/utils/format'\n\n  defineOptions({\n    name: 'Customer'\n  })\n\n  const form = ref({\n    customerName: '',\n    customerPhoneData: ''\n  })\n\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getExaCustomerList({\n      page: page.value,\n      pageSize: pageSize.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  const drawerFormVisible = ref(false)\n  const type = ref('')\n  const updateCustomer = async (row) => {\n    const res = await getExaCustomer({ ID: row.ID })\n    type.value = 'update'\n    if (res.code === 0) {\n      form.value = res.data.customer\n      drawerFormVisible.value = true\n    }\n  }\n  const closeDrawer = () => {\n    drawerFormVisible.value = false\n    form.value = {\n      customerName: '',\n      customerPhoneData: ''\n    }\n  }\n  const deleteCustomer = async (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await deleteExaCustomer({ ID: row.ID })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === 1 && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n  const enterDrawer = async () => {\n    let res\n    switch (type.value) {\n      case 'create':\n        res = await createExaCustomer(form.value)\n        break\n      case 'update':\n        res = await updateExaCustomer(form.value)\n        break\n      default:\n        res = await createExaCustomer(form.value)\n        break\n    }\n\n    if (res.code === 0) {\n      closeDrawer()\n      getTableData()\n    }\n  }\n  const openDrawer = () => {\n    type.value = 'create'\n    drawerFormVisible.value = true\n  }\n</script>\n\n<style></style>\n"
  },
  {
    "path": "web/src/view/example/index.vue",
    "content": "<template>\n  <div>\n    <router-view v-slot=\"{ Component }\">\n      <transition mode=\"out-in\" name=\"el-fade-in-linear\">\n        <keep-alive :include=\"routerStore.keepAliveRouters\">\n          <component :is=\"Component\" />\n        </keep-alive>\n      </transition>\n    </router-view>\n  </div>\n</template>\n\n<script setup>\n  import { useRouterStore } from '@/pinia/modules/router'\n  const routerStore = useRouterStore()\n  defineOptions({\n    name: 'Example'\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/example/upload/scanUpload.vue",
    "content": "<template>\n  <div class=\"flex justify-center w-full pt-2\">\n    <el-upload\n        ref=\"uploadRef\"\n        class=\"h5-uploader\"\n        :action=\"`${getBaseUrl()}/fileUploadAndDownload/upload`\"\n        accept=\"image/*\"\n        :show-file-list=\"false\"\n        :auto-upload=\"false\"\n        :headers=\"{ 'x-token': token }\"\n        :data=\"{'classId': classId}\"\n        :on-success=\"handleImageSuccess\"\n        :on-change=\"handleFileChange\"\n    >\n      <el-icon class=\"h5-uploader-icon\"><Plus /></el-icon>\n    </el-upload>\n  </div>\n\n  <div class=\"flex flex-col w-full h-auto p-0 pt-4\">\n    <!-- 左侧编辑区 -->\n    <div class=\"flex-1 min-h-[60vh]\">\n      <div class=\"w-screen h-[calc(100vh-175px)] rounded\">\n        <template v-if=\"isCrop\">\n          <VueCropper\n              ref=\"cropperRef\"\n              :img=\"imgSrc\"\n              mode=\"contain\"\n              outputType=\"jpeg\"\n              :autoCrop=\"true\"\n              :autoCropWidth=\"cropWidth\"\n              :autoCropHeight=\"cropHeight\"\n              :fixedBox=\"false\"\n              :fixed=\"fixedRatio\"\n              :fixedNumber=\"fixedNumber\"\n              :centerBox=\"true\"\n              :canMoveBox=\"true\"\n              :full=\"false\"\n              :maxImgSize=\"windowWidth\"\n              :original=\"true\"\n          ></VueCropper>\n        </template>\n        <template v-else>\n          <div class=\"flex justify-center items-center w-full h-[calc(100vh-175px)]\">\n            <el-image v-if=\"imgSrc\" :src=\"imgSrc\" class=\"max-w-full max-h-full\" mode=\"cover\" />\n          </div>\n        </template>\n      </div>\n    </div>\n  </div>\n  <!-- 工具栏 -->\n  <div class=\"toolbar\">\n    <el-button-group v-if=\"isCrop\">\n      <el-tooltip content=\"向左旋转\">\n        <el-button @click=\"rotate(-90)\" :icon=\"RefreshLeft\" />\n      </el-tooltip>\n      <el-tooltip content=\"向右旋转\">\n        <el-button @click=\"rotate(90)\" :icon=\"RefreshRight\" />\n      </el-tooltip>\n      <el-button :icon=\"Plus\" @click=\"changeScale(1)\"></el-button>\n      <el-button :icon=\"Minus\" @click=\"changeScale(-1)\"></el-button>\n    </el-button-group>\n\n\n    <el-switch\n        size=\"large\"\n        v-model=\"isCrop\"\n        inline-prompt\n        active-text=\"裁剪\"\n        inactive-text=\"裁剪\"\n    />\n\n    <el-button type=\"primary\" @click=\"handleUpload\" :loading=\"uploading\"> {{ uploading ? '上传中...' : '上 传' }}\n    </el-button>\n  </div>\n\n\n</template>\n\n<script setup>\nimport { ref, getCurrentInstance, onMounted } from 'vue'\nimport { ElLoading, ElMessage } from 'element-plus'\nimport { RefreshLeft, RefreshRight, Plus, Minus } from '@element-plus/icons-vue'\nimport 'vue-cropper/dist/index.css'\nimport { VueCropper } from 'vue-cropper'\nimport { getBaseUrl } from '@/utils/format'\nimport { useRouter } from 'vue-router'\nimport { useUserStore } from \"@/pinia\";\n\ndefineOptions({\n  name: 'scanUpload'\n})\n\nconst classId = ref(0)\nconst token = ref('')\nconst isCrop = ref(false)\n\nconst windowWidth = ref(300)\n\n// 获取屏幕宽度\nconst getWindowResize = function() {\n  windowWidth.value = window.innerWidth\n}\n\n// 生命周期\nonMounted(() => {\n  getWindowResize()\n  window.addEventListener('resize', getWindowResize)\n})\n\nconst router = useRouter()\nrouter.isReady().then(() => {\n  let query = router.currentRoute.value.query\n  //console.log(query)\n  classId.value = query.id\n  token.value = query.token\n}).catch((err) => {\n  console.log(err)\n})\n\nconst uploadRef = ref(null)\n// 响应式数据\nconst imgSrc = ref('')\nconst cropperRef = ref(null)\nconst { proxy } = getCurrentInstance()\nconst previews = ref({})\nconst uploading = ref(false)\n\n// 缩放控制\nconst changeScale = (value) => {\n  proxy.$refs.cropperRef.changeScale(value)\n}\n\nconst fixedNumber = ref([1, 1])\nconst cropWidth = ref(300)\nconst cropHeight = ref(300)\n\nconst fixedRatio = ref(false)\n\n// 文件处理\nconst handleFileChange = (file) => {\n  const isImage = file.raw.type.includes('image')\n  if (!isImage) {\n    ElMessage.error('请选择图片文件')\n    return\n  }\n\n  if (file.raw.size / 1024 / 1024 > 8) {\n    ElMessage.error('文件大小不能超过8MB!')\n    return false\n  }\n\n  const loading = ElLoading.service({\n    lock: true,\n    text: '请稍后',\n    background: 'rgba(0, 0, 0, 0.7)',\n  })\n\n  const reader = new FileReader()\n  reader.onload = (e) => {\n    imgSrc.value = e.target.result\n    loading.close()\n  }\n  reader.readAsDataURL(file.raw)\n}\n\n// 旋转控制\nconst rotate = (degree) => {\n  if (degree === -90) {\n    proxy.$refs.cropperRef.rotateLeft()\n  } else {\n    proxy.$refs.cropperRef.rotateRight()\n  }\n}\n\n// 上传处理\nconst handleUpload = () => {\n  uploading.value = true\n  if(isCrop.value === false){\n    uploadRef.value.submit()\n    return true\n  }\n  proxy.$refs.cropperRef.getCropBlob((blob) => {\n    try {\n      const file = new File([blob], `${Date.now()}.jpg`, { type: 'image/jpeg' })\n      uploadRef.value.clearFiles()\n      uploadRef.value.handleStart(file)\n      uploadRef.value.submit()\n\n    } catch (error) {\n      uploading.value = false\n      ElMessage.error('上传失败: ' + error.message)\n    }\n  })\n}\n\nconst handleImageSuccess = (res) => {\n  const { data } = res\n  if (data) {\n    imgSrc.value = null\n    uploading.value = false\n    previews.value = {}\n    ElMessage.success('上传成功')\n  }\n}\n\n</script>\n\n<style scoped>\n\n/* 工具栏（固定在底部） */\n.toolbar {\n  @apply fixed bottom-0 m-0 rounded-none p-2.5 shadow-[0_-2px_10px_rgba(0,0,0,0.1)] z-[1000] flex justify-between w-screen bg-slate-900;\n\n  /* 按钮组适配 */\n  .el-button-group {\n    @apply flex gap-2;\n\n    .el-button {\n      @apply p-2 w-10;\n    }\n  }\n}\n\n:deep(.vue-cropper) {\n  @apply bg-transparent;\n}\n\n</style>\n\n<style>\n.h5-uploader .el-upload {\n  @apply rounded cursor-pointer relative overflow-hidden;\n  border: 1px dashed var(--el-border-color);\n  border-radius: 6px;\n  transition: var(--el-transition-duration-fast);\n}\n\n.h5-uploader .el-upload:hover {\n  border-color: var(--el-color-primary);\n}\n\n.el-icon.h5-uploader-icon {\n  @apply text-2xl text-gray-500 w-32 h-32 text-center;\n}\n</style>\n"
  },
  {
    "path": "web/src/view/example/upload/upload.vue",
    "content": "<template>\n  <div v-loading.fullscreen.lock=\"fullscreenLoading\">\n    <div class=\"flex gap-4 pt-2\">\n      <div\n        class=\"flex-none w-64 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded p-4\"\n      >\n        <el-scrollbar style=\"height: calc(100vh - 300px)\">\n          <el-tree\n            :data=\"categories\"\n            node-key=\"id\"\n            :props=\"defaultProps\"\n            @node-click=\"handleNodeClick\"\n            default-expand-all\n          >\n            <template #default=\"{ node, data }\">\n              <div\n                class=\"w-36\"\n                :class=\"\n                  search.classId === data.ID ? 'text-blue-500 font-bold' : ''\n                \"\n              >\n                {{ data.name }}\n              </div>\n              <el-dropdown>\n                <el-icon class=\"ml-3 text-right\" v-if=\"data.ID > 0\"\n                  ><MoreFilled\n                /></el-icon>\n                <el-icon class=\"ml-3 text-right mt-1\" v-else><Plus /></el-icon>\n                <template #dropdown>\n                  <el-dropdown-menu>\n                    <el-dropdown-item @click=\"addCategoryFun(data)\"\n                      >添加分类</el-dropdown-item\n                    >\n                    <el-dropdown-item\n                      @click=\"editCategory(data)\"\n                      v-if=\"data.ID > 0\"\n                      >编辑分类</el-dropdown-item\n                    >\n                    <el-dropdown-item\n                      @click=\"deleteCategoryFun(data.ID)\"\n                      v-if=\"data.ID > 0\"\n                      >删除分类</el-dropdown-item\n                    >\n                  </el-dropdown-menu>\n                </template>\n              </el-dropdown>\n            </template>\n          </el-tree>\n        </el-scrollbar>\n      </div>\n      <div\n        class=\"flex-1 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900\"\n      >\n        <div class=\"gva-table-box mt-0 mb-0\">\n          <warning-bar\n            title=\"点击“文件名”可以编辑；选择的类别即是上传的类别。\"\n          />\n          <div class=\"gva-btn-list gap-3\">\n            <upload-common\n              :image-common=\"imageCommon\"\n              :classId=\"search.classId\"\n              @on-success=\"onSuccess\"\n            />\n            <cropper-image :classId=\"search.classId\" @on-success=\"onSuccess\" />\n            <QRCodeUpload :classId=\"search.classId\" @on-success=\"onSuccess\" />\n            <upload-image\n              :image-url=\"imageUrl\"\n              :file-size=\"512\"\n              :max-w-h=\"1080\"\n              :classId=\"search.classId\"\n              @on-success=\"onSuccess\"\n            />\n            <el-button type=\"primary\" icon=\"upload\" @click=\"importUrlFunc\">\n              导入URL\n            </el-button>\n            <el-input\n              v-model=\"search.keyword\"\n              class=\"w-72\"\n              placeholder=\"请输入文件名或备注\"\n            />\n            <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\"\n              >查询\n            </el-button>\n          </div>\n\n          <el-table :data=\"tableData\">\n            <el-table-column align=\"left\" label=\"预览\" width=\"100\">\n              <template #default=\"scope\">\n                <CustomPic pic-type=\"file\" :pic-src=\"scope.row.url\" preview />\n              </template>\n            </el-table-column>\n            <el-table-column\n              align=\"left\"\n              label=\"日期\"\n              prop=\"UpdatedAt\"\n              width=\"180\"\n            >\n              <template #default=\"scope\">\n                <div>{{ formatDate(scope.row.UpdatedAt) }}</div>\n              </template>\n            </el-table-column>\n            <el-table-column\n              align=\"left\"\n              label=\"文件名/备注\"\n              prop=\"name\"\n              width=\"180\"\n            >\n              <template #default=\"scope\">\n                <div\n                  class=\"cursor-pointer\"\n                  @click=\"editFileNameFunc(scope.row)\"\n                >\n                  {{ scope.row.name }}\n                </div>\n              </template>\n            </el-table-column>\n            <el-table-column\n              align=\"left\"\n              label=\"链接\"\n              prop=\"url\"\n              min-width=\"300\"\n            />\n            <el-table-column align=\"left\" label=\"标签\" prop=\"tag\" width=\"100\">\n              <template #default=\"scope\">\n                <el-tag\n                  :type=\"\n                    scope.row.tag?.toLowerCase() === 'jpg' ? 'info' : 'success'\n                  \"\n                  disable-transitions\n                  >{{ scope.row.tag }}\n                </el-tag>\n              </template>\n            </el-table-column>\n            <el-table-column align=\"left\" label=\"操作\" width=\"160\">\n              <template #default=\"scope\">\n                <el-button\n                  icon=\"download\"\n                  type=\"primary\"\n                  link\n                  @click=\"downloadFile(scope.row)\"\n                  >下载\n                </el-button>\n                <el-button\n                  icon=\"delete\"\n                  type=\"primary\"\n                  link\n                  @click=\"deleteFileFunc(scope.row)\"\n                  >删除\n                </el-button>\n              </template>\n            </el-table-column>\n          </el-table>\n          <div class=\"gva-pagination\">\n            <el-pagination\n              :current-page=\"page\"\n              :page-size=\"pageSize\"\n              :page-sizes=\"[10, 30, 50, 100]\"\n              :style=\"{ float: 'right', padding: '20px' }\"\n              :total=\"total\"\n              layout=\"total, sizes, prev, pager, next, jumper\"\n              @current-change=\"handleCurrentChange\"\n              @size-change=\"handleSizeChange\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <!-- 添加分类弹窗 -->\n    <el-dialog\n      v-model=\"categoryDialogVisible\"\n      @close=\"closeAddCategoryDialog\"\n      width=\"520\"\n      :title=\"(categoryFormData.ID === 0 ? '添加' : '编辑') + '分类'\"\n      draggable\n    >\n      <el-form\n        ref=\"categoryForm\"\n        :rules=\"rules\"\n        :model=\"categoryFormData\"\n        label-width=\"80px\"\n      >\n        <el-form-item label=\"上级分类\">\n          <el-tree-select\n            v-model=\"categoryFormData.pid\"\n            :data=\"categories\"\n            check-strictly\n            :props=\"defaultProps\"\n            :render-after-expand=\"false\"\n            style=\"width: 240px\"\n          />\n        </el-form-item>\n        <el-form-item label=\"分类名称\" prop=\"name\">\n          <el-input\n            v-model.trim=\"categoryFormData.name\"\n            placeholder=\"分类名称\"\n          ></el-input>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"closeAddCategoryDialog\">取消</el-button>\n        <el-button type=\"primary\" @click=\"confirmAddCategory\">确定</el-button>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getFileList,\n    deleteFile,\n    editFileName,\n    importURL\n  } from '@/api/fileUploadAndDownload'\n  import { downloadImage } from '@/utils/downloadImg'\n  import CustomPic from '@/components/customPic/index.vue'\n  import UploadImage from '@/components/upload/image.vue'\n  import UploadCommon from '@/components/upload/common.vue'\n  import { CreateUUID, formatDate } from '@/utils/format'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n\n  import { ref } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import {\n    addCategory,\n    deleteCategory,\n    getCategoryList\n  } from '@/api/attachmentCategory'\n  import CropperImage from '@/components/upload/cropper.vue'\n  import QRCodeUpload from '@/components/upload/QR-code.vue'\n\n  defineOptions({\n    name: 'Upload'\n  })\n\n  const fullscreenLoading = ref(false)\n  const path = ref(import.meta.env.VITE_BASE_API)\n\n  const imageUrl = ref('')\n  const imageCommon = ref('')\n\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const search = ref({\n    keyword: null,\n    classId: 0\n  })\n  const tableData = ref([])\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  const onSubmit = () => {\n    search.value.classId = 0\n    page.value = 1\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getFileList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...search.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n  getTableData()\n\n  const deleteFileFunc = async (row) => {\n    ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    })\n      .then(async () => {\n        const res = await deleteFile(row)\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '删除成功!'\n          })\n          if (tableData.value.length === 1 && page.value > 1) {\n            page.value--\n          }\n          await getTableData()\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '已取消删除'\n        })\n      })\n  }\n\n  const downloadFile = (row) => {\n    if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {\n      downloadImage(row.url, row.name)\n    } else {\n      downloadImage(path.value + '/' + row.url, row.name)\n    }\n  }\n\n  /**\n   * 编辑文件名或者备注\n   * @param row\n   * @returns {Promise<void>}\n   */\n  const editFileNameFunc = async (row) => {\n    ElMessageBox.prompt('请输入文件名或者备注', '编辑', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      inputPattern: /\\S/,\n      inputErrorMessage: '不能为空',\n      inputValue: row.name\n    })\n      .then(async ({ value }) => {\n        row.name = value\n        // console.log(row)\n        const res = await editFileName(row)\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '编辑成功!'\n          })\n          await getTableData()\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '取消修改'\n        })\n      })\n  }\n\n  /**\n   * 导入URL\n   */\n  const importUrlFunc = () => {\n    ElMessageBox.prompt('格式：文件名|链接或者仅链接。', '导入', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      inputType: 'textarea',\n      inputPlaceholder:\n        '我的图片|https://my-oss.com/my.png\\nhttps://my-oss.com/my_1.png',\n      inputPattern: /\\S/,\n      inputErrorMessage: '不能为空'\n    })\n      .then(async ({ value }) => {\n        let data = value.split('\\n')\n        let importData = []\n        data.forEach((item) => {\n          let oneData = item.trim().split('|')\n          let url, name\n          if (oneData.length > 1) {\n            name = oneData[0].trim()\n            url = oneData[1]\n          } else {\n            url = oneData[0].trim()\n            let str = url.substring(url.lastIndexOf('/') + 1)\n            name = str.substring(0, str.lastIndexOf('.'))\n          }\n          if (url) {\n            importData.push({\n              name: name,\n              url: url,\n              classId: search.value.classId,\n              tag: url.substring(url.lastIndexOf('.') + 1),\n              key: CreateUUID()\n            })\n          }\n        })\n\n        const res = await importURL(importData)\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '导入成功!'\n          })\n          await getTableData()\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '取消导入'\n        })\n      })\n  }\n\n  const onSuccess = () => {\n    search.value.keyword = null\n    page.value = 1\n    getTableData()\n  }\n\n  const defaultProps = {\n    children: 'children',\n    label: 'name',\n    value: 'ID'\n  }\n\n  const categories = ref([])\n  const fetchCategories = async () => {\n    const res = await getCategoryList()\n    let data = {\n      name: '全部分类',\n      ID: 0,\n      pid: 0,\n      children: []\n    }\n    if (res.code === 0) {\n      categories.value = res.data || []\n      categories.value.unshift(data)\n    }\n  }\n\n  const handleNodeClick = (node) => {\n    search.value.keyword = null\n    search.value.classId = node.ID\n    page.value = 1\n    getTableData()\n  }\n\n  const categoryDialogVisible = ref(false)\n  const categoryFormData = ref({\n    ID: 0,\n    pid: 0,\n    name: ''\n  })\n\n  const categoryForm = ref(null)\n  const rules = ref({\n    name: [\n      { required: true, message: '请输入分类名称', trigger: 'blur' },\n      { max: 20, message: '最多20位字符', trigger: 'blur' }\n    ]\n  })\n\n  const addCategoryFun = (category) => {\n    categoryDialogVisible.value = true\n    categoryFormData.value.ID = 0\n    categoryFormData.value.pid = category.ID\n  }\n\n  const editCategory = (category) => {\n    categoryFormData.value = {\n      ID: category.ID,\n      pid: category.pid,\n      name: category.name\n    }\n    categoryDialogVisible.value = true\n  }\n\n  const deleteCategoryFun = async (id) => {\n    const res = await deleteCategory({ id: id })\n    if (res.code === 0) {\n      ElMessage.success({ type: 'success', message: '删除成功' })\n      await fetchCategories()\n    }\n  }\n\n  const confirmAddCategory = async () => {\n    categoryForm.value.validate(async (valid) => {\n      if (valid) {\n        const res = await addCategory(categoryFormData.value)\n        if (res.code === 0) {\n          ElMessage({ type: 'success', message: '操作成功' })\n          await fetchCategories()\n          closeAddCategoryDialog()\n        }\n      }\n    })\n  }\n\n  const closeAddCategoryDialog = () => {\n    categoryDialogVisible.value = false\n    categoryFormData.value = {\n      ID: 0,\n      pid: 0,\n      name: ''\n    }\n  }\n\n  fetchCategories()\n</script>\n"
  },
  {
    "path": "web/src/view/init/index.vue",
    "content": "<template>\n  <div\n    class=\"rounded-lg flex items-center justify-evenly w-full h-full relative md:w-screen md:h-screen md:bg-[#194bfb] overflow-hidden\"\n  >\n    <div\n      class=\"rounded-md w-full h-full flex items-center justify-center overflow-hidden\"\n    >\n      <div\n        class=\"oblique h-[130%] w-3/5 bg-white dark:bg-slate-900 transform -rotate-12 absolute -ml-80\"\n      />\n      <div\n        v-if=\"!page.showForm\"\n        :class=\"[page.showReadme ? 'slide-out-right' : 'slide-in-fwd-top']\"\n      >\n        <div class=\"text-lg\">\n          <div\n            class=\"font-sans text-4xl font-bold text-center mb-4 dark:text-white\"\n          >\n            GIN-VUE-ADMIN\n          </div>\n          <p class=\"text-gray-600 dark:text-gray-300 mb-2\">初始化须知</p>\n          <p class=\"text-gray-600 dark:text-gray-300 mb-2\">\n            1.您需有用一定的VUE和GOLANG基础\n          </p>\n          <p class=\"text-gray-600 dark:text-gray-300 mb-2\">\n            2.请您确认是否已经阅读过<a\n              class=\"text-blue-600 font-bold\"\n              href=\"https://www.gin-vue-admin.com\"\n              target=\"_blank\"\n              >官方文档</a\n            >\n            <a\n              class=\"text-blue-600 font-bold\"\n              href=\"https://www.bilibili.com/video/BV1kv4y1g7nT?p=2\"\n              target=\"_blank\"\n              >初始化视频</a\n            >\n          </p>\n          <p class=\"text-gray-600 dark:text-gray-300 mb-2\">\n            3.请您确认是否了解后续的配置流程\n          </p>\n          <p class=\"text-gray-600 dark:text-gray-300 mb-2\">\n            4.如果您使用mysql数据库，请确认数据库引擎为<span\n              class=\"text-red-600 font-bold text-3xl ml-2\"\n              >innoDB</span\n            >\n          </p>\n          <p class=\"text-gray-600 dark:text-gray-300 mb-2\">\n            注：开发组不为文档中书写过的内容提供无偿服务\n          </p>\n          <p class=\"flex items-center justify-between mt-8\">\n            <el-button type=\"primary\" size=\"large\" @click=\"goDoc\">\n              阅读文档\n            </el-button>\n            <el-button type=\"primary\" size=\"large\" @click=\"showNext\">\n              我已确认\n            </el-button>\n          </p>\n        </div>\n      </div>\n      <div\n        v-if=\"page.showForm\"\n        :class=\"[page.showForm ? 'slide-in-left' : 'slide-out-right']\"\n        class=\"w-96\"\n      >\n        <el-form ref=\"formRef\" :model=\"form\" label-width=\"100px\" size=\"large\">\n          <el-form-item label=\"管理员密码\">\n            <el-input\n              v-model=\"form.adminPassword\"\n              placeholder=\"admin账号的默认密码\"\n            ></el-input>\n          </el-form-item>\n          <el-form-item label=\"数据库类型\">\n            <el-select\n              v-model=\"form.dbType\"\n              placeholder=\"请选择\"\n              class=\"w-full\"\n              @change=\"changeDB\"\n            >\n              <el-option key=\"mysql\" label=\"mysql\" value=\"mysql\" />\n              <el-option key=\"pgsql\" label=\"pgsql\" value=\"pgsql\" />\n              <el-option key=\"oracle\" label=\"oracle\" value=\"oracle\" />\n              <el-option key=\"mssql\" label=\"mssql\" value=\"mssql\" />\n              <el-option key=\"sqlite\" label=\"sqlite\" value=\"sqlite\" />\n            </el-select>\n          </el-form-item>\n          <el-form-item v-if=\"form.dbType !== 'sqlite'\" label=\"host\">\n            <el-input v-model=\"form.host\" placeholder=\"请输入数据库链接\" />\n          </el-form-item>\n          <el-form-item v-if=\"form.dbType !== 'sqlite'\" label=\"port\">\n            <el-input v-model=\"form.port\" placeholder=\"请输入数据库端口\" />\n          </el-form-item>\n          <el-form-item v-if=\"form.dbType !== 'sqlite'\" label=\"userName\">\n            <el-input\n              v-model=\"form.userName\"\n              placeholder=\"请输入数据库用户名\"\n            />\n          </el-form-item>\n          <el-form-item v-if=\"form.dbType !== 'sqlite'\" label=\"password\">\n            <el-input\n              v-model=\"form.password\"\n              placeholder=\"请输入数据库密码（没有则为空）\"\n            />\n          </el-form-item>\n          <el-form-item label=\"dbName\">\n            <el-input v-model=\"form.dbName\" placeholder=\"请输入数据库名称\" />\n          </el-form-item>\n          <el-form-item v-if=\"form.dbType === 'sqlite'\" label=\"dbPath\">\n            <el-input\n              v-model=\"form.dbPath\"\n              placeholder=\"请输入sqlite数据库文件存放路径\"\n            />\n          </el-form-item>\n          <el-form-item v-if=\"form.dbType === 'pgsql'\" label=\"template\">\n            <el-input\n              v-model=\"form.template\"\n              placeholder=\"请输入postgresql指定template\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <div style=\"text-align: right\">\n              <el-button type=\"primary\" @click=\"onSubmit\">立即初始化</el-button>\n            </div>\n          </el-form-item>\n        </el-form>\n      </div>\n    </div>\n\n    <div class=\"hidden md:block w-1/2 h-full float-right bg-[#194bfb]\">\n      <img class=\"h-full\" src=\"@/assets/login_right_banner.jpg\" alt=\"banner\" />\n    </div>\n  </div>\n</template>\n\n<script setup>\n  // @ts-ignore\n  import { initDB } from '@/api/initdb'\n  import { reactive, ref } from 'vue'\n  import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'\n  import { useRouter } from 'vue-router'\n\n  defineOptions({\n    name: 'Init'\n  })\n\n  const router = useRouter()\n\n  const page = reactive({\n    showReadme: false,\n    showForm: false\n  })\n\n  const showNext = () => {\n    page.showReadme = false\n    setTimeout(() => {\n      page.showForm = true\n    }, 20)\n  }\n\n  const goDoc = () => {\n    window.open('https://www.gin-vue-admin.com/guide/start-quickly/env.html')\n  }\n\n  const out = ref(false)\n\n  const form = reactive({\n    adminPassword: '123456',\n    dbType: 'mysql',\n    host: '127.0.0.1',\n    port: '3306',\n    userName: 'root',\n    password: '',\n    dbName: 'gva',\n    dbPath: ''\n  })\n\n  const changeDB = (val) => {\n    switch (val) {\n      case 'mysql':\n        Object.assign(form, {\n          adminPassword: '123456',\n          reAdminPassword: '',\n          dbType: 'mysql',\n          host: '127.0.0.1',\n          port: '3306',\n          userName: 'root',\n          password: '',\n          dbName: 'gva',\n          dbPath: ''\n        })\n        break\n      case 'pgsql':\n        Object.assign(form, {\n          adminPassword: '123456',\n          dbType: 'pgsql',\n          host: '127.0.0.1',\n          port: '5432',\n          userName: 'postgres',\n          password: '',\n          dbName: 'gva',\n          dbPath: '',\n          template: 'template0'\n        })\n        break\n      case 'oracle':\n        Object.assign(form, {\n          adminPassword: '123456',\n          dbType: 'oracle',\n          host: '127.0.0.1',\n          port: '1521',\n          userName: 'oracle',\n          password: '',\n          dbName: 'gva',\n          dbPath: ''\n        })\n        break\n      case 'mssql':\n        Object.assign(form, {\n          adminPassword: '123456',\n          dbType: 'mssql',\n          host: '127.0.0.1',\n          port: '1433',\n          userName: 'mssql',\n          password: '',\n          dbName: 'gva',\n          dbPath: ''\n        })\n        break\n      case 'sqlite':\n        Object.assign(form, {\n          adminPassword: '123456',\n          dbType: 'sqlite',\n          host: '',\n          port: '',\n          userName: '',\n          password: '',\n          dbName: 'gva',\n          dbPath: ''\n        })\n        break\n      default:\n        Object.assign(form, {\n          adminPassword: '123456',\n          dbType: 'mysql',\n          host: '127.0.0.1',\n          port: '3306',\n          userName: 'root',\n          password: '',\n          dbName: 'gva',\n          dbPath: ''\n        })\n    }\n  }\n  const onSubmit = async () => {\n    if (form.adminPassword.length < 6) {\n      ElMessage({\n        type: 'error',\n        message: '密码长度不能小于6位'\n      })\n      return\n    }\n\n    const loading = ElLoading.service({\n      lock: true,\n      text: '正在初始化数据库，请稍候',\n      spinner: 'loading',\n      background: 'rgba(0, 0, 0, 0.7)'\n    })\n    try {\n      const res = await initDB(form)\n      if (res.code === 0) {\n        out.value = true\n        ElMessage({\n          type: 'success',\n          message: res.msg\n        })\n        \n        // 显示AI助手配置提示弹窗\n        ElMessageBox.confirm(\n          '已经完成基础数据库初始化！建议先进行编辑器AI助手配置，以获得更好的开发体验。',\n          '配置完成',\n          {\n            confirmButtonText: '查看AI配置文档',\n            cancelButtonText: '稍后配置',\n            type: 'success',\n            center: true\n          }\n        ).then(() => {\n          // 点击确认按钮，打开AI配置文档\n          window.open('https://www.gin-vue-admin.com/guide/server/mcp.html', '_blank')\n          router.push({ name: 'Login' })\n        }).catch(() => {\n          // 点击取消按钮或关闭弹窗，直接跳转到登录页\n          router.push({ name: 'Login' })\n        })\n      }\n      loading.close()\n    } catch (_) {\n      loading.close()\n    }\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .slide-in-fwd-top {\n    -webkit-animation: slide-in-fwd-top 0.4s\n      cubic-bezier(0.25, 0.46, 0.45, 0.94) both;\n    animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;\n  }\n  .slide-out-right {\n    -webkit-animation: slide-out-right 0.5s\n      cubic-bezier(0.55, 0.085, 0.68, 0.53) both;\n    animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;\n  }\n  .slide-in-left {\n    -webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)\n      both;\n    animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;\n  }\n  @-webkit-keyframes slide-in-fwd-top {\n    0% {\n      transform: translateZ(-1400px) translateY(-800px);\n      opacity: 0;\n    }\n    100% {\n      transform: translateZ(0) translateY(0);\n      opacity: 1;\n    }\n  }\n  @keyframes slide-in-fwd-top {\n    0% {\n      transform: translateZ(-1400px) translateY(-800px);\n      opacity: 0;\n    }\n    100% {\n      transform: translateZ(0) translateY(0);\n      opacity: 1;\n    }\n  }\n  @-webkit-keyframes slide-out-right {\n    0% {\n      transform: translateX(0);\n      opacity: 1;\n    }\n    100% {\n      transform: translateX(1000px);\n      opacity: 0;\n    }\n  }\n  @keyframes slide-out-right {\n    0% {\n      transform: translateX(0);\n      opacity: 1;\n    }\n    100% {\n      transform: translateX(1000px);\n      opacity: 0;\n    }\n  }\n  @-webkit-keyframes slide-in-left {\n    0% {\n      transform: translateX(-1000px);\n      opacity: 0;\n    }\n    100% {\n      transform: translateX(0);\n      opacity: 1;\n    }\n  }\n  @keyframes slide-in-left {\n    0% {\n      transform: translateX(-1000px);\n      opacity: 0;\n    }\n    100% {\n      transform: translateX(0);\n      opacity: 1;\n    }\n  }\n  @media (max-width: 750px) {\n    .form {\n      width: 94vw !important;\n      padding: 0;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/layout/aside/asideComponent/asyncSubmenu.vue",
    "content": "<template>\n  <el-sub-menu\n    ref=\"subMenu\"\n    :index=\"routerInfo.name\"\n    class=\"gva-sub-menu dark:text-slate-300 relative\"\n  >\n    <template #title>\n      <div\n        v-if=\"!isCollapse\"\n        class=\"flex items-center\"\n        :style=\"{\n          height: sideHeight\n        }\"\n      >\n        <el-icon v-if=\"routerInfo.meta.icon\">\n          <component :is=\"routerInfo.meta.icon\" />\n        </el-icon>\n        <span>{{ routerInfo.meta.title }}</span>\n      </div>\n      <template v-else>\n        <el-icon v-if=\"routerInfo.meta.icon\">\n          <component :is=\"routerInfo.meta.icon\" />\n        </el-icon>\n        <span>{{ routerInfo.meta.title }}</span>\n      </template>\n    </template>\n    <slot />\n  </el-sub-menu>\n</template>\n\n<script setup>\n  import { inject, computed } from 'vue'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { config } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'AsyncSubmenu'\n  })\n\n  defineProps({\n    routerInfo: {\n      default: function () {\n        return null\n      },\n      type: Object\n    }\n  })\n\n  const isCollapse = inject('isCollapse', {\n    default: false\n  })\n\n  const sideHeight = computed(() => {\n    return config.value.layout_side_item_height + 'px'\n  })\n</script>\n\n<style lang=\"scss\">\n  .gva-sub-menu {\n    .el-sub-menu__title {\n      height: v-bind('sideHeight') !important;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/layout/aside/asideComponent/index.vue",
    "content": "<template>\n  <component\n    :is=\"menuComponent\"\n    v-if=\"!routerInfo.hidden\"\n    :router-info=\"routerInfo\"\n  >\n    <template v-if=\"routerInfo.children && routerInfo.children.length\">\n      <AsideComponent\n        v-for=\"item in routerInfo.children\"\n        :key=\"item.name\"\n        :router-info=\"item\"\n      />\n    </template>\n  </component>\n</template>\n\n<script setup>\n  import MenuItem from './menuItem.vue'\n  import AsyncSubmenu from './asyncSubmenu.vue'\n  import { computed } from 'vue'\n\n  defineOptions({\n    name: 'AsideComponent'\n  })\n\n  const props = defineProps({\n    routerInfo: {\n      type: Object,\n      default: () => null\n    },\n    mode: {\n      type: String,\n      default: 'vertical'\n    }\n  })\n\n  const menuComponent = computed(() => {\n    if (\n      props.routerInfo.children &&\n      props.routerInfo.children?.filter((item) => !item.hidden).length\n    ) {\n      return AsyncSubmenu\n    } else {\n      return MenuItem\n    }\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/layout/aside/asideComponent/menuItem.vue",
    "content": "<template>\n  <el-menu-item\n    :index=\"routerInfo.name\"\n    :style=\"{\n          height: sideHeight\n        }\"\n  >\n    <el-icon v-if=\"routerInfo.meta.icon\">\n      <component :is=\"routerInfo.meta.icon\" />\n    </el-icon>\n    <template v-else>\n      {{ isCollapse ? routerInfo.meta.title[0] : \"\" }}\n    </template>\n    <template #title>\n      {{ routerInfo.meta.title }}\n    </template>\n  </el-menu-item>\n</template>\n\n<script setup>\nimport {computed, inject} from 'vue'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { config } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'MenuItem'\n  })\n\n  defineProps({\n    routerInfo: {\n      default: function () {\n        return null\n      },\n      type: Object\n    }\n  })\n\nconst isCollapse = inject('isCollapse', {\n  default: false\n})\n\n  const sideHeight = computed(() => {\n    return config.value.layout_side_item_height + 'px'\n  })\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/layout/aside/combinationMode.vue",
    "content": "<template>\n  <div class=\"h-full\">\n    <div\n      v-if=\"mode === 'head'\"\n      class=\"bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-[calc(100vw-600px)] overflow-auto\"\n    >\n      <el-menu\n        :default-active=\"routerStore.topActive\"\n        mode=\"horizontal\"\n        class=\"!border-r-0 border-b-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]\"\n        unique-opened\n        @select=\"(index, _, ele) => selectMenuItem(index, _, ele, true)\"\n      >\n        <template v-for=\"item in routerStore.topMenu\">\n          <aside-component\n            v-if=\"!item.hidden\"\n            :key=\"item.name\"\n            :router-info=\"item\"\n            mode=\"horizontal\"\n          />\n        </template>\n      </el-menu>\n    </div>\n    <div\n      v-if=\"mode === 'normal'\"\n      class=\"relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700\"\n      :class=\"isCollapse ? '' : '  px-2'\"\n      :style=\"{\n        width: layoutSideWidth + 'px'\n      }\"\n    >\n      <el-scrollbar>\n        <el-menu\n          :collapse=\"isCollapse\"\n          :collapse-transition=\"false\"\n          :default-active=\"active\"\n          class=\"!border-r-0 w-full\"\n          unique-opened\n          @select=\"(index, _, ele) => selectMenuItem(index, _, ele, false)\"\n        >\n          <template v-for=\"item in routerStore.leftMenu\">\n            <aside-component\n              v-if=\"!item.hidden\"\n              :key=\"item.name\"\n              :router-info=\"item\"\n            />\n          </template>\n        </el-menu>\n      </el-scrollbar>\n      <div\n        class=\"absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer\"\n        :class=\"isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'\"\n        @click=\"toggleCollapse\"\n      >\n        <el-icon v-if=\"!isCollapse\">\n          <DArrowLeft />\n        </el-icon>\n        <el-icon v-else>\n          <DArrowRight />\n        </el-icon>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup>\n  import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'\n  import { ref, provide, watchEffect, computed } from 'vue'\n  import { useRoute, useRouter } from 'vue-router'\n  import { useRouterStore } from '@/pinia/modules/router'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { device, config } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'GvaAside'\n  })\n\n  defineProps({\n    mode: {\n      type: String,\n      default: 'normal'\n    }\n  })\n\n  const route = useRoute()\n  const router = useRouter()\n  const routerStore = useRouterStore()\n  const isCollapse = ref(false)\n  const active = ref('')\n  const layoutSideWidth = computed(() => {\n    if (!isCollapse.value) {\n      return config.value.layout_side_width\n    } else {\n      return config.value.layout_side_collapsed_width\n    }\n  })\n  watchEffect(() => {\n    active.value = route.meta.activeName || route.name\n  })\n\n  watchEffect(() => {\n    if (device.value === 'mobile') {\n      isCollapse.value = true\n    } else {\n      isCollapse.value = false\n    }\n  })\n\n  provide('isCollapse', isCollapse)\n\n  const selectMenuItem = (index, _, ele, top) => {\n    const query = {}\n    const params = {}\n    routerStore.routeMap[index]?.parameters &&\n      routerStore.routeMap[index]?.parameters.forEach((item) => {\n        if (item.type === 'query') {\n          query[item.key] = item.value\n        } else {\n          params[item.key] = item.value\n        }\n      })\n    if (index === route.name) return\n    if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {\n        window.open(index, '_blank')\n        return\n    }\n\n      if (!top) {\n        router.push({ name: index, query, params })\n        return\n      }\n      const leftMenu = routerStore.setLeftMenu(index)\n      if (!leftMenu) {\n        router.push({ name: index, query, params })\n        return;\n      }\n      const firstMenu = leftMenu.find((item) => !item.hidden && item.path.indexOf(\"http://\") === -1 && item.path.indexOf(\"https://\") === -1)\n      router.push({ name: firstMenu.name, query, params })\n\n  }\n\n  const toggleCollapse = () => {\n    isCollapse.value = !isCollapse.value\n  }\n</script>\n"
  },
  {
    "path": "web/src/view/layout/aside/headMode.vue",
    "content": "<template>\n  <div\n    class=\"h-full text-slate-700 dark:text-slate-300 mx-2 flex items-center w-[calc(100vw-600px)] overflow-auto\"\n    ref=\"menuContainer\"\n  >\n    <el-menu\n      :default-active=\"active\"\n      mode=\"horizontal\"\n      class=\"!border-r-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]\"\n      unique-opened\n      :ellipsis=\"shouldEllipsis\"\n      @select=\"selectMenuItem\"\n      ref=\"menuRef\"\n    >\n      <template v-for=\"item in routerStore.asyncRouters[0].children\">\n        <aside-component\n          v-if=\"!item.hidden\"\n          :key=\"item.name\"\n          :router-info=\"item\"\n          mode=\"horizontal\"\n        />\n      </template>\n    </el-menu>\n  </div>\n</template>\n\n<script setup>\n  import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'\n  import { ref, provide, watchEffect, onMounted, nextTick } from 'vue'\n  import { useRoute, useRouter } from 'vue-router'\n  import { useRouterStore } from '@/pinia/modules/router'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { device } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'GvaAside'\n  })\n  const route = useRoute()\n  const router = useRouter()\n  const routerStore = useRouterStore()\n  const isCollapse = ref(false)\n  const active = ref('')\n  const menuRef = ref(null)\n  const menuContainer = ref(null)\n  const shouldEllipsis = ref(false)\n\n  // 计算是否需要启用省略功能\n  const calculateEllipsis = async () => {\n    await nextTick()\n    if (!menuRef.value || !menuContainer.value) return\n\n    const menuItems = menuRef.value.$el.querySelectorAll('.el-menu-item, .el-sub-menu')\n    let totalWidth = 0\n\n    menuItems.forEach(item => {\n      totalWidth += item.offsetWidth\n    })\n\n    const containerWidth = menuContainer.value.offsetWidth\n    shouldEllipsis.value = totalWidth > containerWidth\n  }\n\n  watchEffect(() => {\n    if (route.name === 'gvaLayoutIframe') {\n      active.value = decodeURIComponent(route.query.url)\n      return\n    }\n    active.value = route.meta.activeName || route.name\n  })\n\n  watchEffect(() => {\n    if (device.value === 'mobile') {\n      isCollapse.value = true\n    } else {\n      isCollapse.value = false\n    }\n    // 设备变化时重新计算\n    calculateEllipsis()\n  })\n\n  // 当路由变化时重新计算\n  watchEffect(() => {\n    if (route.name) {\n      nextTick(calculateEllipsis)\n    }\n  })\n\n  provide('isCollapse', isCollapse)\n\n  onMounted(() => {\n    calculateEllipsis()\n    window.addEventListener('resize', calculateEllipsis)\n  })\n\n  const selectMenuItem = (index) => {\n    const query = {}\n    const params = {}\n    routerStore.routeMap[index]?.parameters &&\n      routerStore.routeMap[index]?.parameters.forEach((item) => {\n        if (item.type === 'query') {\n          query[item.key] = item.value\n        } else {\n          params[item.key] = item.value\n        }\n      })\n    if (index === route.name) return\n    if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {\n        window.open(index, '_blank')\n        return\n    }\n    if (index === 'gvaLayoutIframe') {\n      query.url = decodeURIComponent(index)\n    }\n    router.push({ name: index, query, params })\n  }\n</script>\n\n<style lang=\"scss\">\n  .el-menu--horizontal.el-menu,\n  .el-menu--horizontal > .el-menu-item.is-active {\n    border-bottom: none !important;\n  }\n\n  .el-menu--horizontal>.el-sub-menu.is-active .el-sub-menu__title {\n    border-bottom: none !important;\n  }\n\n  .el-menu-item.is-active {\n    background-color: var(--el-color-primary-light-8) !important;\n  }\n\n  .dark {\n    .el-menu-item.is-active {\n      background-color: var(--el-color-primary-bg) !important;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/layout/aside/index.vue",
    "content": "<template>\n  <div>\n    <normal-mode\n      v-if=\"\n        config.side_mode === 'normal' ||\n        (device === 'mobile' && config.side_mode == 'head') ||\n        (device === 'mobile' && config.side_mode == 'combination') ||\n        (device === 'mobile' && config.side_mode == 'sidebar')\n      \"\n    />\n    <head-mode v-if=\"config.side_mode === 'head' && device !== 'mobile'\" />\n    <combination-mode\n      v-if=\"config.side_mode === 'combination' && device !== 'mobile'\"\n      :mode=\"mode\"\n    />\n    <sidebar-mode\n      v-if=\"config.side_mode === 'sidebar' && device !== 'mobile'\"\n    />\n  </div>\n</template>\n\n<script setup>\n  import NormalMode from './normalMode.vue'\n  import HeadMode from './headMode.vue'\n  import CombinationMode from './combinationMode.vue'\n  import SidebarMode from './sidebarMode.vue'\n\n  defineProps({\n    mode: {\n      type: String,\n      default: 'normal'\n    }\n  })\n\n  import { storeToRefs } from 'pinia'\n  import { useAppStore } from '@/pinia'\n  const appStore = useAppStore()\n  const { config, device } = storeToRefs(appStore)\n</script>\n"
  },
  {
    "path": "web/src/view/layout/aside/normalMode.vue",
    "content": "<template>\n  <div\n    class=\"relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700\"\n    :class=\"isCollapse ? '' : '  px-2'\"\n    :style=\"{\n      width: layoutSideWidth + 'px'\n    }\"\n  >\n    <el-scrollbar>\n      <el-menu\n        :collapse=\"isCollapse\"\n        :collapse-transition=\"false\"\n        :default-active=\"active\"\n        class=\"!border-r-0 w-full\"\n        unique-opened\n        @select=\"selectMenuItem\"\n      >\n        <template v-for=\"item in routerStore.asyncRouters[0]?.children || []\">\n          <aside-component\n            v-if=\"!item.hidden\"\n            :key=\"item.name\"\n            :router-info=\"item\"\n          />\n        </template>\n      </el-menu>\n    </el-scrollbar>\n    <div\n      class=\"absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer\"\n      :class=\"isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'\"\n      @click=\"toggleCollapse\"\n    >\n      <el-icon v-if=\"!isCollapse\">\n        <DArrowLeft />\n      </el-icon>\n      <el-icon v-else>\n        <DArrowRight />\n      </el-icon>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'\n  import { ref, provide, watchEffect, computed } from 'vue'\n  import { useRoute, useRouter } from 'vue-router'\n  import { useRouterStore } from '@/pinia/modules/router'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { device, config } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'GvaAside'\n  })\n  const route = useRoute()\n  const router = useRouter()\n  const routerStore = useRouterStore()\n  const isCollapse = ref(false)\n  const active = ref('')\n  const layoutSideWidth = computed(() => {\n    if (!isCollapse.value) {\n      return config.value.layout_side_width\n    } else {\n      return config.value.layout_side_collapsed_width\n    }\n  })\n  watchEffect(() => {\n    if (route.name === 'gvaLayoutIframe') {\n      active.value = decodeURIComponent(route.query.url)\n      return\n    }\n    active.value = route.meta.activeName || route.name\n  })\n\n  watchEffect(() => {\n    if (device.value === 'mobile') {\n      isCollapse.value = true\n    } else {\n      isCollapse.value = false\n    }\n  })\n\n  provide('isCollapse', isCollapse)\n\n  const selectMenuItem = (index) => {\n    const query = {}\n    const params = {}\n    routerStore.routeMap[index]?.parameters &&\n      routerStore.routeMap[index]?.parameters.forEach((item) => {\n        if (item.type === 'query') {\n          query[item.key] = item.value\n        } else {\n          params[item.key] = item.value\n        }\n      })\n    if (index === route.name) return\n    if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {\n        window.open(index, '_blank')\n        return\n    } else {\n      router.push({ name: index, query, params })\n    }\n  }\n\n  const toggleCollapse = () => {\n    isCollapse.value = !isCollapse.value\n  }\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/layout/aside/sidebarMode.vue",
    "content": "<template>\n  <div class=\"flex h-full\">\n    <!-- 一级菜单常驻侧边栏 -->\n    <div\n      class=\"relative !h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700\"\n      :style=\"{\n        width: config.layout_side_collapsed_width + 'px'\n      }\"\n    >\n      <el-scrollbar>\n        <el-menu\n          :collapse=\"true\"\n          :collapse-transition=\"false\"\n          :default-active=\"topActive\"\n          class=\"!border-r-0 w-full\"\n          unique-opened\n          @select=\"selectTopMenuItem\"\n        >\n          <template v-for=\"item in routerStore.asyncRouters[0]?.children || []\">\n            <el-menu-item\n              v-if=\"!item.hidden && (!item.children || item.children.length === 0)\"\n              :key=\"item.name\"\n              :index=\"item.name\"\n              class=\"dark:text-slate-300 overflow-hidden\"\n              :style=\"{\n                height: config.layout_side_item_height + 'px'\n              }\"\n            >\n              <el-icon v-if=\"item.meta.icon\">\n                <component :is=\"item.meta.icon\" />\n              </el-icon>\n              <template v-else>\n                {{ item.meta.title[0] }}\n              </template>\n              <template #title>\n                {{ item.meta.title }}\n              </template>\n            </el-menu-item>\n            <template v-else-if=\"!item.hidden\" >\n             <el-menu-item\n              :key=\"item.name\"\n              :index=\"item.name\"\n              :class=\"{'is-active': topActive === item.name}\"\n              class=\"dark:text-slate-300 overflow-hidden\"\n              :style=\"{\n                height: config.layout_side_item_height + 'px'\n              }\"\n            >\n              <el-icon v-if=\"item.meta.icon\">\n                <component :is=\"item.meta.icon\" />\n              </el-icon>\n              <template v-else>\n                {{ item.meta.title[0] }}\n                </template>\n              <template #title>\n                {{ item.meta.title }}\n              </template>\n            </el-menu-item>\n            </template>\n          </template>\n        </el-menu>\n      </el-scrollbar>\n    </div>\n\n    <!-- 二级菜单并列显示 -->\n    <div\n      class=\"relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 px-2\"\n      :style=\"{\n        width: layoutSideWidth + 'px'\n      }\"\n    >\n      <el-scrollbar>\n        <el-menu\n          :collapse=\"isCollapse\"\n          :collapse-transition=\"false\"\n          :default-active=\"active\"\n          class=\"!border-r-0 w-full\"\n          unique-opened\n          @select=\"selectMenuItem\"\n        >\n          <template v-for=\"item in secondLevelMenus\">\n            <aside-component\n              v-if=\"!item.hidden\"\n              :key=\"item.name\"\n              :router-info=\"item\"\n            />\n          </template>\n        </el-menu>\n      </el-scrollbar>\n      <div\n        class=\"absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer\"\n        :class=\"isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'\"\n        @click=\"toggleCollapse\"\n      >\n        <el-icon v-if=\"!isCollapse\">\n          <DArrowLeft />\n        </el-icon>\n        <el-icon v-else>\n          <DArrowRight />\n        </el-icon>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'\n  import { ref, provide, watchEffect, computed } from 'vue'\n  import { useRoute, useRouter } from 'vue-router'\n  import { useRouterStore } from '@/pinia/modules/router'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n\n  const appStore = useAppStore()\n  const { device, config } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'SidebarMode'\n  })\n\n  const route = useRoute()\n  const router = useRouter()\n  const routerStore = useRouterStore()\n  const isCollapse = ref(false)\n  const active = ref('')\n  const topActive = ref('')\n  const secondLevelMenus = ref([])\n\n  const layoutSideWidth = computed(() => {\n    if (!isCollapse.value) {\n      return config.value.layout_side_width\n    } else {\n      return config.value.layout_side_collapsed_width\n    }\n  })\n\n\n  provide('isCollapse', isCollapse)\n\n  // 更新二级菜单\n  const updateSecondLevelMenus = (menuName) => {\n    const menu = routerStore.asyncRouters[0]?.children.find(item => item.name === menuName)\n    if (menu && menu.children && menu.children.length > 0) {\n      secondLevelMenus.value = menu.children\n    }\n  }\n\n  // 选择一级菜单\n  const selectTopMenuItem = (index) => {\n    topActive.value = index\n\n    // 获取选中的菜单项\n    const menu = routerStore.asyncRouters[0]?.children.find(item => item.name === index)\n\n    // 只有当选中的菜单有子菜单时，才更新二级菜单区域\n    if (menu && menu.children && menu.children.length > 0) {\n      updateSecondLevelMenus(index)\n\n      // 导航到第一个可见的子菜单\n      const firstVisibleChild = menu.children.find(child => !child.hidden)\n      if (firstVisibleChild) {\n        navigateToMenuItem(firstVisibleChild.name)\n      }\n    } else {\n      // 如果没有子菜单，直接导航到该菜单，但不更新二级菜单区域\n      navigateToMenuItem(index)\n    }\n  }\n\n  // 选择二级或更深层级的菜单\n  const selectMenuItem = (index) => {\n    navigateToMenuItem(index)\n  }\n\n  // 导航到指定菜单\n  const navigateToMenuItem = (index) => {\n    const query = {}\n    const params = {}\n    routerStore.routeMap[index]?.parameters &&\n      routerStore.routeMap[index]?.parameters.forEach((item) => {\n        if (item.type === 'query') {\n          query[item.key] = item.value\n        } else {\n          params[item.key] = item.value\n        }\n      })\n    if (index === route.name) return\n    if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {\n        window.open(index, '_blank')\n        return\n    } else {\n      router.push({ name: index, query, params })\n    }\n  }\n\n  const toggleCollapse = () => {\n    isCollapse.value = !isCollapse.value\n  }\n\n\n\n  watchEffect(() => {\n    if (route.name === 'gvaLayoutIframe') {\n      active.value = decodeURIComponent(route.query.url)\n      return\n    }\n    active.value = route.meta.activeName || route.name\n\n    // 找到当前路由所属的一级菜单\n    const findParentMenu = () => {\n      // 首先检查当前路由是否就是一级菜单\n      const isTopMenu = routerStore.asyncRouters[0]?.children.some(\n        item => !item.hidden && item.name === route.name\n      )\n\n      if (isTopMenu) {\n        return route.name\n      }\n\n      for (const topMenu of routerStore.asyncRouters[0]?.children || []) {\n        if (topMenu.hidden) continue\n\n        // 检查当前路由是否是这个一级菜单的子菜单\n        if (topMenu.children && topMenu.children.some(child => child.name === route.name)) {\n          return topMenu.name\n        }\n\n        // 递归检查更深层级\n        const checkChildren = (items) => {\n          for (const item of items || []) {\n            if (item.name === route.name) {\n              return true\n            }\n            if (item.children && checkChildren(item.children)) {\n              return true\n            }\n          }\n          return false\n        }\n\n        if (topMenu.children && checkChildren(topMenu.children)) {\n          return topMenu.name\n        }\n      }\n      return null\n    }\n\n    const parentMenu = findParentMenu()\n    if (parentMenu) {\n      topActive.value = parentMenu\n\n      // 只有当父菜单有子菜单时，才更新二级菜单区域\n      const menu = routerStore.asyncRouters[0]?.children.find(item => item.name === parentMenu)\n      if (menu && menu.children && menu.children.length > 0) {\n        updateSecondLevelMenus(parentMenu)\n      } else {\n        // 如果找到的父菜单没有子菜单，保持当前一级菜单高亮，但需要显示一些二级菜单\n        // 寻找第一个有子菜单的一级菜单来显示其子菜单\n        const firstMenuWithChildren = routerStore.asyncRouters[0].children.find(\n          item => !item.hidden && item.children && item.children.length > 0\n        )\n\n        if (firstMenuWithChildren) {\n          // 只更新二级菜单区域，但保持当前一级菜单的高亮状态\n          updateSecondLevelMenus(firstMenuWithChildren.name)\n        }\n      }\n    } else if (routerStore.asyncRouters[0]?.children?.length > 0) {\n      // 如果没有找到父菜单，保持当前路由名称作为高亮，但需要显示一些二级菜单\n      // 寻找第一个有子菜单的一级菜单来显示其子菜单\n      const firstMenuWithChildren = routerStore.asyncRouters[0].children.find(\n        item => !item.hidden && item.children && item.children.length > 0\n      )\n\n      if (firstMenuWithChildren) {\n        // 只更新二级菜单区域，高亮状态保持为当前路由\n        topActive.value = route.name\n        secondLevelMenus.value = firstMenuWithChildren.children\n      }\n    }\n  })\n\n  watchEffect(() => {\n    if (device.value === 'mobile') {\n      isCollapse.value = true\n    } else {\n      isCollapse.value = false\n    }\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/layout/header/index.vue",
    "content": "<template>\n  <div\n    class=\"flex justify-between fixed top-0 left-0 right-0 z-10 h-16 bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 items-center px-2\"\n  >\n    <div class=\"flex items-center cursor-pointer flex-1\">\n      <div\n        class=\"flex items-center justify-center cursor-pointer\"\n        :class=\"isMobile ? '' : 'min-w-48'\"\n        @click=\"router.push({ path: '/' })\"\n      >\n        <Logo />\n        <div\n          v-if=\"!isMobile\"\n          class=\"inline-flex font-bold text-2xl ml-2\"\n          :class=\"\n            (config.side_mode === 'head' ||\n              config.side_mode === 'combination') &&\n            'min-w-fit'\n          \"\n        >\n          {{ $GIN_VUE_ADMIN.appName }}\n        </div>\n      </div>\n\n      <el-breadcrumb\n        v-show=\"!isMobile\"\n        v-if=\"config.side_mode !== 'head' && config.side_mode !== 'combination'\"\n        class=\"ml-4\"\n      >\n        <el-breadcrumb-item\n          v-for=\"item in matched.slice(1, matched.length)\"\n          :key=\"item.path\"\n        >\n          {{ fmtTitle(item.meta.title, route) }}\n        </el-breadcrumb-item>\n      </el-breadcrumb>\n      <gva-aside\n        v-if=\"config.side_mode === 'head' && !isMobile\"\n        class=\"flex-1\"\n      />\n      <gva-aside\n        v-if=\"config.side_mode === 'combination' && !isMobile\"\n        mode=\"head\"\n        class=\"flex-1\"\n      />\n    </div>\n\n    <div class=\"ml-2 flex items-center\">\n      <tools />\n      <el-dropdown>\n        <div class=\"flex justify-center items-center h-full w-full\">\n          <span\n            class=\"cursor-pointer flex justify-center items-center text-black dark:text-gray-100\"\n          >\n            <CustomPic />\n            <span v-show=\"!isMobile\" class=\"w-16\">{{\n              userStore.userInfo.nickName\n            }}</span>\n            <el-icon>\n              <arrow-down />\n            </el-icon>\n          </span>\n        </div>\n        <template #dropdown>\n          <el-dropdown-menu>\n            <el-dropdown-item>\n              <span class=\"font-bold\">\n                当前角色：{{ userStore.userInfo.authority.authorityName }}\n              </span>\n            </el-dropdown-item>\n            <template v-if=\"userStore.userInfo.authorities\">\n              <el-dropdown-item\n                v-for=\"item in userStore.userInfo.authorities.filter(\n                  (i) => i.authorityId !== userStore.userInfo.authorityId\n                )\"\n                :key=\"item.authorityId\"\n                @click=\"changeUserAuth(item.authorityId)\"\n              >\n                <span> 切换为：{{ item.authorityName }} </span>\n              </el-dropdown-item>\n            </template>\n            <el-dropdown-item icon=\"avatar\" @click=\"toPerson\">\n              个人信息\n            </el-dropdown-item>\n            <el-dropdown-item icon=\"reading-lamp\" @click=\"userStore.LoginOut\">\n              登 出\n            </el-dropdown-item>\n          </el-dropdown-menu>\n        </template>\n      </el-dropdown>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import tools from './tools.vue'\n  import CustomPic from '@/components/customPic/index.vue'\n  import { useUserStore } from '@/pinia/modules/user'\n  import { useRoute, useRouter } from 'vue-router'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  import { computed } from 'vue'\n  import { setUserAuthority } from '@/api/user'\n  import { fmtTitle } from '@/utils/fmtRouterTitle'\n  import gvaAside from '@/view/layout/aside/index.vue'\n  import Logo from '@/components/logo/index.vue'\n\n  const userStore = useUserStore()\n  const router = useRouter()\n  const route = useRoute()\n  const appStore = useAppStore()\n  const { device, config } = storeToRefs(appStore)\n  const isMobile = computed(() => {\n    return device.value === 'mobile'\n  })\n  const toPerson = () => {\n    router.push({ name: 'person' })\n  }\n  const matched = computed(() => route.meta.matched)\n\n  const changeUserAuth = async (id) => {\n    const res = await setUserAuthority({\n      authorityId: id\n    })\n    if (res.code === 0) {\n      window.sessionStorage.setItem('needCloseAll', 'true')\n      window.sessionStorage.setItem('needToHome', 'true')\n      window.location.reload()\n    }\n  }\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/layout/header/tools.vue",
    "content": "<template>\n  <div class=\"flex items-center mx-4 gap-4\">\n    <el-tooltip v-if=\"isDev\" class=\"\" effect=\"dark\" content=\"视频教程\" placement=\"bottom\">\n      <el-dropdown @command=\"toDoc\">\n        <span class=\"w-8 h-8 p-2 rounded-full flex items-center justify-center shadow border border-gray-200 dark:border-gray-600 cursor-pointer border-solid\">\n          <el-icon>\n          <Film />\n        </el-icon>\n        </span>\n        <template #dropdown>\n          <el-dropdown-menu>\n            <el-dropdown-item\n              v-for=\"item in videoList\"\n              :key=\"item.link\"\n              :command=\"item.link\"\n              >{{ item.title }}</el-dropdown-item\n            >\n          </el-dropdown-menu>\n        </template>\n      </el-dropdown>\n    </el-tooltip>\n\n    <el-tooltip class=\"\" effect=\"dark\" content=\"搜索\" placement=\"bottom\">\n        <span class=\"w-8 h-8 p-2 rounded-full flex items-center justify-center shadow border border-gray-200 dark:border-gray-600 cursor-pointer border-solid\">\n        <el-icon\n            @click=\"handleCommand\"\n        >\n        <Search />\n      </el-icon>\n        </span>\n\n    </el-tooltip>\n\n    <el-tooltip class=\"\" effect=\"dark\" content=\"系统设置\" placement=\"bottom\">\n        <span class=\"w-8 h-8 p-2 rounded-full flex items-center justify-center shadow border border-gray-200 dark:border-gray-600 cursor-pointer border-solid\">\n         <el-icon\n             @click=\"toggleSetting\"\n         >\n        <Setting />\n      </el-icon>\n        </span>\n\n    </el-tooltip>\n\n    <el-tooltip class=\"\" effect=\"dark\" content=\"刷新\" placement=\"bottom\">\n      <span class=\"w-8 h-8 p-2 rounded-full flex items-center justify-center shadow border border-gray-200 dark:border-gray-600 cursor-pointer border-solid\">\n      <el-icon\n          :class=\"showRefreshAnmite ? 'animate-spin' : ''\"\n          @click=\"toggleRefresh\"\n      >\n        <Refresh />\n      </el-icon>\n      </span>\n\n    </el-tooltip>\n    <el-tooltip\n      class=\"\"\n      effect=\"dark\"\n      content=\"切换主题\"\n      placement=\"bottom\"\n    >\n      <span class=\"w-8 h-8 p-2 rounded-full flex items-center justify-center shadow border border-gray-200 dark:border-gray-600 cursor-pointer border-solid\">\n        <el-icon\n            v-if=\"appStore.isDark\"\n            @click=\"appStore.toggleTheme(false)\"\n        >\n        <Sunny />\n      </el-icon>\n      <el-icon\n          v-else\n          @click=\"appStore.toggleTheme(true)\"\n      >\n        <Moon />\n      </el-icon>\n      </span>\n\n    </el-tooltip>\n\n    <gva-setting v-model:drawer=\"showSettingDrawer\"></gva-setting>\n    <command-menu ref=\"command\" />\n  </div>\n</template>\n\n<script setup>\n  import { useAppStore } from '@/pinia'\n  import GvaSetting from '@/view/layout/setting/index.vue'\n  import { ref } from 'vue'\n  import { emitter } from '@/utils/bus.js'\n  import CommandMenu from '@/components/commandMenu/index.vue'\n  import { toDoc } from '@/utils/doc'\n  import { isDev } from '@/utils/env.js'\n\n  const appStore = useAppStore()\n  const showSettingDrawer = ref(false)\n  const showRefreshAnmite = ref(false)\n  const toggleRefresh = () => {\n    showRefreshAnmite.value = true\n    emitter.emit('reload')\n    setTimeout(() => {\n      showRefreshAnmite.value = false\n    }, 1000)\n  }\n\n  const toggleSetting = () => {\n    showSettingDrawer.value = true\n  }\n\n  const first = ref('')\n  const command = ref()\n\n  const handleCommand = () => {\n    command.value.open()\n  }\n  const initPage = () => {\n    // 判断当前用户的操作系统\n    if (window.localStorage.getItem('osType') === 'WIN') {\n      first.value = 'Ctrl'\n    } else {\n      first.value = '⌘'\n    }\n    // 当用户同时按下ctrl和k键的时候\n    const handleKeyDown = (e) => {\n      if (e.ctrlKey && e.key === 'k') {\n        // 阻止浏览器默认事件\n        e.preventDefault()\n        handleCommand()\n      }\n    }\n    window.addEventListener('keydown', handleKeyDown)\n  }\n\n  initPage()\n\n  const videoList = [\n    {\n      title: '1.clone项目和安装依赖',\n      link: 'https://www.bilibili.com/video/BV1jx4y1s7xx'\n    },\n    {\n      title: '2.初始化项目',\n      link: 'https://www.bilibili.com/video/BV1sr421K7sv'\n    },\n    {\n      title: '3.开启调试工具+创建初始化包',\n      link: 'https://www.bilibili.com/video/BV1iH4y1c7Na'\n    },\n    {\n      title: '4.手动使用自动化创建功能',\n      link: 'https://www.bilibili.com/video/BV1UZ421T7fV'\n    },\n    {\n      title: '5.使用已有表格创建业务',\n      link: 'https://www.bilibili.com/video/BV1NE4m1977s'\n    },\n    {\n      title: '6.使用AI创建业务和创建数据源模式的可选项',\n      link: 'https://www.bilibili.com/video/BV17i421a7DE'\n    },\n    {\n      title: '7.创建自己的后端方法',\n      link: 'https://www.bilibili.com/video/BV1Yw4m1k7fg'\n    },\n    {\n      title: '8.新增一个前端页面',\n      link: 'https://www.bilibili.com/video/BV12y411i7oE'\n    },\n    {\n      title: '9.配置一个前端二级页面',\n      link: 'https://www.bilibili.com/video/BV1ZM4m1y7i3'\n    },\n    {\n      title: '10.配置一个前端菜单参数',\n      link: 'https://www.bilibili.com/video/BV1WS42197DZ'\n    },\n    {\n      title: '11.菜单参数实战+动态菜单标题+菜单高亮配置',\n      link: 'https://www.bilibili.com/video/BV1NE4m1979c'\n    },\n    {\n      title: '12.增加菜单可控按钮',\n      link: 'https://www.bilibili.com/video/BV1Sw4m1k746'\n    },\n    {\n      title: '14.新增客户角色和其相关配置教学',\n      link: 'https://www.bilibili.com/video/BV1Ki421a7X2'\n    },\n    {\n      title: '15.发布项目上线',\n      link: 'https://www.bilibili.com/video/BV1Lx4y1s77D'\n    }\n  ]\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/layout/iframe.vue",
    "content": "<template>\n  <div\n    class=\"bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800 w-screen h-screen\"\n  >\n    <iframe\n        v-if=\"reloadFlag\"\n        id=\"gva-base-load-dom\"\n        class=\"gva-body-h bg-gray-50 dark:bg-slate-800 w-full border-t border-gray-200 dark:border-slate-700\"\n        :src=\"url\"\n    ></iframe>\n  </div>\n</template>\n\n<script setup>\n  import useResponsive from '@/hooks/responsive'\n  import { emitter } from '@/utils/bus.js'\n  import { ref, onMounted, nextTick, reactive, watchEffect } from 'vue'\n  import { useRouter, useRoute } from 'vue-router'\n  import { useUserStore } from '@/pinia/modules/user'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  const appStore = useAppStore()\n  const { isDark } = storeToRefs(appStore)\n\n\n  defineOptions({\n    name: 'GvaLayoutIframe'\n  })\n\n  useResponsive(true)\n  const font = reactive({\n    color: 'rgba(0, 0, 0, .15)'\n  })\n\n  watchEffect(() => {\n    font.color = isDark.value ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'\n  })\n\n  const router = useRouter()\n  const route = useRoute()\n\n  const url = route.query.url || 'https://www.gin-vue-admin.com'\n\n  onMounted(() => {\n    // 挂载一些通用的事件\n    emitter.on('reload', reload)\n    if (userStore.loadingInstance) {\n      userStore.loadingInstance.close()\n    }\n  })\n\n  const userStore = useUserStore()\n\n  const reloadFlag = ref(true)\n  let reloadTimer = null\n  const reload = async () => {\n    if (reloadTimer) {\n      window.clearTimeout(reloadTimer)\n    }\n    reloadTimer = window.setTimeout(async () => {\n      if (route.meta.keepAlive) {\n        reloadFlag.value = false\n        await nextTick()\n        reloadFlag.value = true\n      } else {\n        const title = route.meta.title\n        router.push({ name: 'Reload', params: { title } })\n      }\n    }, 400)\n  }\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/layout/index.vue",
    "content": "<template>\n  <div\n    class=\"bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800 w-screen h-screen\"\n  >\n    <el-watermark\n      v-if=\"config.show_watermark\"\n      :font=\"font\"\n      :z-index=\"9999\"\n      :gap=\"[180, 150]\"\n      class=\"!absolute !inset-0 !pointer-events-none\"\n      :content=\"userStore.userInfo.nickName\"\n    />\n    <gva-header />\n    <div class=\"flex flex-row w-full gva-container pt-16 box-border !h-full\">\n      <gva-aside\n        v-if=\"\n          config.side_mode === 'normal' ||\n          config.side_mode === 'sidebar' ||\n          (device === 'mobile' && config.side_mode == 'head') ||\n          (device === 'mobile' && config.side_mode == 'combination')\n        \"\n      />\n      <gva-aside\n        v-if=\"config.side_mode === 'combination' && device !== 'mobile'\"\n        mode=\"normal\"\n      />\n      <div class=\"flex-1 w-0 h-full\">\n        <gva-tabs v-if=\"config.showTabs\" />\n        <div\n          class=\"overflow-auto px-2\"\n          :class=\"config.showTabs ? 'gva-container2' : 'gva-container pt-1'\"\n        >\n          <router-view v-if=\"reloadFlag\" v-slot=\"{ Component, route }\">\n            <div\n              id=\"gva-base-load-dom\"\n              class=\"gva-body-h bg-gray-50 dark:bg-slate-800\"\n            >\n              <transition\n                mode=\"out-in\"\n                :name=\"route.meta.transitionType || config.transition_type\"\n              >\n                <keep-alive :include=\"routerStore.keepAliveRouters\">\n                  <component :is=\"Component\" :key=\"route.fullPath\" />\n                </keep-alive>\n              </transition>\n            </div>\n          </router-view>\n          <BottomInfo />\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import GvaAside from '@/view/layout/aside/index.vue'\n  import GvaHeader from '@/view/layout/header/index.vue'\n  import useResponsive from '@/hooks/responsive'\n  import GvaTabs from './tabs/index.vue'\n  import BottomInfo from '@/components/bottomInfo/bottomInfo.vue'\n  import { emitter } from '@/utils/bus.js'\n  import { ref, onMounted, nextTick, reactive, watchEffect } from 'vue'\n  import { useRouter, useRoute } from 'vue-router'\n  import { useRouterStore } from '@/pinia/modules/router'\n  import { useUserStore } from '@/pinia/modules/user'\n  import { useAppStore } from '@/pinia'\n  import { storeToRefs } from 'pinia'\n  import '@/style/transition.scss'\n  const appStore = useAppStore()\n  const { config, isDark, device } = storeToRefs(appStore)\n\n  defineOptions({\n    name: 'GvaLayout'\n  })\n\n  useResponsive(true)\n  const font = reactive({\n    color: 'rgba(0, 0, 0, .15)'\n  })\n\n  watchEffect(() => {\n    font.color = isDark.value ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'\n  })\n\n  const router = useRouter()\n  const route = useRoute()\n  const routerStore = useRouterStore()\n\n  onMounted(() => {\n    // 挂载一些通用的事件\n    emitter.on('reload', reload)\n    if (userStore.loadingInstance) {\n      userStore.loadingInstance.close()\n    }\n  })\n\n  const userStore = useUserStore()\n\n  const reloadFlag = ref(true)\n  let reloadTimer = null\n  const reload = async () => {\n    if (reloadTimer) {\n      window.clearTimeout(reloadTimer)\n    }\n    reloadTimer = window.setTimeout(async () => {\n      if (route.meta.keepAlive) {\n        reloadFlag.value = false\n        await nextTick()\n        reloadFlag.value = true\n      } else {\n        const title = route.meta.title\n        router.push({ name: 'Reload', params: { title } })\n      }\n    }, 400)\n  }\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/layout/screenfull/index.vue",
    "content": "<template>\n  <div @click=\"clickFull\">\n    <div v-if=\"isShow\" class=\"gvaIcon gvaIcon-fullscreen-expand\" />\n    <div v-else class=\"gvaIcon gvaIcon-fullscreen-shrink\" />\n  </div>\n</template>\n\n<script setup>\n  import screenfull from 'screenfull' // 引入screenfull\n  import { onMounted, onUnmounted, ref } from 'vue'\n\n  defineOptions({\n    name: 'Screenfull'\n  })\n\n  defineProps({\n    width: {\n      type: Number,\n      default: 22\n    },\n    height: {\n      type: Number,\n      default: 22\n    },\n    fill: {\n      type: String,\n      default: '#48576a'\n    }\n  })\n\n  onMounted(() => {\n    if (screenfull.isEnabled) {\n      screenfull.on('change', changeFullShow)\n    }\n  })\n\n  onUnmounted(() => {\n    screenfull.off('change')\n  })\n\n  const clickFull = () => {\n    if (screenfull.isEnabled) {\n      screenfull.toggle()\n    }\n  }\n\n  const isShow = ref(true)\n  const changeFullShow = () => {\n    isShow.value = !screenfull.isFullscreen\n  }\n</script>\n\n<style scoped lang=\"scss\">\n  .screenfull-svg {\n    width: 16px;\n    height: 16px;\n    cursor: pointer;\n    vertical-align: middle;\n    margin-right: 32px;\n    fill: rgba(0, 0, 0, 0.45);\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/layout/search/search.vue",
    "content": "<template>\n  <div class=\"search-component items-center\">\n    <div\n      class=\"gvaIcon gvaIcon-refresh\"\n      :class=\"[reload ? 'reloading' : '']\"\n      @click=\"handleReload\"\n    />\n    <Screenfull class=\"search-icon\" />\n    <div class=\"gvaIcon gvaIcon-customer-service\" @click=\"toService\" />\n    <el-switch\n      v-model=\"isDark\"\n      :active-action-icon=\"Moon\"\n      :inactive-action-icon=\"Sunny\"\n      @change=\"handleDarkSwitch\"\n    />\n  </div>\n</template>\n\n<script setup>\n  import Screenfull from '@/view/layout/screenfull/index.vue'\n  import { emitter } from '@/utils/bus.js'\n  import { Sunny, Moon } from '@element-plus/icons-vue'\n  import { ref, watchEffect } from 'vue'\n\n  defineOptions({\n    name: 'BtnBox'\n  })\n  const isDark = ref(localStorage.getItem('isDark') === 'true' || true)\n\n  watchEffect(() => {\n    if (isDark.value) {\n      document.documentElement.classList.add('dark')\n      localStorage.setItem('isDark', true)\n    } else {\n      document.documentElement.classList.remove('dark')\n      localStorage.setItem('isDark', false)\n    }\n  })\n  const reload = ref(false)\n  const handleReload = () => {\n    reload.value = true\n    emitter.emit('reload')\n    setTimeout(() => {\n      reload.value = false\n    }, 500)\n  }\n  const toService = () => {\n    window.open('https://support.qq.com/product/371961')\n  }\n\n  const handleDarkSwitch = (e) => {\n    isDark.value = e\n  }\n</script>\n<style scoped lang=\"scss\">\n  .search-component {\n    @apply inline-flex overflow-hidden text-center gap-5 mr-5 text-black dark:text-gray-100;\n    div {\n      @apply cursor-pointer;\n    }\n    .el-input__inner {\n      @apply border-b border-solid border-gray-300;\n    }\n    .el-dropdown-link {\n      @apply cursor-pointer;\n    }\n  }\n\n  .reload {\n    font-size: 18px;\n  }\n\n  .reloading {\n    animation: turn 0.5s linear infinite;\n  }\n\n  @keyframes turn {\n    0% {\n      transform: rotate(0deg);\n    }\n\n    25% {\n      transform: rotate(90deg);\n    }\n\n    50% {\n      transform: rotate(180deg);\n    }\n\n    75% {\n      transform: rotate(270deg);\n    }\n\n    100% {\n      transform: rotate(360deg);\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/layout/setting/components/layoutModeCard.vue",
    "content": "<template>\n  <div class=\"grid grid-cols-2 gap-6 gva-theme-font px-6\">\n    <div\n      v-for=\"layout in layoutModes\"\n      :key=\"layout.value\"\n      class=\"gva-theme-layout-card\"\n      :class=\"{\n        'ring-2 ring-offset-2 ring-offset-gray-50 dark:ring-offset-gray-900 transform -translate-y-1 shadow-xl': modelValue === layout.value\n      }\"\n      :style=\"modelValue === layout.value ? {\n        borderColor: primaryColor,\n        ringColor: primaryColor + '40'\n      } : {}\"\n      @click=\"handleLayoutChange(layout.value)\"\n    >\n      <div class=\"flex justify-center mb-5\">\n        <div\n          class=\"w-28 h-20 bg-gray-50 dark:bg-gray-600 border border-gray-200 dark:border-gray-500 rounded-lg p-2 flex gap-1.5 shadow-inner\"\n          :class=\"layout.containerClass\"\n        >\n          <div\n            v-if=\"layout.showSidebar\"\n            class=\"rounded-sm\"\n            :class=\"[layout.sidebarClass]\"\n            :style=\"getSidebarStyle(layout)\"\n          ></div>\n\n          <div class=\"flex-1 flex flex-col gap-1.5\">\n            <div\n              v-if=\"layout.showHeader\"\n              class=\"rounded-sm\"\n              :class=\"layout.headerClass\"\n              :style=\"getHeaderStyle(layout)\"\n            ></div>\n\n            <div\n              class=\"flex-1 rounded-sm\"\n              :class=\"layout.contentClass\"\n              :style=\"getContentStyle(layout)\"\n            ></div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"text-center\">\n        <span class=\"block text-base font-semibold gva-theme-text-main mb-2\" :class=\"{ 'text-current': modelValue === layout.value }\" :style=\"modelValue === layout.value ? { color: primaryColor } : {}\">{{ layout.label }}</span>\n        <span class=\"block text-sm text-gray-500 dark:text-gray-400\">{{ layout.description }}</span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { computed } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useAppStore } from '@/pinia'\n\ndefineOptions({\n  name: 'LayoutModeCard'\n})\n\nconst props = defineProps({\n  modelValue: {\n    type: String,\n    default: 'normal'\n  }\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst appStore = useAppStore()\nconst { config } = storeToRefs(appStore)\n\nconst primaryColor = computed(() => config.value.primaryColor)\nconst lighterPrimaryColor = computed(() => {\n  const hex = config.value.primaryColor.replace('#', '')\n  const r = parseInt(hex.substr(0, 2), 16)\n  const g = parseInt(hex.substr(2, 2), 16)\n  const b = parseInt(hex.substr(4, 2), 16)\n  return `rgba(${r}, ${g}, ${b}, 0.7)`\n})\nconst lightestPrimaryColor = computed(() => {\n  const hex = config.value.primaryColor.replace('#', '')\n  const r = parseInt(hex.substr(0, 2), 16)\n  const g = parseInt(hex.substr(2, 2), 16)\n  const b = parseInt(hex.substr(4, 2), 16)\n  return `rgba(${r}, ${g}, ${b}, 0.4)`\n})\n\nconst layoutModes = [\n  {\n    value: 'normal',\n    label: '经典布局',\n    description: '左侧导航，顶部标题栏',\n    containerClass: '',\n    showSidebar: true,\n    sidebarClass: 'w-1/4',\n    showHeader: true,\n    headerClass: 'h-1/4',\n    contentClass: '',\n    showRightSidebar: false,\n    primaryElement: 'sidebar'\n  },\n  {\n    value: 'head',\n    label: '顶部导航',\n    description: '水平导航栏布局',\n    containerClass: 'flex-col',\n    showSidebar: false,\n    showHeader: true,\n    headerClass: 'h-1/3',\n    contentClass: '',\n    showRightSidebar: false,\n    primaryElement: 'header'\n  },\n  {\n    value: 'combination',\n    label: '混合布局',\n    description: '多级导航组合模式',\n    containerClass: '',\n    showSidebar: true,\n    sidebarClass: 'w-1/5',\n    showHeader: true,\n    headerClass: 'h-1/4',\n    contentClass: '',\n    showRightSidebar: true,\n    rightSidebarClass: 'w-1/5',\n    primaryElement: 'header',\n    secondaryElement: 'sidebar'\n  },\n  {\n    value: 'sidebar',\n    label: '侧栏常驻',\n    description: '二级菜单会始终打开',\n    containerClass: '',\n    showSidebar: true,\n    sidebarClass: 'w-1/3',\n    showHeader: true,\n    headerClass: 'h-1/4',\n    contentClass: '',\n    showRightSidebar: false,\n    primaryElement: 'sidebar'\n  }\n]\n\nconst getSidebarStyle = (layout) => {\n  if (layout.primaryElement === 'sidebar') {\n    return { backgroundColor: primaryColor.value, opacity: '0.95' }\n  } else if (layout.secondaryElement === 'sidebar') {\n    return { backgroundColor: lighterPrimaryColor.value, opacity: '0.85' }\n  } else {\n    return { backgroundColor: lightestPrimaryColor.value, opacity: '0.6' }\n  }\n}\n\nconst getHeaderStyle = (layout) => {\n  if (layout.primaryElement === 'header') {\n    return { backgroundColor: primaryColor.value, opacity: '0.95' }\n  } else if (layout.secondaryElement === 'header') {\n    return { backgroundColor: lighterPrimaryColor.value, opacity: '0.85' }\n  } else {\n    return { backgroundColor: lightestPrimaryColor.value, opacity: '0.6' }\n  }\n}\n\nconst getContentStyle = () => {\n  return { backgroundColor: lightestPrimaryColor.value, opacity: '0.5' }\n}\n\nconst handleLayoutChange = (layout) => {\n  emit('update:modelValue', layout)\n}\n</script>\n\n<style scoped>\n.flex-col {\n  flex-direction: column;\n}\n\n.w-1\\/4 {\n  width: 25%;\n}\n\n.w-1\\/3 {\n  width: 33.333333%;\n}\n\n.w-1\\/5 {\n  width: 20%;\n}\n\n.h-1\\/4 {\n  height: 25%;\n}\n\n.h-1\\/3 {\n  height: 33.333333%;\n}\n\n@media (max-width: 480px) {\n  .grid-cols-2 {\n    grid-template-columns: repeat(1, minmax(0, 1fr));\n  }\n}\n</style>\n"
  },
  {
    "path": "web/src/view/layout/setting/components/settingItem.vue",
    "content": "<template>\n  <div class=\"gva-theme-setting-item\">\n    <div class=\"flex items-center gap-2\">\n      <span class=\"gva-theme-setting-label\">{{ label }}</span>\n      <slot name=\"suffix\"></slot>\n    </div>\n    <div class=\"flex items-center setting-controls\">\n      <slot></slot>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { computed } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { useAppStore } from '@/pinia'\n\ndefineOptions({\n  name: 'SettingItem'\n})\n\ndefineProps({\n  label: {\n    type: String,\n    required: true\n  }\n})\n\nconst appStore = useAppStore()\nconst { config } = storeToRefs(appStore)\n\nconst primaryColor = computed(() => config.value.primaryColor)\nconst primaryColorWithOpacity = computed(() => config.value.primaryColor + '40')\n</script>\n\n<style scoped>\n\n\n.setting-controls {\n  ::v-deep(.el-switch) {\n    --el-switch-on-color: v-bind(primaryColor);\n    --el-switch-off-color: #d1d5db;\n  }\n\n  ::v-deep(.el-select) {\n    .el-input__wrapper {\n      border: 1px solid #e5e7eb;\n      border-radius: 6px;\n      transition: all 150ms ease-in-out;\n\n      &:hover {\n        border-color: v-bind(primaryColor);\n        transform: translateY(-1px);\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n      }\n\n      &.is-focus {\n        border-color: v-bind(primaryColor);\n        box-shadow: 0 0 0 2px v-bind(primaryColorWithOpacity);\n      }\n    }\n  }\n\n  ::v-deep(.el-input-number) {\n    .el-input__wrapper {\n      border: 1px solid #e5e7eb;\n      border-radius: 6px;\n      transition: all 150ms ease-in-out;\n\n      &:hover {\n        border-color: v-bind(primaryColor);\n        transform: translateY(-1px);\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n      }\n\n      &.is-focus {\n        border-color: v-bind(primaryColor);\n        box-shadow: 0 0 0 2px v-bind(primaryColorWithOpacity);\n      }\n    }\n  }\n}\n\n.dark .setting-controls {\n  ::v-deep(.el-switch) {\n    --el-switch-off-color: #4b5563;\n  }\n\n  ::v-deep(.el-select) {\n    .el-input__wrapper {\n      border-color: #4b5563;\n      background-color: #374151;\n\n      &:hover {\n        border-color: v-bind(primaryColor);\n      }\n    }\n  }\n\n  ::v-deep(.el-input-number) {\n    .el-input__wrapper {\n      border-color: #4b5563;\n      background-color: #374151;\n\n      &:hover {\n        border-color: v-bind(primaryColor);\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "web/src/view/layout/setting/components/themeColorPicker.vue",
    "content": "<template>\n  <div class=\"gva-theme-font\">\n    <div class=\"gva-theme-card-bg p-4\">\n      <div class=\"mb-4\">\n        <p class=\"text-base font-semibold text-gray-700 dark:text-gray-300 mb-4\">精选色彩</p>\n        <div class=\"grid grid-cols-3 gap-4\">\n          <div\n            v-for=\"colorItem in presetColors\"\n            :key=\"colorItem.color\"\n            class=\"flex items-center gap-4 p-2 bg-white dark:bg-gray-700 border-2 border-gray-200 dark:border-gray-600 rounded-xl cursor-pointer transition-all duration-150 ease-in-out hover:transform hover:-translate-y-1 hover:shadow-lg\"\n            :class=\"{\n              'ring-2 ring-offset-2 ring-offset-gray-50 dark:ring-offset-gray-800 transform -translate-y-1 shadow-lg': modelValue === colorItem.color\n            }\"\n            :style=\"modelValue === colorItem.color ? {\n              borderColor: colorItem.color,\n              ringColor: colorItem.color + '40'\n            } : {}\"\n            @click=\"handleColorChange(colorItem.color)\"\n          >\n            <div\n              class=\"relative w-10 h-10 rounded-lg border border-gray-300 dark:border-gray-500 flex-shrink-0 shadow-sm\"\n              :style=\"{ backgroundColor: colorItem.color }\"\n            >\n              <div\n                v-if=\"modelValue === colorItem.color\"\n                class=\"absolute inset-0 flex items-center justify-center text-white text-base\"\n                style=\"text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);\"\n              >\n                <el-icon>\n                  <Check />\n                </el-icon>\n              </div>\n            </div>\n            <div class=\"min-w-0 flex-1\">\n              <span class=\"block text-sm font-semibold gva-theme-text-main\">{{ colorItem.name }}</span>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"flex items-center justify-between p-4 bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-xl mb-6 shadow-sm\">\n        <div class=\"flex-1\">\n          <h4 class=\"text-base font-semibold gva-theme-text-main\">自定义颜色</h4>\n          <p class=\"text-sm text-gray-500 dark:text-gray-400 mt-1\">选择任意颜色作为主题色</p>\n        </div>\n        <el-color-picker\n          v-model=\"customColor\"\n          size=\"large\"\n          :predefine=\"presetColors.map(item => item.color)\"\n          @change=\"handleCustomColorChange\"\n          class=\"custom-color-picker\"\n        />\n      </div>\n\n      <div class=\"bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-xl p-4 shadow-sm\">\n        <div class=\"flex items-center justify-between\">\n          <span class=\"text-base font-semibold text-gray-700 dark:text-gray-300\">当前主题色</span>\n          <div class=\"flex items-center gap-3\">\n            <div\n              class=\"w-6 h-6 rounded-lg border border-gray-300 dark:border-gray-500 shadow-sm\"\n              :style=\"{ backgroundColor: modelValue }\"\n            ></div>\n            <code class=\"text-sm font-mono bg-gray-100 dark:bg-gray-600 text-gray-700 dark:text-gray-300 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-500\">\n              {{ modelValue }}\n            </code>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { ref, watch } from 'vue'\nimport { Check } from '@element-plus/icons-vue'\n\ndefineOptions({\n  name: 'ThemeColorPicker'\n})\n\nconst props = defineProps({\n  modelValue: {\n    type: String,\n    default: '#3b82f6'\n  }\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst customColor = ref(props.modelValue)\n\nconst presetColors = [\n  { color: '#4E80EE', name: '默认' },\n  { color: '#8bb5d1', name: '晨雾蓝' },\n  { color: '#a8c8a8', name: '薄荷绿' },\n  { color: '#d4a5a5', name: '玫瑰粉' },\n  { color: '#c8a8d8', name: '薰衣草' },\n  { color: '#f0c674', name: '暖阳黄' },\n  { color: '#b8b8b8', name: '月光银' },\n  { color: '#d8a8a8', name: '珊瑚橙' },\n  { color: '#a8d8d8', name: '海雾青' },\n  { color: '#c8c8a8', name: '橄榄绿' },\n  { color: '#d8c8a8', name: '奶茶棕' },\n  { color: '#a8a8d8', name: '梦幻紫' },\n  { color: '#c8d8a8', name: '抹茶绿' }\n]\n\nconst handleColorChange = (color) => {\n  customColor.value = color\n  emit('update:modelValue', color)\n}\n\nconst handleCustomColorChange = (color) => {\n  if (color) {\n    emit('update:modelValue', color)\n  }\n}\n\nwatch(() => props.modelValue, (newValue) => {\n  customColor.value = newValue\n})\n</script>\n\n<style scoped>\n\n\n.custom-color-picker {\n  ::v-deep(.el-color-picker__trigger) {\n    border: 1px solid #e5e7eb;\n    border-radius: 6px;\n    transition: all 150ms ease-in-out;\n\n    &:hover {\n      border-color: #9ca3af;\n      transform: translateY(-1px);\n      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n    }\n  }\n}\n\n.dark .custom-color-picker {\n  ::v-deep(.el-color-picker__trigger) {\n    border-color: #4b5563;\n\n    &:hover {\n      border-color: #6b7280;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "web/src/view/layout/setting/components/themeModeSelector.vue",
    "content": "<template>\n  <div class=\"flex justify-center\">\n    <div class=\"gva-theme-mode-selector\">\n      <div\n        v-for=\"mode in themeModes\"\n        :key=\"mode.value\"\n        class=\"gva-theme-mode-item\"\n        :class=\"[\n          modelValue === mode.value\n            ? 'text-white shadow-sm transform -translate-y-0.5'\n            : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700'\n        ]\"\n        :style=\"modelValue === mode.value ? { backgroundColor: primaryColor } : {}\"\n        @click=\"handleModeChange(mode.value)\"\n      >\n        <el-icon class=\"text-lg mb-1\">\n          <component :is=\"mode.icon\" />\n        </el-icon>\n        <span class=\"text-xs font-medium\">{{ mode.label }}</span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { computed } from 'vue'\nimport { storeToRefs } from 'pinia'\nimport { Sunny, Moon, Monitor } from '@element-plus/icons-vue'\nimport { useAppStore } from '@/pinia'\n\ndefineOptions({\n  name: 'ThemeModeSelector'\n})\n\ndefineProps({\n  modelValue: {\n    type: String,\n    default: 'auto'\n  }\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst appStore = useAppStore()\nconst { config } = storeToRefs(appStore)\n\nconst primaryColor = computed(() => config.value.primaryColor)\n\nconst themeModes = [\n  {\n    value: 'light',\n    label: '浅色',\n    icon: Sunny\n  },\n  {\n    value: 'dark',\n    label: '深色',\n    icon: Moon\n  },\n  {\n    value: 'auto',\n    label: '跟随系统',\n    icon: Monitor\n  }\n]\n\nconst handleModeChange = (mode) => {\n  emit('update:modelValue', mode)\n}\n</script>\n"
  },
  {
    "path": "web/src/view/layout/setting/index.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    title=\"系统配置\"\n    direction=\"rtl\"\n    :size=\"width\"\n    :show-close=\"false\"\n    class=\"gva-theme-drawer\"\n  >\n    <template #header>\n      <div class=\"flex items-center justify-between w-full px-6 py-4 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700\">\n        <h2 class=\"text-xl font-semibold gva-theme-text-main gva-theme-font\">系统配置</h2>\n        <el-button\n          type=\"primary\"\n          size=\"small\"\n          class=\"reset-btn\"\n          :style=\"{ backgroundColor: config.primaryColor, borderColor: config.primaryColor }\"\n          @click=\"resetConfig\"\n        >\n          重置配置\n        </el-button>\n      </div>\n    </template>\n\n    <div class=\"bg-white dark:bg-gray-900 px-6\">\n      <div class=\"px-8 pt-4 pb-6\">\n        <div class=\"flex justify-center\">\n          <div class=\"inline-flex bg-gray-100 dark:bg-gray-800 rounded-xl p-1.5 border border-gray-200 dark:border-gray-700 shadow-sm\">\n            <div\n              v-for=\"tab in tabs\"\n              :key=\"tab.key\"\n              class=\"px-4 py-2 text-base text-center cursor-pointer font-medium rounded-lg transition-all duration-150 ease-in-out min-w-[80px]\"\n              :class=\"[\n                activeTab === tab.key\n                  ? 'text-white shadow-md transform -translate-y-0.5'\n                  : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700'\n              ]\"\n              :style=\"activeTab === tab.key ? { backgroundColor: config.primaryColor } : {}\"\n              @click=\"activeTab = tab.key\"\n            >\n              {{ tab.label }}\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"pb-8 h-full overflow-y-auto\">\n        <div class=\"transition-all duration-300 ease-in-out\">\n          <AppearanceSettings v-if=\"activeTab === 'appearance'\" />\n          <LayoutSettings v-else-if=\"activeTab === 'layout'\" />\n          <GeneralSettings v-else-if=\"activeTab === 'general'\" />\n        </div>\n      </div>\n    </div>\n  </el-drawer>\n</template>\n\n<script setup>\n  import { ref, computed, watch } from 'vue'\n  import { storeToRefs } from 'pinia'\n  import { ElMessage } from 'element-plus'\n  import { useAppStore } from '@/pinia'\n  import { setSelfSetting } from '@/api/user'\n  import AppearanceSettings from './modules/appearance/index.vue'\n  import LayoutSettings from './modules/layout/index.vue'\n  import GeneralSettings from './modules/general/index.vue'\n\n  defineOptions({\n    name: 'GvaSetting'\n  })\n\n  const appStore = useAppStore()\n  const { config, device } = storeToRefs(appStore)\n\n  const activeTab = ref('appearance')\n\n  const tabs = [\n    { key: 'appearance', label: '外观' },\n    { key: 'layout', label: '布局' },\n    { key: 'general', label: '通用' }\n  ]\n\n  const width = computed(() => {\n    return device.value === 'mobile' ? '100%' : '500px'\n  })\n\n  const drawer = defineModel('drawer', {\n    default: true,\n    type: Boolean\n  })\n\n  const saveConfig = async () => {\n    const res = await setSelfSetting(config.value)\n    if (res.code === 0) {\n      localStorage.setItem('originSetting', JSON.stringify(config.value))\n      ElMessage.success('保存成功')\n    }\n  }\n\n  const resetConfig = () => {\n    appStore.resetConfig()\n  }\n\n  watch(config, async () => {\n    await saveConfig();\n  }, { deep: true });\n</script>\n\n<style lang=\"scss\">\n.gva-theme-drawer {\n  .el-drawer {\n    @apply bg-white dark:bg-gray-900;\n  }\n\n  .el-drawer__header {\n    @apply p-0 border-0;\n  }\n\n  .el-drawer__body {\n    @apply p-0;\n  }\n}\n\n.gva-theme-font {\n  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n.gva-theme-card-bg {\n  @apply bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 shadow-sm;\n}\n\n.gva-theme-card-white {\n  @apply bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-5 hover:shadow-md transition-all duration-150 ease-in-out hover:-translate-y-0.5;\n}\n\n.gva-theme-section-header {\n  @apply flex items-center justify-center mb-6;\n}\n\n.gva-theme-section-title {\n  @apply px-6 text-lg font-semibold text-gray-700 dark:text-gray-300;\n}\n\n.gva-theme-divider {\n  @apply h-px bg-gray-200 dark:bg-gray-700 flex-1;\n}\n\n.gva-theme-text-main {\n  @apply text-gray-900 dark:text-white;\n}\n\n.gva-theme-text-sub {\n  @apply text-gray-600 dark:text-gray-400;\n}\n\n.gva-theme-section-content {\n  animation: fadeInUp 0.3s ease;\n}\n\n.gva-theme-setting-item {\n  @apply flex items-center justify-between py-4 gva-theme-font border-b border-gray-100 dark:border-gray-700 last:border-b-0;\n}\n\n.gva-theme-setting-label {\n  @apply text-sm font-medium gva-theme-text-main;\n}\n\n.gva-theme-mode-selector {\n  @apply inline-flex bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-1 gap-1;\n}\n\n.gva-theme-mode-item {\n  @apply flex flex-col items-center justify-center px-3 py-2 rounded-md cursor-pointer transition-all duration-150 ease-in-out min-w-[64px];\n}\n\n.gva-theme-layout-card {\n  @apply bg-white dark:bg-gray-700 border-2 border-gray-200 dark:border-gray-600 rounded-xl p-3 cursor-pointer transition-all duration-150 ease-in-out hover:-translate-y-1 hover:shadow-xl;\n}\n\n@keyframes fadeInUp {\n  from {\n    opacity: 0;\n    transform: translateY(12px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n/* Custom scrollbar for webkit browsers */\n.gva-theme-drawer ::-webkit-scrollbar {\n  width: 6px;\n}\n\n.gva-theme-drawer ::-webkit-scrollbar-track {\n  background: #f3f4f6;\n  border-radius: 3px;\n}\n\n.gva-theme-drawer ::-webkit-scrollbar-thumb {\n  background: #d1d5db;\n  border-radius: 3px;\n\n  &:hover {\n    background: #9ca3af;\n  }\n}\n\n.dark .gva-theme-drawer ::-webkit-scrollbar-track {\n  background: #1f2937;\n}\n\n.dark .gva-theme-drawer ::-webkit-scrollbar-thumb {\n  background: #4b5563;\n\n  &:hover {\n    background: #6b7280;\n  }\n}\n</style>\n\n<style lang=\"scss\" scoped>\n.reset-btn {\n  @apply rounded-lg font-medium transition-all duration-150 ease-in-out hover:-translate-y-0.5 hover:brightness-90 hover:shadow-lg;\n}\n</style>\n"
  },
  {
    "path": "web/src/view/layout/setting/modules/appearance/index.vue",
    "content": "<template>\n  <div class=\"gva-theme-font\">\n    <!-- Theme Mode Section -->\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">主题模式</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <ThemeModeSelector v-model=\"config.darkMode\" @update:modelValue=\"appStore.toggleDarkMode\" />\n      </div>\n    </div>\n\n    <!-- Theme Color Section -->\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">主题颜色</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <ThemeColorPicker v-model=\"config.primaryColor\" @update:modelValue=\"appStore.togglePrimaryColor\" />\n      </div>\n    </div>\n\n    <!-- Global Size Section -->\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">全局尺寸</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <SettingItem label=\"全局尺寸\">\n            <template #suffix>\n              <span class=\"text-xs text-gray-400 dark:text-gray-500 ml-2\">设置全局组件尺寸</span>\n            </template>\n            <div class=\"w-39\">\n              <el-select v-model=\"config.global_size\" placeholder=\"请选择\" @change=\"appStore.toggleGlobalSize\">\n                <el-option label=\"默认就好了\" value=\"default\" />\n                <el-option label=\"大点好\" value=\"large\" />\n                <el-option label=\"小的也不错\" value=\"small\" />\n              </el-select>\n            </div>\n          </SettingItem>\n        </div>\n      </div>\n    </div>\n\n    <!-- Visual Accessibility Section -->\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">视觉辅助</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <SettingItem label=\"灰色模式\">\n            <template #suffix>\n              <span class=\"text-xs text-gray-400 dark:text-gray-500 ml-2\">降低色彩饱和度</span>\n            </template>\n            <el-switch v-model=\"config.grey\" @change=\"appStore.toggleGrey\" />\n          </SettingItem>\n\n          <SettingItem label=\"色弱模式\">\n            <template #suffix>\n              <span class=\"text-xs text-gray-400 dark:text-gray-500 ml-2\">优化色彩对比度</span>\n            </template>\n            <el-switch v-model=\"config.weakness\" @change=\"appStore.toggleWeakness\" />\n          </SettingItem>\n\n          <SettingItem label=\"显示水印\">\n            <template #suffix>\n              <span class=\"text-xs text-gray-400 dark:text-gray-500 ml-2\">在页面显示水印标识</span>\n            </template>\n            <el-switch v-model=\"config.show_watermark\" @change=\"appStore.toggleConfigWatermark\" />\n          </SettingItem>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { storeToRefs } from 'pinia'\nimport { useAppStore } from '@/pinia'\nimport ThemeModeSelector from '../../components/themeModeSelector.vue'\nimport ThemeColorPicker from '../../components/themeColorPicker.vue'\nimport SettingItem from '../../components/settingItem.vue'\n\ndefineOptions({\n  name: 'AppearanceSettings'\n})\n\nconst appStore = useAppStore()\nconst { config } = storeToRefs(appStore)\n</script>\n\n\n"
  },
  {
    "path": "web/src/view/layout/setting/modules/general/index.vue",
    "content": "<template>\n  <div class=\"gva-theme-font\">\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">系统信息</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <div class=\"grid grid-cols-2 gap-4 text-sm\">\n            <div class=\"flex justify-between items-center py-3 border-b border-gray-200 dark:border-gray-600\">\n              <span class=\"gva-theme-text-sub font-medium\">版本</span>\n              <span class=\"font-mono gva-theme-text-main font-semibold\">v2.7.4</span>\n            </div>\n            <div class=\"flex justify-between items-center py-3 border-b border-gray-200 dark:border-gray-600\">\n              <span class=\"gva-theme-text-sub font-medium\">前端框架</span>\n              <span class=\"font-mono gva-theme-text-main font-semibold\">Vue 3</span>\n            </div>\n            <div class=\"flex justify-between items-center py-3 border-b border-gray-200 dark:border-gray-600\">\n              <span class=\"gva-theme-text-sub font-medium\">UI 组件库</span>\n              <span class=\"font-mono gva-theme-text-main font-semibold\">Element Plus</span>\n            </div>\n            <div class=\"flex justify-between items-center py-3 border-b border-gray-200 dark:border-gray-600\">\n              <span class=\"gva-theme-text-sub font-medium\">构建工具</span>\n              <span class=\"font-mono gva-theme-text-main font-semibold\">Vite</span>\n            </div>\n            <div class=\"flex justify-between items-center py-3\">\n              <span class=\"gva-theme-text-sub font-medium\">浏览器</span>\n              <span class=\"font-mono gva-theme-text-main font-semibold\">{{ browserInfo }}</span>\n            </div>\n            <div class=\"flex justify-between items-center py-3\">\n              <span class=\"gva-theme-text-sub font-medium\">屏幕分辨率</span>\n              <span class=\"font-mono gva-theme-text-main font-semibold\">{{ screenResolution }}</span>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">配置管理</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <div class=\"space-y-5\">\n            <div\n              class=\"gva-theme-card-white flex items-center justify-between\">\n              <div class=\"flex items-center gap-4\">\n                <div\n                  class=\"w-12 h-12 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl flex items-center justify-center text-red-600 dark:text-red-400 text-xl\">\n                  🔄\n                </div>\n                <div>\n                  <h4 class=\"text-sm font-semibold gva-theme-text-main\">重置配置</h4>\n                  <p class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">将所有设置恢复为默认值</p>\n                </div>\n              </div>\n              <el-button type=\"danger\" size=\"small\"\n                class=\"rounded-lg font-medium transition-all duration-150 ease-in-out hover:-translate-y-0.5\"\n                @click=\"handleResetConfig\">\n                重置配置\n              </el-button>\n            </div>\n\n            <div\n              class=\"gva-theme-card-white flex items-center justify-between\">\n              <div class=\"flex items-center gap-4\">\n                <div\n                  class=\"w-12 h-12 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-xl flex items-center justify-center text-blue-600 dark:text-blue-400 text-xl\">\n                  📤\n                </div>\n                <div>\n                  <h4 class=\"text-sm font-semibold gva-theme-text-main\">导出配置</h4>\n                  <p class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">导出当前配置为 JSON 文件</p>\n                </div>\n              </div>\n              <el-button type=\"primary\" size=\"small\"\n                class=\"rounded-lg font-medium transition-all duration-150 ease-in-out hover:-translate-y-0.5\"\n                :style=\"{ backgroundColor: config.primaryColor, borderColor: config.primaryColor }\"\n                @click=\"handleExportConfig\">\n                导出配置\n              </el-button>\n            </div>\n\n            <div\n              class=\"gva-theme-card-white flex items-center justify-between\">\n              <div class=\"flex items-center gap-4\">\n                <div\n                  class=\"w-12 h-12 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-xl flex items-center justify-center text-green-600 dark:text-green-400 text-xl\">\n                  📥\n                </div>\n                <div>\n                  <h4 class=\"text-sm font-semibold gva-theme-text-main\">导入配置</h4>\n                  <p class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">从 JSON 文件导入配置</p>\n                </div>\n              </div>\n              <el-upload ref=\"uploadRef\" :auto-upload=\"false\" :show-file-list=\"false\" accept=\".json\"\n                @change=\"handleImportConfig\">\n                <el-button type=\"success\" size=\"small\"\n                  class=\"rounded-lg font-medium transition-all duration-150 ease-in-out hover:-translate-y-0.5\">\n                  导入配置\n                </el-button>\n              </el-upload>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">关于项目</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <div class=\"flex items-start gap-5\">\n            <div\n              class=\"w-16 h-16 bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm\">\n              <Logo />\n            </div>\n            <div class=\"flex-1\">\n              <h4 class=\"text-xl font-semibold gva-theme-text-main mb-3\">Gin-Vue-Admin</h4>\n              <p class=\"text-sm gva-theme-text-sub mb-5 leading-relaxed\">\n                基于 Vue3 + Gin 的全栈开发基础平台，提供完整的后台管理解决方案\n              </p>\n              <div class=\"flex items-center gap-3 text-sm\">\n                <a href=\"https://github.com/flipped-aurora/gin-vue-admin\" target=\"_blank\"\n                  class=\"font-medium transition-colors duration-150 hover:underline\"\n                  :style=\"{ color: config.primaryColor }\">\n                  GitHub 仓库\n                </a>\n                <span class=\"text-gray-400 dark:text-gray-500\">·</span>\n                <a href=\"https://www.gin-vue-admin.com/\" target=\"_blank\"\n                  class=\"font-medium transition-colors duration-150 hover:underline\"\n                  :style=\"{ color: config.primaryColor }\">\n                  官方文档\n                </a>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { ref, onMounted } from 'vue'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport { storeToRefs } from 'pinia'\nimport { useAppStore } from '@/pinia'\nimport Logo from '@/components/logo/index.vue'\n\ndefineOptions({\n  name: 'GeneralSettings'\n})\n\nconst appStore = useAppStore()\nconst { config } = storeToRefs(appStore)\nconst uploadRef = ref()\n\nconst browserInfo = ref('')\nconst screenResolution = ref('')\n\nonMounted(() => {\n  const userAgent = navigator.userAgent\n  if (userAgent.includes('Chrome')) {\n    browserInfo.value = 'Chrome'\n  } else if (userAgent.includes('Firefox')) {\n    browserInfo.value = 'Firefox'\n  } else if (userAgent.includes('Safari')) {\n    browserInfo.value = 'Safari'\n  } else if (userAgent.includes('Edge')) {\n    browserInfo.value = 'Edge'\n  } else {\n    browserInfo.value = 'Unknown'\n  }\n\n  screenResolution.value = `${screen.width}×${screen.height}`\n})\n\nconst handleResetConfig = async () => {\n  try {\n    await ElMessageBox.confirm(\n      '确定要重置所有配置吗？此操作不可撤销。',\n      '重置配置',\n      {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    )\n\n    appStore.resetConfig()\n    ElMessage.success('配置已重置')\n  } catch {\n    // User cancelled\n  }\n}\n\nconst handleExportConfig = () => {\n  const configData = JSON.stringify(config.value, null, 2)\n  const blob = new Blob([configData], { type: 'application/json' })\n  const url = URL.createObjectURL(blob)\n\n  const link = document.createElement('a')\n  link.href = url\n  link.download = `gin-vue-admin-config-${new Date().toISOString().split('T')[0]}.json`\n  document.body.appendChild(link)\n  link.click()\n  document.body.removeChild(link)\n  URL.revokeObjectURL(url)\n\n  ElMessage.success('配置已导出')\n}\n\nconst handleImportConfig = (file) => {\n  const reader = new FileReader()\n  reader.onload = (e) => {\n    try {\n      const importedConfig = JSON.parse(e.target.result)\n\n      Object.keys(importedConfig).forEach(key => {\n        if (key in config.value) {\n          config.value[key] = importedConfig[key]\n        }\n      })\n\n      ElMessage.success('配置已导入')\n    } catch (error) {\n      ElMessage.error('配置文件格式错误')\n    }\n  }\n  reader.readAsText(file.raw)\n}\n</script>\n\n\n"
  },
  {
    "path": "web/src/view/layout/setting/modules/layout/index.vue",
    "content": "<template>\n  <div class=\"gva-theme-font\">\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">布局模式</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <LayoutModeCard\n          v-model=\"config.side_mode\"\n          @update:modelValue=\"appStore.toggleSideMode\"\n        />\n      </div>\n    </div>\n\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">界面配置</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <SettingItem label=\"显示标签页\">\n            <template #suffix>\n              <span class=\"text-xs text-gray-400 dark:text-gray-500 ml-2\">页面标签导航</span>\n            </template>\n            <el-switch\n              v-model=\"config.showTabs\"\n              @change=\"appStore.toggleTabs\"\n            />\n          </SettingItem>\n\n          <SettingItem label=\"页面切换动画\">\n            <template #suffix>\n              <span class=\"text-xs text-gray-400 dark:text-gray-500 ml-2\">页面过渡效果</span>\n            </template>\n            <el-select\n              v-model=\"config.transition_type\"\n              @change=\"appStore.toggleTransition\"\n              class=\"w-32\"\n              size=\"small\"\n            >\n              <el-option value=\"fade\" label=\"淡入淡出\" />\n              <el-option value=\"slide\" label=\"滑动\" />\n              <el-option value=\"zoom\" label=\"缩放\" />\n              <el-option value=\"none\" label=\"无动画\" />\n            </el-select>\n          </SettingItem>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mb-10\">\n      <div class=\"gva-theme-section-header\">\n        <div class=\"gva-theme-divider\"></div>\n        <span class=\"gva-theme-section-title\">尺寸配置</span>\n        <div class=\"gva-theme-divider\"></div>\n      </div>\n\n      <div class=\"gva-theme-section-content\">\n        <div class=\"gva-theme-card-bg\">\n          <div class=\"space-y-4\">\n            <div class=\"gva-theme-card-white\">\n              <div class=\"flex items-center justify-between\">\n                <div>\n                  <h4 class=\"text-sm font-medium gva-theme-text-main\">侧边栏展开宽度</h4>\n                  <p class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">侧边栏完全展开时的宽度</p>\n                </div>\n                <div class=\"flex items-center gap-2\">\n                  <el-input-number\n                    v-model=\"config.layout_side_width\"\n                    :min=\"150\"\n                    :max=\"400\"\n                    :step=\"10\"\n                    size=\"small\"\n                    class=\"w-24\"\n                  />\n                  <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">px</span>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"gva-theme-card-white\">\n              <div class=\"flex items-center justify-between\">\n                <div>\n                  <h4 class=\"text-sm font-medium gva-theme-text-main\">侧边栏收缩宽度</h4>\n                  <p class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">侧边栏收缩时的最小宽度</p>\n                </div>\n                <div class=\"flex items-center gap-2\">\n                  <el-input-number\n                    v-model=\"config.layout_side_collapsed_width\"\n                    :min=\"60\"\n                    :max=\"100\"\n                    size=\"small\"\n                    class=\"w-24\"\n                  />\n                  <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">px</span>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"gva-theme-card-white\">\n              <div class=\"flex items-center justify-between\">\n                <div>\n                  <h4 class=\"text-sm font-medium gva-theme-text-main\">菜单项高度</h4>\n                  <p class=\"text-xs text-gray-500 dark:text-gray-400 mt-1\">侧边栏菜单项的行高</p>\n                </div>\n                <div class=\"flex items-center gap-2\">\n                  <el-input-number\n                    v-model=\"config.layout_side_item_height\"\n                    :min=\"30\"\n                    :max=\"50\"\n                    size=\"small\"\n                    class=\"w-24\"\n                  />\n                  <span class=\"text-xs font-medium text-gray-500 dark:text-gray-400\">px</span>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { storeToRefs } from 'pinia'\nimport { useAppStore } from '@/pinia'\nimport LayoutModeCard from '../../components/layoutModeCard.vue'\nimport SettingItem from '../../components/settingItem.vue'\n\ndefineOptions({\n  name: 'LayoutSettings'\n})\n\nconst appStore = useAppStore()\nconst { config } = storeToRefs(appStore)\n</script>\n\n\n"
  },
  {
    "path": "web/src/view/layout/tabs/index.vue",
    "content": "<template>\n  <div class=\"gva-tabs\">\n    <el-tabs\n      v-model=\"activeValue\"\n      :closable=\"!(historys.length === 1 && $route.name === defaultRouter)\"\n      type=\"card\"\n      class=\"bg-white text-slate-700 dark:text-slate-500 dark:bg-slate-900 pt-1\"\n      @contextmenu.prevent=\"openContextMenu($event)\"\n      @tab-click=\"changeTab\"\n      @tab-remove=\"removeTab\"\n      @click.middle.prevent=\"middleCloseTab($event)\"\n    >\n      <el-tab-pane\n        v-for=\"item in historys\"\n        :key=\"getFmtString(item)\"\n        :label=\"item.meta.title\"\n        :name=\"getFmtString(item)\"\n        :tab=\"item\"\n        class=\"border-none\"\n      >\n        <template #label>\n          <span\n            :tab=\"item\"\n            :class=\"\n              activeValue === getFmtString(item)\n                ? 'text-active'\n                : 'text-gray-600 dark:text-slate-400 '\n            \"\n            ><i\n              :class=\"\n                activeValue === getFmtString(item)\n                  ? 'text-active'\n                  : 'text-gray-600 dark:text-slate-400'\n              \"\n            />\n            {{ fmtTitle(item.meta.title, item) }}</span\n          >\n        </template>\n      </el-tab-pane>\n    </el-tabs>\n\n    <!--自定义右键菜单html代码-->\n    <ul\n      v-show=\"contextMenuVisible\"\n      :style=\"{ left: left + 'px', top: top + 'px' }\"\n      class=\"contextmenu\"\n    >\n      <li @click=\"closeAll\">关闭所有</li>\n      <li @click=\"closeLeft\">关闭左侧</li>\n      <li @click=\"closeRight\">关闭右侧</li>\n      <li @click=\"closeOther\">关闭其他</li>\n    </ul>\n  </div>\n</template>\n\n<script setup>\n  import { emitter } from '@/utils/bus.js'\n  import { computed, onUnmounted, ref, watch, nextTick } from 'vue'\n  import { useRoute, useRouter } from 'vue-router'\n  import { useUserStore } from '@/pinia/modules/user'\n  import { fmtTitle } from '@/utils/fmtRouterTitle'\n\n  defineOptions({\n    name: 'HistoryComponent'\n  })\n\n  const route = useRoute()\n  const router = useRouter()\n\n  const getFmtString = (item) => {\n    return item.name + JSON.stringify(item.query) + JSON.stringify(item.params)\n  }\n\n  const historys = ref([])\n  const activeValue = ref('')\n  const contextMenuVisible = ref(false)\n\n  const userStore = useUserStore()\n\n  const left = ref(0)\n  const top = ref(0)\n  const isCollapse = ref(false)\n  const isMobile = ref(false)\n  const rightActive = ref('')\n  const defaultRouter = computed(\n    () => userStore.userInfo.authority.defaultRouter\n  )\n  const openContextMenu = (e) => {\n    if (historys.value.length === 1 && route.name === defaultRouter.value) {\n      return false\n    }\n    let id = ''\n    if (e.srcElement.nodeName === 'SPAN') {\n      id = e.srcElement.offsetParent.id\n    } else {\n      id = e.srcElement.id\n    }\n    if (id) {\n      contextMenuVisible.value = true\n\n      left.value = e.clientX\n      top.value = e.clientY + 10\n      rightActive.value = id.substring(4)\n    }\n  }\n  const closeAll = () => {\n    historys.value = [\n      {\n        name: defaultRouter.value,\n        meta: {\n          title: '首页'\n        },\n        query: {},\n        params: {}\n      }\n    ]\n    router.push({ name: defaultRouter.value })\n    contextMenuVisible.value = false\n    sessionStorage.setItem('historys', JSON.stringify(historys.value))\n  }\n  const closeLeft = () => {\n    let right\n    const rightIndex = historys.value.findIndex((item) => {\n      if (getFmtString(item) === rightActive.value) {\n        right = item\n      }\n      return getFmtString(item) === rightActive.value\n    })\n    const activeIndex = historys.value.findIndex(\n      (item) => getFmtString(item) === activeValue.value\n    )\n    historys.value.splice(0, rightIndex)\n    if (rightIndex > activeIndex) {\n      router.push(right)\n    }\n    sessionStorage.setItem('historys', JSON.stringify(historys.value))\n  }\n  const closeRight = () => {\n    let right\n    const leftIndex = historys.value.findIndex((item) => {\n      if (getFmtString(item) === rightActive.value) {\n        right = item\n      }\n      return getFmtString(item) === rightActive.value\n    })\n    const activeIndex = historys.value.findIndex(\n      (item) => getFmtString(item) === activeValue.value\n    )\n    historys.value.splice(leftIndex + 1, historys.value.length)\n    if (leftIndex < activeIndex) {\n      router.push(right)\n    }\n    sessionStorage.setItem('historys', JSON.stringify(historys.value))\n  }\n  const closeOther = () => {\n    let right\n    historys.value = historys.value.filter((item) => {\n      if (getFmtString(item) === rightActive.value) {\n        right = item\n      }\n      return getFmtString(item) === rightActive.value\n    })\n    router.push(right)\n    sessionStorage.setItem('historys', JSON.stringify(historys.value))\n  }\n  const isSame = (route1, route2) => {\n    if (route1.name !== route2.name) {\n      return false\n    }\n    if (\n      Object.keys(route1.query).length !== Object.keys(route2.query).length ||\n      Object.keys(route1.params).length !== Object.keys(route2.params).length\n    ) {\n      return false\n    }\n    for (const key in route1.query) {\n      if (route1.query[key] !== route2.query[key]) {\n        return false\n      }\n    }\n    for (const key in route1.params) {\n      if (route1.params[key] !== route2.params[key]) {\n        return false\n      }\n    }\n    return true\n  }\n  const setTab = (route) => {\n    if (!historys.value.some((item) => isSame(item, route))) {\n      const obj = {}\n      obj.name = route.name\n      obj.meta = { ...route.meta }\n      delete obj.meta.matched\n      obj.query = route.query\n      obj.params = route.params\n      historys.value.push(obj)\n    }\n    window.sessionStorage.setItem('activeValue', getFmtString(route))\n  }\n\n  const historyMap = ref({})\n\n  const changeTab = (TabsPaneContext) => {\n    const name = TabsPaneContext?.props?.name\n    if (!name) return\n    const tab = historyMap.value[name]\n    router.push({\n      name: tab.name,\n      query: tab.query,\n      params: tab.params\n    })\n  }\n  const removeTab = (tab) => {\n    const index = historys.value.findIndex((item) => getFmtString(item) === tab)\n    if (getFmtString(route) === tab) {\n      if (historys.value.length === 1) {\n        router.push({ name: defaultRouter.value })\n      } else {\n        if (index < historys.value.length - 1) {\n          router.push({\n            name: historys.value[index + 1].name,\n            query: historys.value[index + 1].query,\n            params: historys.value[index + 1].params\n          })\n        } else {\n          router.push({\n            name: historys.value[index - 1].name,\n            query: historys.value[index - 1].query,\n            params: historys.value[index - 1].params\n          })\n        }\n      }\n    }\n    historys.value.splice(index, 1)\n  }\n\n  watch(\n    () => contextMenuVisible.value,\n    () => {\n      if (contextMenuVisible.value) {\n        document.body.addEventListener('click', () => {\n          contextMenuVisible.value = false\n        })\n      } else {\n        document.body.removeEventListener('click', () => {\n          contextMenuVisible.value = false\n        })\n      }\n    }\n  )\n\n  watch(\n    () => route,\n    (to) => {\n      if (to.name === 'Login' || to.name === 'Reload') {\n        return\n      }\n      historys.value = historys.value.filter((item) => !item.meta.closeTab)\n      setTab(to)\n      sessionStorage.setItem('historys', JSON.stringify(historys.value))\n      activeValue.value = window.sessionStorage.getItem('activeValue')\n    },\n    { deep: true }\n  )\n\n  watch(\n    () => historys.value,\n    () => {\n      sessionStorage.setItem('historys', JSON.stringify(historys.value))\n      historyMap.value = {}\n      historys.value.forEach((item) => {\n        historyMap.value[getFmtString(item)] = item\n      })\n      emitter.emit('setKeepAlive', historys.value)\n    },\n    {\n      deep: true\n    }\n  )\n\n  const initPage = () => {\n    // 全局监听 关闭当前页面函数\n    emitter.on('closeThisPage', () => {\n      removeTab(getFmtString(route))\n    })\n    // 全局监听 关闭所有页面函数\n    emitter.on('closeAllPage', () => {\n      closeAll()\n    })\n    emitter.on('mobile', (data) => {\n      isMobile.value = data\n    })\n    emitter.on('collapse', (data) => {\n      isCollapse.value = data\n    })\n\n    emitter.on('setQuery', (data) => {\n      const index = historys.value.findIndex(\n        (item) => getFmtString(item) === activeValue.value\n      )\n      historys.value[index].query = data\n      activeValue.value = getFmtString(historys.value[index])\n      const currentUrl = window.location.href.split('?')[0]\n      const currentSearchParams = new URLSearchParams(data).toString()\n      window.history.replaceState(\n        {},\n        '',\n        `${currentUrl}?${currentSearchParams}`\n      )\n      sessionStorage.setItem('historys', JSON.stringify(historys.value))\n    })\n\n    emitter.on('switchTab', async (data) => {\n      const index = historys.value.findIndex((item) => item.name === data.name)\n      if (index < 0) {\n        return\n      }\n      for (const key in data.query) {\n        data.query[key] = String(data.query[key])\n      }\n      for (const key in data.params) {\n        data.params[key] = String(data.params[key])\n      }\n\n      historys.value[index].query = data.query || {}\n      historys.value[index].params = data.params || {}\n      await nextTick()\n      router.push(historys.value[index])\n    })\n    const initHistorys = [\n      {\n        name: defaultRouter.value,\n        meta: {\n          title: '首页'\n        },\n        query: {},\n        params: {}\n      }\n    ]\n    setTab(route)\n    historys.value =\n      JSON.parse(sessionStorage.getItem('historys')) || initHistorys\n    if (!window.sessionStorage.getItem('activeValue')) {\n      activeValue.value = getFmtString(route)\n    } else {\n      activeValue.value = window.sessionStorage.getItem('activeValue')\n    }\n    if (window.sessionStorage.getItem('needCloseAll') === 'true') {\n      closeAll()\n      window.sessionStorage.removeItem('needCloseAll')\n    }\n  }\n  initPage()\n\n  onUnmounted(() => {\n    emitter.off('collapse')\n    emitter.off('mobile')\n  })\n\n  const middleCloseTab = (e) => {\n    if (historys.value.length === 1 && route.name === defaultRouter.value) {\n      return false\n    }\n    let id = ''\n    if (e.srcElement.nodeName === 'SPAN') {\n      id = e.srcElement.offsetParent.id\n    } else {\n      id = e.srcElement.id\n    }\n    if (id) {\n      removeTab(id.substring(4))\n    }\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .contextmenu {\n    @apply bg-white dark:bg-slate-900 w-28 m-0 py-2.5 px-0 border border-gray-200 text-sm shadow-md rounded absolute z-50 border-solid dark:border-slate-800;\n  }\n\n  .contextmenu li {\n    @apply text-slate-700 dark:text-slate-200 text-base list-none px-4 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer;\n  }\n\n  $base-tag-item-height: 4rem;\n\n  .gva-tabs {\n    ::v-deep(.el-tabs--card > .el-tabs__header) {\n      border: none;\n    }\n    ::v-deep(.el-tabs__nav-scroll) {\n      padding: 4px 4px;\n    }\n\n    ::v-deep(.el-tabs__nav) {\n      border: 0;\n    }\n\n    ::v-deep(.el-tabs__header) {\n      border-bottom: 0;\n    }\n    ::v-deep(.el-tabs__item) {\n      box-sizing: border-box;\n      border: 1px solid var(--el-border-color-darker);\n      border-radius: 2px;\n      margin-right: 5px;\n      margin-left: 2px;\n      transition: padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;\n      height: 34px;\n      &.is-active {\n        border: 1px solid var(--el-color-primary);\n      }\n    }\n    ::v-deep(.el-tabs__item):first-child {\n      border: 1px solid var(--el-border-color-darker);\n      &.is-active {\n        border: 1px solid var(--el-color-primary);\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/login/index.vue",
    "content": "<template>\n  <div id=\"userLayout\" class=\"w-full h-full relative\">\n    <div\n      class=\"rounded-lg flex items-center justify-evenly w-full h-full md:w-screen md:h-screen md:bg-[#194bfb] bg-white\"\n    >\n      <div class=\"md:w-3/5 w-10/12 h-full flex items-center justify-evenly\">\n        <div\n          class=\"oblique h-[130%] w-3/5 bg-white dark:bg-slate-900 transform -rotate-12 absolute -ml-52\"\n        />\n        <!-- 分割斜块 -->\n        <div\n          class=\"z-[999] pt-12 pb-10 md:w-96 w-full rounded-lg flex flex-col justify-between box-border\"\n        >\n          <div>\n            <div class=\"flex items-center justify-center\">\n              <Logo :size=\"6\" />\n            </div>\n            <div class=\"mb-9\">\n              <p class=\"text-center text-4xl font-bold\">\n                {{ $GIN_VUE_ADMIN.appName }}\n              </p>\n              <p class=\"text-center text-sm font-normal text-gray-500 mt-2.5\">\n                A management platform using Golang and Vue\n              </p>\n            </div>\n            <el-form\n              ref=\"loginForm\"\n              :model=\"loginFormData\"\n              :rules=\"rules\"\n              :validate-on-rule-change=\"false\"\n              @keyup.enter=\"submitForm\"\n            >\n              <el-form-item prop=\"username\" class=\"mb-6\">\n                <el-input\n                  v-model=\"loginFormData.username\"\n                  size=\"large\"\n                  placeholder=\"请输入用户名\"\n                  suffix-icon=\"user\"\n                />\n              </el-form-item>\n              <el-form-item prop=\"password\" class=\"mb-6\">\n                <el-input\n                  v-model=\"loginFormData.password\"\n                  show-password\n                  size=\"large\"\n                  type=\"password\"\n                  placeholder=\"请输入密码\"\n                />\n              </el-form-item>\n              <el-form-item\n                v-if=\"loginFormData.openCaptcha\"\n                prop=\"captcha\"\n                class=\"mb-6\"\n              >\n                <div class=\"flex w-full justify-between\">\n                  <el-input\n                    v-model=\"loginFormData.captcha\"\n                    placeholder=\"请输入验证码\"\n                    size=\"large\"\n                    class=\"flex-1 mr-5\"\n                  />\n                  <div class=\"w-1/3 h-11 bg-[#c3d4f2] rounded\">\n                    <img\n                      v-if=\"picPath\"\n                      class=\"w-full h-full\"\n                      :src=\"picPath\"\n                      alt=\"请输入验证码\"\n                      @click=\"loginVerify()\"\n                    />\n                  </div>\n                </div>\n              </el-form-item>\n              <el-form-item class=\"mb-6\">\n                <el-button\n                  class=\"shadow shadow-active h-11 w-full\"\n                  type=\"primary\"\n                  size=\"large\"\n                  @click=\"submitForm\"\n                  >登 录</el-button\n                >\n              </el-form-item>\n              <el-form-item v-if=\"isDev\" class=\"mb-6\">\n                <el-button\n                  class=\"shadow shadow-active h-11 w-full\"\n                  type=\"primary\"\n                  size=\"large\"\n                  @click=\"checkInit\"\n                  >前往初始化</el-button\n                >\n              </el-form-item>\n            </el-form>\n          </div>\n        </div>\n      </div>\n      <div class=\"hidden md:block w-1/2 h-full float-right bg-[#194bfb]\">\n        <img\n          class=\"h-full\"\n          src=\"@/assets/login_right_banner.jpg\"\n          alt=\"banner\"\n        />\n      </div>\n    </div>\n\n    <BottomInfo class=\"left-0 right-0 absolute bottom-3 mx-auto w-full z-20\">\n      <div class=\"links items-center justify-center gap-2 hidden md:flex\">\n        <a href=\"https://www.gin-vue-admin.com/\" target=\"_blank\">\n          <img src=\"@/assets/docs.png\" class=\"w-8 h-8\" alt=\"文档\" />\n        </a>\n        <a href=\"https://support.qq.com/product/371961\" target=\"_blank\">\n          <img src=\"@/assets/kefu.png\" class=\"w-8 h-8\" alt=\"客服\" />\n        </a>\n        <a\n          href=\"https://github.com/flipped-aurora/gin-vue-admin\"\n          target=\"_blank\"\n        >\n          <img src=\"@/assets/github.png\" class=\"w-8 h-8\" alt=\"github\" />\n        </a>\n        <a href=\"https://space.bilibili.com/322210472\" target=\"_blank\">\n          <img src=\"@/assets/video.png\" class=\"w-8 h-8\" alt=\"视频站\" />\n        </a>\n      </div>\n    </BottomInfo>\n  </div>\n</template>\n\n<script setup>\n  import { captcha } from '@/api/user'\n  import { checkDB } from '@/api/initdb'\n  import BottomInfo from '@/components/bottomInfo/bottomInfo.vue'\n  import { reactive, ref } from 'vue'\n  import { ElMessage } from 'element-plus'\n  import { useRouter } from 'vue-router'\n  import { useUserStore } from '@/pinia/modules/user'\n  import Logo from '@/components/logo/index.vue'\n  import { isDev } from '@/utils/env.js'\n\n  defineOptions({\n    name: 'Login'\n  })\n\n  const router = useRouter()\n  const captchaRequiredLength = ref(6)\n  // 验证函数\n  const checkUsername = (rule, value, callback) => {\n    if (value.length < 5) {\n      return callback(new Error('请输入正确的用户名'))\n    } else {\n      callback()\n    }\n  }\n  const checkPassword = (rule, value, callback) => {\n    if (value.length < 6) {\n      return callback(new Error('请输入正确的密码'))\n    } else {\n      callback()\n    }\n  }\n  const checkCaptcha = (rule, value, callback) => {\n    if (!loginFormData.openCaptcha) {\n      return callback()\n    }\n    const sanitizedValue = (value || '').replace(/\\s+/g, '')\n    if (!sanitizedValue) {\n      return callback(new Error('请输入验证码'))\n    }\n    if (!/^\\d+$/.test(sanitizedValue)) {\n      return callback(new Error('验证码须为数字'))\n    }\n    if (sanitizedValue.length < captchaRequiredLength.value) {\n      return callback(\n        new Error(`请输入至少${captchaRequiredLength.value}位数字验证码`)\n      )\n    }\n    if (sanitizedValue !== value) {\n      loginFormData.captcha = sanitizedValue\n    }\n    callback()\n  }\n\n  // 获取验证码\n  const loginVerify = async () => {\n    const ele = await captcha()\n    captchaRequiredLength.value = Number(ele.data?.captchaLength) || 0\n    picPath.value = ele.data?.picPath\n    loginFormData.captchaId = ele.data?.captchaId\n    loginFormData.openCaptcha = ele.data?.openCaptcha\n  }\n  loginVerify()\n\n  // 登录相关操作\n  const loginForm = ref(null)\n  const picPath = ref('')\n  const loginFormData = reactive({\n    username: 'admin',\n    password: '',\n    captcha: '',\n    captchaId: '',\n    openCaptcha: false\n  })\n  const rules = reactive({\n    username: [{ validator: checkUsername, trigger: 'blur' }],\n    password: [{ validator: checkPassword, trigger: 'blur' }],\n    captcha: [{ validator: checkCaptcha, trigger: 'blur' }]\n  })\n\n  const userStore = useUserStore()\n  const login = async () => {\n    return await userStore.LoginIn(loginFormData)\n  }\n  const submitForm = () => {\n    loginForm.value.validate(async (v) => {\n      if (!v) {\n        // 未通过前端静态验证\n        ElMessage({\n          type: 'error',\n          message: '请正确填写登录信息',\n          showClose: true\n        })\n        return false\n      }\n\n      // 通过验证，请求登陆\n      const flag = await login()\n\n      // 登陆失败，刷新验证码\n      if (!flag) {\n        await loginVerify()\n        return false\n      }\n\n      // 登陆成功\n      return true\n    })\n  }\n\n  // 跳转初始化\n  const checkInit = async () => {\n    const res = await checkDB()\n    if (res.code === 0) {\n      if (res.data?.needInit) {\n        userStore.NeedInit()\n        await router.push({ name: 'Init' })\n      } else {\n        ElMessage({\n          type: 'info',\n          message: '已配置数据库信息，无法初始化'\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/view/person/person.vue",
    "content": "<template>\n  <div class=\"profile-container\">\n    <!-- 顶部个人信息卡片 -->\n    <div class=\"bg-white dark:bg-slate-800 rounded-2xl shadow-sm mb-8\">\n      <!-- 顶部背景图 -->\n      <div class=\"h-48 bg-blue-50 dark:bg-slate-600 relative\">\n        <div class=\"absolute inset-0 bg-pattern opacity-7\"></div>\n      </div>\n\n      <!-- 个人信息区 -->\n      <div class=\"px-8 -mt-20 pb-8\">\n        <div class=\"flex flex-col lg:flex-row items-start gap-8\">\n          <!-- 左侧头像 -->\n          <div class=\"profile-avatar-wrapper flex-shrink-0 mx-auto lg:mx-0\">\n            <SelectImage\n                v-model=\"userStore.userInfo.headerImg\"\n                file-type=\"image\"\n                rounded\n            />\n          </div>\n\n          <!-- 右侧信息 -->\n          <div class=\"flex-1 pt-12 lg:pt-20 w-full\">\n            <div\n              class=\"flex flex-col lg:flex-row items-start lg:items-start justify-between gap-4\"\n            >\n              <div class=\"lg:mt-4\">\n                <div class=\"flex items-center gap-4 mb-4\">\n                  <div\n                    v-if=\"!editFlag\"\n                    class=\"text-2xl font-bold flex items-center gap-3 text-gray-800 dark:text-gray-100\"\n                  >\n                    {{ userStore.userInfo.nickName }}\n                    <el-icon\n                      class=\"cursor-pointer text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors duration-200\"\n                      @click=\"openEdit\"\n                    >\n                      <edit />\n                    </el-icon>\n                  </div>\n                  <div v-else class=\"flex items-center\">\n                    <el-input v-model=\"nickName\" class=\"w-48 mr-4\" />\n                    <el-button type=\"primary\" plain @click=\"enterEdit\">\n                      确认\n                    </el-button>\n                    <el-button type=\"danger\" plain @click=\"closeEdit\">\n                      取消\n                    </el-button>\n                  </div>\n                </div>\n\n                <div\n                  class=\"flex flex-col lg:flex-row items-start lg:items-center gap-4 lg:gap-8 text-gray-500 dark:text-gray-400\"\n                >\n                  <div class=\"flex items-center gap-2\">\n                    <el-icon><location /></el-icon>\n                    <span>中国·北京市·朝阳区</span>\n                  </div>\n                  <div class=\"flex items-center gap-2\">\n                    <el-icon><office-building /></el-icon>\n                    <span>北京翻转极光科技有限公司</span>\n                  </div>\n                  <div class=\"flex items-center gap-2\">\n                    <el-icon><user /></el-icon>\n                    <span>技术部·前端事业群</span>\n                  </div>\n                </div>\n              </div>\n\n              <div class=\"flex gap-4 mt-4\">\n                <el-button type=\"primary\" plain icon=\"message\">\n                  发送消息\n                </el-button>\n                <el-button icon=\"share\"> 分享主页 </el-button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <!-- 主要内容区 -->\n    <div class=\"grid lg:grid-cols-12 md:grid-cols-1 gap-8\">\n      <!-- 左侧信息栏 -->\n      <div class=\"lg:col-span-4\">\n        <div\n          class=\"bg-white dark:bg-slate-800 rounded-xl p-6 mb-6 profile-card\"\n        >\n          <h2 class=\"text-lg font-semibold mb-4 flex items-center gap-2\">\n            <el-icon class=\"text-blue-500\"><info-filled /></el-icon>\n            基本信息\n          </h2>\n          <div class=\"space-y-4\">\n            <div\n              class=\"flex items-center gap-1 lg:gap-3 text-gray-600 dark:text-gray-300\"\n            >\n              <el-icon class=\"text-blue-500\"><phone /></el-icon>\n              <span class=\"font-medium\">手机号码：</span>\n              <span>{{ userStore.userInfo.phone || '未设置' }}</span>\n              <el-button\n                link\n                type=\"primary\"\n                class=\"ml-auto\"\n                @click=\"changePhoneFlag = true\"\n              >\n                修改\n              </el-button>\n            </div>\n            <div\n              class=\"flex items-center gap-1 lg:gap-3 text-gray-600 dark:text-gray-300\"\n            >\n              <el-icon class=\"text-green-500\"><message /></el-icon>\n              <span class=\"font-medium flex-shrink-0\">邮箱地址：</span>\n              <span>{{ userStore.userInfo.email || '未设置' }}</span>\n              <el-button\n                link\n                type=\"primary\"\n                class=\"ml-auto\"\n                @click=\"changeEmailFlag = true\"\n              >\n                修改\n              </el-button>\n            </div>\n            <div\n              class=\"flex items-center gap-1 lg:gap-3 text-gray-600 dark:text-gray-300\"\n            >\n              <el-icon class=\"text-purple-500\"><lock /></el-icon>\n              <span class=\"font-medium\">账号密码：</span>\n              <span>已设置</span>\n              <el-button\n                link\n                type=\"primary\"\n                class=\"ml-auto\"\n                @click=\"showPassword = true\"\n              >\n                修改\n              </el-button>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"bg-white dark:bg-slate-800 rounded-xl p-6 profile-card\">\n          <h2 class=\"text-lg font-semibold mb-4 flex items-center gap-2\">\n            <el-icon class=\"text-blue-500\"><medal /></el-icon>\n            技能特长\n          </h2>\n          <div class=\"flex flex-wrap gap-2\">\n            <el-tag effect=\"plain\" type=\"success\">GoLang</el-tag>\n            <el-tag effect=\"plain\" type=\"warning\">JavaScript</el-tag>\n            <el-tag effect=\"plain\" type=\"danger\">Vue</el-tag>\n            <el-tag effect=\"plain\" type=\"info\">Gorm</el-tag>\n            <el-button link class=\"text-sm\">\n              <el-icon><plus /></el-icon>\n              添加技能\n            </el-button>\n          </div>\n        </div>\n      </div>\n\n      <!-- 右侧内容区 -->\n      <div class=\"lg:col-span-8\">\n        <div class=\"bg-white dark:bg-slate-800 rounded-xl p-6 profile-card\">\n          <el-tabs class=\"custom-tabs\">\n            <el-tab-pane>\n              <template #label>\n                <div class=\"flex items-center gap-2\">\n                  <el-icon><data-line /></el-icon>\n                  数据统计\n                </div>\n              </template>\n              <div class=\"grid grid-cols-2 md:grid-cols-4 gap-4 lg:gap-6 py-6\">\n                <div class=\"stat-card\">\n                  <div\n                    class=\"text-2xl lg:text-4xl font-bold text-blue-500 mb-2\"\n                  >\n                    138\n                  </div>\n                  <div class=\"text-gray-500 text-sm\">项目参与</div>\n                </div>\n                <div class=\"stat-card\">\n                  <div\n                    class=\"text-2xl lg:text-4xl font-bold text-green-500 mb-2\"\n                  >\n                    2.3k\n                  </div>\n                  <div class=\"text-gray-500 text-sm\">代码提交</div>\n                </div>\n                <div class=\"stat-card\">\n                  <div\n                    class=\"text-2xl lg:text-4xl font-bold text-purple-500 mb-2\"\n                  >\n                    95%\n                  </div>\n                  <div class=\"text-gray-500 text-sm\">任务完成</div>\n                </div>\n                <div class=\"stat-card\">\n                  <div\n                    class=\"text-2xl lg:text-4xl font-bold text-yellow-500 mb-2\"\n                  >\n                    12\n                  </div>\n                  <div class=\"text-gray-500 text-sm\">获得勋章</div>\n                </div>\n              </div>\n            </el-tab-pane>\n            <el-tab-pane>\n              <template #label>\n                <div class=\"flex items-center gap-2\">\n                  <el-icon><calendar /></el-icon>\n                  近期动态\n                </div>\n              </template>\n              <div class=\"py-6\">\n                <el-timeline>\n                  <el-timeline-item\n                    v-for=\"(activity, index) in activities\"\n                    :key=\"index\"\n                    :type=\"activity.type\"\n                    :timestamp=\"activity.timestamp\"\n                    :hollow=\"true\"\n                    class=\"pb-6\"\n                  >\n                    <h3 class=\"text-base font-medium mb-1\">\n                      {{ activity.title }}\n                    </h3>\n                    <p class=\"text-gray-500 text-sm\">{{ activity.content }}</p>\n                  </el-timeline-item>\n                </el-timeline>\n              </div>\n            </el-tab-pane>\n          </el-tabs>\n        </div>\n      </div>\n    </div>\n\n    <!-- 弹窗 -->\n    <el-dialog\n      v-model=\"showPassword\"\n      title=\"修改密码\"\n      width=\"400px\"\n      class=\"custom-dialog\"\n      @close=\"clearPassword\"\n    >\n      <el-form\n        ref=\"modifyPwdForm\"\n        :model=\"pwdModify\"\n        :rules=\"rules\"\n        label-width=\"90px\"\n        class=\"py-4\"\n      >\n        <el-form-item :minlength=\"6\" label=\"原密码\" prop=\"password\">\n          <el-input v-model=\"pwdModify.password\" show-password />\n        </el-form-item>\n        <el-form-item :minlength=\"6\" label=\"新密码\" prop=\"newPassword\">\n          <el-input v-model=\"pwdModify.newPassword\" show-password />\n        </el-form-item>\n        <el-form-item :minlength=\"6\" label=\"确认密码\" prop=\"confirmPassword\">\n          <el-input v-model=\"pwdModify.confirmPassword\" show-password />\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"showPassword = false\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"savePassword\">确 定</el-button>\n        </div>\n      </template>\n    </el-dialog>\n\n    <el-dialog\n      v-model=\"changePhoneFlag\"\n      title=\"修改手机号\"\n      width=\"400px\"\n      class=\"custom-dialog\"\n    >\n      <el-form :model=\"phoneForm\" label-width=\"80px\" class=\"py-4\">\n        <el-form-item label=\"手机号\">\n          <el-input v-model=\"phoneForm.phone\" placeholder=\"请输入新的手机号码\">\n            <template #prefix>\n              <el-icon><phone /></el-icon>\n            </template>\n          </el-input>\n        </el-form-item>\n        <el-form-item label=\"验证码\">\n          <div class=\"flex gap-4\">\n            <el-input\n              v-model=\"phoneForm.code\"\n              placeholder=\"请输入验证码[模拟]\"\n              class=\"flex-1\"\n            >\n              <template #prefix>\n                <el-icon><key /></el-icon>\n              </template>\n            </el-input>\n            <el-button\n              type=\"primary\"\n              :disabled=\"time > 0\"\n              class=\"w-32\"\n              @click=\"getCode\"\n            >\n              {{ time > 0 ? `${time}s` : '获取验证码' }}\n            </el-button>\n          </div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"closeChangePhone\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"changePhone\">确 定</el-button>\n        </div>\n      </template>\n    </el-dialog>\n\n    <el-dialog\n      v-model=\"changeEmailFlag\"\n      title=\"修改邮箱\"\n      width=\"400px\"\n      class=\"custom-dialog\"\n    >\n      <el-form :model=\"emailForm\" label-width=\"80px\" class=\"py-4\">\n        <el-form-item label=\"邮箱\">\n          <el-input v-model=\"emailForm.email\" placeholder=\"请输入新的邮箱地址\">\n            <template #prefix>\n              <el-icon><message /></el-icon>\n            </template>\n          </el-input>\n        </el-form-item>\n        <el-form-item label=\"验证码\">\n          <div class=\"flex gap-4\">\n            <el-input\n              v-model=\"emailForm.code\"\n              placeholder=\"请输入验证码[模拟]\"\n              class=\"flex-1\"\n            >\n              <template #prefix>\n                <el-icon><key /></el-icon>\n              </template>\n            </el-input>\n            <el-button\n              type=\"primary\"\n              :disabled=\"emailTime > 0\"\n              class=\"w-32\"\n              @click=\"getEmailCode\"\n            >\n              {{ emailTime > 0 ? `${emailTime}s` : '获取验证码' }}\n            </el-button>\n          </div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"closeChangeEmail\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"changeEmail\">确 定</el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\n  import { setSelfInfo, changePassword } from '@/api/user.js'\n  import { reactive, ref, watch } from 'vue'\n  import { ElMessage } from 'element-plus'\n  import { useUserStore } from '@/pinia/modules/user'\n  import SelectImage from '@/components/selectImage/selectImage.vue'\n  defineOptions({\n    name: 'Person'\n  })\n\n  const userStore = useUserStore()\n  const modifyPwdForm = ref(null)\n  const showPassword = ref(false)\n  const pwdModify = ref({})\n  const nickName = ref('')\n  const editFlag = ref(false)\n\n  const rules = reactive({\n    password: [\n      { required: true, message: '请输入密码', trigger: 'blur' },\n      { min: 6, message: '最少6个字符', trigger: 'blur' }\n    ],\n    newPassword: [\n      { required: true, message: '请输入新密码', trigger: 'blur' },\n      { min: 6, message: '最少6个字符', trigger: 'blur' }\n    ],\n    confirmPassword: [\n      { required: true, message: '请输入确认密码', trigger: 'blur' },\n      { min: 6, message: '最少6个字符', trigger: 'blur' },\n      {\n        validator: (rule, value, callback) => {\n          if (value !== pwdModify.value.newPassword) {\n            callback(new Error('两次密码不一致'))\n          } else {\n            callback()\n          }\n        },\n        trigger: 'blur'\n      }\n    ]\n  })\n\n  const savePassword = async () => {\n    modifyPwdForm.value.validate((valid) => {\n      if (valid) {\n        changePassword({\n          password: pwdModify.value.password,\n          newPassword: pwdModify.value.newPassword\n        }).then((res) => {\n          if (res.code === 0) {\n            ElMessage.success('修改密码成功！')\n          }\n          showPassword.value = false\n        })\n      }\n    })\n  }\n\n  const clearPassword = () => {\n    pwdModify.value = {\n      password: '',\n      newPassword: '',\n      confirmPassword: ''\n    }\n    modifyPwdForm.value?.clearValidate()\n  }\n\n  const openEdit = () => {\n    nickName.value = userStore.userInfo.nickName\n    editFlag.value = true\n  }\n\n  const closeEdit = () => {\n    nickName.value = ''\n    editFlag.value = false\n  }\n\n  const enterEdit = async () => {\n    const res = await setSelfInfo({\n      nickName: nickName.value\n    })\n    if (res.code === 0) {\n      userStore.ResetUserInfo({ nickName: nickName.value })\n      ElMessage.success('修改成功')\n    }\n    nickName.value = ''\n    editFlag.value = false\n  }\n\n  const changePhoneFlag = ref(false)\n  const time = ref(0)\n  const phoneForm = reactive({\n    phone: '',\n    code: ''\n  })\n\n  const getCode = async () => {\n    time.value = 60\n    let timer = setInterval(() => {\n      time.value--\n      if (time.value <= 0) {\n        clearInterval(timer)\n        timer = null\n      }\n    }, 1000)\n  }\n\n  const closeChangePhone = () => {\n    changePhoneFlag.value = false\n    phoneForm.phone = ''\n    phoneForm.code = ''\n  }\n\n  const changePhone = async () => {\n    const res = await setSelfInfo({ phone: phoneForm.phone })\n    if (res.code === 0) {\n      ElMessage.success('修改成功')\n      userStore.ResetUserInfo({ phone: phoneForm.phone })\n      closeChangePhone()\n    }\n  }\n\n  const changeEmailFlag = ref(false)\n  const emailTime = ref(0)\n  const emailForm = reactive({\n    email: '',\n    code: ''\n  })\n\n  const getEmailCode = async () => {\n    emailTime.value = 60\n    let timer = setInterval(() => {\n      emailTime.value--\n      if (emailTime.value <= 0) {\n        clearInterval(timer)\n        timer = null\n      }\n    }, 1000)\n  }\n\n  const closeChangeEmail = () => {\n    changeEmailFlag.value = false\n    emailForm.email = ''\n    emailForm.code = ''\n  }\n\n  const changeEmail = async () => {\n    const res = await setSelfInfo({ email: emailForm.email })\n    if (res.code === 0) {\n      ElMessage.success('修改成功')\n      userStore.ResetUserInfo({ email: emailForm.email })\n      closeChangeEmail()\n    }\n  }\n\n  watch(() => userStore.userInfo.headerImg, async(val) => {\n    const res = await setSelfInfo({ headerImg: val })\n    if (res.code === 0) {\n      userStore.ResetUserInfo({ headerImg: val })\n      ElMessage({\n        type: 'success',\n        message: '设置成功',\n      })\n    }\n  })\n\n  // 添加活动数据\n  const activities = [\n    {\n      timestamp: '2024-01-10',\n      title: '完成项目里程碑',\n      content: '成功完成第三季度主要项目开发任务，获得团队一致好评',\n      type: 'primary'\n    },\n    {\n      timestamp: '2024-01-11',\n      title: '代码审核完成',\n      content: '完成核心模块代码审核，提出多项改进建议并获采纳',\n      type: 'success'\n    },\n    {\n      timestamp: '2024-01-12',\n      title: '技术分享会',\n      content: '主持团队技术分享会，分享前端性能优化经验',\n      type: 'warning'\n    },\n    {\n      timestamp: '2024-01-13',\n      title: '新功能上线',\n      content: '成功上线用户反馈的新特性，显著提升用户体验',\n      type: 'danger'\n    }\n  ]\n</script>\n\n<style lang=\"scss\">\n  .profile-container {\n    @apply p-4 lg:p-6 min-h-screen bg-gray-50 dark:bg-slate-900;\n\n    .bg-pattern {\n      background-image: url(\"data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23000000' fill-opacity='0.1'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\");\n    }\n\n    .profile-card {\n      @apply shadow-sm hover:shadow-md transition-shadow duration-300;\n    }\n\n    .profile-action-btn {\n      @apply bg-white/10 hover:bg-white/20 border-white/20;\n      .el-icon {\n        @apply mr-1;\n      }\n    }\n\n    .stat-card {\n      @apply p-4 lg:p-6 rounded-lg bg-gray-50 dark:bg-slate-700/50 text-center hover:shadow-md transition-all duration-300;\n    }\n\n    .custom-tabs {\n      :deep(.el-tabs__nav-wrap::after) {\n        @apply h-0.5 bg-gray-100 dark:bg-gray-700;\n      }\n      :deep(.el-tabs__active-bar) {\n        @apply h-0.5 bg-blue-500;\n      }\n      :deep(.el-tabs__item) {\n        @apply text-base font-medium px-6;\n        .el-icon {\n          @apply mr-1 text-lg;\n        }\n        &.is-active {\n          @apply text-blue-500;\n        }\n      }\n      :deep(.el-timeline-item__node--normal) {\n        @apply left-[-2px];\n      }\n      :deep(.el-timeline-item__wrapper) {\n        @apply pl-8;\n      }\n      :deep(.el-timeline-item__timestamp) {\n        @apply text-gray-400 text-sm;\n      }\n    }\n\n    .custom-dialog {\n      :deep(.el-dialog__header) {\n        @apply mb-0 pb-4 border-b border-gray-100 dark:border-gray-700;\n      }\n      :deep(.el-dialog__footer) {\n        @apply mt-0 pt-4 border-t border-gray-100 dark:border-gray-700;\n      }\n      :deep(.el-input__wrapper) {\n        @apply shadow-none;\n      }\n      :deep(.el-input__prefix) {\n        @apply mr-2;\n      }\n    }\n\n    .edit-input {\n      :deep(.el-input__wrapper) {\n        @apply bg-white/10 border-white/20 shadow-none;\n        input {\n          @apply text-white;\n          &::placeholder {\n            @apply text-white/60;\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/routerHolder.vue",
    "content": "<!-- 此路由可作为父类路由通用路由页面使用 如需自定义父类路由页面 请参考 @/view/superAdmin/index.vue -->\n<template>\n  <div>\n    <router-view v-slot=\"{ Component }\">\n      <transition mode=\"out-in\" name=\"el-fade-in-linear\">\n        <keep-alive :include=\"routerStore.keepAliveRouters\">\n          <div>\n            <component :is=\"Component\" />\n          </div>\n        </keep-alive>\n      </transition>\n    </router-view>\n  </div>\n</template>\n\n<script setup>\n  defineOptions({\n    name: 'RouterHolder'\n  })\n  import { useRouterStore } from '@/pinia/modules/router'\n  const routerStore = useRouterStore()\n</script>\n"
  },
  {
    "path": "web/src/view/superAdmin/api/api.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form ref=\"searchForm\" :inline=\"true\" :model=\"searchInfo\">\n        <el-form-item label=\"路径\">\n          <el-input v-model=\"searchInfo.path\" placeholder=\"路径\" />\n        </el-form-item>\n        <el-form-item label=\"描述\">\n          <el-input v-model=\"searchInfo.description\" placeholder=\"描述\" />\n        </el-form-item>\n        <el-form-item label=\"API分组\">\n          <el-select\n            v-model=\"searchInfo.apiGroup\"\n            clearable\n            placeholder=\"请选择\"\n          >\n            <el-option\n              v-for=\"item in apiGroupOptions\"\n              :key=\"item.value\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"请求\">\n          <el-select v-model=\"searchInfo.method\" clearable placeholder=\"请选择\">\n            <el-option\n              v-for=\"item in methodOptions\"\n              :key=\"item.value\"\n              :label=\"`${item.label}(${item.value})`\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">\n            查询\n          </el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\"> 重置 </el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDialog('addApi')\">\n          新增\n        </el-button>\n        <el-button icon=\"delete\" :disabled=\"!apis.length\" @click=\"onDelete\">\n          删除\n        </el-button>\n        <el-button icon=\"Refresh\" @click=\"onFresh\"> 刷新缓存 </el-button>\n        <el-button icon=\"Compass\" @click=\"onSync\"> 同步API </el-button>\n        <ExportTemplate template-id=\"api\" />\n        <ExportExcel template-id=\"api\" :limit=\"9999\" />\n        <ImportExcel template-id=\"api\" @on-success=\"getTableData\" />\n      </div>\n      <el-table\n        :data=\"tableData\"\n        @sort-change=\"sortChange\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column\n          align=\"left\"\n          label=\"id\"\n          min-width=\"60\"\n          prop=\"ID\"\n          sortable=\"custom\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API路径\"\n          min-width=\"150\"\n          prop=\"path\"\n          sortable=\"custom\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API分组\"\n          min-width=\"150\"\n          prop=\"apiGroup\"\n          sortable=\"custom\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API简介\"\n          min-width=\"150\"\n          prop=\"description\"\n          sortable=\"custom\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"请求\"\n          min-width=\"150\"\n          prop=\"method\"\n          sortable=\"custom\"\n        >\n          <template #default=\"scope\">\n            <div>\n              {{ scope.row.method }} / {{ methodFilter(scope.row.method) }}\n            </div>\n          </template>\n        </el-table-column>\n\n        <el-table-column align=\"left\" fixed=\"right\" label=\"操作\" :min-width=\"appStore.operateMinWith\">\n          <template #default=\"scope\">\n            <el-button\n              icon=\"edit\"\n              type=\"primary\"\n              link\n              @click=\"editApiFunc(scope.row)\"\n            >\n              编辑\n            </el-button>\n            <el-button\n              icon=\"user\"\n              type=\"primary\"\n              link\n              @click=\"openAssignRoleDrawer(scope.row)\"\n            >\n              分配角色\n            </el-button>\n            <el-button\n              icon=\"delete\"\n              type=\"primary\"\n              link\n              @click=\"deleteApiFunc(scope.row)\"\n            >\n              删除\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n\n    <el-drawer\n      v-model=\"syncApiFlag\"\n      :size=\"appStore.drawerSize\"\n      :before-close=\"closeSyncDialog\"\n      :show-close=\"false\"\n    >\n      <warning-bar\n        title=\"同步API，不输入路由分组将不会被自动同步，如果api不需要参与鉴权，可以按忽略按钮进行忽略。\"\n      />\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">同步路由</span>\n          <div>\n            <el-button :loading=\"apiCompletionLoading\" @click=\"closeSyncDialog\">\n              取 消\n            </el-button>\n            <el-button\n              type=\"primary\"\n              :loading=\"syncing || apiCompletionLoading\"\n              @click=\"enterSyncDialog\"\n            >\n              确 定\n            </el-button>\n          </div>\n        </div>\n      </template>\n\n      <h4>\n        新增路由\n        <span class=\"text-xs text-gray-500 mx-2 font-normal\"\n          >存在于当前路由中，但是不存在于api表</span\n        >\n        <el-button type=\"primary\" size=\"small\" @click=\"apiCompletion\">\n          <el-icon size=\"18\">\n            <ai-gva />\n          </el-icon>\n          自动填充\n        </el-button>\n      </h4>\n      <el-table\n        v-loading=\"syncing || apiCompletionLoading\"\n        element-loading-text=\"小淼正在思考...\"\n        :data=\"syncApiData.newApis\"\n      >\n        <el-table-column\n          align=\"left\"\n          label=\"API路径\"\n          min-width=\"150\"\n          prop=\"path\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API分组\"\n          min-width=\"150\"\n          prop=\"apiGroup\"\n        >\n          <template #default=\"{ row }\">\n            <el-select\n              v-model=\"row.apiGroup\"\n              placeholder=\"请选择或新增\"\n              allow-create\n              filterable\n              default-first-option\n            >\n              <el-option\n                v-for=\"item in apiGroupOptions\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\"\n              />\n            </el-select>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"API简介\"\n          min-width=\"150\"\n          prop=\"description\"\n        >\n          <template #default=\"{ row }\">\n            <el-input v-model=\"row.description\" autocomplete=\"off\" />\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"请求\"\n          min-width=\"150\"\n          prop=\"method\"\n        >\n          <template #default=\"scope\">\n            <div>\n              {{ scope.row.method }} / {{ methodFilter(scope.row.method) }}\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"操作\" min-width=\"150\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <el-button icon=\"plus\" type=\"primary\" link @click=\"addApiFunc(row)\">\n              单条新增\n            </el-button>\n            <el-button\n              icon=\"sunrise\"\n              type=\"primary\"\n              link\n              @click=\"ignoreApiFunc(row, true)\"\n            >\n              忽略\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n\n      <h4>\n        已删除路由\n        <span class=\"text-xs text-gray-500 ml-2 font-normal\"\n          >已经不存在于当前项目的路由中，确定同步后会自动从apis表删除</span\n        >\n      </h4>\n      <el-table :data=\"syncApiData.deleteApis\">\n        <el-table-column\n          align=\"left\"\n          label=\"API路径\"\n          min-width=\"150\"\n          prop=\"path\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API分组\"\n          min-width=\"150\"\n          prop=\"apiGroup\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API简介\"\n          min-width=\"150\"\n          prop=\"description\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"请求\"\n          min-width=\"150\"\n          prop=\"method\"\n        >\n          <template #default=\"scope\">\n            <div>\n              {{ scope.row.method }} / {{ methodFilter(scope.row.method) }}\n            </div>\n          </template>\n        </el-table-column>\n      </el-table>\n\n      <h4>\n        忽略路由\n        <span class=\"text-xs text-gray-500 ml-2 font-normal\"\n          >忽略路由不参与api同步，常见为不需要进行鉴权行为的路由</span\n        >\n      </h4>\n      <el-table :data=\"syncApiData.ignoreApis\">\n        <el-table-column\n          align=\"left\"\n          label=\"API路径\"\n          min-width=\"150\"\n          prop=\"path\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API分组\"\n          min-width=\"150\"\n          prop=\"apiGroup\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"API简介\"\n          min-width=\"150\"\n          prop=\"description\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"请求\"\n          min-width=\"150\"\n          prop=\"method\"\n        >\n          <template #default=\"scope\">\n            <div>\n              {{ scope.row.method }} / {{ methodFilter(scope.row.method) }}\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"操作\" min-width=\"150\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <el-button\n              icon=\"sunny\"\n              type=\"primary\"\n              link\n              @click=\"ignoreApiFunc(row, false)\"\n            >\n              取消忽略\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n    </el-drawer>\n\n    <el-drawer\n      v-model=\"dialogFormVisible\"\n      :size=\"appStore.drawerSize\"\n      :before-close=\"closeDialog\"\n      :show-close=\"false\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{ dialogTitle }}</span>\n          <div>\n            <el-button @click=\"closeDialog\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"enterDialog\"> 确 定 </el-button>\n          </div>\n        </div>\n      </template>\n\n      <warning-bar title=\"新增API，需要在角色管理内配置权限才可使用\" />\n      <el-form ref=\"apiForm\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n        <el-form-item label=\"路径\" prop=\"path\">\n          <el-input v-model=\"form.path\" autocomplete=\"off\" />\n        </el-form-item>\n        <el-form-item label=\"请求\" prop=\"method\">\n          <el-select\n            v-model=\"form.method\"\n            placeholder=\"请选择\"\n            style=\"width: 100%\"\n          >\n            <el-option\n              v-for=\"item in methodOptions\"\n              :key=\"item.value\"\n              :label=\"`${item.label}(${item.value})`\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"api分组\" prop=\"apiGroup\">\n          <el-select\n            v-model=\"form.apiGroup\"\n            placeholder=\"请选择或新增\"\n            allow-create\n            filterable\n            default-first-option\n          >\n            <el-option\n              v-for=\"item in apiGroupOptions\"\n              :key=\"item.value\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"api简介\" prop=\"description\">\n          <el-input v-model=\"form.description\" autocomplete=\"off\" />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n\n    <!-- 分配给角色抽屉 -->\n    <el-drawer\n      v-model=\"assignRoleDrawerVisible\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n      destroy-on-close\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">分配角色 - {{ assignApiRow.description }}</span>\n          <div>\n            <el-button @click=\"assignRoleDrawerVisible = false\">取 消</el-button>\n            <el-button type=\"primary\" :loading=\"assignRoleSubmitting\" @click=\"confirmAssignRole\">确 定</el-button>\n          </div>\n        </div>\n      </template>\n      <warning-bar title=\"注：保存时将全量覆盖该API的角色关联关系，并自动刷新Casbin缓存\" />\n      <el-tree\n        ref=\"roleTreeRef\"\n        v-loading=\"assignRoleLoading\"\n        :data=\"authorityTreeData\"\n        :props=\"{ label: 'authorityName', children: 'children' }\"\n        node-key=\"authorityId\"\n        show-checkbox\n        check-strictly\n        default-expand-all\n      />\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getApiById,\n    getApiList,\n    createApi,\n    updateApi,\n    deleteApi,\n    deleteApisByIds,\n    freshCasbin,\n    syncApi,\n    getApiGroups,\n    ignoreApi,\n    enterSyncApi,\n    getApiRoles,\n    setApiRoles\n  } from '@/api/api'\n  import { getAuthorityList } from '@/api/authority'\n  import { toSQLLine } from '@/utils/stringFun'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { ref, nextTick } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import ExportExcel from '@/components/exportExcel/exportExcel.vue'\n  import ExportTemplate from '@/components/exportExcel/exportTemplate.vue'\n  import ImportExcel from '@/components/exportExcel/importExcel.vue'\n  import { llmAuto } from '@/api/autoCode'\n  import { useAppStore } from \"@/pinia\";\n\n  defineOptions({\n    name: 'Api'\n  })\n\n  const appStore = useAppStore()\n\n  const methodFilter = (value) => {\n    const target = methodOptions.value.filter((item) => item.value === value)[0]\n    return target && `${target.label}`\n  }\n\n  const apis = ref([])\n  const form = ref({\n    path: '',\n    apiGroup: '',\n    method: '',\n    description: ''\n  })\n  const methodOptions = ref([\n    {\n      value: 'POST',\n      label: '创建',\n      type: 'success'\n    },\n    {\n      value: 'GET',\n      label: '查看',\n      type: ''\n    },\n    {\n      value: 'PUT',\n      label: '更新',\n      type: 'warning'\n    },\n    {\n      value: 'DELETE',\n      label: '删除',\n      type: 'danger'\n    }\n  ])\n\n  const type = ref('')\n  const rules = ref({\n    path: [{ required: true, message: '请输入api路径', trigger: 'blur' }],\n    apiGroup: [{ required: true, message: '请输入组名称', trigger: 'blur' }],\n    method: [{ required: true, message: '请选择请求方式', trigger: 'blur' }],\n    description: [{ required: true, message: '请输入api介绍', trigger: 'blur' }]\n  })\n\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const searchInfo = ref({})\n  const apiGroupOptions = ref([])\n  const apiGroupMap = ref({})\n\n  const getGroup = async () => {\n    const res = await getApiGroups()\n    if (res.code === 0) {\n      const groups = res.data.groups\n      apiGroupOptions.value = groups.map((item) => ({\n        label: item,\n        value: item\n      }))\n      apiGroupMap.value = res.data.apiGroupMap\n    }\n  }\n\n  const ignoreApiFunc = async (row, flag) => {\n    const res = await ignoreApi({ path: row.path, method: row.method, flag })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: res.msg\n      })\n      if (flag) {\n        syncApiData.value.newApis = syncApiData.value.newApis.filter(\n          (item) => !(item.path === row.path && item.method === row.method)\n        )\n        syncApiData.value.ignoreApis.push(row)\n        return\n      }\n      syncApiData.value.ignoreApis = syncApiData.value.ignoreApis.filter(\n        (item) => !(item.path === row.path && item.method === row.method)\n      )\n      syncApiData.value.newApis.push(row)\n    }\n  }\n\n  const addApiFunc = async (row) => {\n    if (!row.apiGroup) {\n      ElMessage({\n        type: 'error',\n        message: '请先选择API分组'\n      })\n      return\n    }\n    if (!row.description) {\n      ElMessage({\n        type: 'error',\n        message: '请先填写API描述'\n      })\n      return\n    }\n    const res = await createApi(row)\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '添加成功，请到角色管理页面分配权限',\n        showClose: true\n      })\n      syncApiData.value.newApis = syncApiData.value.newApis.filter(\n        (item) => !(item.path === row.path && item.method === row.method)\n      )\n    }\n    getTableData()\n    getGroup()\n  }\n\n  const closeSyncDialog = () => {\n    syncApiFlag.value = false\n  }\n\n  const syncing = ref(false)\n\n  const enterSyncDialog = async () => {\n    if (\n      syncApiData.value.newApis.some(\n        (item) => !item.apiGroup || !item.description\n      )\n    ) {\n      ElMessage({\n        type: 'error',\n        message: '存在API未分组或未填写描述'\n      })\n      return\n    }\n\n    syncing.value = true\n    const res = await enterSyncApi(syncApiData.value)\n    syncing.value = false\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: res.msg\n      })\n      syncApiFlag.value = false\n      getTableData()\n    }\n  }\n\n  const onReset = () => {\n    searchInfo.value = {}\n    getTableData()\n  }\n  // 搜索\n\n  const onSubmit = () => {\n    page.value = 1\n    getTableData()\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 排序\n  const sortChange = ({ prop, order }) => {\n    if (prop) {\n      if (prop === 'ID') {\n        prop = 'id'\n      }\n      searchInfo.value.orderKey = toSQLLine(prop)\n      searchInfo.value.desc = order === 'descending'\n    }\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getApiList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n  getGroup()\n  // 批量操作\n  const handleSelectionChange = (val) => {\n    apis.value = val\n  }\n\n  const onDelete = async () => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const ids = apis.value.map((item) => item.ID)\n      const res = await deleteApisByIds({ ids })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: res.msg\n        })\n        if (tableData.value.length === ids.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n  const onFresh = async () => {\n    ElMessageBox.confirm('确定要刷新缓存吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await freshCasbin()\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: res.msg\n        })\n      }\n    })\n  }\n\n  const syncApiData = ref({\n    newApis: [],\n    deleteApis: [],\n    ignoreApis: []\n  })\n\n  const syncApiFlag = ref(false)\n\n  const onSync = async () => {\n    const res = await syncApi()\n    if (res.code === 0) {\n      res.data.newApis.forEach((item) => {\n        item.apiGroup = apiGroupMap.value[item.path.split('/')[1]]\n      })\n\n      syncApiData.value = res.data\n      syncApiFlag.value = true\n    }\n  }\n\n  // 弹窗相关\n  const apiForm = ref(null)\n  const initForm = () => {\n    apiForm.value.resetFields()\n    form.value = {\n      path: '',\n      apiGroup: '',\n      method: '',\n      description: ''\n    }\n  }\n\n  const dialogTitle = ref('新增Api')\n  const dialogFormVisible = ref(false)\n  const openDialog = (key) => {\n    switch (key) {\n      case 'addApi':\n        dialogTitle.value = '新增Api'\n        break\n      case 'edit':\n        dialogTitle.value = '编辑Api'\n        break\n      default:\n        break\n    }\n    type.value = key\n    dialogFormVisible.value = true\n  }\n  const closeDialog = () => {\n    initForm()\n    dialogFormVisible.value = false\n  }\n\n  const editApiFunc = async (row) => {\n    const res = await getApiById({ id: row.ID })\n    form.value = res.data.api\n    openDialog('edit')\n  }\n\n  const enterDialog = async () => {\n    apiForm.value.validate(async (valid) => {\n      if (valid) {\n        switch (type.value) {\n          case 'addApi':\n            {\n              const res = await createApi(form.value)\n              if (res.code === 0) {\n                ElMessage({\n                  type: 'success',\n                  message: '添加成功',\n                  showClose: true\n                })\n              }\n              getTableData()\n              getGroup()\n              closeDialog()\n            }\n\n            break\n          case 'edit':\n            {\n              const res = await updateApi(form.value)\n              if (res.code === 0) {\n                ElMessage({\n                  type: 'success',\n                  message: '编辑成功',\n                  showClose: true\n                })\n              }\n              getTableData()\n              closeDialog()\n            }\n            break\n          default:\n            {\n              ElMessage({\n                type: 'error',\n                message: '未知操作',\n                showClose: true\n              })\n            }\n            break\n        }\n      }\n    })\n  }\n\n  const deleteApiFunc = async (row) => {\n    ElMessageBox.confirm('此操作将永久删除所有角色下该api, 是否继续?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await deleteApi(row)\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功!'\n        })\n        if (tableData.value.length === 1 && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n        getGroup()\n      }\n    })\n  }\n  const apiCompletionLoading = ref(false)\n  const apiCompletion = async () => {\n    apiCompletionLoading.value = true\n    const routerPaths = syncApiData.value.newApis\n      .filter((item) => !item.apiGroup || !item.description)\n      .map((item) => item.path)\n    const res = await llmAuto({ data: String(routerPaths), mode: 'apiCompletion' })\n    apiCompletionLoading.value = false\n    if (res.code === 0) {\n      try {\n        const data = JSON.parse(res.data)\n        syncApiData.value.newApis.forEach((item) => {\n          const target = data.find((d) => d.path === item.path)\n          if (target) {\n            if (!item.apiGroup) {\n              item.apiGroup = target.apiGroup\n            }\n            if (!item.description) {\n              item.description = target.description\n            }\n          }\n        })\n      } catch (_) {\n        ElMessage({\n          type: 'error',\n          message: 'AI自动填充失败,请重新生成'\n        })\n      }\n    }\n  }\n\n  // 分配给角色\n  const assignRoleDrawerVisible = ref(false)\n  const assignApiRow = ref({})\n  const authorityTreeData = ref([])\n  const assignRoleLoading = ref(false)\n  const assignRoleSubmitting = ref(false)\n  const roleTreeRef = ref(null)\n\n  const openAssignRoleDrawer = async (row) => {\n    assignApiRow.value = row\n    assignRoleDrawerVisible.value = true\n    assignRoleLoading.value = true\n    const [authRes, rolesRes] = await Promise.all([\n      getAuthorityList(),\n      getApiRoles(row.path, row.method)\n    ])\n    if (authRes.code === 0) {\n      authorityTreeData.value = authRes.data\n    }\n    if (rolesRes.code === 0 && rolesRes.data) {\n      nextTick(() => {\n        roleTreeRef.value?.setCheckedKeys(rolesRes.data)\n      })\n    }\n    assignRoleLoading.value = false\n  }\n\n  const confirmAssignRole = async () => {\n    assignRoleSubmitting.value = true\n    try {\n      const checkedKeys = roleTreeRef.value?.getCheckedKeys(false) || []\n      const res = await setApiRoles({\n        path: assignApiRow.value.path,\n        method: assignApiRow.value.method,\n        authorityIds: checkedKeys\n      })\n      if (res.code === 0) {\n        ElMessage({ type: 'success', message: '分配成功!' })\n        assignRoleDrawerVisible.value = false\n      }\n    } catch {\n      ElMessage({ type: 'error', message: '分配失败，请重试' })\n    }\n    assignRoleSubmitting.value = false\n  }\n</script>\n\n<style scoped lang=\"scss\">\n  .warning {\n    color: #dc143c;\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/authority/authority.vue",
    "content": "<template>\n  <div class=\"authority\">\n    <warning-bar title=\"注：右上角头像下拉可切换角色\" />\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"addAuthority(0)\"\n          >新增角色</el-button\n        >\n      </div>\n      <el-table\n        :data=\"tableData\"\n        :tree-props=\"{ children: 'children', hasChildren: 'hasChildren' }\"\n        row-key=\"authorityId\"\n        style=\"width: 100%\"\n      >\n        <el-table-column label=\"角色ID\" min-width=\"180\" prop=\"authorityId\" />\n        <el-table-column\n          align=\"left\"\n          label=\"角色名称\"\n          min-width=\"180\"\n          prop=\"authorityName\"\n        />\n        <el-table-column align=\"left\" label=\"操作\" width=\"560\">\n          <template #default=\"scope\">\n            <el-button\n              icon=\"setting\"\n              type=\"primary\"\n              link\n              @click=\"openDrawer(scope.row)\"\n              >设置权限</el-button\n            >\n            <el-button\n              icon=\"user\"\n              type=\"primary\"\n              link\n              @click=\"openAssignDrawer(scope.row)\"\n              >分配给用户</el-button\n            >\n            <el-button\n              icon=\"plus\"\n              type=\"primary\"\n              link\n              @click=\"addAuthority(scope.row.authorityId)\"\n              >新增子角色</el-button\n            >\n            <el-button\n              icon=\"copy-document\"\n              type=\"primary\"\n              link\n              @click=\"copyAuthorityFunc(scope.row)\"\n              >拷贝</el-button\n            >\n            <el-button\n              icon=\"edit\"\n              type=\"primary\"\n              link\n              @click=\"editAuthority(scope.row)\"\n              >编辑</el-button\n            >\n            <el-button\n              icon=\"delete\"\n              type=\"primary\"\n              link\n              @click=\"deleteAuth(scope.row)\"\n              >删除</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n    </div>\n    <!-- 新增角色弹窗 -->\n    <el-drawer v-model=\"authorityFormVisible\" :size=\"appStore.drawerSize\" :show-close=\"false\">\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{ authorityTitleForm }}</span>\n          <div>\n            <el-button @click=\"closeAuthorityForm\">取 消</el-button>\n            <el-button type=\"primary\" @click=\"submitAuthorityForm\"\n              >确 定</el-button\n            >\n          </div>\n        </div>\n      </template>\n      <el-form\n        ref=\"authorityForm\"\n        :model=\"form\"\n        :rules=\"rules\"\n        label-width=\"80px\"\n      >\n        <el-form-item label=\"父级角色\" prop=\"parentId\">\n          <el-cascader\n            v-model=\"form.parentId\"\n            style=\"width: 100%\"\n            :disabled=\"dialogType === 'add'\"\n            :options=\"AuthorityOption\"\n            :props=\"{\n              checkStrictly: true,\n              label: 'authorityName',\n              value: 'authorityId',\n              disabled: 'disabled',\n              emitPath: false\n            }\"\n            :show-all-levels=\"false\"\n            filterable\n          />\n        </el-form-item>\n        <el-form-item label=\"角色ID\" prop=\"authorityId\">\n          <el-input\n            v-model=\"form.authorityId\"\n            :disabled=\"dialogType === 'edit'\"\n            autocomplete=\"off\"\n            maxlength=\"15\"\n          />\n        </el-form-item>\n        <el-form-item label=\"角色姓名\" prop=\"authorityName\">\n          <el-input v-model=\"form.authorityName\" autocomplete=\"off\" />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n\n    <el-drawer\n      v-if=\"drawer\"\n      v-model=\"drawer\"\n      :size=\"appStore.drawerSize\"\n      title=\"角色配置\"\n    >\n      <el-tabs :before-leave=\"autoEnter\" type=\"border-card\">\n        <el-tab-pane label=\"角色菜单\">\n          <Menus ref=\"menus\" :row=\"activeRow\" @changeRow=\"changeRow\" />\n        </el-tab-pane>\n        <el-tab-pane label=\"角色api\">\n          <Apis ref=\"apis\" :row=\"activeRow\" @changeRow=\"changeRow\" />\n        </el-tab-pane>\n        <el-tab-pane label=\"资源权限\">\n          <Datas\n            ref=\"datas\"\n            :authority=\"tableData\"\n            :row=\"activeRow\"\n            @changeRow=\"changeRow\"\n          />\n        </el-tab-pane>\n      </el-tabs>\n    </el-drawer>\n\n    <!-- 分配给用户抽屉 -->\n    <el-drawer\n      v-model=\"assignDrawerVisible\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n      destroy-on-close\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">分配用户 - {{ assignRow.authorityName }}</span>\n          <div>\n            <el-button @click=\"assignDrawerVisible = false\">取 消</el-button>\n            <el-button type=\"primary\" :loading=\"assignSubmitting\" @click=\"confirmAssign\">确 定</el-button>\n          </div>\n        </div>\n      </template>\n      <warning-bar title=\"注：保存时将全量覆盖该角色的用户关联关系；若用户仅剩此一个角色，移除后其主角色保持不变\" />\n      <div class=\"gva-search-box\">\n        <el-form :inline=\"true\" :model=\"userSearchInfo\">\n          <el-form-item label=\"用户名\">\n            <el-input v-model=\"userSearchInfo.username\" placeholder=\"请输入用户名\" />\n          </el-form-item>\n          <el-form-item label=\"昵称\">\n            <el-input v-model=\"userSearchInfo.nickName\" placeholder=\"请输入昵称\" />\n          </el-form-item>\n          <el-form-item>\n            <el-button type=\"primary\" icon=\"search\" @click=\"searchUserData\">查 询</el-button>\n            <el-button icon=\"refresh\" @click=\"resetUserSearch\">重 置</el-button>\n          </el-form-item>\n        </el-form>\n      </div>\n      <el-table\n        ref=\"userTableRef\"\n        v-loading=\"assignLoading\"\n        :data=\"userTableData\"\n        row-key=\"ID\"\n        :default-sort=\"{ prop: 'ID', order: 'descending' }\"\n        @sort-change=\"sortChange\"\n        @select=\"handleSelect\"\n        @select-all=\"handleSelectAll\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column label=\"ID\" prop=\"ID\" width=\"80\" sortable=\"custom\" />\n        <el-table-column label=\"用户名\" prop=\"userName\" min-width=\"120\" />\n        <el-table-column label=\"昵称\" prop=\"nickName\" min-width=\"120\" />\n      </el-table>\n      <div class=\"flex justify-center mt-4\">\n        <el-pagination\n          :current-page=\"userSearchInfo.page\"\n          :page-size=\"userSearchInfo.pageSize\"\n          :page-sizes=\"[10, 20, 50]\"\n          :total=\"userTotal\"\n          layout=\"total, sizes, prev, pager, next\"\n          @current-change=\"handleUserPageChange\"\n          @size-change=\"handleUserSizeChange\"\n        />\n      </div>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getAuthorityList,\n    deleteAuthority,\n    createAuthority,\n    updateAuthority,\n    copyAuthority,\n    getUsersByAuthorityId,\n    setRoleUsers\n  } from '@/api/authority'\n  import { getUserList } from '@/api/user'\n\n  import Menus from '@/view/superAdmin/authority/components/menus.vue'\n  import Apis from '@/view/superAdmin/authority/components/apis.vue'\n  import Datas from '@/view/superAdmin/authority/components/datas.vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n\n  import { ref, nextTick } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { useAppStore } from \"@/pinia\"\n  import { toSQLLine } from '@/utils/stringFun'\n\n  defineOptions({\n    name: 'Authority'\n  })\n\n  const mustUint = (rule, value, callback) => {\n    if (!/^[0-9]*[1-9][0-9]*$/.test(value)) {\n      return callback(new Error('请输入正整数'))\n    }\n    return callback()\n  }\n\n  const AuthorityOption = ref([\n    {\n      authorityId: 0,\n      authorityName: '根角色/严格模式下为当前角色'\n    }\n  ])\n  const drawer = ref(false)\n  const dialogType = ref('add')\n  const activeRow = ref({})\n  const appStore = useAppStore()\n\n  const authorityTitleForm = ref('新增角色')\n  const authorityFormVisible = ref(false)\n  const apiDialogFlag = ref(false)\n  const copyForm = ref({})\n\n  const form = ref({\n    authorityId: 0,\n    authorityName: '',\n    parentId: 0\n  })\n  const rules = ref({\n    authorityId: [\n      { required: true, message: '请输入角色ID', trigger: 'blur' },\n      { validator: mustUint, trigger: 'blur', message: '必须为正整数' }\n    ],\n    authorityName: [\n      { required: true, message: '请输入角色名', trigger: 'blur' }\n    ],\n    parentId: [{ required: true, message: '请选择父角色', trigger: 'blur' }]\n  })\n\n  const tableData = ref([])\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getAuthorityList()\n    if (table.code === 0) {\n      tableData.value = table.data\n    }\n  }\n\n  getTableData()\n\n  const changeRow = (key, value) => {\n    activeRow.value[key] = value\n  }\n  const menus = ref(null)\n  const apis = ref(null)\n  const datas = ref(null)\n  const autoEnter = (activeName, oldActiveName) => {\n    const paneArr = [menus, apis, datas]\n    if (oldActiveName) {\n      if (paneArr[oldActiveName].value.needConfirm) {\n        paneArr[oldActiveName].value.enterAndNext()\n        paneArr[oldActiveName].value.needConfirm = false\n      }\n    }\n  }\n  // 拷贝角色\n  const copyAuthorityFunc = (row) => {\n    setOptions()\n    authorityTitleForm.value = '拷贝角色'\n    dialogType.value = 'copy'\n    for (const k in form.value) {\n      form.value[k] = row[k]\n    }\n    copyForm.value = row\n    authorityFormVisible.value = true\n  }\n  const openDrawer = (row) => {\n    drawer.value = true\n    activeRow.value = row\n  }\n  // 删除角色\n  const deleteAuth = (row) => {\n    ElMessageBox.confirm('此操作将永久删除该角色, 是否继续?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    })\n      .then(async () => {\n        const res = await deleteAuthority({ authorityId: row.authorityId })\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '删除成功!'\n          })\n\n          getTableData()\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '已取消删除'\n        })\n      })\n  }\n  // 初始化表单\n  const authorityForm = ref(null)\n  const initForm = () => {\n    if (authorityForm.value) {\n      authorityForm.value.resetFields()\n    }\n    form.value = {\n      authorityId: 0,\n      authorityName: '',\n      parentId: 0\n    }\n  }\n  // 关闭窗口\n  const closeAuthorityForm = () => {\n    initForm()\n    authorityFormVisible.value = false\n    apiDialogFlag.value = false\n  }\n  // 确定弹窗\n\n  const submitAuthorityForm = () => {\n    authorityForm.value.validate(async (valid) => {\n      if (valid) {\n        form.value.authorityId = Number(form.value.authorityId)\n        switch (dialogType.value) {\n          case 'add':\n            {\n              const res = await createAuthority(form.value)\n              if (res.code === 0) {\n                ElMessage({\n                  type: 'success',\n                  message: '添加成功!'\n                })\n                getTableData()\n                closeAuthorityForm()\n              }\n            }\n            break\n          case 'edit':\n            {\n              const res = await updateAuthority(form.value)\n              if (res.code === 0) {\n                ElMessage({\n                  type: 'success',\n                  message: '添加成功!'\n                })\n                getTableData()\n                closeAuthorityForm()\n              }\n            }\n            break\n          case 'copy': {\n            const data = {\n              authority: {\n                authorityId: 0,\n                authorityName: '',\n                datauthorityId: [],\n                parentId: 0\n              },\n              oldAuthorityId: 0\n            }\n            data.authority.authorityId = form.value.authorityId\n            data.authority.authorityName = form.value.authorityName\n            data.authority.parentId = form.value.parentId\n            data.authority.dataAuthorityId = copyForm.value.dataAuthorityId\n            data.oldAuthorityId = copyForm.value.authorityId\n            const res = await copyAuthority(data)\n            if (res.code === 0) {\n              ElMessage({\n                type: 'success',\n                message: '复制成功！'\n              })\n              getTableData()\n            }\n          }\n        }\n\n        initForm()\n        authorityFormVisible.value = false\n      }\n    })\n  }\n  const setOptions = () => {\n    AuthorityOption.value = [\n      {\n        authorityId: 0,\n        authorityName: '根角色(严格模式下为当前用户角色)'\n      }\n    ]\n    setAuthorityOptions(tableData.value, AuthorityOption.value, false)\n  }\n  const setAuthorityOptions = (AuthorityData, optionsData, disabled) => {\n    AuthorityData &&\n      AuthorityData.forEach((item) => {\n        if (item.children && item.children.length) {\n          const option = {\n            authorityId: item.authorityId,\n            authorityName: item.authorityName,\n            disabled: disabled || item.authorityId === form.value.authorityId,\n            children: []\n          }\n          setAuthorityOptions(\n            item.children,\n            option.children,\n            disabled || item.authorityId === form.value.authorityId\n          )\n          optionsData.push(option)\n        } else {\n          const option = {\n            authorityId: item.authorityId,\n            authorityName: item.authorityName,\n            disabled: disabled || item.authorityId === form.value.authorityId\n          }\n          optionsData.push(option)\n        }\n      })\n  }\n  // 增加角色\n  const addAuthority = (parentId) => {\n    initForm()\n    authorityTitleForm.value = '新增角色'\n    dialogType.value = 'add'\n    form.value.parentId = parentId\n    setOptions()\n    authorityFormVisible.value = true\n  }\n  // 编辑角色\n  const editAuthority = (row) => {\n    setOptions()\n    authorityTitleForm.value = '编辑角色'\n    dialogType.value = 'edit'\n    for (const key in form.value) {\n      form.value[key] = row[key]\n    }\n    setOptions()\n    authorityForm.value && authorityForm.value.clearValidate()\n    authorityFormVisible.value = true\n  }\n\n  // 分配给用户\n  const assignDrawerVisible = ref(false)\n  const assignRow = ref({})\n  const userTableData = ref([])\n  const userTotal = ref(0)\n  const userSearchInfo = ref({ page: 1, pageSize: 10, username: '', nickName: '', orderKey: 'id', desc: true })\n  const assignLoading = ref(false)\n  const assignSubmitting = ref(false)\n  const userTableRef = ref(null)\n\n  const selectedUserIds = ref(new Set())\n\n  const openAssignDrawer = async (row) => {\n    assignRow.value = row\n    userSearchInfo.value = { page: 1, pageSize: 10, username: '', nickName: '' }\n    selectedUserIds.value = new Set()\n    assignDrawerVisible.value = true\n    const res = await getUsersByAuthorityId(row.authorityId)\n    if (res.code === 0 && res.data) {\n      selectedUserIds.value = new Set(res.data)\n    }\n    getUserData()\n  }\n\n  const getUserData = async () => {\n    assignLoading.value = true\n    const res = await getUserList(userSearchInfo.value)\n    if (res.code === 0) {\n      userTableData.value = res.data.list\n      userTotal.value = res.data.total\n      await nextTick()\n      userTableData.value.forEach((user) => {\n        userTableRef.value && userTableRef.value.toggleRowSelection(user, selectedUserIds.value.has(user.ID))\n      })\n    }\n    assignLoading.value = false\n  }\n\n  const handleSelect = (selection, row) => {\n    if (selection.some(u => u.ID === row.ID)) {\n      selectedUserIds.value.add(row.ID)\n    } else {\n      selectedUserIds.value.delete(row.ID)\n    }\n  }\n\n  const handleSelectAll = (selection) => {\n    const selectedIds = new Set(selection.map(u => u.ID))\n    userTableData.value.forEach((user) => {\n      if (selectedIds.has(user.ID)) {\n        selectedUserIds.value.add(user.ID)\n      } else {\n        selectedUserIds.value.delete(user.ID)\n      }\n    })\n  }\n\n  const sortChange = ({ prop, order }) => {\n    if (prop) {\n      userSearchInfo.value.orderKey = prop === 'ID' ? 'id' : toSQLLine(prop)\n      userSearchInfo.value.desc = order === 'descending'\n    }\n    getUserData()\n  }\n\n  const searchUserData = () => {\n    userSearchInfo.value.page = 1\n    getUserData()\n  }\n\n  const resetUserSearch = () => {\n    userSearchInfo.value = { page: 1, pageSize: 10, username: '', nickName: '' }\n    getUserData()\n  }\n\n  const handleUserPageChange = (page) => {\n    userSearchInfo.value.page = page\n    getUserData()\n  }\n\n  const handleUserSizeChange = (size) => {\n    userSearchInfo.value.pageSize = size\n    userSearchInfo.value.page = 1\n    getUserData()\n  }\n\n  const confirmAssign = async () => {\n    assignSubmitting.value = true\n    try {\n      const res = await setRoleUsers({\n        authorityId: assignRow.value.authorityId,\n        userIds: [...selectedUserIds.value]\n      })\n      if (res.code === 0) {\n        ElMessage({ type: 'success', message: '分配成功!' })\n        assignDrawerVisible.value = false\n      }\n    } catch {\n      ElMessage({ type: 'error', message: '分配失败，请重试' })\n    }\n    assignSubmitting.value = false\n  }\n</script>\n\n<style lang=\"scss\">\n  .authority {\n    .el-input-number {\n      margin-left: 15px;\n      span {\n        display: none;\n      }\n    }\n  }\n  .tree-content {\n    margin-top: 10px;\n    height: calc(100vh - 158px);\n    overflow: auto;\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/authority/components/apis.vue",
    "content": "<template>\n  <div>\n    <div class=\"sticky top-0.5 z-10 flex space-x-2\">\n      <el-input\n        v-model=\"filterTextName\"\n        class=\"flex-1\"\n        placeholder=\"筛选名字\"\n      />\n      <el-input\n        v-model=\"filterTextPath\"\n        class=\"flex-1\"\n        placeholder=\"筛选路径\"\n      />\n      <el-button class=\"float-right\" type=\"primary\" @click=\"authApiEnter\"\n        >确 定</el-button\n      >\n    </div>\n    <div class=\"tree-content\">\n      <el-scrollbar>\n        <el-tree\n          ref=\"apiTree\"\n          :data=\"apiTreeData\"\n          :default-checked-keys=\"apiTreeIds\"\n          :props=\"apiDefaultProps\"\n          default-expand-all\n          highlight-current\n          node-key=\"onlyId\"\n          show-checkbox\n          :filter-node-method=\"filterNode\"\n          @check=\"nodeChange\"\n        >\n          <template #default=\"{ _, data }\">\n            <div class=\"flex items-center justify-between w-full pr-1\">\n              <span>{{ data.description }} </span>\n              <el-tooltip :content=\"data.path\">\n                <span\n                  class=\"max-w-[240px] break-all overflow-ellipsis overflow-hidden\"\n                  >{{ data.path }}</span\n                >\n              </el-tooltip>\n            </div>\n          </template>\n        </el-tree>\n      </el-scrollbar>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { getAllApis } from '@/api/api'\n  import { UpdateCasbin, getPolicyPathByAuthorityId } from '@/api/casbin'\n  import { ref, watch } from 'vue'\n  import { ElMessage } from 'element-plus'\n\n  defineOptions({\n    name: 'Apis'\n  })\n\n  const props = defineProps({\n    row: {\n      default: function () {\n        return {}\n      },\n      type: Object\n    }\n  })\n\n  const apiDefaultProps = ref({\n    children: 'children',\n    label: 'description'\n  })\n  const filterTextName = ref('')\n  const filterTextPath = ref('')\n  const apiTreeData = ref([])\n  const apiTreeIds = ref([])\n  const activeUserId = ref('')\n  const init = async () => {\n    const res2 = await getAllApis()\n    const apis = res2.data.apis\n\n    apiTreeData.value = buildApiTree(apis)\n    const res = await getPolicyPathByAuthorityId({\n      authorityId: props.row.authorityId\n    })\n    activeUserId.value = props.row.authorityId\n    apiTreeIds.value = []\n    res.data.paths &&\n      res.data.paths.forEach((item) => {\n        apiTreeIds.value.push('p:' + item.path + 'm:' + item.method)\n      })\n  }\n\n  init()\n\n  const needConfirm = ref(false)\n  const nodeChange = () => {\n    needConfirm.value = true\n  }\n  // 暴露给外层使用的切换拦截统一方法\n  const enterAndNext = () => {\n    authApiEnter()\n  }\n\n  // 创建api树方法\n  const buildApiTree = (apis) => {\n    const apiObj = {}\n    apis &&\n      apis.forEach((item) => {\n        item.onlyId = 'p:' + item.path + 'm:' + item.method\n        if (Object.prototype.hasOwnProperty.call(apiObj, item.apiGroup)) {\n          apiObj[item.apiGroup].push(item)\n        } else {\n          Object.assign(apiObj, { [item.apiGroup]: [item] })\n        }\n      })\n    const apiTree = []\n    for (const key in apiObj) {\n      const treeNode = {\n        ID: key,\n        description: key + '组',\n        children: apiObj[key]\n      }\n      apiTree.push(treeNode)\n    }\n    return apiTree\n  }\n\n  // 关联关系确定\n  const apiTree = ref(null)\n  const authApiEnter = async () => {\n    const checkArr = apiTree.value.getCheckedNodes(true)\n    var casbinInfos = []\n    checkArr &&\n      checkArr.forEach((item) => {\n        var casbinInfo = {\n          path: item.path,\n          method: item.method\n        }\n        casbinInfos.push(casbinInfo)\n      })\n    const res = await UpdateCasbin({\n      authorityId: activeUserId.value,\n      casbinInfos\n    })\n    if (res.code === 0) {\n      ElMessage({ type: 'success', message: 'api设置成功' })\n    }\n  }\n\n  defineExpose({\n    needConfirm,\n    enterAndNext\n  })\n\n  const filterNode = (value, data) => {\n    if (!filterTextName.value && !filterTextPath.value) return true\n    let matchesName, matchesPath\n    if (!filterTextName.value) {\n      matchesName = true\n    } else {\n      matchesName =\n        data.description && data.description.includes(filterTextName.value)\n    }\n    if (!filterTextPath.value) {\n      matchesPath = true\n    } else {\n      matchesPath = data.path && data.path.includes(filterTextPath.value)\n    }\n    return matchesName && matchesPath\n  }\n  watch([filterTextName, filterTextPath], () => {\n    apiTree.value.filter('')\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/superAdmin/authority/components/datas.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      title=\"此功能仅用于创建角色和角色的many2many关系表，具体使用还须自己结合表实现业务，详情参考示例代码（客户示例）。此功能不建议使用，建议使用插件市场【组织管理功能（点击前往）】来管理资源权限。\"\n      href=\"https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=36\"\n    />\n    <div class=\"sticky top-0.5 z-10 my-4\">\n      <el-button class=\"float-left\" type=\"primary\" @click=\"all\">全选</el-button>\n      <el-button class=\"float-left\" type=\"primary\" @click=\"self\"\n        >本角色</el-button\n      >\n      <el-button class=\"float-left\" type=\"primary\" @click=\"selfAndChildren\"\n        >本角色及子角色</el-button\n      >\n      <el-button class=\"float-right\" type=\"primary\" @click=\"authDataEnter\"\n        >确 定</el-button\n      >\n    </div>\n    <div class=\"clear-both pt-4\">\n      <el-checkbox-group v-model=\"dataAuthorityId\" @change=\"selectAuthority\">\n        <el-checkbox\n          v-for=\"(item, key) in authoritys\"\n          :key=\"key\"\n          :label=\"item\"\n          >{{ item.authorityName }}</el-checkbox\n        >\n      </el-checkbox-group>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { setDataAuthority } from '@/api/authority'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { ref } from 'vue'\n  import { ElMessage } from 'element-plus'\n\n  defineOptions({\n    name: 'Datas'\n  })\n\n  const props = defineProps({\n    row: {\n      default: function () {\n        return {}\n      },\n      type: Object\n    },\n    authority: {\n      default: function () {\n        return []\n      },\n      type: Array\n    }\n  })\n\n  const authoritys = ref([])\n  const needConfirm = ref(false)\n  //   平铺角色\n  const roundAuthority = (authoritysData) => {\n    authoritysData &&\n      authoritysData.forEach((item) => {\n        const obj = {}\n        obj.authorityId = item.authorityId\n        obj.authorityName = item.authorityName\n        authoritys.value.push(obj)\n        if (item.children && item.children.length) {\n          roundAuthority(item.children)\n        }\n      })\n  }\n\n  const dataAuthorityId = ref([])\n  const init = () => {\n    roundAuthority(props.authority)\n    props.row.dataAuthorityId &&\n      props.row.dataAuthorityId.forEach((item) => {\n        const obj =\n          authoritys.value &&\n          authoritys.value.filter(\n            (au) => au.authorityId === item.authorityId\n          ) &&\n          authoritys.value.filter(\n            (au) => au.authorityId === item.authorityId\n          )[0]\n        dataAuthorityId.value.push(obj)\n      })\n  }\n\n  init()\n\n  // 暴露给外层使用的切换拦截统一方法\n  const enterAndNext = () => {\n    authDataEnter()\n  }\n\n  const emit = defineEmits(['changeRow'])\n  const all = () => {\n    dataAuthorityId.value = [...authoritys.value]\n    emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)\n    needConfirm.value = true\n  }\n  const self = () => {\n    dataAuthorityId.value = authoritys.value.filter(\n      (item) => item.authorityId === props.row.authorityId\n    )\n    emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)\n    needConfirm.value = true\n  }\n  const selfAndChildren = () => {\n    const arrBox = []\n    getChildrenId(props.row, arrBox)\n    dataAuthorityId.value = authoritys.value.filter(\n      (item) => arrBox.indexOf(item.authorityId) > -1\n    )\n    emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)\n    needConfirm.value = true\n  }\n  const getChildrenId = (row, arrBox) => {\n    arrBox.push(row.authorityId)\n    row.children &&\n      row.children.forEach((item) => {\n        getChildrenId(item, arrBox)\n      })\n  }\n  // 提交\n  const authDataEnter = async () => {\n    const res = await setDataAuthority(props.row)\n    if (res.code === 0) {\n      ElMessage({ type: 'success', message: '资源设置成功' })\n    }\n  }\n\n  //   选择\n  const selectAuthority = () => {\n    dataAuthorityId.value = dataAuthorityId.value.filter((item) => item)\n    emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)\n    needConfirm.value = true\n  }\n\n  defineExpose({\n    enterAndNext,\n    needConfirm\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/superAdmin/authority/components/menus.vue",
    "content": "<template>\n  <div>\n    <div class=\"sticky top-0.5 z-10 pb-2\">\n      <div class=\"flex gap-2 items-center mb-2\">\n        <el-input v-model=\"filterText\" class=\"flex-1\" placeholder=\"筛选\" />\n        <el-button type=\"primary\" @click=\"relation\">确 定</el-button>\n      </div>\n      <div class=\"flex items-center gap-2\">\n        <span class=\"whitespace-nowrap\">默认首页：</span>\n        <el-select\n          :model-value=\"row.defaultRouter\"\n          filterable\n          placeholder=\"请选择默认首页\"\n          class=\"flex-1\"\n          @change=\"handleDefaultRouterChange\"\n        >\n          <el-option\n            v-for=\"item in menuOptions\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n          />\n        </el-select>\n      </div>\n    </div>\n    <div class=\"tree-content clear-both\">\n      <el-scrollbar>\n        <el-tree\n          ref=\"menuTree\"\n          :data=\"menuTreeData\"\n          :default-checked-keys=\"menuTreeIds\"\n          :props=\"menuDefaultProps\"\n          default-expand-all\n          highlight-current\n          node-key=\"ID\"\n          show-checkbox\n          :filter-node-method=\"filterNode\"\n          @check=\"nodeChange\"\n        >\n          <template #default=\"{ node, data }\">\n            <div class=\"flex items-center gap-2\">\n              <span>{{ node.label }}</span>\n                <SvgIcon v-if=\"row.defaultRouter === data.name\" icon=\"ant-design:home-filled\" class=\"inline text-lg text-active\" />\n              <span v-if=\"data.menuBtn.length\">\n                <el-button type=\"primary\" link @click.stop=\"() => OpenBtn(data)\">\n                  分配按钮\n                </el-button>\n              </span>\n            </div>\n          </template>\n        </el-tree>\n      </el-scrollbar>\n    </div>\n    <el-dialog v-model=\"btnVisible\" title=\"分配按钮\" destroy-on-close>\n      <el-table\n        ref=\"btnTableRef\"\n        :data=\"btnData\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column label=\"按钮名称\" prop=\"name\" />\n        <el-table-column label=\"按钮备注\" prop=\"desc\" />\n      </el-table>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"closeDialog\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"enterDialog\">确 定</el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getBaseMenuTree,\n    getMenuAuthority,\n    addMenuAuthority\n  } from '@/api/menu'\n  import { updateAuthority } from '@/api/authority'\n  import { getAuthorityBtnApi, setAuthorityBtnApi } from '@/api/authorityBtn'\n  import { nextTick, ref, watch } from 'vue'\n  import { ElMessage } from 'element-plus'\n\n  defineOptions({\n    name: 'Menus'\n  })\n\n  const props = defineProps({\n    row: {\n      default: function () {\n        return {}\n      },\n      type: Object\n    }\n  })\n\n  const emit = defineEmits(['changeRow'])\n  const filterText = ref('')\n  const menuTreeData = ref([])\n  const menuTreeIds = ref([])\n  const needConfirm = ref(false)\n  const menuTree = ref(null)\n  const menuDefaultProps = ref({\n    children: 'children',\n    label: function (data) {\n      return data.meta.title\n    },\n    disabled: function (data) {\n      if (props.row.defaultRouter !== data.name) return false\n      // 只在该节点已勾选时禁用，避免出现“默认首页未勾选却无法勾选”的死锁状态\n      const checkedKeys = menuTree.value?.getCheckedKeys?.() || menuTreeIds.value\n      return checkedKeys.includes(Number(data.ID))\n    }\n  })\n\n  const menuOptions = ref([])\n\n  const isExternalRoute = (name) => {\n    if (!name) return false\n    return name.startsWith('http://') || name.startsWith('https://')\n  }\n\n  const findMenuByName = (menus, name) => {\n    for (const item of menus || []) {\n      if (item?.name === name) return item\n      if (item?.children?.length) {\n        const found = findMenuByName(item.children, name)\n        if (found) return found\n      }\n    }\n    return null\n  }\n\n  const buildOptionsFromCheckedLeafMenus = () => {\n    const checkedLeafMenus = menuTree.value\n      ? menuTree.value.getCheckedNodes(false, true)\n      : []\n    const options = checkedLeafMenus\n      .filter((item) => item?.name && !isExternalRoute(item.name))\n      .map((item) => ({\n        label: item?.meta?.title || item.name,\n        value: item.name\n      }))\n\n    // 确保当前默认首页能正常显示（即使历史数据不一致）\n    if (props.row.defaultRouter && !options.some(o => o.value === props.row.defaultRouter)) {\n      const found = findMenuByName(menuTreeData.value, props.row.defaultRouter)\n      if (found && !isExternalRoute(found.name)) {\n        options.push({\n          label: found?.meta?.title || found.name,\n          value: found.name\n        })\n      }\n    }\n\n    return options\n  }\n\n  const refreshDefaultRouterOptions = () => {\n    menuOptions.value = buildOptionsFromCheckedLeafMenus()\n  }\n\n  const isDefaultRouterAllowed = (routeName) => {\n    if (!routeName) return false\n    const checkedLeafMenus = menuTree.value\n      ? menuTree.value.getCheckedNodes(false, true)\n      : []\n    return checkedLeafMenus.some((item) => item?.name === routeName)\n  }\n\n  const init = async () => {\n    // 获取所有菜单树\n    const res = await getBaseMenuTree()\n    menuTreeData.value = res.data.menus\n    const res1 = await getMenuAuthority({ authorityId: props.row.authorityId })\n    const menus = res1.data.menus\n    const arr = []\n    menus.forEach((item) => {\n      // 防止直接选中父级造成全选\n      if (!menus.some((same) => same.parentId === item.menuId)) {\n        arr.push(Number(item.menuId))\n      }\n    })\n    menuTreeIds.value = arr\n\n    // 确保异步数据加载后，树的勾选状态与选项同步\n    await nextTick()\n    if (menuTree.value?.setCheckedKeys) {\n      menuTree.value.setCheckedKeys(menuTreeIds.value)\n      await nextTick()\n    }\n    refreshDefaultRouterOptions()\n  }\n\n  init()\n\n  const setDefault = async (data) => {\n    const res = await updateAuthority({\n      authorityId: props.row.authorityId,\n      AuthorityName: props.row.authorityName,\n      parentId: props.row.parentId,\n      defaultRouter: data.name\n    })\n    if (res.code === 0) {\n      relation()\n      emit('changeRow', 'defaultRouter', res.data.authority.defaultRouter)\n    }\n  }\n\n  const handleDefaultRouterChange = (val) => {\n    // 兜底校验：未勾选菜单不允许被设置为默认首页\n    if (!isDefaultRouterAllowed(val)) {\n      ElMessage.warning('未勾选的菜单不可设置为默认首页，请先勾选后再选择')\n      return\n    }\n    setDefault({ name: val })\n  }\n\n  const nodeChange = () => {\n    needConfirm.value = true\n    refreshDefaultRouterOptions()\n  }\n  // 暴露给外层使用的切换拦截统一方法\n  const enterAndNext = () => {\n    relation()\n  }\n  // 关联树 确认方法\n  const relation = async () => {\n    const checkArr = menuTree.value.getCheckedNodes(false, true)\n    const res = await addMenuAuthority({\n      menus: checkArr,\n      authorityId: props.row.authorityId\n    })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '菜单设置成功!'\n      })\n\n      refreshDefaultRouterOptions()\n    }\n  }\n\n  defineExpose({ enterAndNext, needConfirm })\n\n  const btnVisible = ref(false)\n\n  const btnData = ref([])\n  const multipleSelection = ref([])\n  const btnTableRef = ref()\n  let menuID = ''\n  const OpenBtn = async (data) => {\n    menuID = data.ID\n    const res = await getAuthorityBtnApi({\n      menuID: menuID,\n      authorityId: props.row.authorityId\n    })\n    if (res.code === 0) {\n      openDialog(data)\n      await nextTick()\n      if (res.data.selected) {\n        res.data.selected.forEach((id) => {\n          btnData.value.some((item) => {\n            if (item.ID === id) {\n              btnTableRef.value.toggleRowSelection(item, true)\n            }\n          })\n        })\n      }\n    }\n  }\n\n  const handleSelectionChange = (val) => {\n    multipleSelection.value = val\n  }\n\n  const openDialog = (data) => {\n    btnVisible.value = true\n    btnData.value = data.menuBtn\n  }\n\n  const closeDialog = () => {\n    btnVisible.value = false\n  }\n  const enterDialog = async () => {\n    const selected = multipleSelection.value.map((item) => item.ID)\n    const res = await setAuthorityBtnApi({\n      menuID,\n      selected,\n      authorityId: props.row.authorityId\n    })\n    if (res.code === 0) {\n      ElMessage({ type: 'success', message: '设置成功' })\n      btnVisible.value = false\n    }\n  }\n\n  const filterNode = (value, data) => {\n    if (!value) return true\n    // console.log(data.mate.title)\n    return data.meta.title.indexOf(value) !== -1\n  }\n\n  watch(filterText, (val) => {\n    menuTree.value.filter(val)\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/superAdmin/dictionary/sysDictionary.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      title=\"获取字典且缓存方法已在前端utils/dictionary 已经封装完成 不必自己书写 使用方法查看文件内注释\"\n    />\n    <el-splitter class=\"h-full\">\n      <el-splitter-panel size=\"300px\" min=\"200px\" max=\"800px\" collapsible>\n        <div\n          class=\"flex-none bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded p-4\"\n        >\n          <div class=\"flex justify-between items-center relative\">\n            <span class=\"text font-bold\">字典列表</span>\n            <el-input\n              class=\"!absolute top-0 left-0 z-2 ease-in-out animate-slide-left\"\n              placeholder=\"搜索\"\n              v-if=\"showSearchInput\"\n              v-model=\"searchName\"\n              clearable\n              :autofocus=\"showSearchInput\"\n              @clear=\"clearSearchInput\"\n              :prefix-icon=\"Search\"\n              v-click-outside=\"handleCloseSearchInput\"\n              @keydown=\"handleInputKeyDown\"\n            >\n              <template #append>\n                <el-button\n                  :type=\"searchName ? 'primary' : 'info'\"\n                  @click=\"getTableData\"\n                  >搜索</el-button\n                >\n              </template>\n            </el-input>\n            <el-button-group class=\"ml-auto\">\n              <el-tooltip content=\"搜索\" placement=\"top\">\n                <el-button\n                  :icon=\"Search\"\n                  @click=\"showSearchInputHandler\"\n                />\n              </el-tooltip>\n              <el-tooltip content=\"导入字典\" placement=\"top\">\n                <el-button\n                  type=\"success\"\n                  :icon=\"Upload\"\n                  @click=\"openImportDialog\"\n                />\n              </el-tooltip>\n              <el-tooltip content=\"AI 生成字典\" placement=\"top\">\n                <el-button\n                  type=\"warning\"\n                  @click=\"openAiDialog\"\n                >\n                  AI\n                </el-button>\n              </el-tooltip>\n              <el-tooltip content=\"新建字典\" placement=\"top\">\n                <el-button\n                  type=\"primary\"\n                  :icon=\"Plus\"\n                  @click=\"openDrawer\"\n                />\n              </el-tooltip>\n            </el-button-group>\n          </div>\n          <el-scrollbar class=\"mt-4\" style=\"height: calc(100vh - 300px)\">\n            <div\n              v-for=\"dictionary in dictionaryData\"\n              :key=\"dictionary.ID\"\n              class=\"rounded flex justify-between items-center px-2 py-4 cursor-pointer mt-2 hover:bg-blue-50 dark:hover:bg-blue-900 bg-gray-50 dark:bg-gray-800 gap-4\"\n              :class=\"[\n                selectID === dictionary.ID\n                  ? 'text-active'\n                  : 'text-slate-700 dark:text-slate-50',\n                dictionary.parentID ? 'ml-4 border-l-2 border-blue-200' : ''\n              ]\"\n              @click=\"toDetail(dictionary)\"\n            >\n              <div class=\"max-w-[160px] truncate\">\n                <span\n                  v-if=\"dictionary.parentID\"\n                  class=\"text-xs text-gray-400 mr-1\"\n                  >└─</span\n                >\n                {{ dictionary.name }}\n                <span class=\"mr-auto text-sm\">（{{ dictionary.type }}）</span>\n              </div>\n\n              <div class=\"min-w-[60px] flex items-center gap-2\">\n                <el-icon\n                  class=\"!text-green-500\"\n                  @click.stop=\"exportDictionary(dictionary)\"\n                  title=\"导出字典\"\n                >\n                  <Download />\n                </el-icon>\n                <el-icon\n                  class=\"!text-blue-500\"\n                  @click.stop=\"updateSysDictionaryFunc(dictionary)\"\n                >\n                  <Edit />\n                </el-icon>\n                <el-icon\n                  class=\"!text-red-500\"\n                  @click=\"deleteSysDictionaryFunc(dictionary)\"\n                >\n                  <Delete />\n                </el-icon>\n              </div>\n            </div>\n          </el-scrollbar>\n        </div>\n      </el-splitter-panel>\n      <el-splitter-panel :min=\"200\">\n        <div\n          class=\"flex-1 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900\"\n        >\n          <sysDictionaryDetail :sys-dictionary-i-d=\"selectID\" />\n        </div>\n      </el-splitter-panel>\n    </el-splitter>\n\n    <el-drawer\n      v-model=\"drawerFormVisible\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n      :before-close=\"closeDrawer\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{\n            type === 'create' ? '添加字典' : '修改字典'\n          }}</span>\n          <div>\n            <el-button @click=\"closeDrawer\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"enterDrawer\"> 确 定 </el-button>\n          </div>\n        </div>\n      </template>\n      <el-form\n        ref=\"drawerForm\"\n        :model=\"formData\"\n        :rules=\"rules\"\n        label-width=\"110px\"\n      >\n        <el-form-item label=\"父级字典\" prop=\"parentID\">\n          <el-select\n            v-model=\"formData.parentID\"\n            placeholder=\"请选择父级字典（可选）\"\n            clearable\n            filterable\n            :style=\"{ width: '100%' }\"\n          >\n            <el-option\n              v-for=\"dict in availableParentDictionaries\"\n              :key=\"dict.ID\"\n              :label=\"`${dict.name}（${dict.type}）`\"\n              :value=\"dict.ID\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"字典名（中）\" prop=\"name\">\n          <el-input\n            v-model=\"formData.name\"\n            placeholder=\"请输入字典名（中）\"\n            clearable\n            :style=\"{ width: '100%' }\"\n          />\n        </el-form-item>\n        <el-form-item label=\"字典名（英）\" prop=\"type\">\n          <el-input\n            v-model=\"formData.type\"\n            placeholder=\"请输入字典名（英）\"\n            clearable\n            :style=\"{ width: '100%' }\"\n          />\n        </el-form-item>\n        <el-form-item label=\"状态\" prop=\"status\" required>\n          <el-switch\n            v-model=\"formData.status\"\n            active-text=\"开启\"\n            inactive-text=\"停用\"\n          />\n        </el-form-item>\n        <el-form-item label=\"描述\" prop=\"desc\">\n          <el-input\n            v-model=\"formData.desc\"\n            placeholder=\"请输入描述\"\n            clearable\n            :style=\"{ width: '100%' }\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n\n    <!-- 导入字典抽屉 -->\n    <el-drawer\n      v-model=\"importDrawerVisible\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n      :before-close=\"closeImportDrawer\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">导入字典JSON</span>\n          <div>\n            <el-button @click=\"closeImportDrawer\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"handleImport\" :loading=\"importing\">\n              确认导入\n            </el-button>\n          </div>\n        </div>\n      </template>\n      \n      <div class=\"import-drawer-content\">\n        <div class=\"mb-4\">\n          <el-alert\n            title=\"请粘贴、编辑或拖拽JSON文件到下方区域\"\n            type=\"info\"\n            :closable=\"false\"\n            show-icon\n          />\n        </div>\n\n        <!-- 拖拽上传区域 -->\n        <div\n          class=\"drag-upload-area\"\n          :class=\"{ 'is-dragging': isDragging }\"\n          @drop.prevent=\"handleDrop\"\n          @dragover.prevent=\"handleDragOver\"\n          @dragleave.prevent=\"handleDragLeave\"\n          @click=\"triggerFileInput\"\n        >\n          <el-icon class=\"upload-icon\"><Upload /></el-icon>\n          <div class=\"upload-text\">\n            <p>将 JSON 文件拖到此处，或点击选择文件</p>\n            <p class=\"upload-hint\">也可以在下方文本框直接编辑</p>\n          </div>\n          <input\n            ref=\"fileInputRef\"\n            type=\"file\"\n            accept=\".json,application/json\"\n            style=\"display: none\"\n            @change=\"handleFileSelect\"\n          />\n        </div>\n\n        <div class=\"json-editor-container mt-4\">\n          <el-input\n            v-model=\"importJsonText\"\n            type=\"textarea\"\n            :rows=\"15\"\n            placeholder='请输入JSON数据，例如：\n{\n  \"name\": \"性别\",\n  \"type\": \"gender\",\n  \"status\": true,\n  \"desc\": \"性别字典\",\n  \"sysDictionaryDetails\": [\n    {\n      \"label\": \"男\",\n      \"value\": \"1\",\n      \"status\": true,\n      \"sort\": 1\n    },\n    {\n      \"label\": \"女\",\n      \"value\": \"2\",\n      \"status\": true,\n      \"sort\": 2\n    }\n  ]\n}'\n            class=\"json-textarea\"\n          />\n        </div>\n\n        <div class=\"mt-4\" v-if=\"jsonPreviewError\">\n          <el-alert\n            :title=\"jsonPreviewError\"\n            type=\"error\"\n            :closable=\"false\"\n            show-icon\n          />\n        </div>\n\n    \n      </div>\n    </el-drawer>\n\n    <!-- AI 对话框 -->\n    <el-dialog\n      v-model=\"aiDialogVisible\"\n      title=\"AI 生成字典\"\n      width=\"520px\"\n      :before-close=\"closeAiDialog\"\n    >\n      <div class=\"relative\">\n        <el-input\n          v-model=\"aiPrompt\"\n          type=\"textarea\"\n          :rows=\"6\"\n          :maxlength=\"2000\"\n          placeholder=\"请输入生成字典的描述，例如：生成一个用户状态字典（启用/禁用）\\n支持粘贴或上传图片后识图生成。\"\n          resize=\"none\"\n          @keydown.ctrl.enter=\"handleAiGenerate\"\n          @paste=\"handlePaste\"\n          @focus=\"handleFocus\"\n          @blur=\"handleBlur\"\n        />\n\n        <input\n          ref=\"imageFileInputRef\"\n          type=\"file\"\n          accept=\"image/*\"\n          style=\"display:none\"\n          @change=\"handleImageSelect\"\n        />\n\n        <div class=\"flex absolute right-2 bottom-2\">\n          <el-tooltip effect=\"light\">\n            <template #content>\n              <div>粘贴或上传图片后，识别图片内容生成字典。</div>\n            </template>\n            <el-button type=\"primary\" @click=\"eyeFunc\">\n                <el-icon size=\"18\">\n                <ai-gva />\n              </el-icon>\n              识图\n            </el-button>\n          </el-tooltip>\n        </div>\n      </div>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"closeAiDialog\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"handleAiGenerate\" :loading=\"aiGenerating\">\n            确 定\n          </el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\n  import {\n    createSysDictionary,\n    deleteSysDictionary,\n    updateSysDictionary,\n    findSysDictionary,\n    getSysDictionaryList,\n    exportSysDictionary,\n    importSysDictionary\n  } from '@/api/sysDictionary' // 此处请自行替换地址\n  import { llmAuto } from '@/api/autoCode'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { ref, computed, watch } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n\n  import sysDictionaryDetail from './sysDictionaryDetail.vue'\n  import { Edit, Plus, Search, Download, Upload } from '@element-plus/icons-vue'\n  import { useAppStore } from '@/pinia'\n\n  defineOptions({\n    name: 'SysDictionary'\n  })\n\n  const appStore = useAppStore()\n\n  const selectID = ref(0)\n\n  const formData = ref({\n    name: null,\n    type: null,\n    status: true,\n    desc: null,\n    parentID: null\n  })\n  const searchName = ref('')\n  const showSearchInput = ref(false)\n  const rules = ref({\n    name: [\n      {\n        required: true,\n        message: '请输入字典名（中）',\n        trigger: 'blur'\n      }\n    ],\n    type: [\n      {\n        required: true,\n        message: '请输入字典名（英）',\n        trigger: 'blur'\n      }\n    ],\n    desc: [\n      {\n        required: true,\n        message: '请输入描述',\n        trigger: 'blur'\n      }\n    ]\n  })\n\n  const dictionaryData = ref([])\n  const availableParentDictionaries = ref([])\n\n  // 导入相关\n  const importDrawerVisible = ref(false)\n  const importJsonText = ref('')\n  const importing = ref(false)\n  const jsonPreviewError = ref('')\n  const jsonPreview = ref(null)\n  const isDragging = ref(false)\n  const fileInputRef = ref(null)\n\n  // AI 相关\n  const aiDialogVisible = ref(false)\n  const aiPrompt = ref('')\n  const aiGenerating = ref(false)\n\n  // 图片上传/识别相关\n  const imageFileInputRef = ref(null)\n  const focused = ref(false)\n\n  const handleFocus = () => {\n    focused.value = true\n  }\n  const handleBlur = () => {\n    focused.value = false\n  }\n\n  // 触发图片选择\n  const triggerImageSelect = () => {\n    imageFileInputRef.value?.click()\n  }\n\n  const handlePaste = (event) => {\n    const items = event.clipboardData.items;\n    for (let i = 0; i < items.length; i++) {\n      if (items[i].type.indexOf('image') !== -1) {\n        const file = items[i].getAsFile();\n        const reader = new FileReader();\n        reader.onload =async (e) => {\n          const base64String = e.target.result;\n          const res = await llmAuto({ _file_path: base64String, mode:\"dictEye\" })\n          if (res.code === 0) {\n            aiPrompt.value = res.data.text\n          }\n        };\n        reader.readAsDataURL(file);\n      }\n    }\n  };\n\n  const eyeFunc = async () => {\n    const input = document.createElement('input');\n    input.type = 'file';\n    input.accept = 'image/*';\n\n    input.onchange = (event) => {\n      const file = event.target.files[0];\n      if (file) {\n        const reader = new FileReader();\n        reader.onload = async (e) => {\n          const base64String = e.target.result;\n\n          const res = await llmAuto({ _file_path: base64String, mode:\"dictEye\" })\n          if (res.code === 0) {\n            aiPrompt.value = res.data.text\n          }\n        };\n        reader.readAsDataURL(file);\n      }\n    };\n\n    input.click();\n  }\n\n\n\n  // 监听JSON文本变化，实时预览\n  watch(importJsonText, (newVal) => {\n    if (!newVal.trim()) {\n      jsonPreview.value = null\n      jsonPreviewError.value = ''\n      return\n    }\n    try {\n      jsonPreview.value = JSON.parse(newVal)\n      jsonPreviewError.value = ''\n    } catch (e) {\n      jsonPreviewError.value = 'JSON格式错误: ' + e.message\n      jsonPreview.value = null\n    }\n  })\n\n  // 格式化JSON预览\n  const jsonPreviewFormatted = computed(() => {\n    if (!jsonPreview.value) return ''\n    return JSON.stringify(jsonPreview.value, null, 2)\n  })\n\n\n  // 查询\n  const getTableData = async () => {\n    const res = await getSysDictionaryList({\n      name: searchName.value.trim()\n    })\n    if (res.code === 0) {\n      dictionaryData.value = res.data\n      selectID.value = res.data[0].ID\n      // 更新可选父级字典列表\n      updateAvailableParentDictionaries()\n    }\n  }\n\n  // 更新可选父级字典列表\n  const updateAvailableParentDictionaries = () => {\n    // 如果是编辑模式，排除当前字典及其子字典\n    if (type.value === 'update' && formData.value.ID) {\n      availableParentDictionaries.value = dictionaryData.value.filter(\n        (dict) => {\n          return (\n            dict.ID !== formData.value.ID &&\n            !isChildDictionary(dict.ID, formData.value.ID)\n          )\n        }\n      )\n    } else {\n      // 创建模式，显示所有字典\n      availableParentDictionaries.value = [...dictionaryData.value]\n    }\n  }\n\n  // 检查是否为子字典（防止循环引用）\n  const isChildDictionary = (dictId, parentId) => {\n    const dict = dictionaryData.value.find((d) => d.ID === dictId)\n    if (!dict || !dict.parentID) return false\n    if (dict.parentID === parentId) return true\n    return isChildDictionary(dict.parentID, parentId)\n  }\n\n  getTableData()\n\n  const toDetail = (row) => {\n    selectID.value = row.ID\n  }\n\n  const drawerFormVisible = ref(false)\n  const type = ref('')\n  const updateSysDictionaryFunc = async (row) => {\n    const res = await findSysDictionary({ ID: row.ID, status: row.status })\n    type.value = 'update'\n    if (res.code === 0) {\n      formData.value = res.data.resysDictionary\n      drawerFormVisible.value = true\n      // 更新可选父级字典列表\n      updateAvailableParentDictionaries()\n    }\n  }\n  const closeDrawer = () => {\n    drawerFormVisible.value = false\n    formData.value = {\n      name: null,\n      type: null,\n      status: true,\n      desc: null,\n      parentID: null\n    }\n  }\n  const deleteSysDictionaryFunc = async (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await deleteSysDictionary({ ID: row.ID })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        getTableData()\n      }\n    })\n  }\n\n  const drawerForm = ref(null)\n  const enterDrawer = async () => {\n    drawerForm.value.validate(async (valid) => {\n      if (!valid) return\n      let res\n      switch (type.value) {\n        case 'create':\n          res = await createSysDictionary(formData.value)\n          break\n        case 'update':\n          res = await updateSysDictionary(formData.value)\n          break\n        default:\n          res = await createSysDictionary(formData.value)\n          break\n      }\n      if (res.code === 0) {\n        ElMessage.success('操作成功')\n        closeDrawer()\n        getTableData()\n      }\n    })\n  }\n  const openDrawer = () => {\n    type.value = 'create'\n    drawerForm.value && drawerForm.value.clearValidate()\n    drawerFormVisible.value = true\n    // 更新可选父级字典列表\n    updateAvailableParentDictionaries()\n  }\n\n  const clearSearchInput = () => {\n    if (!showSearchInput.value) return\n    searchName.value = ''\n    showSearchInput.value = false\n    getTableData()\n  }\n  const handleCloseSearchInput = () => {\n    if (!showSearchInput.value || searchName.value.trim() != '') return\n    showSearchInput.value = false\n  }\n\n  const showSearchInputHandler = () => {\n    showSearchInput.value = true\n  }\n\n  const handleInputKeyDown = (e) => {\n    if (e.key === 'Enter' && searchName.value.trim() !== '') {\n      getTableData()\n    }\n  }\n\n  // 导出字典\n  const exportDictionary = async (row) => {\n    try {\n      const res = await exportSysDictionary({ ID: row.ID })\n      if (res.code === 0) {\n        // 将JSON数据转换为字符串并下载\n        const jsonStr = JSON.stringify(res.data, null, 2)\n        const blob = new Blob([jsonStr], { type: 'application/json' })\n        const url = URL.createObjectURL(blob)\n        const link = document.createElement('a')\n        link.href = url\n        link.download = `${row.type}_${row.name}_dictionary.json`\n        document.body.appendChild(link)\n        link.click()\n        document.body.removeChild(link)\n        URL.revokeObjectURL(url)\n        ElMessage.success('导出成功')\n      }\n    } catch (error) {\n      ElMessage.error('导出失败: ' + error.message)\n    }\n  }\n\n  // 打开导入抽屉\n  const openImportDialog = () => {\n    importDrawerVisible.value = true\n    importJsonText.value = ''\n    jsonPreview.value = null\n    jsonPreviewError.value = ''\n    isDragging.value = false\n  }\n\n  // 关闭导入抽屉\n  const closeImportDrawer = () => {\n    importDrawerVisible.value = false\n    importJsonText.value = ''\n    jsonPreview.value = null\n    jsonPreviewError.value = ''\n    isDragging.value = false\n  }\n\n  // 处理拖拽进入\n  const handleDragOver = (e) => {\n    isDragging.value = true\n  }\n\n  // 处理拖拽离开\n  const handleDragLeave = (e) => {\n    isDragging.value = false\n  }\n  // 处理文件拖拽\n  const handleDrop = (e) => {\n    isDragging.value = false\n    const files = e.dataTransfer.files\n    if (files.length === 0) return\n\n    const file = files[0]\n    readJsonFile(file)\n  }\n\n  // 触发文件选择\n  const triggerFileInput = () => {\n    fileInputRef.value?.click()\n  }\n\n  // 处理文件选择\n  const handleFileSelect = (e) => {\n    const files = e.target.files\n    if (files.length === 0) return\n\n    const file = files[0]\n    readJsonFile(file)\n    \n    // 清空input，以便可以重复选择同一文件\n    e.target.value = ''\n  }\n\n  // 读取JSON文件\n  const readJsonFile = (file) => {\n    // 检查文件类型\n    if (!file.name.endsWith('.json')) {\n      ElMessage.warning('请上传 JSON 文件')\n      return\n    }\n\n    // 读取文件内容\n    const reader = new FileReader()\n    reader.onload = (event) => {\n      try {\n        const content = event.target.result\n        // 验证是否为有效的 JSON\n        JSON.parse(content)\n        importJsonText.value = content\n        ElMessage.success('文件读取成功')\n      } catch (error) {\n        ElMessage.error('文件内容不是有效的 JSON 格式')\n      }\n    }\n    reader.onerror = () => {\n      ElMessage.error('文件读取失败')\n    }\n    reader.readAsText(file)\n  }\n\n  // 处理导入\n  const handleImport = async () => {\n    if (!importJsonText.value.trim()) {\n      ElMessage.warning('请输入JSON数据')\n      return\n    }\n\n    if (jsonPreviewError.value) {\n      ElMessage.error('JSON格式错误，请检查后重试')\n      return\n    }\n\n    try {\n      importing.value = true\n      const res = await importSysDictionary({ json: importJsonText.value })\n      if (res.code === 0) {\n        ElMessage.success('导入成功')\n        closeImportDrawer()\n        getTableData()\n      }\n    } catch (error) {\n      ElMessage.error('导入失败: ' + error.message)\n    } finally {\n      importing.value = false\n    }\n  }\n\n  // 打开 AI 对话框\n  const openAiDialog = () => {\n    aiDialogVisible.value = true\n    aiPrompt.value = ''\n  }\n\n  // 关闭 AI 对话框\n  const closeAiDialog = () => {\n    aiDialogVisible.value = false\n    aiPrompt.value = ''\n  }\n\n  // 处理 AI 生成\n  const handleAiGenerate = async () => {\n    if (!aiPrompt.value.trim()) {\n      ElMessage.warning('请输入描述内容')\n      return\n    }\n    try {\n      aiGenerating.value = true\n      const aiRes = await llmAuto({\n        prompt: aiPrompt.value,\n        mode: 'dict'\n      })\n      if (aiRes && aiRes.code === 0) {\n        ElMessage.success('AI 生成成功')\n        try {\n          // 将 AI 返回的数据填充到导入文本框（支持字符串或对象）\n          if (typeof aiRes.data === 'string') {\n            importJsonText.value = aiRes.data\n          } else {\n            importJsonText.value = JSON.stringify(aiRes.data, null, 2)\n          }\n          // 清除可能的解析错误并打开导入抽屉\n          jsonPreviewError.value = ''\n          importDrawerVisible.value = true\n          closeAiDialog()\n        } catch (e) {\n          ElMessage.error('处理 AI 返回结果失败: ' + (e.message || e))\n        }\n      } \n    } catch (err) {\n      ElMessage.error('AI 调用失败: ' + (err.message || err))\n    } finally {\n      aiGenerating.value = false\n    }\n  }\n</script>\n\n<style scoped>\n  .dict-box {\n    height: calc(100vh - 240px);\n  }\n\n  .active {\n    background-color: var(--el-color-primary) !important;\n    color: #fff;\n  }\n\n  .import-drawer-content {\n    padding: 0 4px;\n  }\n\n  /* 拖拽上传区域 */\n  .drag-upload-area {\n    border: 2px dashed #dcdfe6;\n    border-radius: 8px;\n    padding: 40px 20px;\n    text-align: center;\n    background-color: #fafafa;\n    transition: all 0.3s ease;\n    cursor: pointer;\n  }\n\n  .drag-upload-area:hover {\n    border-color: #409eff;\n    background-color: #ecf5ff;\n  }\n\n  .drag-upload-area.is-dragging {\n    border-color: #409eff;\n    background-color: #ecf5ff;\n    transform: scale(1.02);\n  }\n\n  .upload-icon {\n    font-size: 48px;\n    color: #8c939d;\n    margin-bottom: 16px;\n  }\n\n  .drag-upload-area.is-dragging .upload-icon {\n    color: #409eff;\n  }\n\n  .upload-text {\n    color: #606266;\n  }\n\n  .upload-text p {\n    margin: 4px 0;\n  }\n\n  .upload-hint {\n    font-size: 12px;\n    color: #909399;\n  }\n\n  .json-editor-container {\n    border: 1px solid #dcdfe6;\n    border-radius: 4px;\n    overflow: hidden;\n  }\n\n  .json-textarea :deep(.el-textarea__inner) {\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 13px;\n    line-height: 1.5;\n  }\n\n  .json-preview {\n    background-color: #f5f7fa;\n    border: 1px solid #dcdfe6;\n    border-radius: 4px;\n    padding: 16px;\n    max-height: 400px;\n    overflow: auto;\n  }\n\n  .json-preview pre {\n    margin: 0;\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 13px;\n    line-height: 1.5;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n  }\n\n  .dark .drag-upload-area {\n    background-color: #1d1e1f;\n    border-color: #414243;\n  }\n\n  .dark .drag-upload-area:hover,\n  .dark .drag-upload-area.is-dragging {\n    background-color: #1a3a52;\n    border-color: #409eff;\n  }\n\n  .dark .json-preview {\n    background-color: #1d1e1f;\n    border-color: #414243;\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/dictionary/sysDictionaryDetail.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list justify-between flex items-center\">\n        <span class=\"text font-bold\">字典详细内容</span>\n        <div class=\"flex items-center gap-2\">\n          <el-input\n            placeholder=\"搜索展示值\"\n            v-model=\"searchName\"\n            clearable\n            class=\"!w-64\"\n            @clear=\"clearSearchInput\"\n            :prefix-icon=\"Search\"\n            v-click-outside=\"handleCloseSearchInput\"\n            @keydown=\"handleInputKeyDown\"\n          >\n            <template #append>\n              <el-button\n                :type=\"searchName ? 'primary' : 'info'\"\n                @click=\"applySearch\"\n                >搜索</el-button\n              >\n            </template>\n          </el-input>\n          <el-button type=\"primary\" icon=\"plus\" @click=\"openDrawer\">\n            新增字典项\n          </el-button>\n        </div>\n      </div>\n      <!-- 表格视图 -->\n      <el-table\n        :data=\"displayTreeData\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :tree-props=\"{ children: 'children'}\"\n        row-key=\"ID\"\n        default-expand-all\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n\n        <el-table-column align=\"left\" label=\"展示值\" prop=\"label\" min-width=\"100\"/>\n\n        <el-table-column align=\"left\" label=\"字典值\" prop=\"value\" />\n\n        <el-table-column align=\"left\" label=\"扩展值\" prop=\"extend\" />\n\n        <el-table-column align=\"left\" label=\"层级\" prop=\"level\" width=\"80\" />\n\n        <el-table-column\n          align=\"left\"\n          label=\"启用状态\"\n          prop=\"status\"\n          width=\"100\"\n        >\n          <template #default=\"scope\">\n            {{ formatBoolean(scope.row.status) }}\n          </template>\n        </el-table-column>\n\n        <el-table-column\n          align=\"left\"\n          label=\"排序标记\"\n          prop=\"sort\"\n          width=\"100\"\n        />\n\n        <el-table-column\n          align=\"left\"\n          label=\"操作\"\n          :min-width=\"appStore.operateMinWith\"\n          fixed=\"right\"\n        >\n          <template #default=\"scope\">\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"plus\"\n              @click=\"addChildNode(scope.row)\"\n            >\n              添加子项\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              @click=\"updateSysDictionaryDetailFunc(scope.row)\"\n            >\n              变更\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteSysDictionaryDetailFunc(scope.row)\"\n            >\n              删除\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n    </div>\n\n    <el-drawer\n      v-model=\"drawerFormVisible\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n      :before-close=\"closeDrawer\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{\n            type === 'create' ? '添加字典项' : '修改字典项'\n          }}</span>\n          <div>\n            <el-button @click=\"closeDrawer\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"enterDrawer\"> 确 定 </el-button>\n          </div>\n        </div>\n      </template>\n      <el-form\n        ref=\"drawerForm\"\n        :model=\"formData\"\n        :rules=\"rules\"\n        label-width=\"110px\"\n      >\n        <el-form-item label=\"父级字典项\" prop=\"parentID\">\n          <el-cascader\n            v-model=\"formData.parentID\"\n            :options=\"[rootOption,...treeData]\"\n            :props=\"cascadeProps\"\n            placeholder=\"请选择父级字典项（可选）\"\n            clearable\n            filterable\n            :style=\"{ width: '100%' }\"\n            @change=\"handleParentChange\"\n          />\n        </el-form-item>\n        <el-form-item label=\"展示值\" prop=\"label\">\n          <el-input\n            v-model=\"formData.label\"\n            placeholder=\"请输入展示值\"\n            clearable\n            :style=\"{ width: '100%' }\"\n          />\n        </el-form-item>\n        <el-form-item label=\"字典值\" prop=\"value\">\n          <el-input\n            v-model=\"formData.value\"\n            placeholder=\"请输入字典值\"\n            clearable\n            :style=\"{ width: '100%' }\"\n          />\n        </el-form-item>\n        <el-form-item label=\"扩展值\" prop=\"extend\">\n          <el-input\n            v-model=\"formData.extend\"\n            placeholder=\"请输入扩展值\"\n            clearable\n            :style=\"{ width: '100%' }\"\n          />\n        </el-form-item>\n        <el-form-item label=\"启用状态\" prop=\"status\" required>\n          <el-switch\n            v-model=\"formData.status\"\n            active-text=\"开启\"\n            inactive-text=\"停用\"\n          />\n        </el-form-item>\n        <el-form-item label=\"排序标记\" prop=\"sort\">\n          <el-input-number\n            v-model.number=\"formData.sort\"\n            placeholder=\"排序标记\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    createSysDictionaryDetail,\n    deleteSysDictionaryDetail,\n    updateSysDictionaryDetail,\n    findSysDictionaryDetail,\n    getDictionaryTreeList\n  } from '@/api/sysDictionaryDetail' // 此处请自行替换地址\n  import { ref, watch } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { formatBoolean, formatDate } from '@/utils/format'\n  import { useAppStore } from '@/pinia'\n  import { Search } from '@element-plus/icons-vue'\n\n  defineOptions({\n    name: 'SysDictionaryDetail'\n  })\n\n  const appStore = useAppStore()\n  const searchName = ref('')\n\n  const props = defineProps({\n    sysDictionaryID: {\n      type: Number,\n      default: 0\n    }\n  })\n\n  const formData = ref({\n    label: null,\n    value: null,\n    status: true,\n    sort: null,\n    parentID: null\n  })\n\n  const rules = ref({\n    label: [\n      {\n        required: true,\n        message: '请输入展示值',\n        trigger: 'blur'\n      }\n    ],\n    value: [\n      {\n        required: true,\n        message: '请输入字典值',\n        trigger: 'blur'\n      }\n    ],\n    sort: [\n      {\n        required: true,\n        message: '排序标记',\n        trigger: 'blur'\n      }\n    ]\n  })\n\n  const treeData = ref([])\n  const displayTreeData = ref([])\n\n  // 级联选择器配置\n  const cascadeProps = {\n    value: 'ID',\n    label: 'label',\n    children: 'children',\n    checkStrictly: true, // 允许选择任意级别\n    emitPath: false // 只返回选中节点的值\n  }\n\n\n  const normalizeSearch = (value) => (value ?? '').toString().toLowerCase()\n\n  const filterTree = (nodes, keyword) => {\n    const trimmed = normalizeSearch(keyword).trim()\n    if (!trimmed) {\n      return nodes\n    }\n    const walk = (list) => {\n      const result = []\n      for (const node of list) {\n        const label = normalizeSearch(node.label)\n        const children = Array.isArray(node.children) ? walk(node.children) : []\n        if (label.includes(trimmed) || children.length > 0) {\n          result.push({\n            ...node,\n            children\n          })\n        }\n      }\n      return result\n    }\n    return walk(nodes)\n  }\n\n  const applySearch = () => {\n    displayTreeData.value = filterTree(treeData.value, searchName.value)\n  }\n\n  // 获取树形数据\n  const getTreeData = async () => {\n    if (!props.sysDictionaryID) return\n    try {\n      const res = await getDictionaryTreeList({\n        sysDictionaryID: props.sysDictionaryID\n      })\n      if (res.code === 0) {\n        treeData.value = res.data.list || []\n        applySearch()\n      }\n    } catch (error) {\n      console.error('获取树形数据失败:', error)\n      ElMessage.error('获取层级数据失败')\n    }\n  }\n\n  const rootOption = {\n    ID: null,\n    label: '无父级（根级）'\n  }\n\n\n  // 初始加载\n  getTreeData()\n\n  const type = ref('')\n  const drawerFormVisible = ref(false)\n\n  const updateSysDictionaryDetailFunc = async (row) => {\n    drawerForm.value && drawerForm.value.clearValidate()\n    const res = await findSysDictionaryDetail({ ID: row.ID })\n    type.value = 'update'\n    if (res.code === 0) {\n      formData.value = res.data.reSysDictionaryDetail\n      drawerFormVisible.value = true\n    }\n  }\n\n  // 添加子节点\n  const addChildNode = (parentNode) => {\n    console.log(parentNode)\n    type.value = 'create'\n    formData.value = {\n      label: null,\n      value: null,\n      status: true,\n      sort: null,\n      parentID: parentNode.ID,\n      sysDictionaryID: props.sysDictionaryID\n    }\n    drawerForm.value && drawerForm.value.clearValidate()\n    drawerFormVisible.value = true\n  }\n\n  // 处理父级选择变化\n  const handleParentChange = (value) => {\n    formData.value.parentID = value\n  }\n\n  const closeDrawer = () => {\n    drawerFormVisible.value = false\n    formData.value = {\n      label: null,\n      value: null,\n      status: true,\n      sort: null,\n      parentID: null,\n      sysDictionaryID: props.sysDictionaryID\n    }\n  }\n\n  const deleteSysDictionaryDetailFunc = async (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await deleteSysDictionaryDetail({ ID: row.ID })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        await getTreeData() // 重新加载数据\n      }\n    })\n  }\n\n  const drawerForm = ref(null)\n  const enterDrawer = async () => {\n    drawerForm.value.validate(async (valid) => {\n      formData.value.sysDictionaryID = props.sysDictionaryID\n      if (!valid) return\n      let res\n      switch (type.value) {\n        case 'create':\n          res = await createSysDictionaryDetail(formData.value)\n          break\n        case 'update':\n          res = await updateSysDictionaryDetail(formData.value)\n          break\n        default:\n          res = await createSysDictionaryDetail(formData.value)\n          break\n      }\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '创建/更改成功'\n        })\n        closeDrawer()\n        await getTreeData() // 重新加载数据\n      }\n    })\n  }\n\n  const openDrawer = () => {\n    type.value = 'create'\n    formData.value.parentID = null\n    drawerForm.value && drawerForm.value.clearValidate()\n    drawerFormVisible.value = true\n  }\n\n  const clearSearchInput = () => {\n    searchName.value = ''\n    applySearch()\n  }\n\n  const handleCloseSearchInput = () => {\n    // 处理搜索输入框关闭\n  }\n\n  const handleInputKeyDown = (e) => {\n    if (e.key === 'Enter') {\n      applySearch()\n    }\n  }\n\n  watch(\n    () => props.sysDictionaryID,\n    () => {\n      getTreeData()\n    }\n  )\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/index.vue",
    "content": "<template>\n  <div>\n    <router-view v-slot=\"{ Component }\">\n      <transition mode=\"out-in\" name=\"el-fade-in-linear\">\n        <keep-alive :include=\"routerStore.keepAliveRouters\">\n          <component :is=\"Component\" />\n        </keep-alive>\n      </transition>\n    </router-view>\n  </div>\n</template>\n\n<script setup>\n  import { useRouterStore } from '@/pinia/modules/router'\n  const routerStore = useRouterStore()\n\n  defineOptions({\n    name: 'SuperAdmin'\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/superAdmin/menu/components/components-cascader.vue",
    "content": "<template>\n  <div class=\"flex justify-between items-center gap-2 w-full\">\n    <el-cascader\n      v-if=\"pathIsSelect\"\n      placeholder=\"请选择文件路径\"\n      :options=\"pathOptions\"\n      v-model=\"activeComponent\"\n      filterable\n      class=\"!w-full\"\n      clearable\n      @change=\"emitChange\"\n    />\n    <el-input\n      v-else\n      v-model=\"tempPath\"\n      placeholder=\"页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue\"\n      @change=\"emitChange\"\n    />\n    <el-button @click=\"togglePathIsSelect\"\n      >{{ pathIsSelect ? '手动输入' : '快捷选择' }}\n    </el-button>\n  </div>\n</template>\n\n<script setup>\n  import { onMounted, ref, watch } from 'vue'\n  import pathInfo from '@/pathInfo.json'\n\n  const props = defineProps({\n    component: {\n      type: String,\n      default: ''\n    }\n  })\n\n  const emits = defineEmits(['change'])\n\n  const pathOptions = ref([])\n  const tempPath = ref('')\n  const activeComponent = ref([])\n  const pathIsSelect = ref(true)\n\n  const togglePathIsSelect = () => {\n    if (pathIsSelect.value) {\n      tempPath.value = activeComponent.value?.join('/') || ''\n    } else {\n      activeComponent.value = tempPath.value?.split('/') || []\n    }\n\n    pathIsSelect.value = !pathIsSelect.value\n    emitChange()\n  }\n\n  function convertToCascaderOptions(data) {\n    const result = []\n\n    for (const path in data) {\n      const label = data[path]\n      const parts = path.split('/').filter(Boolean)\n\n      // 如果第一个部分是 'src'，则从第二个部分开始处理\n      const startIndex = parts[0] === 'src' ? 1 : 0\n\n      let currentLevel = result\n\n      for (let i = startIndex; i < parts.length; i++) {\n        const part = parts[i]\n        let node = currentLevel.find((item) => item.value === part)\n\n        if (!node) {\n          node = {\n            value: part,\n            label: part,\n            children: []\n          }\n          currentLevel.push(node)\n        }\n\n        if (i === parts.length - 1) {\n          // 如果是路径的最后一部分，设置标签并移除 children\n          node.label = label\n          delete node.children\n        }\n\n        currentLevel = node.children || []\n      }\n    }\n\n    return result\n  }\n\n  watch(\n    () => props.component,\n    (value) => {\n      initCascader(value)\n    }\n  )\n\n  onMounted(() => {\n    pathOptions.value = convertToCascaderOptions(pathInfo)\n    initCascader(props.component)\n  })\n\n  const initCascader = (value) => {\n    // 新增的时候\n    if (value === '') {\n      pathIsSelect.value = true\n      return\n    }\n\n    // 编辑的时候，根据路径判断是选择框还是输入框\n    if (pathInfo[`/src/${value}`]) {\n      activeComponent.value = value.split('/').filter(Boolean)\n      tempPath.value = ''\n      pathIsSelect.value = true\n      return\n    }\n    tempPath.value = value\n    activeComponent.value = []\n    pathIsSelect.value = false\n  }\n\n  const emitChange = () => {\n    emits(\n      'change',\n      pathIsSelect.value ? activeComponent.value?.join('/') : tempPath.value\n    )\n  }\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "web/src/view/superAdmin/menu/icon.vue",
    "content": "<template>\n  <div class=\"w-full\">\n    <el-select\n      v-model=\"value\"\n      clearable\n      filterable\n      placeholder=\"请选择\"\n      class=\"w-full\"\n    >\n      <template #prefix>\n        <el-icon>\n          <component v-if=\"value\" :is=\"value\" />\n        </el-icon>\n      </template>\n      <el-option\n        v-for=\"item in options.concat(config.logs)\"\n        :key=\"item.key\"\n        class=\"select__option_item\"\n        :label=\"item.key\"\n        :value=\"item.key\"\n      >\n        <span class=\"gva-icon\" style=\"padding: 3px 0 0\" :class=\"item.label\">\n          <el-icon>\n            <component v-if=\"item.label\" :is=\"item.label\" />\n          </el-icon>\n        </span>\n        <span style=\"text-align: left\">{{ item.key }}</span>\n      </el-option>\n    </el-select>\n  </div>\n</template>\n\n<script setup>\n  import { reactive } from 'vue'\n  import config from \"@/core/config\";\n\n  defineOptions({\n    name: 'Icon'\n  })\n\n  const value = defineModel()\n\n  const options = reactive([\n    {\n      key: 'aim',\n      label: 'aim'\n    },\n    {\n      key: 'add-location',\n      label: 'add-location'\n    },\n    {\n      key: 'apple',\n      label: 'apple'\n    },\n    {\n      key: 'alarm-clock',\n      label: 'alarm-clock'\n    },\n    {\n      key: 'arrow-down',\n      label: 'arrow-down'\n    },\n    {\n      key: 'arrow-down-bold',\n      label: 'arrow-down-bold'\n    },\n    {\n      key: 'arrow-left',\n      label: 'arrow-left'\n    },\n    {\n      key: 'arrow-left-bold',\n      label: 'arrow-left-bold'\n    },\n    {\n      key: 'arrow-right-bold',\n      label: 'arrow-right-bold'\n    },\n    {\n      key: 'arrow-up',\n      label: 'arrow-up'\n    },\n    {\n      key: 'back',\n      label: 'back'\n    },\n    {\n      key: 'bell',\n      label: 'bell'\n    },\n    {\n      key: 'baseball',\n      label: 'baseball'\n    },\n    {\n      key: 'bicycle',\n      label: 'bicycle'\n    },\n    {\n      key: 'bell-filled',\n      label: 'bell-filled'\n    },\n    {\n      key: 'basketball',\n      label: 'basketball'\n    },\n    {\n      key: 'bottom',\n      label: 'bottom'\n    },\n    {\n      key: 'box',\n      label: 'box'\n    },\n    {\n      key: 'briefcase',\n      label: 'briefcase'\n    },\n    {\n      key: 'brush-filled',\n      label: 'brush-filled'\n    },\n    {\n      key: 'bowl',\n      label: 'bowl'\n    },\n    {\n      key: 'avatar',\n      label: 'avatar'\n    },\n    {\n      key: 'brush',\n      label: 'brush'\n    },\n    {\n      key: 'burger',\n      label: 'burger'\n    },\n    {\n      key: 'camera',\n      label: 'camera'\n    },\n    {\n      key: 'bottom-left',\n      label: 'bottom-left'\n    },\n    {\n      key: 'calendar',\n      label: 'calendar'\n    },\n    {\n      key: 'caret-bottom',\n      label: 'caret-bottom'\n    },\n    {\n      key: 'caret-left',\n      label: 'caret-left'\n    },\n    {\n      key: 'caret-right',\n      label: 'caret-right'\n    },\n    {\n      key: 'caret-top',\n      label: 'caret-top'\n    },\n    {\n      key: 'chat-dot-square',\n      label: 'chat-dot-square'\n    },\n    {\n      key: 'cellphone',\n      label: 'cellphone'\n    },\n    {\n      key: 'chat-dot-round',\n      label: 'chat-dot-round'\n    },\n    {\n      key: 'chat-line-square',\n      label: 'chat-line-square'\n    },\n    {\n      key: 'chat-line-round',\n      label: 'chat-line-round'\n    },\n    {\n      key: 'chat-round',\n      label: 'chat-round'\n    },\n    {\n      key: 'check',\n      label: 'check'\n    },\n    {\n      key: 'chat-square',\n      label: 'chat-square'\n    },\n    {\n      key: 'cherry',\n      label: 'cherry'\n    },\n    {\n      key: 'chicken',\n      label: 'chicken'\n    },\n    {\n      key: 'circle-check-filled',\n      label: 'circle-check-filled'\n    },\n    {\n      key: 'circle-check',\n      label: 'circle-check'\n    },\n    {\n      key: 'checked',\n      label: 'checked'\n    },\n    {\n      key: 'circle-close-filled',\n      label: 'circle-close-filled'\n    },\n    {\n      key: 'circle-close',\n      label: 'circle-close'\n    },\n    {\n      key: 'arrow-right',\n      label: 'arrow-right'\n    },\n    {\n      key: 'circle-plus',\n      label: 'circle-plus'\n    },\n    {\n      key: 'clock',\n      label: 'clock'\n    },\n    {\n      key: 'close-bold',\n      label: 'close-bold'\n    },\n    {\n      key: 'close',\n      label: 'close'\n    },\n    {\n      key: 'cloudy',\n      label: 'cloudy'\n    },\n    {\n      key: 'circle-plus-filled',\n      label: 'circle-plus-filled'\n    },\n    {\n      key: 'coffee-cup',\n      label: 'coffee-cup'\n    },\n    {\n      key: 'cold-drink',\n      label: 'cold-drink'\n    },\n    {\n      key: 'coin',\n      label: 'coin'\n    },\n    {\n      key: 'arrow-up-bold',\n      label: 'arrow-up-bold'\n    },\n    {\n      key: 'collection-tag',\n      label: 'collection-tag'\n    },\n    {\n      key: 'bottom-right',\n      label: 'bottom-right'\n    },\n    {\n      key: 'coffee',\n      label: 'coffee'\n    },\n    {\n      key: 'camera-filled',\n      label: 'camera-filled'\n    },\n    {\n      key: 'collection',\n      label: 'collection'\n    },\n    {\n      key: 'cpu',\n      label: 'cpu'\n    },\n    {\n      key: 'crop',\n      label: 'crop'\n    },\n    {\n      key: 'coordinate',\n      label: 'coordinate'\n    },\n    {\n      key: 'd-arrow-left',\n      label: 'd-arrow-left'\n    },\n    {\n      key: 'compass',\n      label: 'compass'\n    },\n    {\n      key: 'connection',\n      label: 'connection'\n    },\n    {\n      key: 'credit-card',\n      label: 'credit-card'\n    },\n    {\n      key: 'data-board',\n      label: 'data-board'\n    },\n    {\n      key: 'd-arrow-right',\n      label: 'd-arrow-right'\n    },\n    {\n      key: 'dessert',\n      label: 'dessert'\n    },\n    {\n      key: 'delete-location',\n      label: 'delete-location'\n    },\n    {\n      key: 'd-caret',\n      label: 'd-caret'\n    },\n    {\n      key: 'delete',\n      label: 'delete'\n    },\n    {\n      key: 'dish',\n      label: 'dish'\n    },\n    {\n      key: 'dish-dot',\n      label: 'dish-dot'\n    },\n    {\n      key: 'document-copy',\n      label: 'document-copy'\n    },\n    {\n      key: 'discount',\n      label: 'discount'\n    },\n    {\n      key: 'document-checked',\n      label: 'document-checked'\n    },\n    {\n      key: 'document-add',\n      label: 'document-add'\n    },\n    {\n      key: 'document-remove',\n      label: 'document-remove'\n    },\n    {\n      key: 'data-analysis',\n      label: 'data-analysis'\n    },\n    {\n      key: 'delete-filled',\n      label: 'delete-filled'\n    },\n    {\n      key: 'download',\n      label: 'download'\n    },\n    {\n      key: 'drizzling',\n      label: 'drizzling'\n    },\n    {\n      key: 'eleme',\n      label: 'eleme'\n    },\n    {\n      key: 'eleme-filled',\n      label: 'eleme-filled'\n    },\n    {\n      key: 'edit',\n      label: 'edit'\n    },\n    {\n      key: 'failed',\n      label: 'failed'\n    },\n    {\n      key: 'expand',\n      label: 'expand'\n    },\n    {\n      key: 'female',\n      label: 'female'\n    },\n    {\n      key: 'document',\n      label: 'document'\n    },\n    {\n      key: 'film',\n      label: 'film'\n    },\n    {\n      key: 'finished',\n      label: 'finished'\n    },\n    {\n      key: 'data-line',\n      label: 'data-line'\n    },\n    {\n      key: 'filter',\n      label: 'filter'\n    },\n    {\n      key: 'flag',\n      label: 'flag'\n    },\n    {\n      key: 'folder-checked',\n      label: 'folder-checked'\n    },\n    {\n      key: 'first-aid-kit',\n      label: 'first-aid-kit'\n    },\n    {\n      key: 'folder-add',\n      label: 'folder-add'\n    },\n    {\n      key: 'fold',\n      label: 'fold'\n    },\n    {\n      key: 'folder-delete',\n      label: 'folder-delete'\n    },\n    {\n      key: 'document-delete',\n      label: 'document-delete'\n    },\n    {\n      key: 'folder',\n      label: 'folder'\n    },\n    {\n      key: 'food',\n      label: 'food'\n    },\n    {\n      key: 'folder-opened',\n      label: 'folder-opened'\n    },\n    {\n      key: 'football',\n      label: 'football'\n    },\n    {\n      key: 'folder-remove',\n      label: 'folder-remove'\n    },\n    {\n      key: 'fries',\n      label: 'fries'\n    },\n    {\n      key: 'full-screen',\n      label: 'full-screen'\n    },\n    {\n      key: 'fork-spoon',\n      label: 'fork-spoon'\n    },\n    {\n      key: 'goblet',\n      label: 'goblet'\n    },\n    {\n      key: 'goblet-full',\n      label: 'goblet-full'\n    },\n    {\n      key: 'goods',\n      label: 'goods'\n    },\n    {\n      key: 'goblet-square-full',\n      label: 'goblet-square-full'\n    },\n    {\n      key: 'goods-filled',\n      label: 'goods-filled'\n    },\n    {\n      key: 'grid',\n      label: 'grid'\n    },\n    {\n      key: 'grape',\n      label: 'grape'\n    },\n    {\n      key: 'goblet-square',\n      label: 'goblet-square'\n    },\n    {\n      key: 'headset',\n      label: 'headset'\n    },\n    {\n      key: 'comment',\n      label: 'comment'\n    },\n    {\n      key: 'help-filled',\n      label: 'help-filled'\n    },\n    {\n      key: 'histogram',\n      label: 'histogram'\n    },\n    {\n      key: 'home-filled',\n      label: 'home-filled'\n    },\n    {\n      key: 'help',\n      label: 'help'\n    },\n    {\n      key: 'house',\n      label: 'house'\n    },\n    {\n      key: 'ice-cream-round',\n      label: 'ice-cream-round'\n    },\n    {\n      key: 'hot-water',\n      label: 'hot-water'\n    },\n    {\n      key: 'ice-cream',\n      label: 'ice-cream'\n    },\n    {\n      key: 'files',\n      label: 'files'\n    },\n    {\n      key: 'ice-cream-square',\n      label: 'ice-cream-square'\n    },\n    {\n      key: 'key',\n      label: 'key'\n    },\n    {\n      key: 'ice-tea',\n      label: 'ice-tea'\n    },\n    {\n      key: 'knife-fork',\n      label: 'knife-fork'\n    },\n    {\n      key: 'iphone',\n      label: 'iphone'\n    },\n    {\n      key: 'info-filled',\n      label: 'info-filled'\n    },\n    {\n      key: 'link',\n      label: 'link'\n    },\n    {\n      key: 'ice-drink',\n      label: 'ice-drink'\n    },\n    {\n      key: 'lightning',\n      label: 'lightning'\n    },\n    {\n      key: 'loading',\n      label: 'loading'\n    },\n    {\n      key: 'lollipop',\n      label: 'lollipop'\n    },\n    {\n      key: 'location-information',\n      label: 'location-information'\n    },\n    {\n      key: 'lock',\n      label: 'lock'\n    },\n    {\n      key: 'location-filled',\n      label: 'location-filled'\n    },\n    {\n      key: 'magnet',\n      label: 'magnet'\n    },\n    {\n      key: 'male',\n      label: 'male'\n    },\n    {\n      key: 'location',\n      label: 'location'\n    },\n    {\n      key: 'menu',\n      label: 'menu'\n    },\n    {\n      key: 'magic-stick',\n      label: 'magic-stick'\n    },\n    {\n      key: 'message-box',\n      label: 'message-box'\n    },\n    {\n      key: 'map-location',\n      label: 'map-location'\n    },\n    {\n      key: 'mic',\n      label: 'mic'\n    },\n    {\n      key: 'message',\n      label: 'message'\n    },\n    {\n      key: 'medal',\n      label: 'medal'\n    },\n    {\n      key: 'milk-tea',\n      label: 'milk-tea'\n    },\n    {\n      key: 'microphone',\n      label: 'microphone'\n    },\n    {\n      key: 'minus',\n      label: 'minus'\n    },\n    {\n      key: 'money',\n      label: 'money'\n    },\n    {\n      key: 'moon-night',\n      label: 'moon-night'\n    },\n    {\n      key: 'monitor',\n      label: 'monitor'\n    },\n    {\n      key: 'moon',\n      label: 'moon'\n    },\n    {\n      key: 'more',\n      label: 'more'\n    },\n    {\n      key: 'mostly-cloudy',\n      label: 'mostly-cloudy'\n    },\n    {\n      key: 'more-filled',\n      label: 'more-filled'\n    },\n    {\n      key: 'mouse',\n      label: 'mouse'\n    },\n    {\n      key: 'mug',\n      label: 'mug'\n    },\n    {\n      key: 'mute',\n      label: 'mute'\n    },\n    {\n      key: 'no-smoking',\n      label: 'no-smoking'\n    },\n    {\n      key: 'mute-notification',\n      label: 'mute-notification'\n    },\n    {\n      key: 'notification',\n      label: 'notification'\n    },\n    {\n      key: 'notebook',\n      label: 'notebook'\n    },\n    {\n      key: 'odometer',\n      label: 'odometer'\n    },\n    {\n      key: 'office-building',\n      label: 'office-building'\n    },\n    {\n      key: 'operation',\n      label: 'operation'\n    },\n    {\n      key: 'opportunity',\n      label: 'opportunity'\n    },\n    {\n      key: 'orange',\n      label: 'orange'\n    },\n    {\n      key: 'open',\n      label: 'open'\n    },\n    {\n      key: 'paperclip',\n      label: 'paperclip'\n    },\n    {\n      key: 'pear',\n      label: 'pear'\n    },\n    {\n      key: 'partly-cloudy',\n      label: 'partly-cloudy'\n    },\n    {\n      key: 'phone',\n      label: 'phone'\n    },\n    {\n      key: 'picture-filled',\n      label: 'picture-filled'\n    },\n    {\n      key: 'phone-filled',\n      label: 'phone-filled'\n    },\n    {\n      key: 'picture-rounded',\n      label: 'picture-rounded'\n    },\n    {\n      key: 'guide',\n      label: 'guide'\n    },\n    {\n      key: 'place',\n      label: 'place'\n    },\n    {\n      key: 'platform',\n      label: 'platform'\n    },\n    {\n      key: 'pie-chart',\n      label: 'pie-chart'\n    },\n    {\n      key: 'pointer',\n      label: 'pointer'\n    },\n    {\n      key: 'plus',\n      label: 'plus'\n    },\n    {\n      key: 'position',\n      label: 'position'\n    },\n    {\n      key: 'postcard',\n      label: 'postcard'\n    },\n    {\n      key: 'present',\n      label: 'present'\n    },\n    {\n      key: 'price-tag',\n      label: 'price-tag'\n    },\n    {\n      key: 'promotion',\n      label: 'promotion'\n    },\n    {\n      key: 'pouring',\n      label: 'pouring'\n    },\n    {\n      key: 'reading-lamp',\n      label: 'reading-lamp'\n    },\n    {\n      key: 'question-filled',\n      label: 'question-filled'\n    },\n    {\n      key: 'printer',\n      label: 'printer'\n    },\n    {\n      key: 'picture',\n      label: 'picture'\n    },\n    {\n      key: 'refresh-right',\n      label: 'refresh-right'\n    },\n    {\n      key: 'reading',\n      label: 'reading'\n    },\n    {\n      key: 'refresh-left',\n      label: 'refresh-left'\n    },\n    {\n      key: 'refresh',\n      label: 'refresh'\n    },\n    {\n      key: 'refrigerator',\n      label: 'refrigerator'\n    },\n    {\n      key: 'remove-filled',\n      label: 'remove-filled'\n    },\n    {\n      key: 'right',\n      label: 'right'\n    },\n    {\n      key: 'scale-to-original',\n      label: 'scale-to-original'\n    },\n    {\n      key: 'school',\n      label: 'school'\n    },\n    {\n      key: 'remove',\n      label: 'remove'\n    },\n    {\n      key: 'scissor',\n      label: 'scissor'\n    },\n    {\n      key: 'select',\n      label: 'select'\n    },\n    {\n      key: 'management',\n      label: 'management'\n    },\n    {\n      key: 'search',\n      label: 'search'\n    },\n    {\n      key: 'sell',\n      label: 'sell'\n    },\n    {\n      key: 'semi-select',\n      label: 'semi-select'\n    },\n    {\n      key: 'share',\n      label: 'share'\n    },\n    {\n      key: 'setting',\n      label: 'setting'\n    },\n    {\n      key: 'service',\n      label: 'service'\n    },\n    {\n      key: 'ship',\n      label: 'ship'\n    },\n    {\n      key: 'set-up',\n      label: 'set-up'\n    },\n    {\n      key: 'shopping-bag',\n      label: 'shopping-bag'\n    },\n    {\n      key: 'shop',\n      label: 'shop'\n    },\n    {\n      key: 'shopping-cart',\n      label: 'shopping-cart'\n    },\n    {\n      key: 'shopping-cart-full',\n      label: 'shopping-cart-full'\n    },\n    {\n      key: 'soccer',\n      label: 'soccer'\n    },\n    {\n      key: 'sold-out',\n      label: 'sold-out'\n    },\n    {\n      key: 'smoking',\n      label: 'smoking'\n    },\n    {\n      key: 'sort-down',\n      label: 'sort-down'\n    },\n    {\n      key: 'sort',\n      label: 'sort'\n    },\n    {\n      key: 'sort-up',\n      label: 'sort-up'\n    },\n    {\n      key: 'star',\n      label: 'star'\n    },\n    {\n      key: 'stamp',\n      label: 'stamp'\n    },\n    {\n      key: 'star-filled',\n      label: 'star-filled'\n    },\n    {\n      key: 'stopwatch',\n      label: 'stopwatch'\n    },\n    {\n      key: 'success-filled',\n      label: 'success-filled'\n    },\n    {\n      key: 'suitcase',\n      label: 'suitcase'\n    },\n    {\n      key: 'sugar',\n      label: 'sugar'\n    },\n    {\n      key: 'sunny',\n      label: 'sunny'\n    },\n    {\n      key: 'sunrise',\n      label: 'sunrise'\n    },\n    {\n      key: 'switch',\n      label: 'switch'\n    },\n    {\n      key: 'ticket',\n      label: 'ticket'\n    },\n    {\n      key: 'sunset',\n      label: 'sunset'\n    },\n    {\n      key: 'tickets',\n      label: 'tickets'\n    },\n    {\n      key: 'switch-button',\n      label: 'switch-button'\n    },\n    {\n      key: 'takeaway-box',\n      label: 'takeaway-box'\n    },\n    {\n      key: 'toilet-paper',\n      label: 'toilet-paper'\n    },\n    {\n      key: 'timer',\n      label: 'timer'\n    },\n    {\n      key: 'tools',\n      label: 'tools'\n    },\n    {\n      key: 'top-left',\n      label: 'top-left'\n    },\n    {\n      key: 'top',\n      label: 'top'\n    },\n    {\n      key: 'top-right',\n      label: 'top-right'\n    },\n    {\n      key: 'trend-charts',\n      label: 'trend-charts'\n    },\n    {\n      key: 'turn-off',\n      label: 'turn-off'\n    },\n    {\n      key: 'unlock',\n      label: 'unlock'\n    },\n    {\n      key: 'trophy',\n      label: 'trophy'\n    },\n    {\n      key: 'umbrella',\n      label: 'umbrella'\n    },\n    {\n      key: 'upload-filled',\n      label: 'upload-filled'\n    },\n    {\n      key: 'user-filled',\n      label: 'user-filled'\n    },\n    {\n      key: 'upload',\n      label: 'upload'\n    },\n    {\n      key: 'user',\n      label: 'user'\n    },\n    {\n      key: 'van',\n      label: 'van'\n    },\n    {\n      key: 'copy-document',\n      label: 'copy-document'\n    },\n    {\n      key: 'video-pause',\n      label: 'video-pause'\n    },\n    {\n      key: 'video-camera-filled',\n      label: 'video-camera-filled'\n    },\n    {\n      key: 'view',\n      label: 'view'\n    },\n    {\n      key: 'wallet',\n      label: 'wallet'\n    },\n    {\n      key: 'warning-filled',\n      label: 'warning-filled'\n    },\n    {\n      key: 'watch',\n      label: 'watch'\n    },\n    {\n      key: 'video-play',\n      label: 'video-play'\n    },\n    {\n      key: 'watermelon',\n      label: 'watermelon'\n    },\n    {\n      key: 'video-camera',\n      label: 'video-camera'\n    },\n    {\n      key: 'wallet-filled',\n      label: 'wallet-filled'\n    },\n    {\n      key: 'warning',\n      label: 'warning'\n    },\n    {\n      key: 'list',\n      label: 'list'\n    },\n    {\n      key: 'zoom-in',\n      label: 'zoom-in'\n    },\n    {\n      key: 'zoom-out',\n      label: 'zoom-out'\n    },\n    {\n      key: 'rank',\n      label: 'rank'\n    },\n    {\n      key: 'wind-power',\n      label: 'wind-power'\n    }\n  ])\n</script>\n\n<style lang=\"scss\">\n  .gva-icon {\n    color: rgb(132, 146, 166);\n    font-size: 14px;\n    margin-right: 10px;\n  }\n\n  .select__option_item {\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/menu/menu.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"addMenu(0)\">\n          新增根菜单\n        </el-button>\n      </div>\n\n      <!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->\n      <el-table :data=\"tableData\" row-key=\"ID\">\n        <el-table-column align=\"left\" label=\"ID\" min-width=\"100\" prop=\"ID\" />\n        <el-table-column\n          align=\"left\"\n          label=\"展示名称\"\n          min-width=\"120\"\n          prop=\"authorityName\"\n        >\n          <template #default=\"scope\">\n            <span>{{ scope.row.meta.title }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"图标\"\n          min-width=\"140\"\n          prop=\"authorityName\"\n        >\n          <template #default=\"scope\">\n            <div v-if=\"scope.row.meta.icon\" class=\"icon-column\">\n              <el-icon>\n                <component :is=\"scope.row.meta.icon\" />\n              </el-icon>\n              <span>{{ scope.row.meta.icon }}</span>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"路由Name\"\n          show-overflow-tooltip\n          min-width=\"160\"\n          prop=\"name\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"路由Path\"\n          show-overflow-tooltip\n          min-width=\"160\"\n          prop=\"path\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"是否隐藏\"\n          min-width=\"100\"\n          prop=\"hidden\"\n        >\n          <template #default=\"scope\">\n            <span>{{ scope.row.hidden ? '隐藏' : '显示' }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"父节点\"\n          min-width=\"90\"\n          prop=\"parentId\"\n        />\n        <el-table-column align=\"left\" label=\"排序\" min-width=\"70\" prop=\"sort\" />\n        <el-table-column\n          align=\"left\"\n          label=\"文件路径\"\n          min-width=\"360\"\n          prop=\"component\"\n        />\n        <el-table-column align=\"left\" fixed=\"right\" label=\"操作\" :min-width=\"appStore.operateMinWith\">\n          <template #default=\"scope\">\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"plus\"\n              @click=\"addMenu(scope.row.ID)\"\n            >\n              添加子菜单\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              @click=\"editMenu(scope.row.ID)\"\n            >\n              编辑\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"user\"\n              @click=\"openAssignRoleDrawer(scope.row)\"\n            >\n              分配角色\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteMenu(scope.row.ID)\"\n            >\n              删除\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n    </div>\n    <el-drawer\n      v-model=\"dialogFormVisible\"\n      :size=\"appStore.drawerSize\"\n      :before-close=\"handleClose\"\n      :show-close=\"false\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{ dialogTitle }}</span>\n          <div>\n            <el-button @click=\"closeDialog\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"enterDialog\"> 确 定 </el-button>\n          </div>\n        </div>\n      </template>\n\n      <warning-bar title=\"新增菜单，需要在角色管理内配置权限才可使用\" />\n      \n      <!-- 基础信息区域 -->\n      <div class=\"border-b border-gray-200\">\n        <h3 class=\"font-semibold text-gray-700 mb-4\">基础信息</h3>\n        <el-form\n          v-if=\"dialogFormVisible\"\n          ref=\"menuForm\"\n          :inline=\"true\"\n          :model=\"form\"\n          :rules=\"rules\"\n          label-position=\"top\"\n        >\n          <el-row class=\"w-full\">\n            <el-col :span=\"24\">\n              <el-form-item label=\"文件路径\" prop=\"component\">\n                <components-cascader\n                  :component=\"form.component\"\n                  @change=\"fmtComponent\"\n                />\n                <div class=\"form-tip\">\n                  <el-icon><InfoFilled /></el-icon>\n                  <span>如果菜单包含子菜单，请创建router-view二级路由页面或者</span>\n                  <el-button\n                    size=\"small\"\n                    type=\"text\"\n                    @click=\"form.component = 'view/routerHolder.vue'\"\n                  >\n                    点我设置\n                  </el-button>\n                </div>\n              </el-form-item>\n            </el-col>\n          </el-row>\n          <el-row class=\"w-full\">\n            <el-col :span=\"12\">\n              <el-form-item label=\"展示名称\" prop=\"meta.title\">\n                <el-input \n                  v-model=\"form.meta.title\" \n                  autocomplete=\"off\" \n                  placeholder=\"请输入菜单展示名称\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"12\">\n              <el-form-item label=\"路由Name\" prop=\"path\">\n                <el-input\n                  v-model=\"form.name\"\n                  autocomplete=\"off\"\n                  placeholder=\"唯一英文字符串\"\n                  @change=\"changeName\"\n                />\n              </el-form-item>\n            </el-col>\n          </el-row>\n        </el-form>\n      </div>\n       \n      <!-- 路由配置区域 -->\n      <div class=\"border-b border-gray-200\">\n        <h3 class=\"font-semibold text-gray-700 mb-4\">路由配置</h3>\n        <el-form\n          :inline=\"true\"\n          :model=\"form\"\n          :rules=\"rules\"\n          label-position=\"top\"\n        >\n           <el-row class=\"w-full\">\n             <el-col :span=\"12\">\n               <el-form-item label=\"父节点ID\">\n                 <el-cascader\n                   v-model=\"form.parentId\"\n                   style=\"width: 100%\"\n                   :disabled=\"!isEdit\"\n                   :options=\"menuOption\"\n                   :props=\"{\n                     checkStrictly: true,\n                     label: 'title',\n                     value: 'ID',\n                     disabled: 'disabled',\n                     emitPath: false\n                   }\"\n                   :show-all-levels=\"false\"\n                   filterable\n                   placeholder=\"请选择父节点\"\n                 />\n               </el-form-item>\n             </el-col>\n             <el-col :span=\"12\">\n               <el-form-item prop=\"path\">\n                 <template #label>\n                  <div class=\"inline-flex items-center h-4\">\n                     <span>路由Path</span>\n                     <el-checkbox\n                       class=\"ml-2\"\n                       v-model=\"checkFlag\"\n                       >添加参数</el-checkbox\n                     >\n                    </div>\n                 </template>\n                 <el-input\n                   v-model=\"form.path\"\n                   :disabled=\"!checkFlag\"\n                   autocomplete=\"off\"\n                   placeholder=\"建议只在后方拼接参数\"\n                 />\n               </el-form-item>\n             </el-col>\n           </el-row>\n        </el-form>\n      </div>\n       \n      <!-- 显示设置区域 -->\n      <div class=\"border-b border-gray-200\">\n        <h3 class=\"font-semibold text-gray-700 mb-4\">显示设置</h3>\n        <el-form\n          :inline=\"true\"\n          :model=\"form\"\n          :rules=\"rules\"\n          label-position=\"top\"\n        >\n           <el-row class=\"w-full\">\n              <el-col :span=\"8\">\n                <el-form-item label=\"图标\" prop=\"meta.icon\">\n                  <icon v-model=\"form.meta.icon\" />\n                </el-form-item>\n              </el-col>\n              <el-col :span=\"8\">\n                <el-form-item label=\"排序标记\" prop=\"sort\">\n                  <el-input \n                    v-model.number=\"form.sort\" \n                    autocomplete=\"off\" \n                    placeholder=\"请输入排序数字\"\n                  />\n                </el-form-item>\n              </el-col>\n              <el-col :span=\"8\">\n                <el-form-item label=\"是否隐藏\">\n                  <el-select\n                    v-model=\"form.hidden\"\n                    style=\"width: 100%\"\n                    placeholder=\"是否在列表隐藏\"\n                  >\n                    <el-option :value=\"false\" label=\"否\" />\n                    <el-option :value=\"true\" label=\"是\" />\n                  </el-select>\n                </el-form-item>\n              </el-col>\n            </el-row>\n        </el-form>\n      </div>\n        \n      <!-- 高级配置区域 -->\n      <div class=\"border-b border-gray-200\">\n        <h3 class=\"font-semibold text-gray-700 mb-4\">高级配置</h3>\n        <el-form\n          :inline=\"true\"\n          :model=\"form\"\n          :rules=\"rules\"\n          label-position=\"top\"\n        >\n            <el-row class=\"w-full\">\n              <el-col :span=\"12\">\n                <el-form-item prop=\"meta.activeName\">\n                  <template #label>\n                    <div class=\"label-with-tooltip\">\n                      <span>高亮菜单</span>\n                      <el-tooltip\n                        content=\"注：当到达此路由时候，指定左侧菜单指定name会处于活跃状态（亮起），可为空，为空则为本路由Name。\"\n                        placement=\"top\"\n                        effect=\"light\"\n                      >\n                        <el-icon><QuestionFilled /></el-icon>\n                      </el-tooltip>\n                    </div>\n                  </template>\n                  <el-input\n                    v-model=\"form.meta.activeName\"\n                    :placeholder=\"form.name || '请输入高亮菜单名称'\"\n                    autocomplete=\"off\"\n                  />\n                </el-form-item>\n              </el-col>\n              <el-col :span=\"12\">\n                <el-form-item label=\"KeepAlive\" prop=\"meta.keepAlive\">\n                  <el-select\n                    v-model=\"form.meta.keepAlive\"\n                    style=\"width: 100%\"\n                    placeholder=\"是否keepAlive缓存页面\"\n                  >\n                    <el-option :value=\"false\" label=\"否\" />\n                    <el-option :value=\"true\" label=\"是\" />\n                  </el-select>\n                </el-form-item>\n              </el-col>\n            </el-row>\n             <el-row class=\"w-full\">\n               <el-col :span=\"8\">\n                 <el-form-item label=\"CloseTab\" prop=\"meta.closeTab\">\n                   <el-select\n                     v-model=\"form.meta.closeTab\"\n                     style=\"width: 100%\"\n                     placeholder=\"是否自动关闭tab\"\n                   >\n                     <el-option :value=\"false\" label=\"否\" />\n                     <el-option :value=\"true\" label=\"是\" />\n                   </el-select>\n                 </el-form-item>\n               </el-col>\n               <el-col :span=\"8\">\n                 <el-form-item>\n                   <template #label>\n                     <div class=\"label-with-tooltip\">\n                       <span>是否为基础页面</span>\n                       <el-tooltip\n                         content=\"此项选择为是，则不会展示左侧菜单以及顶部信息。\"\n                         placement=\"top\"\n                         effect=\"light\"\n                       >\n                         <el-icon><QuestionFilled /></el-icon>\n                       </el-tooltip>\n                     </div>\n                   </template>\n                   <el-select\n                     v-model=\"form.meta.defaultMenu\"\n                     style=\"width: 100%\"\n                     placeholder=\"是否为基础页面\"\n                   >\n                     <el-option :value=\"false\" label=\"否\" />\n                     <el-option :value=\"true\" label=\"是\" />\n                   </el-select>\n                 </el-form-item>\n               </el-col>\n               <el-col :span=\"8\">\n                 <el-form-item>\n                   <template #label>\n                     <div class=\"label-with-tooltip\">\n                       <span>路由切换动画</span>\n                       <el-tooltip\n                         content=\"如果设置了路由切换动画，在本路由下的动画优先级高于全局动画切换优先级。\"\n                         placement=\"top\"\n                         effect=\"light\"\n                       >\n                         <el-icon><QuestionFilled /></el-icon>\n                       </el-tooltip>\n                     </div>\n                   </template>\n                   <el-select\n                     v-model=\"form.meta.transitionType\"\n                     style=\"width: 100%\"\n                     placeholder=\"跟随全局\"\n                     clearable\n                   >\n                     <el-option value=\"fade\" label=\"淡入淡出\" />\n                     <el-option value=\"slide\" label=\"滑动\" />\n                     <el-option value=\"zoom\" label=\"缩放\" />\n                     <el-option value=\"none\" label=\"无动画\" />\n                   </el-select>\n                 </el-form-item>\n               </el-col>\n             </el-row>\n        </el-form>\n      </div>\n          \n      <!-- 菜单参数配置区域 -->\n      <div class=\"border-b border-gray-200\">\n        <div class=\"flex justify-between items-center mb-4\">\n          <h3 class=\"font-semibold text-gray-700\">菜单参数配置</h3>\n          <el-button type=\"primary\" size=\"small\" @click=\"addParameter(form)\">\n            新增菜单参数\n          </el-button>\n        </div>\n            <el-table \n              :data=\"form.parameters\" \n              style=\"width: 100%\"\n              class=\"parameter-table\"\n            >\n              <el-table-column\n                align=\"center\"\n                prop=\"type\"\n                label=\"参数类型\"\n                width=\"150\"\n              >\n                <template #default=\"scope\">\n                  <el-select \n                    v-model=\"scope.row.type\" \n                    placeholder=\"请选择\"\n                    size=\"small\"\n                  >\n                    <el-option key=\"query\" value=\"query\" label=\"query\" />\n                    <el-option key=\"params\" value=\"params\" label=\"params\" />\n                  </el-select>\n                </template>\n              </el-table-column>\n              <el-table-column align=\"center\" prop=\"key\" label=\"参数key\" width=\"150\">\n                <template #default=\"scope\">\n                  <el-input \n                    v-model=\"scope.row.key\" \n                    size=\"small\"\n                    placeholder=\"请输入参数key\"\n                  />\n                </template>\n              </el-table-column>\n              <el-table-column align=\"center\" prop=\"value\" label=\"参数值\">\n                <template #default=\"scope\">\n                  <el-input \n                    v-model=\"scope.row.value\" \n                    size=\"small\"\n                    placeholder=\"请输入参数值\"\n                  />\n                </template>\n              </el-table-column>\n              <el-table-column align=\"center\" label=\"操作\" width=\"100\">\n                <template #default=\"scope\">\n                  <el-button\n                    type=\"danger\"\n                    size=\"small\"\n                    @click=\"deleteParameter(form.parameters, scope.$index)\"\n                  >\n                    <el-icon><Delete /></el-icon>\n                  </el-button>\n                </template>\n              </el-table-column>\n            </el-table>\n      </div>\n           \n      <!-- 可控按钮配置区域 -->\n      <div class=\"mb-2 mt-2\">\n        <div class=\"flex justify-between items-center mb-4\">\n          <h3 class=\"font-semibold text-gray-700\">可控按钮配置</h3>\n          <div class=\"flex items-center gap-2\">\n            <el-button type=\"primary\" size=\"small\" @click=\"addBtn(form)\">\n              新增可控按钮\n            </el-button>\n            <el-tooltip\n              content=\"点击查看按钮权限配置文档\"\n              placement=\"top\"\n              effect=\"light\"\n            >\n              <el-icon\n                class=\"cursor-pointer text-blue-500 hover:text-blue-700\"\n                @click=\"toDoc('https://www.gin-vue-admin.com/guide/web/button-auth.html')\"\n              >\n                <QuestionFilled />\n              </el-icon>\n            </el-tooltip>\n          </div>\n        </div>\n             <el-table \n               :data=\"form.menuBtn\" \n               style=\"width: 100%\"\n               class=\"button-table\"\n             >\n               <el-table-column\n                 align=\"center\"\n                 prop=\"name\"\n                 label=\"按钮名称\"\n                 width=\"150\"\n               >\n                 <template #default=\"scope\">\n                   <el-input \n                     v-model=\"scope.row.name\" \n                     size=\"small\"\n                     placeholder=\"请输入按钮名称\"\n                   />\n                 </template>\n               </el-table-column>\n               <el-table-column align=\"center\" prop=\"desc\" label=\"备注\">\n                 <template #default=\"scope\">\n                   <el-input \n                     v-model=\"scope.row.desc\" \n                     size=\"small\"\n                     placeholder=\"请输入按钮备注\"\n                   />\n                 </template>\n               </el-table-column>\n               <el-table-column align=\"center\" label=\"操作\" width=\"100\">\n                 <template #default=\"scope\">\n                   <el-button\n                     type=\"danger\"\n                     size=\"small\"\n                     @click=\"deleteBtn(form.menuBtn, scope.$index)\"\n                   >\n                     <el-icon><Delete /></el-icon>\n                   </el-button>\n                 </template>\n               </el-table-column>\n             </el-table>\n       </div>\n    </el-drawer>\n\n    <!-- 分配给角色抽屉 -->\n    <el-drawer\n      v-model=\"assignRoleDrawerVisible\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n      destroy-on-close\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">分配角色 - {{ assignMenuRow.meta?.title }}</span>\n          <div>\n            <el-button @click=\"assignRoleDrawerVisible = false\">取 消</el-button>\n            <el-button type=\"primary\" :loading=\"assignRoleSubmitting\" @click=\"confirmAssignRole\">确 定</el-button>\n          </div>\n        </div>\n      </template>\n      <warning-bar title=\"注：保存时将全量覆盖该菜单的角色关联关系；作为角色首页的菜单不可取消勾选\" />\n      <el-tree\n        ref=\"roleTreeRef\"\n        v-loading=\"assignRoleLoading\"\n        :data=\"authorityTreeData\"\n        :props=\"{ label: 'authorityName', children: 'children', disabled: isRoleDisabled }\"\n        node-key=\"authorityId\"\n        show-checkbox\n        check-strictly\n        default-expand-all\n      />\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    updateBaseMenu,\n    getMenuList,\n    addBaseMenu,\n    deleteBaseMenu,\n    getBaseMenuById,\n    getMenuRoles,\n    setMenuRoles\n  } from '@/api/menu'\n  import { getAuthorityList } from '@/api/authority'\n  import icon from '@/view/superAdmin/menu/icon.vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { canRemoveAuthorityBtnApi } from '@/api/authorityBtn'\n  import { reactive, ref, nextTick } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { QuestionFilled, InfoFilled, Delete } from '@element-plus/icons-vue'\n  import { toDoc } from '@/utils/doc'\n  import { toLowerCase } from '@/utils/stringFun'\n  import ComponentsCascader from '@/view/superAdmin/menu/components/components-cascader.vue'\n\n  import pathInfo from '@/pathInfo.json'\n  import { useAppStore } from \"@/pinia\";\n\n  defineOptions({\n    name: 'Menus'\n  })\n\n  const appStore = useAppStore()\n\n  const rules = reactive({\n    path: [{ required: true, message: '请输入菜单name', trigger: 'blur' }],\n    component: [{ required: true, message: '请输入文件路径', trigger: 'blur' }],\n    'meta.title': [\n      { required: true, message: '请输入菜单展示名称', trigger: 'blur' }\n    ]\n  })\n\n  const tableData = ref([])\n  // 查询\n  const getTableData = async () => {\n    const table = await getMenuList()\n    if (table.code === 0) {\n      tableData.value = table.data\n    }\n  }\n\n  getTableData()\n\n  // 新增参数\n  const addParameter = (form) => {\n    if (!form.parameters) {\n      form.parameters = []\n    }\n    form.parameters.push({\n      type: 'query',\n      key: '',\n      value: ''\n    })\n  }\n\n  const fmtComponent = (component) => {\n    form.value.component = component.replace(/\\\\/g, '/')\n    form.value.name = toLowerCase(pathInfo['/src/' + component])\n    form.value.path = form.value.name\n  }\n\n  // 删除参数\n  const deleteParameter = (parameters, index) => {\n    parameters.splice(index, 1)\n  }\n\n  // 新增可控按钮\n  const addBtn = (form) => {\n    if (!form.menuBtn) {\n      form.menuBtn = []\n    }\n    form.menuBtn.push({\n      name: '',\n      desc: ''\n    })\n  }\n  // 删除可控按钮\n  const deleteBtn = async (btns, index) => {\n    const btn = btns[index]\n    if (btn.ID === 0) {\n      btns.splice(index, 1)\n      return\n    }\n    const res = await canRemoveAuthorityBtnApi({ id: btn.ID })\n    if (res.code === 0) {\n      btns.splice(index, 1)\n    }\n  }\n\n  const form = ref({\n    ID: 0,\n    path: '',\n    name: '',\n    hidden: false,\n    parentId: 0,\n    component: '',\n    meta: {\n      activeName: '',\n      title: '',\n      icon: '',\n      defaultMenu: false,\n      closeTab: false,\n      keepAlive: false\n    },\n    parameters: [],\n    menuBtn: []\n  })\n  const changeName = () => {\n    form.value.path = form.value.name\n  }\n\n  const handleClose = (done) => {\n    initForm()\n    done()\n  }\n  // 删除菜单\n  const deleteMenu = (ID) => {\n    ElMessageBox.confirm(\n      '此操作将永久删除所有角色下该菜单, 是否继续?',\n      '提示',\n      {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    )\n      .then(async () => {\n        const res = await deleteBaseMenu({ ID })\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '删除成功!'\n          })\n\n          getTableData()\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '已取消删除'\n        })\n      })\n  }\n  // 初始化弹窗内表格方法\n  const menuForm = ref(null)\n  const checkFlag = ref(false)\n  const initForm = () => {\n    checkFlag.value = false\n    menuForm.value.resetFields()\n    form.value = {\n      ID: 0,\n      path: '',\n      name: '',\n      hidden: false,\n      parentId: 0,\n      component: '',\n      meta: {\n        title: '',\n        icon: '',\n        defaultMenu: false,\n        closeTab: false,\n        keepAlive: false\n      }\n    }\n  }\n  // 关闭弹窗\n\n  const dialogFormVisible = ref(false)\n  const closeDialog = () => {\n    initForm()\n    dialogFormVisible.value = false\n  }\n  // 添加menu\n  const enterDialog = async () => {\n    menuForm.value.validate(async (valid) => {\n      if (valid) {\n        let res\n        if (isEdit.value) {\n          res = await updateBaseMenu(form.value)\n        } else {\n          res = await addBaseMenu(form.value)\n        }\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: isEdit.value ? '编辑成功' : '添加成功，请到角色管理页面分配权限'\n          })\n          getTableData()\n        }\n        initForm()\n        dialogFormVisible.value = false\n      }\n    })\n  }\n\n  const menuOption = ref([\n    {\n      ID: '0',\n      title: '根菜单'\n    }\n  ])\n  const setOptions = () => {\n    menuOption.value = [\n      {\n        ID: 0,\n        title: '根目录'\n      }\n    ]\n    setMenuOptions(tableData.value, menuOption.value, false)\n  }\n  const setMenuOptions = (menuData, optionsData, disabled) => {\n    menuData &&\n      menuData.forEach((item) => {\n        if (item.children && item.children.length) {\n          const option = {\n            title: item.meta.title,\n            ID: item.ID,\n            disabled: disabled || item.ID === form.value.ID,\n            children: []\n          }\n          setMenuOptions(\n            item.children,\n            option.children,\n            disabled || item.ID === form.value.ID\n          )\n          optionsData.push(option)\n        } else {\n          const option = {\n            title: item.meta.title,\n            ID: item.ID,\n            disabled: disabled || item.ID === form.value.ID\n          }\n          optionsData.push(option)\n        }\n      })\n  }\n\n  // 添加菜单方法，id为 0则为添加根菜单\n  const isEdit = ref(false)\n  const dialogTitle = ref('新增菜单')\n  const addMenu = (id) => {\n    dialogTitle.value = '新增菜单'\n    form.value.parentId = id\n    isEdit.value = false\n    setOptions()\n    dialogFormVisible.value = true\n  }\n  // 修改菜单方法\n  const editMenu = async (id) => {\n    dialogTitle.value = '编辑菜单'\n    const res = await getBaseMenuById({ id })\n    form.value = res.data.menu\n    isEdit.value = true\n    setOptions()\n    dialogFormVisible.value = true\n  }\n\n  // 分配给角色\n  const assignRoleDrawerVisible = ref(false)\n  const assignMenuRow = ref({})\n  const authorityTreeData = ref([])\n  const assignRoleLoading = ref(false)\n  const assignRoleSubmitting = ref(false)\n  const roleTreeRef = ref(null)\n  const defaultRouterAuthorityIds = ref(new Set())\n\n  const isRoleDisabled = (data) => {\n    return defaultRouterAuthorityIds.value.has(data.authorityId)\n  }\n\n  const openAssignRoleDrawer = async (row) => {\n    assignMenuRow.value = row\n    defaultRouterAuthorityIds.value = new Set()\n    assignRoleDrawerVisible.value = true\n    assignRoleLoading.value = true\n    // 并行加载角色树和当前菜单已分配的角色\n    const [authRes, rolesRes] = await Promise.all([\n      getAuthorityList(),\n      getMenuRoles(row.ID)\n    ])\n    if (authRes.code === 0) {\n      authorityTreeData.value = authRes.data\n    }\n    if (rolesRes.code === 0 && rolesRes.data) {\n      if (rolesRes.data.defaultRouterAuthorityIds) {\n        defaultRouterAuthorityIds.value = new Set(rolesRes.data.defaultRouterAuthorityIds)\n      }\n      nextTick(() => {\n        roleTreeRef.value?.setCheckedKeys(rolesRes.data.authorityIds || [])\n      })\n    }\n    assignRoleLoading.value = false\n  }\n\n  const confirmAssignRole = async () => {\n    assignRoleSubmitting.value = true\n    try {\n      const checkedKeys = roleTreeRef.value?.getCheckedKeys(false) || []\n      const halfCheckedKeys = roleTreeRef.value?.getHalfCheckedKeys() || []\n      const authorityIds = [...checkedKeys, ...halfCheckedKeys]\n      const res = await setMenuRoles({\n        menuId: assignMenuRow.value.ID,\n        authorityIds\n      })\n      if (res.code === 0) {\n        ElMessage({ type: 'success', message: '分配成功!' })\n        assignRoleDrawerVisible.value = false\n      }\n    } catch {\n      ElMessage({ type: 'error', message: '分配失败，请重试' })\n    }\n    assignRoleSubmitting.value = false\n  }\n</script>\n\n<style scoped lang=\"scss\">\n  .warning {\n    color: #dc143c;\n  }\n  .icon-column {\n    display: flex;\n    align-items: center;\n    .el-icon {\n      margin-right: 8px;\n    }\n  }\n\n\n  \n  .form-tip {\n    margin-top: 8px;\n    font-size: 12px;\n    color: #909399;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    \n    .el-icon {\n      color: #409eff;\n    }\n  }\n  \n  .label-with-tooltip {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    \n    .el-icon {\n      color: #909399;\n      cursor: help;\n      \n      &:hover {\n        color: #409eff;\n      }\n    }\n  }\n  \n  .parameter-table,\n  .button-table {\n    border: 1px solid #ebeef5;\n    border-radius: 6px;\n    \n    :deep(.el-table__header) {\n      background-color: #fafafa;\n    }\n    \n    :deep(.el-table__body) {\n      .el-table__row {\n        &:hover {\n          background-color: #f5f7fa;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/operation/sysOperationRecord.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form :inline=\"true\" :model=\"searchInfo\">\n        <el-form-item label=\"请求方法\">\n          <el-input v-model=\"searchInfo.method\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item label=\"请求路径\">\n          <el-input v-model=\"searchInfo.path\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item label=\"结果状态码\">\n          <el-input v-model=\"searchInfo.status\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\"\n            >查询</el-button\n          >\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button\n          icon=\"delete\"\n          :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\"\n          >删除</el-button\n        >\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        :data=\"tableData\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column align=\"left\" type=\"selection\" width=\"55\" />\n        <el-table-column align=\"left\" label=\"操作人\" width=\"140\">\n          <template #default=\"scope\">\n            <div>\n              {{ scope.row.user.userName }}({{ scope.row.user.nickName }})\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"日期\" width=\"180\">\n          <template #default=\"scope\">{{\n            formatDate(scope.row.CreatedAt)\n          }}</template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"状态码\" prop=\"status\" width=\"120\">\n          <template #default=\"scope\">\n            <div>\n              <el-tag type=\"success\">{{ scope.row.status }}</el-tag>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"请求IP\" prop=\"ip\" width=\"120\" />\n        <el-table-column\n          align=\"left\"\n          label=\"请求方法\"\n          prop=\"method\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"请求路径\"\n          prop=\"path\"\n          width=\"240\"\n        />\n        <el-table-column align=\"left\" label=\"请求\" prop=\"path\" width=\"80\">\n          <template #default=\"scope\">\n            <div>\n              <el-popover\n                v-if=\"scope.row.body\"\n                placement=\"left-start\"\n                :width=\"444\"\n              >\n                <div class=\"popover-box\">\n                  <pre>{{ fmtBody(scope.row.body) }}</pre>\n                </div>\n                <template #reference>\n                  <el-icon style=\"cursor: pointer\"><warning /></el-icon>\n                </template>\n              </el-popover>\n\n              <span v-else>无</span>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"响应\" prop=\"path\" width=\"80\">\n          <template #default=\"scope\">\n            <div>\n              <el-popover\n                v-if=\"scope.row.resp\"\n                placement=\"left-start\"\n                :width=\"444\"\n              >\n                <div class=\"popover-box\">\n                  <pre>{{ fmtBody(scope.row.resp) }}</pre>\n                </div>\n                <template #reference>\n                  <el-icon style=\"cursor: pointer\"><warning /></el-icon>\n                </template>\n              </el-popover>\n              <span v-else>无</span>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"操作\">\n          <template #default=\"scope\">\n            <el-button\n              icon=\"delete\"\n              type=\"primary\"\n              link\n              @click=\"deleteSysOperationRecordFunc(scope.row)\"\n              >删除</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import {\n    deleteSysOperationRecord,\n    getSysOperationRecordList,\n    deleteSysOperationRecordByIds\n  } from '@/api/sysOperationRecord' // 此处请自行替换地址\n  import { formatDate } from '@/utils/format'\n  import { ref } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n\n  defineOptions({\n    name: 'SysOperationRecord'\n  })\n\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const searchInfo = ref({})\n  const onReset = () => {\n    searchInfo.value = {}\n  }\n  // 条件搜索前端看此方法\n  const onSubmit = () => {\n    page.value = 1\n    if (searchInfo.value.status === '') {\n      searchInfo.value.status = null\n    }\n    getTableData()\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getSysOperationRecordList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  const multipleSelection = ref([])\n  const handleSelectionChange = (val) => {\n    multipleSelection.value = val\n  }\n  const onDelete = async () => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const ids = []\n      multipleSelection.value &&\n        multipleSelection.value.forEach((item) => {\n          ids.push(item.ID)\n        })\n      const res = await deleteSysOperationRecordByIds({ ids })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === ids.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n  const deleteSysOperationRecordFunc = async (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await deleteSysOperationRecord({ ID: row.ID })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === 1 && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n  const fmtBody = (value) => {\n    try {\n      return JSON.parse(value)\n    } catch (_) {\n      return value\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n  .table-expand {\n    padding-left: 60px;\n    font-size: 0;\n    label {\n      width: 90px;\n      color: #99a9bf;\n      .el-form-item {\n        margin-right: 0;\n        margin-bottom: 0;\n        width: 50%;\n      }\n    }\n  }\n  .popover-box {\n    background: #112435;\n    color: #f08047;\n    height: 600px;\n    width: 420px;\n    overflow: auto;\n  }\n  .popover-box::-webkit-scrollbar {\n    display: none; /* Chrome Safari */\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/superAdmin/params/sysParams.vue",
    "content": "<template>\n  <div>\n    <warning-bar title=\"获取参数且缓存方法已在前端utils/params 已经封装完成 不必自己书写 使用方法查看文件内注释\" />\n    <div class=\"gva-search-box\">\n      <el-form\n        ref=\"elSearchFormRef\"\n        :inline=\"true\"\n        :model=\"searchInfo\"\n        class=\"demo-form-inline\"\n        :rules=\"searchRule\"\n        @keyup.enter=\"onSubmit\"\n      >\n        <el-form-item label=\"创建日期\" prop=\"createdAt\">\n          <template #label>\n            <span>\n              创建日期\n              <el-tooltip\n                content=\"搜索范围是开始日期（包含）至结束日期（不包含）\"\n              >\n                <el-icon><QuestionFilled /></el-icon>\n              </el-tooltip>\n            </span>\n          </template>\n          <el-date-picker\n            v-model=\"searchInfo.startCreatedAt\"\n            type=\"datetime\"\n            placeholder=\"开始日期\"\n            :disabled-date=\"\n              (time) =>\n                searchInfo.endCreatedAt\n                  ? time.getTime() > searchInfo.endCreatedAt.getTime()\n                  : false\n            \"\n          ></el-date-picker>\n          —\n          <el-date-picker\n            v-model=\"searchInfo.endCreatedAt\"\n            type=\"datetime\"\n            placeholder=\"结束日期\"\n            :disabled-date=\"\n              (time) =>\n                searchInfo.startCreatedAt\n                  ? time.getTime() < searchInfo.startCreatedAt.getTime()\n                  : false\n            \"\n          ></el-date-picker>\n        </el-form-item>\n\n        <el-form-item label=\"参数名称\" prop=\"name\">\n          <el-input v-model=\"searchInfo.name\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item label=\"参数键\" prop=\"key\">\n          <el-input v-model=\"searchInfo.key\" placeholder=\"搜索条件\" />\n        </el-form-item>\n\n        <template v-if=\"showAllQuery\">\n          <!-- 将需要控制显示状态的查询条件添加到此范围内 -->\n        </template>\n\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\"\n            >查询</el-button\n          >\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n          <el-button\n            link\n            type=\"primary\"\n            icon=\"arrow-down\"\n            @click=\"showAllQuery = true\"\n            v-if=\"!showAllQuery\"\n            >展开</el-button\n          >\n          <el-button\n            link\n            type=\"primary\"\n            icon=\"arrow-up\"\n            @click=\"showAllQuery = false\"\n            v-else\n            >收起</el-button\n          >\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDialog\"\n          >新增</el-button\n        >\n        <el-button\n          icon=\"delete\"\n          style=\"margin-left: 10px\"\n          :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\"\n          >删除</el-button\n        >\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :data=\"tableData\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n\n        <el-table-column align=\"left\" label=\"日期\" prop=\"createdAt\" width=\"180\">\n          <template #default=\"scope\">{{\n            formatDate(scope.row.CreatedAt)\n          }}</template>\n        </el-table-column>\n\n        <el-table-column\n          align=\"left\"\n          label=\"参数名称\"\n          prop=\"name\"\n          width=\"120\"\n        />\n        <el-table-column align=\"left\" label=\"参数键\" prop=\"key\" width=\"120\" />\n        <el-table-column align=\"left\" label=\"参数值\" prop=\"value\" width=\"120\" />\n        <el-table-column\n          align=\"left\"\n          label=\"参数说明\"\n          prop=\"desc\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"操作\"\n          fixed=\"right\"\n          min-width=\"240\"\n        >\n          <template #default=\"scope\">\n            <el-button\n              type=\"primary\"\n              link\n              class=\"table-button\"\n              @click=\"getDetails(scope.row)\"\n              ><el-icon style=\"margin-right: 5px\"><InfoFilled /></el-icon\n              >查看详情</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              class=\"table-button\"\n              @click=\"updateSysParamsFunc(scope.row)\"\n              >变更</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteRow(scope.row)\"\n              >删除</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n    <el-drawer\n      destroy-on-close\n      size=\"800\"\n      v-model=\"dialogFormVisible\"\n      :show-close=\"false\"\n      :before-close=\"closeDialog\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{ type === 'create' ? '添加' : '修改' }}</span>\n          <div>\n            <el-button type=\"primary\" @click=\"enterDialog\">确 定</el-button>\n            <el-button @click=\"closeDialog\">取 消</el-button>\n          </div>\n        </div>\n      </template>\n\n      <el-form\n        :model=\"formData\"\n        label-position=\"top\"\n        ref=\"elFormRef\"\n        :rules=\"rule\"\n        label-width=\"80px\"\n      >\n        <el-form-item label=\"参数名称:\" prop=\"name\">\n          <el-input\n            v-model=\"formData.name\"\n            :clearable=\"true\"\n            placeholder=\"请输入参数名称\"\n          />\n        </el-form-item>\n        <el-form-item label=\"参数键:\" prop=\"key\">\n          <el-input\n            v-model=\"formData.key\"\n            :clearable=\"true\"\n            placeholder=\"请输入参数键\"\n          />\n        </el-form-item>\n        <el-form-item label=\"参数值:\" prop=\"value\">\n          <el-input\n            type=\"textarea\"\n            :rows=\"5\"\n            v-model=\"formData.value\"\n            :clearable=\"true\"\n            placeholder=\"请输入参数值\"\n          />\n        </el-form-item>\n        <el-form-item label=\"参数说明:\" prop=\"desc\">\n          <el-input\n            v-model=\"formData.desc\"\n            :clearable=\"true\"\n            placeholder=\"请输入参数说明\"\n          />\n        </el-form-item>\n      </el-form>\n\n      <div\n        class=\"usage-instructions bg-gray-100 border border-gray-300 rounded-lg p-4 mt-5\"\n      >\n        <h3 class=\"mb-3 text-lg text-gray-800\">使用说明</h3>\n        <p class=\"mb-2 text-sm text-gray-600\">\n          前端可以通过引入\n          <code class=\"bg-blue-100 px-1 py-0.5 rounded\"\n            >import { getParams } from '@/utils/params'</code\n          >\n          然后通过\n          <code class=\"bg-blue-100 px-1 py-0.5 rounded\"\n            >await getParams(\"{{ formData.key }}\")</code\n          >\n          来获取对应的参数。\n        </p>\n        <p class=\"text-sm text-gray-600\">\n          后端需要提前\n          <code class=\"bg-blue-100 px-1 py-0.5 rounded\"\n            >import\n            \"github.com/flipped-aurora/gin-vue-admin/server/service/system\"</code\n          >\n        </p>\n        <p class=\"mb-2 text-sm text-gray-600\">\n          然后调用\n          <code class=\"bg-blue-100 px-1 py-0.5 rounded\"\n            >new(system.SysParamsService).GetSysParam(\"{{\n              formData.key\n            }}\")</code\n          >\n          来获取对应的 value 值。\n        </p>\n      </div>\n    </el-drawer>\n\n    <el-drawer\n      destroy-on-close\n      size=\"800\"\n      v-model=\"detailShow\"\n      :show-close=\"true\"\n      :before-close=\"closeDetailShow\"\n    >\n      <el-descriptions :column=\"1\" border>\n        <el-descriptions-item label=\"参数名称\">\n          {{ detailForm.name }}\n        </el-descriptions-item>\n        <el-descriptions-item label=\"参数键\">\n          {{ detailForm.key }}\n        </el-descriptions-item>\n        <el-descriptions-item label=\"参数值\">\n          {{ detailForm.value }}\n        </el-descriptions-item>\n        <el-descriptions-item label=\"参数说明\">\n          {{ detailForm.desc }}\n        </el-descriptions-item>\n      </el-descriptions>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    createSysParams,\n    deleteSysParams,\n    deleteSysParamsByIds,\n    updateSysParams,\n    findSysParams,\n    getSysParamsList\n  } from '@/api/sysParams'\n\n  // 全量引入格式化工具 请按需保留\n  import { formatDate } from '@/utils/format'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { ref, reactive } from 'vue'\n  import WarningBar from \"@/components/warningBar/warningBar.vue\";\n\n  defineOptions({\n    name: 'SysParams'\n  })\n\n  // 控制更多查询条件显示/隐藏状态\n  const showAllQuery = ref(false)\n\n  // 自动化生成的字典（可能为空）以及字段\n  const formData = ref({\n    name: '',\n    key: '',\n    value: '',\n    desc: ''\n  })\n\n  // 验证规则\n  const rule = reactive({\n    name: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ],\n    key: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ],\n    value: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ]\n  })\n\n  const searchRule = reactive({\n    createdAt: [\n      {\n        validator: (rule, value, callback) => {\n          if (\n            searchInfo.value.startCreatedAt &&\n            !searchInfo.value.endCreatedAt\n          ) {\n            callback(new Error('请填写结束日期'))\n          } else if (\n            !searchInfo.value.startCreatedAt &&\n            searchInfo.value.endCreatedAt\n          ) {\n            callback(new Error('请填写开始日期'))\n          } else if (\n            searchInfo.value.startCreatedAt &&\n            searchInfo.value.endCreatedAt &&\n            (searchInfo.value.startCreatedAt.getTime() ===\n              searchInfo.value.endCreatedAt.getTime() ||\n              searchInfo.value.startCreatedAt.getTime() >\n                searchInfo.value.endCreatedAt.getTime())\n          ) {\n            callback(new Error('开始日期应当早于结束日期'))\n          } else {\n            callback()\n          }\n        },\n        trigger: 'change'\n      }\n    ]\n  })\n\n  const elFormRef = ref()\n  const elSearchFormRef = ref()\n\n  // =========== 表格控制部分 ===========\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const searchInfo = ref({})\n\n  // 重置\n  const onReset = () => {\n    searchInfo.value = {}\n    getTableData()\n  }\n\n  // 搜索\n  const onSubmit = () => {\n    elSearchFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      page.value = 1\n      getTableData()\n    })\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  // 修改页面容量\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getSysParamsList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  // ============== 表格控制部分结束 ===============\n\n  // 获取需要的字典 可能为空 按需保留\n  const setOptions = async () => {}\n\n  // 获取需要的字典 可能为空 按需保留\n  setOptions()\n\n  // 多选数据\n  const multipleSelection = ref([])\n  // 多选\n  const handleSelectionChange = (val) => {\n    multipleSelection.value = val\n  }\n\n  // 删除行\n  const deleteRow = (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(() => {\n      deleteSysParamsFunc(row)\n    })\n  }\n\n  // 多选删除\n  const onDelete = async () => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const IDs = []\n      if (multipleSelection.value.length === 0) {\n        ElMessage({\n          type: 'warning',\n          message: '请选择要删除的数据'\n        })\n        return\n      }\n      multipleSelection.value &&\n        multipleSelection.value.map((item) => {\n          IDs.push(item.ID)\n        })\n      const res = await deleteSysParamsByIds({ IDs })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === IDs.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n\n  // 行为控制标记（弹窗内部需要增还是改）\n  const type = ref('')\n\n  // 更新行\n  const updateSysParamsFunc = async (row) => {\n    const res = await findSysParams({ ID: row.ID })\n    type.value = 'update'\n    if (res.code === 0) {\n      formData.value = res.data\n      dialogFormVisible.value = true\n    }\n  }\n\n  // 删除行\n  const deleteSysParamsFunc = async (row) => {\n    const res = await deleteSysParams({ ID: row.ID })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '删除成功'\n      })\n      if (tableData.value.length === 1 && page.value > 1) {\n        page.value--\n      }\n      getTableData()\n    }\n  }\n\n  // 弹窗控制标记\n  const dialogFormVisible = ref(false)\n\n  // 打开弹窗\n  const openDialog = () => {\n    type.value = 'create'\n    dialogFormVisible.value = true\n  }\n\n  // 关闭弹窗\n  const closeDialog = () => {\n    dialogFormVisible.value = false\n    formData.value = {\n      name: '',\n      key: '',\n      value: '',\n      desc: ''\n    }\n  }\n  // 弹窗确定\n  const enterDialog = async () => {\n    elFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      let res\n      switch (type.value) {\n        case 'create':\n          res = await createSysParams(formData.value)\n          break\n        case 'update':\n          res = await updateSysParams(formData.value)\n          break\n        default:\n          res = await createSysParams(formData.value)\n          break\n      }\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '创建/更改成功'\n        })\n        closeDialog()\n        getTableData()\n      }\n    })\n  }\n\n  const detailForm = ref({})\n\n  // 查看详情控制标记\n  const detailShow = ref(false)\n\n  // 打开详情弹窗\n  const openDetailShow = () => {\n    detailShow.value = true\n  }\n\n  // 打开详情\n  const getDetails = async (row) => {\n    // 打开弹窗\n    const res = await findSysParams({ ID: row.ID })\n    if (res.code === 0) {\n      detailForm.value = res.data\n      openDetailShow()\n    }\n  }\n\n  // 关闭详情弹窗\n  const closeDetailShow = () => {\n    detailShow.value = false\n    detailForm.value = {}\n  }\n</script>\n\n<style></style>\n"
  },
  {
    "path": "web/src/view/superAdmin/user/user.vue",
    "content": "<template>\n  <div>\n    <warning-bar title=\"注：右上角头像下拉可切换角色\" />\n    <div class=\"gva-search-box\">\n      <el-form ref=\"searchForm\" :inline=\"true\" :model=\"searchInfo\">\n        <el-form-item label=\"用户名\">\n          <el-input v-model=\"searchInfo.username\" placeholder=\"用户名\" />\n        </el-form-item>\n        <el-form-item label=\"昵称\">\n          <el-input v-model=\"searchInfo.nickname\" placeholder=\"昵称\" />\n        </el-form-item>\n        <el-form-item label=\"手机号\">\n          <el-input v-model=\"searchInfo.phone\" placeholder=\"手机号\" />\n        </el-form-item>\n        <el-form-item label=\"邮箱\">\n          <el-input v-model=\"searchInfo.email\" placeholder=\"邮箱\" />\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">\n            查询\n          </el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\"> 重置 </el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"addUser\"\n          >新增用户</el-button\n        >\n      </div>\n      <el-table :data=\"tableData\" row-key=\"ID\" :default-sort=\"{ prop: 'ID', order: 'descending' }\" @sort-change=\"sortChange\">\n        <el-table-column align=\"left\" label=\"头像\" min-width=\"75\">\n          <template #default=\"scope\">\n            <CustomPic style=\"margin-top: 8px\" :pic-src=\"scope.row.headerImg\" />\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"ID\" min-width=\"50\" prop=\"ID\" sortable=\"custom\" />\n        <el-table-column\n          align=\"left\"\n          label=\"用户名\"\n          min-width=\"150\"\n          prop=\"userName\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"昵称\"\n          min-width=\"150\"\n          prop=\"nickName\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"手机号\"\n          min-width=\"180\"\n          prop=\"phone\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"邮箱\"\n          min-width=\"180\"\n          prop=\"email\"\n        />\n        <el-table-column align=\"left\" label=\"用户角色\" min-width=\"200\">\n          <template #default=\"scope\">\n            <el-cascader\n              v-model=\"scope.row.authorityIds\"\n              :options=\"authOptions\"\n              :show-all-levels=\"false\"\n              collapse-tags\n              :props=\"{\n                multiple: true,\n                checkStrictly: true,\n                label: 'authorityName',\n                value: 'authorityId',\n                disabled: 'disabled',\n                emitPath: false\n              }\"\n              :clearable=\"false\"\n              @visible-change=\"\n                (flag) => {\n                  changeAuthority(scope.row, flag, 0)\n                }\n              \"\n              @remove-tag=\"\n                (removeAuth) => {\n                  changeAuthority(scope.row, false, removeAuth)\n                }\n              \"\n            />\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"启用\" min-width=\"150\">\n          <template #default=\"scope\">\n            <el-switch\n              v-model=\"scope.row.enable\"\n              inline-prompt\n              :active-value=\"1\"\n              :inactive-value=\"2\"\n              @change=\"\n                () => {\n                  switchEnable(scope.row)\n                }\n              \"\n            />\n          </template>\n        </el-table-column>\n\n        <el-table-column label=\"操作\" :min-width=\"appStore.operateMinWith\" fixed=\"right\">\n          <template #default=\"scope\">\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteUserFunc(scope.row)\"\n              >删除</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              @click=\"openEdit(scope.row)\"\n              >编辑</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"magic-stick\"\n              @click=\"resetPasswordFunc(scope.row)\"\n              >重置密码</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n    <!-- 重置密码对话框 -->\n    <el-dialog\n      v-model=\"resetPwdDialog\"\n      title=\"重置密码\"\n      width=\"500px\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <el-form :model=\"resetPwdInfo\" ref=\"resetPwdForm\" label-width=\"100px\">\n        <el-form-item label=\"用户账号\">\n          <el-input v-model=\"resetPwdInfo.userName\" disabled />\n        </el-form-item>\n        <el-form-item label=\"用户昵称\">\n          <el-input v-model=\"resetPwdInfo.nickName\" disabled />\n        </el-form-item>\n        <el-form-item label=\"新密码\">\n          <div class=\"flex w-full\">\n            <el-input class=\"flex-1\" v-model=\"resetPwdInfo.password\" placeholder=\"请输入新密码\" show-password />\n            <el-button type=\"primary\" @click=\"generateRandomPassword\" style=\"margin-left: 10px\">\n              生成随机密码\n            </el-button>\n          </div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"closeResetPwdDialog\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"confirmResetPassword\">确 定</el-button>\n        </div>\n      </template>\n    </el-dialog>\n\n    <el-drawer\n      v-model=\"addUserDialog\"\n      :size=\"appStore.drawerSize\"\n      :show-close=\"false\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">用户</span>\n          <div>\n            <el-button @click=\"closeAddUserDialog\">取 消</el-button>\n            <el-button type=\"primary\" @click=\"enterAddUserDialog\"\n              >确 定</el-button\n            >\n          </div>\n        </div>\n      </template>\n\n      <el-form\n        ref=\"userForm\"\n        :rules=\"rules\"\n        :model=\"userInfo\"\n        label-width=\"80px\"\n      >\n        <el-form-item\n          v-if=\"dialogFlag === 'add'\"\n          label=\"用户名\"\n          prop=\"userName\"\n        >\n          <el-input v-model=\"userInfo.userName\" />\n        </el-form-item>\n        <el-form-item v-if=\"dialogFlag === 'add'\" label=\"密码\" prop=\"password\">\n          <el-input v-model=\"userInfo.password\" />\n        </el-form-item>\n        <el-form-item label=\"昵称\" prop=\"nickName\">\n          <el-input v-model=\"userInfo.nickName\" />\n        </el-form-item>\n        <el-form-item label=\"手机号\" prop=\"phone\">\n          <el-input v-model=\"userInfo.phone\" />\n        </el-form-item>\n        <el-form-item label=\"邮箱\" prop=\"email\">\n          <el-input v-model=\"userInfo.email\" />\n        </el-form-item>\n        <el-form-item label=\"用户角色\" prop=\"authorityId\">\n          <el-cascader\n            v-model=\"userInfo.authorityIds\"\n            style=\"width: 100%\"\n            :options=\"authOptions\"\n            :show-all-levels=\"false\"\n            :props=\"{\n              multiple: true,\n              checkStrictly: true,\n              label: 'authorityName',\n              value: 'authorityId',\n              disabled: 'disabled',\n              emitPath: false\n            }\"\n            :clearable=\"false\"\n          />\n        </el-form-item>\n        <el-form-item label=\"启用\" prop=\"disabled\">\n          <el-switch\n            v-model=\"userInfo.enable\"\n            inline-prompt\n            :active-value=\"1\"\n            :inactive-value=\"2\"\n          />\n        </el-form-item>\n        <el-form-item label=\"头像\" label-width=\"80px\">\n          <SelectImage v-model=\"userInfo.headerImg\" />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getUserList,\n    setUserAuthorities,\n    register,\n    deleteUser\n  } from '@/api/user'\n\n  import { getAuthorityList } from '@/api/authority'\n  import CustomPic from '@/components/customPic/index.vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { setUserInfo, resetPassword } from '@/api/user.js'\n\n  import { nextTick, ref, watch } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import SelectImage from '@/components/selectImage/selectImage.vue'\n  import { useAppStore } from \"@/pinia\";\n  import { toSQLLine } from '@/utils/stringFun'\n\n  defineOptions({\n    name: 'User'\n  })\n\n  const appStore = useAppStore()\n\n  const searchInfo = ref({\n    username: '',\n    nickname: '',\n    phone: '',\n    email: ''\n  })\n\n  const onSubmit = () => {\n    page.value = 1\n    getTableData()\n  }\n\n  const onReset = () => {\n    searchInfo.value = {\n      username: '',\n      nickname: '',\n      phone: '',\n      email: ''\n    }\n    orderKey.value = 'id'\n    desc.value = true\n    getTableData()\n  }\n  // 初始化相关\n  const setAuthorityOptions = (AuthorityData, optionsData) => {\n    AuthorityData &&\n      AuthorityData.forEach((item) => {\n        if (item.children && item.children.length) {\n          const option = {\n            authorityId: item.authorityId,\n            authorityName: item.authorityName,\n            children: []\n          }\n          setAuthorityOptions(item.children, option.children)\n          optionsData.push(option)\n        } else {\n          const option = {\n            authorityId: item.authorityId,\n            authorityName: item.authorityName\n          }\n          optionsData.push(option)\n        }\n      })\n  }\n\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const orderKey = ref('id')\n  const desc = ref(true)\n\n  const sortChange = ({ prop, order }) => {\n    if (prop) {\n      orderKey.value = prop === 'ID' ? 'id' : toSQLLine(prop)\n      desc.value = order === 'descending'\n    }\n    getTableData()\n  }\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getUserList({\n      page: page.value,\n      pageSize: pageSize.value,\n      orderKey: orderKey.value,\n      desc: desc.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  watch(\n    () => tableData.value,\n    () => {\n      setAuthorityIds()\n    }\n  )\n\n  const authOptions = ref([])\n  const setOptions = (authData) => {\n    authOptions.value = []\n    setAuthorityOptions(authData, authOptions.value)\n  }\n\n  const initPage = async () => {\n    getTableData()\n    const res = await getAuthorityList()\n    setOptions(res.data)\n  }\n\n  initPage()\n\n  // 重置密码对话框相关\n  const resetPwdDialog = ref(false)\n  const resetPwdForm = ref(null)\n  const resetPwdInfo = ref({\n    ID: '',\n    userName: '',\n    nickName: '',\n    password: ''\n  })\n\n  // 生成随机密码\n  const generateRandomPassword = () => {\n    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'\n    let password = ''\n    for (let i = 0; i < 12; i++) {\n      password += chars.charAt(Math.floor(Math.random() * chars.length))\n    }\n    resetPwdInfo.value.password = password\n    // 复制到剪贴板\n    navigator.clipboard.writeText(password).then(() => {\n      ElMessage({\n        type: 'success',\n        message: '密码已复制到剪贴板'\n      })\n    }).catch(() => {\n      ElMessage({\n        type: 'error',\n        message: '复制失败，请手动复制'\n      })\n    })\n  }\n\n  // 打开重置密码对话框\n  const resetPasswordFunc = (row) => {\n    resetPwdInfo.value.ID = row.ID\n    resetPwdInfo.value.userName = row.userName\n    resetPwdInfo.value.nickName = row.nickName\n    resetPwdInfo.value.password = ''\n    resetPwdDialog.value = true\n  }\n\n  // 确认重置密码\n  const confirmResetPassword = async () => {\n    if (!resetPwdInfo.value.password) {\n      ElMessage({\n        type: 'warning',\n        message: '请输入或生成密码'\n      })\n      return\n    }\n\n    const res = await resetPassword({\n      ID: resetPwdInfo.value.ID,\n      password: resetPwdInfo.value.password\n    })\n\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: res.msg || '密码重置成功'\n      })\n      resetPwdDialog.value = false\n    } else {\n      ElMessage({\n        type: 'error',\n        message: res.msg || '密码重置失败'\n      })\n    }\n  }\n\n  // 关闭重置密码对话框\n  const closeResetPwdDialog = () => {\n    resetPwdInfo.value.password = ''\n    resetPwdDialog.value = false\n  }\n  const setAuthorityIds = () => {\n    tableData.value &&\n      tableData.value.forEach((user) => {\n        user.authorityIds =\n          user.authorities &&\n          user.authorities.map((i) => {\n            return i.authorityId\n          })\n      })\n  }\n\n  const deleteUserFunc = async (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await deleteUser({ id: row.ID })\n      if (res.code === 0) {\n        ElMessage.success('删除成功')\n        await getTableData()\n      }\n    })\n  }\n\n  // 弹窗相关\n  const userInfo = ref({\n    userName: '',\n    password: '',\n    nickName: '',\n    headerImg: '',\n    authorityId: '',\n    authorityIds: [],\n    enable: 1\n  })\n\n  const rules = ref({\n    userName: [\n      { required: true, message: '请输入用户名', trigger: 'blur' },\n      { min: 5, message: '最低5位字符', trigger: 'blur' }\n    ],\n    password: [\n      { required: true, message: '请输入用户密码', trigger: 'blur' },\n      { min: 6, message: '最低6位字符', trigger: 'blur' }\n    ],\n    nickName: [{ required: true, message: '请输入用户昵称', trigger: 'blur' }],\n    phone: [\n      {\n        pattern: /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\\d{8}$/,\n        message: '请输入合法手机号',\n        trigger: 'blur'\n      }\n    ],\n    email: [\n      {\n        pattern: /^([0-9A-Za-z\\-_.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/g,\n        message: '请输入正确的邮箱',\n        trigger: 'blur'\n      }\n    ],\n    authorityId: [\n      { required: true, message: '请选择用户角色', trigger: 'blur' }\n    ]\n  })\n  const userForm = ref(null)\n  const enterAddUserDialog = async () => {\n    userInfo.value.authorityId = userInfo.value.authorityIds[0]\n    userForm.value.validate(async (valid) => {\n      if (valid) {\n        const req = {\n          ...userInfo.value\n        }\n        if (dialogFlag.value === 'add') {\n          const res = await register(req)\n          if (res.code === 0) {\n            ElMessage({ type: 'success', message: '创建成功' })\n            await getTableData()\n            closeAddUserDialog()\n          }\n        }\n        if (dialogFlag.value === 'edit') {\n          const res = await setUserInfo(req)\n          if (res.code === 0) {\n            ElMessage({ type: 'success', message: '编辑成功' })\n            await getTableData()\n            closeAddUserDialog()\n          }\n        }\n      }\n    })\n  }\n\n  const addUserDialog = ref(false)\n  const closeAddUserDialog = () => {\n    userForm.value.resetFields()\n    userInfo.value.headerImg = ''\n    userInfo.value.authorityIds = []\n    addUserDialog.value = false\n  }\n\n  const dialogFlag = ref('add')\n\n  const addUser = () => {\n    dialogFlag.value = 'add'\n    addUserDialog.value = true\n  }\n\n  const tempAuth = {}\n  const changeAuthority = async (row, flag, removeAuth) => {\n    if (flag) {\n      if (!removeAuth) {\n        tempAuth[row.ID] = [...row.authorityIds]\n      }\n      return\n    }\n    await nextTick()\n    const res = await setUserAuthorities({\n      ID: row.ID,\n      authorityIds: row.authorityIds\n    })\n    if (res.code === 0) {\n      ElMessage({ type: 'success', message: '角色设置成功' })\n    } else {\n      if (!removeAuth) {\n        row.authorityIds = [...tempAuth[row.ID]]\n        delete tempAuth[row.ID]\n      } else {\n        row.authorityIds = [removeAuth, ...row.authorityIds]\n      }\n    }\n  }\n\n  const openEdit = (row) => {\n    dialogFlag.value = 'edit'\n    userInfo.value = JSON.parse(JSON.stringify(row))\n    addUserDialog.value = true\n  }\n\n  const switchEnable = async (row) => {\n    userInfo.value = JSON.parse(JSON.stringify(row))\n    await nextTick()\n    const req = {\n      ...userInfo.value\n    }\n    const res = await setUserInfo(req)\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: `${req.enable === 2 ? '禁用' : '启用'}成功`\n      })\n      await getTableData()\n      userInfo.value.headerImg = ''\n      userInfo.value.authorityIds = []\n    }\n  }\n</script>\n\n<style lang=\"scss\">\n  .header-img-box {\n    @apply w-52 h-52 border border-solid border-gray-300 rounded-xl flex justify-center items-center cursor-pointer;\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/system/state.vue",
    "content": "<template>\n  <div>\n    <el-row :gutter=\"15\" class=\"py-1\">\n      <el-col :span=\"12\">\n        <el-card v-if=\"state.os\" class=\"card_item\">\n          <template #header>\n            <div>Runtime</div>\n          </template>\n          <div>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">os:</el-col>\n              <el-col :span=\"12\">{{ state.os.goos }}</el-col>\n            </el-row>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">cpu nums:</el-col>\n              <el-col :span=\"12\">{{ state.os.numCpu }}</el-col>\n            </el-row>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">compiler:</el-col>\n              <el-col :span=\"12\">{{ state.os.compiler }}</el-col>\n            </el-row>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">go version:</el-col>\n              <el-col :span=\"12\">{{ state.os.goVersion }}</el-col>\n            </el-row>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">goroutine nums:</el-col>\n              <el-col :span=\"12\">{{ state.os.numGoroutine }}</el-col>\n            </el-row>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-card\n          v-if=\"state.disk\"\n          class=\"card_item\"\n          :body-style=\"{ height: '180px', 'overflow-y': 'scroll' }\"\n        >\n          <template #header>\n            <div>Disk</div>\n          </template>\n          <div>\n            <el-row\n              v-for=\"(item, index) in state.disk\"\n              :key=\"index\"\n              :gutter=\"10\"\n              style=\"margin-bottom: 2rem\"\n            >\n              <el-col :span=\"12\">\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">MountPoint</el-col>\n                  <el-col :span=\"12\">{{ item.mountPoint }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">total (MB)</el-col>\n                  <el-col :span=\"12\">{{ item.totalMb }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">used (MB)</el-col>\n                  <el-col :span=\"12\">{{ item.usedMb }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">total (GB)</el-col>\n                  <el-col :span=\"12\">{{ item.totalGb }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">used (GB)</el-col>\n                  <el-col :span=\"12\">{{ item.usedGb }}</el-col>\n                </el-row>\n              </el-col>\n              <el-col :span=\"12\">\n                <el-progress\n                  type=\"dashboard\"\n                  :percentage=\"item.usedPercent\"\n                  :color=\"colors\"\n                />\n              </el-col>\n            </el-row>\n          </div>\n        </el-card>\n      </el-col>\n    </el-row>\n    <el-row :gutter=\"15\" class=\"py-1\">\n      <el-col :span=\"12\">\n        <el-card\n          v-if=\"state.cpu\"\n          class=\"card_item\"\n          :body-style=\"{ height: '180px', 'overflow-y': 'scroll' }\"\n        >\n          <template #header>\n            <div>CPU</div>\n          </template>\n          <div>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">physical number of cores:</el-col>\n              <el-col :span=\"12\">{{ state.cpu.cores }}</el-col>\n            </el-row>\n            <el-row\n              v-for=\"(item, index) in state.cpu.cpus\"\n              :key=\"index\"\n              :gutter=\"10\"\n            >\n              <el-col :span=\"12\">core {{ index }}:</el-col>\n              <el-col :span=\"12\">\n                <el-progress\n                  type=\"line\"\n                  :percentage=\"+item.toFixed(0)\"\n                  :color=\"colors\"\n                />\n              </el-col>\n            </el-row>\n          </div>\n        </el-card>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-card v-if=\"state.ram\" class=\"card_item\">\n          <template #header>\n            <div>Ram</div>\n          </template>\n          <div>\n            <el-row :gutter=\"10\">\n              <el-col :span=\"12\">\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">total (MB)</el-col>\n                  <el-col :span=\"12\">{{ state.ram.totalMb }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">used (MB)</el-col>\n                  <el-col :span=\"12\">{{ state.ram.usedMb }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">total (GB)</el-col>\n                  <el-col :span=\"12\">{{ state.ram.totalMb / 1024 }}</el-col>\n                </el-row>\n                <el-row :gutter=\"10\">\n                  <el-col :span=\"12\">used (GB)</el-col>\n                  <el-col :span=\"12\">{{\n                    (state.ram.usedMb / 1024).toFixed(2)\n                  }}</el-col>\n                </el-row>\n              </el-col>\n              <el-col :span=\"12\">\n                <el-progress\n                  type=\"dashboard\"\n                  :percentage=\"state.ram.usedPercent\"\n                  :color=\"colors\"\n                />\n              </el-col>\n            </el-row>\n          </div>\n        </el-card>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n<script setup>\n  import { getSystemState } from '@/api/system'\n  import { onUnmounted, ref } from 'vue'\n\n  defineOptions({\n    name: 'State'\n  })\n\n  const timer = ref(null)\n  const state = ref({})\n  const colors = ref([\n    { color: '#5cb87a', percentage: 20 },\n    { color: '#e6a23c', percentage: 40 },\n    { color: '#f56c6c', percentage: 80 }\n  ])\n\n  const reload = async () => {\n    const { data } = await getSystemState()\n    state.value = data.server\n  }\n\n  reload()\n  timer.value = setInterval(() => {\n    reload()\n  }, 1000 * 10)\n\n  onUnmounted(() => {\n    clearInterval(timer.value)\n    timer.value = null\n  })\n</script>\n\n<style>\n  .card_item {\n    @apply h-80 text-xl p-6  bg-white text-slate-700 dark:text-slate-400  dark:bg-slate-800 rounded m-2;\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/systemTools/apiToken/index.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form :inline=\"true\" :model=\"searchInfo\">\n          <el-form-item label=\"用户ID\">\n              <el-input v-model.number=\"searchInfo.userId\" placeholder=\"搜索用户ID\" />\n          </el-form-item>\n        <el-form-item label=\"状态\">\n             <el-select v-model=\"searchInfo.status\" placeholder=\"请选择\" clearable>\n                 <el-option label=\"有效\" :value=\"true\" />\n                 <el-option label=\"无效\" :value=\"false\" />\n             </el-select>\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">查询</el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDrawer\">签发</el-button>\n      </div>\n      <el-table\n        :data=\"tableData\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        row-key=\"ID\"\n      >\n        <el-table-column align=\"left\" label=\"ID\" prop=\"ID\" width=\"80\" />\n        <el-table-column align=\"left\" label=\"用户\" min-width=\"150\">\n             <template #default=\"scope\">\n                 {{ scope.row.user.nickName }} ({{ scope.row.user.userName }})\n             </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"角色ID\" prop=\"authorityId\" width=\"100\" />\n        <el-table-column align=\"left\" label=\"状态\" width=\"100\">\n          <template #default=\"scope\">\n            <el-tag :type=\"scope.row.status ? 'success' : 'danger'\">\n              {{ scope.row.status ? '有效' : '已作废' }}\n            </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"过期时间\" width=\"180\">\n          <template #default=\"scope\">{{ formatDate(scope.row.expiresAt) }}</template>\n        </el-table-column>\n         <el-table-column align=\"left\" label=\"备注\" prop=\"remark\" min-width=\"150\" show-overflow-tooltip />\n        <el-table-column align=\"left\" label=\"操作\" width=\"220\">\n          <template #default=\"scope\">\n            <el-button type=\"primary\" link icon=\"link\" @click=\"openCurl(scope.row)\">Curl示例</el-button>\n            <el-popover v-if=\"scope.row.status\" v-model:visible=\"scope.row.visible\" placement=\"top\" width=\"160\">\n              <p>确定要作废吗？</p>\n              <div style=\"text-align: right; margin: 0\">\n                <el-button size=\"small\" type=\"primary\" link @click=\"scope.row.visible = false\">取消</el-button>\n                <el-button size=\"small\" type=\"primary\" @click=\"invalidateToken(scope.row)\">确定</el-button>\n              </div>\n              <template #reference>\n                <el-button icon=\"delete\" type=\"danger\" link @click=\"scope.row.visible = true\">作废</el-button>\n              </template>\n            </el-popover>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n\n    <el-drawer v-model=\"drawerVisible\" size=\"400px\" title=\"签发 API Token\">\n         <el-form ref=\"formRef\" :model=\"form\" label-width=\"80px\">\n             <el-form-item label=\"用户\" required>\n                 <el-select \n                    v-model=\"form.userId\" \n                    placeholder=\"请选择用户\" \n                    filterable \n                    style=\"width:100%\"\n                    @change=\"handleUserChange\"\n                 >\n                     <el-option\n                        v-for=\"item in userOptions\"\n                        :key=\"item.ID\"\n                        :label=\"`${item.nickName} (${item.userName})`\"\n                        :value=\"item.ID\"\n                     />\n                 </el-select>\n             </el-form-item>\n             <el-form-item label=\"角色\" required>\n                 <el-select v-model=\"form.authorityId\" placeholder=\"请选择角色\" style=\"width:100%\" :disabled=\"!form.userId\">\n                     <el-option\n                        v-for=\"item in authorityOptions\"\n                        :key=\"item.authorityId\"\n                        :label=\"`${item.authorityName} (${item.authorityId})`\"\n                        :value=\"item.authorityId\"\n                     />\n                 </el-select>\n             </el-form-item>\n            <el-form-item label=\"有效期\">\n                <el-select v-model=\"form.days\" placeholder=\"请选择\" style=\"width:100%\">\n                    <el-option label=\"1天\" :value=\"1\" />\n                    <el-option label=\"7天\" :value=\"7\" />\n                    <el-option label=\"30天\" :value=\"30\" />\n                    <el-option label=\"90天\" :value=\"90\" />\n                    <el-option label=\"永久\" :value=\"-1\" />\n                </el-select>\n            </el-form-item>\n            <el-form-item label=\"备注\">\n                <el-input v-model=\"form.remark\" type=\"textarea\" />\n            </el-form-item>\n         </el-form>\n         <template #footer>\n             <div style=\"flex: auto\">\n                 <el-button @click=\"drawerVisible = false\">取消</el-button>\n                 <el-button type=\"primary\" @click=\"submitIssuer\">签发JWT</el-button>\n             </div>\n         </template>\n    </el-drawer>\n\n    <el-dialog v-model=\"tokenDialogVisible\" title=\"签发成功\" width=\"500px\">\n        <div style=\"text-align: center; margin-bottom: 20px;\">\n            <el-alert title=\"请立即复制保存，关闭后将无法再次查看完整Token\" type=\"warning\" :closable=\"false\" show-icon />\n        </div>\n        <el-input type=\"textarea\" :rows=\"6\" v-model=\"tokenResult\" readonly />\n        <template #footer>\n            <el-button @click=\"copyText(tokenResult)\">复制</el-button>\n            <el-button type=\"primary\" @click=\"tokenDialogVisible = false\">关闭</el-button>\n        </template>\n    </el-dialog>\n\n    <el-drawer v-model=\"curlDrawerVisible\" size=\"500px\" title=\"Curl 示例\">\n        <div style=\"padding: 10px;\">\n            <p style=\"margin-bottom: 10px;\">Header 方式:</p>\n            <el-input type=\"textarea\" :rows=\"4\" v-model=\"curlHeader\" readonly />\n            <el-button style=\"margin-top: 5px;\" size=\"small\" @click=\"copyText(curlHeader)\">复制</el-button>\n            \n            <el-divider />\n            \n            <p style=\"margin-bottom: 10px;\">Cookie 方式:</p>\n            <el-input type=\"textarea\" :rows=\"4\" v-model=\"curlCookie\" readonly />\n            <el-button style=\"margin-top: 5px;\" size=\"small\" @click=\"copyText(curlCookie)\">复制</el-button>\n        </div>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\nimport {\n  getApiTokenList,\n  createApiToken,\n  deleteApiToken\n} from '@/api/sysApiToken'\nimport { getUserList } from '@/api/user'\nimport { ref, computed } from 'vue'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport { formatDate } from '@/utils/format'\n\nconst page = ref(1)\nconst total = ref(0)\nconst pageSize = ref(10)\nconst tableData = ref([])\nconst searchInfo = ref({})\n\nconst drawerVisible = ref(false)\nconst tokenDialogVisible = ref(false)\nconst tokenResult = ref('')\nconst curlDrawerVisible = ref(false)\nconst curlHeader = ref('')\nconst curlCookie = ref('')\n\nconst form = ref({\n    userId: '',\n    authorityId: '',\n    days: 30,\n    remark: ''\n})\n\nconst userOptions = ref([])\nconst authorityOptions = ref([])\n\nconst getTableData = async () => {\n  const table = await getApiTokenList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })\n  if (table.code === 0) {\n    tableData.value = table.data.list\n    total.value = table.data.total\n    page.value = table.data.page\n    pageSize.value = table.data.pageSize\n  }\n}\n\nconst openDrawer = async () => {\n    form.value = { userId: '', authorityId: '', days: 30, remark: '' }\n    authorityOptions.value = []\n    drawerVisible.value = true\n    if (userOptions.value.length === 0) {\n        const res = await getUserList({ page: 1, pageSize: 999 })\n        if (res.code === 0) {\n            userOptions.value = res.data.list\n        }\n    }\n}\n\nconst handleUserChange = (val) => {\n    form.value.authorityId = ''\n    const user = userOptions.value.find(u => u.ID === val)\n    if (user) {\n        authorityOptions.value = user.authorities || []\n        // 默认选中第一个\n        if (authorityOptions.value.length > 0) {\n            form.value.authorityId = authorityOptions.value[0].authorityId\n        }\n    } else {\n        authorityOptions.value = []\n    }\n}\n\nconst submitIssuer = async () => {\n    if (!form.value.userId || !form.value.authorityId) {\n        ElMessage.warning(\"请选择用户和角色\")\n        return\n    }\n    const res = await createApiToken(form.value)\n    if (res.code === 0) {\n        tokenResult.value = res.data.token\n        drawerVisible.value = false\n        tokenDialogVisible.value = true\n        getTableData()\n    }\n}\n\nconst invalidateToken = async (row) => {\n    row.visible = false\n    const res = await deleteApiToken({ ID: row.ID })\n    if (res.code === 0) {\n        ElMessage.success(\"作废成功\")\n        getTableData()\n    }\n}\n\nconst openCurl = (row) => {\n    // 假设 API Host 为当前 origin\n    const origin = window.location.origin\n    // 构造示例 URL\n    const url = `${origin}/api/menu/getMenu`\n    \n    curlHeader.value = `curl -X POST \"${url}\" \\ \n  -H \"x-token: ${row.token}\" \\ \n  -H \"Content-Type: application/json\"`\n    \n    curlCookie.value = `curl -X POST \"${url}\" \\ \n  -b \"x-token=${row.token}\" \\ \n  -H \"Content-Type: application/json\"`\n\n    curlDrawerVisible.value = true\n}\n\nconst copyText = (text) => {\n    if (!text) return\n    const input = document.createElement('textarea')\n    input.value = text\n    document.body.appendChild(input)\n    input.select()\n    document.execCommand('copy')\n    document.body.removeChild(input)\n    ElMessage.success('复制成功')\n}\n\nconst onSubmit = () => {\n  page.value = 1\n  pageSize.value = 10\n  getTableData()\n}\n\nconst onReset = () => {\n  searchInfo.value = {}\n  getTableData()\n}\n\nconst handleSizeChange = (val) => {\n  pageSize.value = val\n  getTableData()\n}\n\nconst handleCurrentChange = (val) => {\n  page.value = val\n  getTableData()\n}\n\ngetTableData()\n</script>\n\n<style scoped>\n</style>\n"
  },
  {
    "path": "web/src/view/systemTools/autoCode/component/fieldDialog.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      title=\"id , created_at , updated_at , deleted_at 会自动生成请勿重复创建。搜索时如果条件为LIKE只支持字符串\"\n    />\n    <el-form\n      ref=\"fieldDialogForm\"\n      :model=\"middleDate\"\n      label-width=\"120px\"\n      label-position=\"right\"\n      :rules=\"rules\"\n      class=\"grid grid-cols-2\"\n    >\n      <el-form-item label=\"字段名称\" prop=\"fieldName\">\n        <el-input\n          v-model=\"middleDate.fieldName\"\n          autocomplete=\"off\"\n          style=\"width: 80%\"\n        />\n        <el-button style=\"width: 18%; margin-left: 2%\" @click=\"autoFill\">\n          <span style=\"font-size: 12px\">自动填充</span>\n        </el-button>\n      </el-form-item>\n      <el-form-item label=\"字段中文名\" prop=\"fieldDesc\">\n        <el-input v-model=\"middleDate.fieldDesc\" autocomplete=\"off\" />\n      </el-form-item>\n      <el-form-item label=\"字段JSON\" prop=\"fieldJson\">\n        <el-input v-model=\"middleDate.fieldJson\" autocomplete=\"off\" />\n      </el-form-item>\n      <el-form-item label=\"数据库字段名\" prop=\"columnName\">\n        <el-input v-model=\"middleDate.columnName\" autocomplete=\"off\" />\n      </el-form-item>\n      <el-form-item label=\"数据库字段描述\" prop=\"comment\">\n        <el-input v-model=\"middleDate.comment\" autocomplete=\"off\" />\n      </el-form-item>\n      <el-form-item label=\"字段类型\" prop=\"fieldType\">\n        <el-select\n          v-model=\"middleDate.fieldType\"\n          style=\"width: 100%\"\n          placeholder=\"请选择字段类型\"\n          clearable\n          @change=\"clearOther\"\n        >\n          <el-option\n            v-for=\"item in typeOptions\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n            :disabled=\"item.disabled\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item\n        :label=\"middleDate.fieldType === 'enum' ? '枚举值' : '类型长度'\"\n        prop=\"dataTypeLong\"\n      >\n        <el-input\n          v-model=\"middleDate.dataTypeLong\"\n          :placeholder=\"\n            middleDate.fieldType === 'enum'\n              ? `例:'北京','天津'`\n              : '数据库类型长度'\n          \"\n        />\n      </el-form-item>\n      <el-form-item label=\"字段查询条件\" prop=\"fieldSearchType\">\n        <el-select\n          v-model=\"middleDate.fieldSearchType\"\n          :disabled=\"middleDate.fieldType === 'json'\"\n          style=\"width: 100%\"\n          placeholder=\"请选择字段查询条件\"\n          clearable\n        >\n          <el-option\n            v-for=\"item in typeSearchOptions\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n            :disabled=\"canSelect(item.value)\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"关联字典\" prop=\"dictType\">\n        <el-select\n          v-model=\"middleDate.dictType\"\n          style=\"width: 100%\"\n          :disabled=\"middleDate.fieldType !== 'string' && middleDate.fieldType !== 'array'\"\n          placeholder=\"请选择字典\"\n          clearable\n        >\n          <el-option\n            v-for=\"item in dictOptions\"\n            :key=\"item.type\"\n            :label=\"`${item.type}(${item.name})`\"\n            :value=\"item.type\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"默认值\">\n        <el-input\n          v-model=\"middleDate.defaultValue\"\n          placeholder=\"请输入默认值\"\n        />\n      </el-form-item>\n      <el-form-item label=\"主键\">\n        <el-checkbox v-model=\"middleDate.primaryKey\" />\n      </el-form-item>\n      <el-form-item label=\"索引类型\" prop=\"fieldIndexType\">\n        <el-select\n          v-model=\"middleDate.fieldIndexType\"\n          :disabled=\"middleDate.fieldType === 'json'\"\n          style=\"width: 100%\"\n          placeholder=\"请选择字段索引类型\"\n          clearable\n        >\n          <el-option\n            v-for=\"item in typeIndexOptions\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n            :disabled=\"canSelect(item.value)\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"前端新建/编辑\">\n        <el-switch v-model=\"middleDate.form\" />\n      </el-form-item>\n      <el-form-item label=\"前端表格列\">\n        <el-switch v-model=\"middleDate.table\" />\n      </el-form-item>\n      <el-form-item label=\"前端详情\">\n        <el-switch v-model=\"middleDate.desc\" />\n      </el-form-item>\n      <el-form-item label=\"导入/导出\">\n        <el-switch v-model=\"middleDate.excel\" />\n      </el-form-item>\n      <el-form-item label=\"是否排序\">\n        <el-switch v-model=\"middleDate.sort\" />\n      </el-form-item>\n      <el-form-item label=\"是否必填\">\n        <el-switch v-model=\"middleDate.require\" />\n      </el-form-item>\n      <el-form-item label=\"是否可清空\">\n        <el-switch v-model=\"middleDate.clearable\" />\n      </el-form-item>\n      <el-form-item label=\"隐藏查询条件\">\n        <el-switch\n          v-model=\"middleDate.fieldSearchHide\"\n          :disabled=\"!middleDate.fieldSearchType\"\n        />\n      </el-form-item>\n      <el-form-item label=\"校验失败文案\">\n        <el-input v-model=\"middleDate.errorText\" />\n      </el-form-item>\n    </el-form>\n    <el-collapse v-model=\"activeNames\">\n      <el-collapse-item\n        title=\"数据源配置（此配置为高级配置，如编程基础不牢，可能导致自动化代码不可用）\"\n        name=\"1\"\n      >\n        <el-row :gutter=\"8\">\n          <el-col :span=\"4\">\n            <el-select\n              v-model=\"middleDate.dataSource.dbName\"\n              placeholder=\"数据库【不填则为GVA库】\"\n              @change=\"dbNameChange\"\n              clearable\n            >\n              <el-option\n                v-for=\"item in dbList\"\n                :key=\"item.aliasName\"\n                :value=\"item.aliasName\"\n                :label=\"item.aliasName\"\n                :disabled=\"item.disable\"\n              >\n                <div>\n                  <span>{{ item.aliasName }}</span>\n                  <span style=\"float: right; color: #8492a6; font-size: 13px\">{{\n                    item.dbName\n                  }}</span>\n                </div>\n              </el-option>\n            </el-select>\n          </el-col>\n          <el-col :span=\"4\">\n            <el-select\n              v-model=\"middleDate.dataSource.association\"\n              placeholder=\"关联模式\"\n              @change=\"associationChange\"\n            >\n              <el-option label=\"一对一\" :value=\"1\" />\n              <el-option label=\"一对多\" :value=\"2\" />\n            </el-select>\n          </el-col>\n          <el-col :span=\"5\">\n            <el-select\n              v-model=\"middleDate.dataSource.table\"\n              placeholder=\"请选择数据源表\"\n              filterable\n              allow-create\n              clearable\n              @focus=\"getDBTableList\"\n              @change=\"selectDB\"\n              @clear=\"clearAccress\"\n            >\n              <el-option\n                v-for=\"item in dbTableList\"\n                :key=\"item.tableName\"\n                :label=\"item.tableName\"\n                :value=\"item.tableName\"\n              />\n            </el-select>\n          </el-col>\n          <el-col :span=\"5\">\n            <el-select\n              v-model=\"middleDate.dataSource.value\"\n              placeholder=\"请先选择需要存储的数据\"\n            >\n              <template #label=\"{ value }\">\n                <span>存储: </span>\n                <span style=\"font-weight: bold\">{{ value }}</span>\n              </template>\n              <el-option\n                v-for=\"item in dbColumnList\"\n                :key=\"item.columnName\"\n                :value=\"item.columnName\"\n              >\n                <span style=\"float: left\">\n                  <el-tag :type=\"item.isPrimary ? 'primary' : 'info'\">\n                    {{ item.isPrimary ? '主&emsp;键' : '非主键' }}\n                  </el-tag>\n                  {{ item.columnName }}</span\n                >\n                <span\n                  style=\"\n                    float: right;\n                    margin-left: 5px;\n                    color: var(--el-text-color-secondary);\n                    font-size: 13px;\n                  \"\n                >\n                  类型：{{ item.type }}\n                  <block v-if=\"item.comment != ''\"\n                    >，字段说明：{{ item.comment }}</block\n                  >\n                </span>\n              </el-option>\n            </el-select>\n          </el-col>\n          <el-col :span=\"5\">\n            <el-select\n              v-model=\"middleDate.dataSource.label\"\n              placeholder=\"请先选择需要展示的数据\"\n            >\n              <template #label=\"{ value }\">\n                <span>展示: </span>\n                <span style=\"font-weight: bold\">{{ value }}</span>\n              </template>\n              <el-option\n                v-for=\"item in dbColumnList\"\n                :key=\"item.columnName\"\n                :value=\"item.columnName\"\n              >\n                <span style=\"float: left\">\n                  <el-tag :type=\"item.isPrimary ? 'primary' : 'info'\">\n                    {{ item.isPrimary ? '主&emsp;键' : '非主键' }}\n                  </el-tag>\n                  {{ item.columnName }}</span\n                >\n                <span\n                  style=\"\n                    float: right;\n                    margin-left: 5px;\n                    color: var(--el-text-color-secondary);\n                    font-size: 13px;\n                  \"\n                >\n                  类型：{{ item.type }}\n                  <span v-if=\"item.comment != ''\"\n                    >，字段说明：{{ item.comment }}</span\n                  >\n                </span>\n              </el-option>\n            </el-select>\n            <!-- <el-input v-model=\"middleDate.dataSource.label\" placeholder=\"展示用字段\" /> -->\n          </el-col>\n        </el-row>\n      </el-collapse-item>\n    </el-collapse>\n  </div>\n</template>\n\n<script setup>\n  import { toLowerCase, toSQLLine } from '@/utils/stringFun'\n  import { getSysDictionaryList } from '@/api/sysDictionary'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { ref, onMounted } from 'vue'\n  import { ElMessageBox } from 'element-plus'\n  import { getColumn, getDB, getTable } from '@/api/autoCode'\n\n  defineOptions({\n    name: 'FieldDialog'\n  })\n\n  const props = defineProps({\n    dialogMiddle: {\n      type: Object,\n      default: function () {\n        return {}\n      }\n    },\n    typeOptions: {\n      type: Array,\n      default: function () {\n        return []\n      }\n    },\n    typeSearchOptions: {\n      type: Array,\n      default: function () {\n        return []\n      }\n    },\n    typeIndexOptions: {\n      type: Array,\n      default: function () {\n        return []\n      }\n    }\n  })\n\n  const activeNames = ref([])\n\n  const middleDate = ref({})\n  const dictOptions = ref([])\n\n  const dbList = ref([])\n\n  const getDbFunc = async () => {\n    const res = await getDB()\n    if (res.code === 0) {\n      dbList.value = res.data.dbList\n    }\n  }\n\n  const validateDataTypeLong = (rule, value, callback) => {\n    const regex = /^('([^']*)'(?:,'([^']+)'*)*)$/\n    if (middleDate.value.fieldType == 'enum' && !regex.test(value)) {\n      callback(new Error('枚举值校验错误'))\n    } else {\n      callback()\n    }\n  }\n\n  const rules = ref({\n    fieldName: [\n      { required: true, message: '请输入字段英文名', trigger: 'blur' }\n    ],\n    fieldDesc: [\n      { required: true, message: '请输入字段中文名', trigger: 'blur' }\n    ],\n    fieldJson: [\n      { required: true, message: '请输入字段格式化json', trigger: 'blur' }\n    ],\n    columnName: [\n      { required: true, message: '请输入数据库字段', trigger: 'blur' }\n    ],\n    fieldType: [{ required: true, message: '请选择字段类型', trigger: 'blur' }],\n    dataTypeLong: [{ validator: validateDataTypeLong, trigger: 'blur' }]\n  })\n\n  const init = async () => {\n    middleDate.value = props.dialogMiddle\n    const dictRes = await getSysDictionaryList({\n      page: 1,\n      pageSize: 999999\n    })\n\n    dictOptions.value = dictRes.data\n  }\n  init()\n\n  const autoFill = () => {\n    middleDate.value.fieldJson = toLowerCase(middleDate.value.fieldName)\n    middleDate.value.columnName = toSQLLine(middleDate.value.fieldJson)\n  }\n\n  const canSelect = (item) => {\n    const fieldType = middleDate.value.fieldType;\n\n    if (fieldType === 'richtext') {\n      return item !== 'LIKE';\n    }\n\n    if (fieldType !== 'string' && item === 'LIKE') {\n      return true;\n    }\n\n    const nonNumericTypes = ['int', 'time.Time', 'float64'];\n    if (!nonNumericTypes.includes(fieldType) && ['BETWEEN', 'NOT BETWEEN'].includes(item)) {\n      return true;\n    }\n\n    return false;\n  }\n\n  const clearOther = () => {\n    middleDate.value.fieldSearchType = ''\n    middleDate.value.dictType = ''\n  }\n\n  const associationChange = (val) => {\n    if (val === 2) {\n      ElMessageBox.confirm(\n        '一对多关联模式下，数据类型会改变为数组，后端表现为json，具体表现为数组模式，是否继续？',\n        '提示',\n        {\n          confirmButtonText: '继续',\n          cancelButtonText: '取消',\n          type: 'warning'\n        }\n      )\n        .then(() => {\n          middleDate.value.fieldType = 'array'\n        })\n        .catch(() => {\n          middleDate.value.dataSource.association = 1\n        })\n    }\n  }\n\n  const clearAccress = () => {\n    middleDate.value.dataSource.value = ''\n    middleDate.value.dataSource.label = ''\n  }\n\n  const clearDataSourceTable = () => {\n    middleDate.value.dataSource.table = ''\n  }\n\n  const dbNameChange = () => {\n    getDBTableList()\n    clearDataSourceTable()\n    clearAccress()\n  }\n\n  const dbTableList = ref([])\n\n  const getDBTableList = async () => {\n    const res = await getTable({\n      businessDB: middleDate.value.dataSource.dbName\n    })\n    if (res.code === 0) {\n      let list = res.data.tables // 确保这里正确获取到 tables 数组\n      dbTableList.value = list.map((item) => ({\n        tableName: item.tableName,\n        value: item.tableName // 这里假设 value 也是 tableName，如果不同请调整\n      }))\n    }\n    clearAccress()\n  }\n\n  const dbColumnList = ref([])\n  const selectDB = async (val, isInit) => {\n    middleDate.value.dataSource.hasDeletedAt = false\n    middleDate.value.dataSource.table = val\n    const res = await getColumn({\n      businessDB: middleDate.value.dataSource.dbName,\n      tableName: val\n    })\n\n    if (res.code === 0) {\n      let list = res.data.columns // 确保这里正确获取到 tables 数组\n      dbColumnList.value = list.map((item) => {\n        if (item.columnName === 'deleted_at') {\n          middleDate.value.dataSource.hasDeletedAt = true\n        }\n        return {\n          columnName: item.columnName,\n          value: item.columnName,\n          type: item.dataType,\n          isPrimary: item.primaryKey,\n          comment: item.columnComment\n        }\n      })\n      if (dbColumnList.value.length > 0 && !isInit) {\n        middleDate.value.dataSource.label = dbColumnList.value[0].columnName\n        middleDate.value.dataSource.value = dbColumnList.value[0].columnName\n      }\n    }\n  }\n\n  const fieldDialogForm = ref(null)\n  defineExpose({ fieldDialogForm })\n\n  onMounted(() => {\n    getDbFunc()\n    if (middleDate.value.dataSource.table) {\n      selectDB(middleDate.value.dataSource.table, true)\n    }\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/autoCode/component/previewCodeDialog.vue",
    "content": "<template>\n  <el-tabs\n    v-model=\"activeName\"\n    tab-position=\"left\"\n    class=\"h-[calc(100vh-110px)]\"\n  >\n    <el-tab-pane\n      v-for=\"(item, key) in useCode\"\n      :key=\"key\"\n      :label=\"key\"\n      :name=\"key\"\n    >\n      <div :id=\"key\" class=\"h-[calc(100vh-110px)] px-5 overflow-y-scroll\"></div>\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<script setup>\n  import { Marked } from 'marked'\n  import { markedHighlight } from 'marked-highlight'\n  import hljs from 'highlight.js'\n  import { ElMessage } from 'element-plus'\n  import { onMounted, ref, watchEffect } from 'vue'\n  import { useAppStore } from '@/pinia'\n\n  const appStore = useAppStore()\n\n  const useCode = ref({})\n\n  const createKey = [\n    'enter.go',\n    'gorm_biz.go',\n    'router_biz.go',\n    'api',\n    'router',\n    'initialize',\n    'gen.go'\n  ]\n\n  onMounted(() => {\n    const isDarkMode = appStore.config.darkMode === 'dark'\n    if (isDarkMode) {\n      import('highlight.js/styles/atom-one-dark.css')\n    } else {\n      import('highlight.js/styles/atom-one-light.css')\n    }\n  })\n\n  const props = defineProps({\n    previewCode: {\n      type: Object,\n      default() {\n        return {}\n      }\n    },\n    isAdd: {\n      type: Boolean,\n      default: false\n    }\n  })\n\n  watchEffect(() => {\n    for (const key in props.previewCode) {\n      if (\n        props.isAdd &&\n        createKey.some((createKeyItem) => key.includes(createKeyItem))\n      ) {\n        continue\n      }\n      useCode.value[key] = props.previewCode[key]\n    }\n  })\n\n  const activeName = ref('')\n  onMounted(() => {\n    const marked = new Marked(\n      markedHighlight({\n        langPrefix: 'hljs language-',\n        highlight(code, lang) {\n          const language = hljs.getLanguage(lang) ? lang : 'plaintext'\n          if (lang === 'vue') {\n            return hljs.highlight(code, { language: 'html' }).value\n          }\n          return hljs.highlight(code, { language }).value\n        }\n      })\n    )\n    for (const key in useCode.value) {\n      if (activeName.value === '') {\n        activeName.value = key\n      }\n      document.getElementById(key).innerHTML = marked.parse(useCode.value[key])\n    }\n  })\n\n  const selectText = () => {\n    const element = document.getElementById(activeName.value)\n    if (document.body.createTextRange) {\n      const range = document.body.createTextRange()\n      range.moveToElementText(element)\n      range.select()\n    } else if (window.getSelection) {\n      const selection = window.getSelection()\n      const range = document.createRange()\n      range.selectNodeContents(element)\n      selection.removeAllRanges()\n      selection.addRange(range)\n    } else {\n      alert('none')\n    }\n  }\n  const copy = () => {\n    selectText()\n    document.execCommand('copy')\n    ElMessage.success('复制成功')\n  }\n\n  defineExpose({ copy, selectText })\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/autoCode/index.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      href=\"https://www.bilibili.com/video/BV1kv4y1g7nT?p=3\"\n      title=\"此功能为开发环境使用，不建议发布到生产，具体使用效果请点我观看。\"\n    />\n    <div class=\"gva-search-box\" v-if=\"!isAdd\">\n      <div class=\"text-lg mb-2 text-gray-600\">\n        使用AI创建<a\n          class=\"text-blue-600 text-sm ml-4\"\n          href=\"https://plugin.gin-vue-admin.com/#/layout/userInfo/center\"\n          target=\"_blank\"\n          >获取AiPath</a\n        >\n      </div>\n      <div class=\"relative\">\n        <el-input\n          v-model=\"prompt\"\n          type=\"textarea\"\n          :rows=\"5\"\n          :maxlength=\"2000\"\n          :placeholder=\"`现已完全免费\\n试试复制一张图片然后按下ctrl+v或者commend+v\\n试试描述你的表，让AI帮你完成。\\n此功能需要到插件市场个人中心获取自己的AI-Path，把AI-Path填入config.yaml下的autocode-->ai-path，重启项目即可使用。\\n按下 Ctrl+Enter 或 Cmd+Enter 直接生成`\"\n          resize=\"none\"\n          @focus=\"handleFocus\"\n          @blur=\"handleBlur\"\n        />\n\n        <div class=\"flex absolute right-28 bottom-2\">\n          <el-tooltip effect=\"light\">\n            <template #content>\n              <div>\n                【完全免费】前往<a\n                  class=\"text-blue-600\"\n                  href=\"https://plugin.gin-vue-admin.com/#/layout/userInfo/center\"\n                  target=\"_blank\"\n              >插件市场个人中心</a\n              >申请AIPath，填入config.yaml的ai-path属性即可使用。\n              </div>\n            </template>\n            <el-button\n                :disabled=\"form.onlyTemplate\"\n                type=\"primary\"\n                @click=\"eyeFunc()\"\n            >\n              <el-icon size=\"18\">\n                <ai-gva />\n              </el-icon>\n              识图\n            </el-button>\n          </el-tooltip>\n        </div>\n\n        <div class=\"flex absolute right-2 bottom-2\">\n          <el-tooltip effect=\"light\">\n            <template #content>\n              <div>\n                【完全免费】前往<a\n                  class=\"text-blue-600\"\n                  href=\"https://plugin.gin-vue-admin.com/#/layout/userInfo/center\"\n                  target=\"_blank\"\n                  >插件市场个人中心</a\n                >申请AIPath，填入config.yaml的ai-path属性即可使用。\n              </div>\n            </template>\n            <el-button\n              :disabled=\"form.onlyTemplate\"\n              type=\"primary\"\n              @click=\"llmAutoFunc()\"\n            >\n              <el-icon size=\"18\">\n                <ai-gva />\n              </el-icon>\n              生成\n            </el-button>\n          </el-tooltip>\n        </div>\n      </div>\n    </div>\n    <!-- 从数据库直接获取字段 -->\n    <div class=\"gva-search-box\" v-if=\"!isAdd\">\n      <div class=\"text-lg mb-2 text-gray-600\">从数据库创建</div>\n      <el-form\n        ref=\"getTableForm\"\n        :inline=\"true\"\n        :model=\"dbform\"\n        label-width=\"120px\"\n      >\n        <el-row class=\"w-full\">\n          <el-col :span=\"6\">\n            <el-form-item label=\"业务库\" prop=\"selectDBtype\" class=\"w-full\">\n              <template #label>\n                <el-tooltip\n                  content=\"注：需要提前到db-list自行配置多数据库，如未配置需配置后重启服务方可使用。（此处可选择对应库表，可理解为从哪个库选择表）\"\n                  placement=\"bottom\"\n                  effect=\"light\"\n                >\n                  <div>\n                    业务库 <el-icon><QuestionFilled /></el-icon>\n                  </div>\n                </el-tooltip>\n              </template>\n              <el-select\n                v-model=\"dbform.businessDB\"\n                clearable\n                placeholder=\"选择业务库\"\n                @change=\"getDbFunc\"\n                class=\"w-full\"\n              >\n                <el-option\n                  v-for=\"item in dbList\"\n                  :key=\"item.aliasName\"\n                  :value=\"item.aliasName\"\n                  :label=\"item.aliasName\"\n                  :disabled=\"item.disable\"\n                >\n                  <div>\n                    <span>{{ item.aliasName }}</span>\n                    <span\n                      style=\"float: right; color: #8492a6; font-size: 13px\"\n                      >{{ item.dbName }}</span\n                    >\n                  </div>\n                </el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item label=\"数据库名\" prop=\"structName\" class=\"w-full\">\n              <el-select\n                v-model=\"dbform.dbName\"\n                clearable\n                filterable\n                placeholder=\"请选择数据库\"\n                class=\"w-full\"\n                @change=\"getTableFunc\"\n              >\n                <el-option\n                  v-for=\"item in dbOptions\"\n                  :key=\"item.database\"\n                  :label=\"item.database\"\n                  :value=\"item.database\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item label=\"表名\" prop=\"structName\" class=\"w-full\">\n              <el-select\n                v-model=\"dbform.tableName\"\n                :disabled=\"!dbform.dbName\"\n                class=\"w-full\"\n                filterable\n                placeholder=\"请选择表\"\n              >\n                <el-option\n                  v-for=\"item in tableOptions\"\n                  :key=\"item.tableName\"\n                  :label=\"item.tableName\"\n                  :value=\"item.tableName\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item class=\"w-full\">\n              <div class=\"flex justify-end w-full\">\n                <el-button type=\"primary\" @click=\"getColumnFunc\">\n                  使用此表\n                </el-button>\n              </div>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n    </div>\n    <div class=\"gva-search-box\">\n      <!-- 初始版本自动化代码工具 -->\n      <div class=\"text-lg mb-2 text-gray-600\">自动化结构</div>\n      <el-form\n        :disabled=\"isAdd\"\n        ref=\"autoCodeForm\"\n        :rules=\"rules\"\n        :model=\"form\"\n        label-width=\"120px\"\n        :inline=\"true\"\n      >\n        <el-row class=\"w-full\">\n          <el-col :span=\"6\">\n            <el-form-item label=\"结构名称\" prop=\"structName\" class=\"w-full\">\n              <div class=\"flex gap-2 w-full\">\n                <el-input\n                  v-model=\"form.structName\"\n                  placeholder=\"首字母自动转换大写\"\n                />\n                <el-button\n                  :disabled=\"form.onlyTemplate\"\n                  type=\"primary\"\n                  @click=\"llmAutoFunc(true)\"\n                >\n                  <el-icon size=\"18\">\n                    <ai-gva />\n                  </el-icon>\n                  生成\n                </el-button>\n              </div>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item label=\"abbreviation\" prop=\"abbreviation\" class=\"w-full\">\n              <template #label>\n                <el-tooltip\n                  content=\"简称会作为入参对象名和路由group\"\n                  placement=\"bottom\"\n                  effect=\"light\"\n                >\n                  <div>\n                    结构简称 <el-icon><QuestionFilled /></el-icon>\n                  </div>\n                </el-tooltip>\n              </template>\n              <el-input\n                v-model=\"form.abbreviation\"\n                placeholder=\"请输入Struct简称\"\n              />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item label=\"中文名称\" prop=\"description\" class=\"w-full\">\n              <el-input\n                v-model=\"form.description\"\n                placeholder=\"中文描述作为自动api描述\"\n              />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item label=\"表名\" prop=\"tableName\" class=\"w-full\">\n              <el-input\n                v-model=\"form.tableName\"\n                placeholder=\"指定表名（非必填）\"\n              />\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row class=\"w-full\">\n          <el-col :span=\"6\">\n            <el-form-item prop=\"packageName\" class=\"w-full\">\n              <template #label>\n                <el-tooltip\n                  content=\"生成文件的默认名称(建议为驼峰格式,首字母小写,如sysXxxXxxx)\"\n                  placement=\"bottom\"\n                  effect=\"light\"\n                >\n                  <div>\n                    文件名称 <el-icon><QuestionFilled /></el-icon>\n                  </div>\n                </el-tooltip>\n              </template>\n              <el-input\n                v-model=\"form.packageName\"\n                placeholder=\"请输入文件名称\"\n                @blur=\"toLowerCaseFunc(form, 'packageName')\"\n              />\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item\n              label=\"选择模板\"\n              prop=\"package\"\n              class=\"w-full relative\"\n            >\n              <el-select v-model=\"form.package\" class=\"w-full pr-12\" filterable>\n                <el-option\n                  v-for=\"item in pkgs\"\n                  :key=\"item.ID\"\n                  :value=\"item.packageName\"\n                  :label=\"item.packageName\"\n                />\n              </el-select>\n              <span class=\"absolute right-0\">\n                <el-icon\n                  class=\"cursor-pointer ml-2 text-gray-600\"\n                  @click=\"getPkgs\"\n                >\n                  <refresh />\n                </el-icon>\n                <el-icon\n                  class=\"cursor-pointer ml-2 text-gray-600\"\n                  @click=\"goPkgs\"\n                >\n                  <document-add />\n                </el-icon>\n              </span>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item label=\"业务库\" prop=\"businessDB\" class=\"w-full\">\n              <template #label>\n                <el-tooltip\n                  content=\"注：需要提前到db-list自行配置多数据库，此项为空则会使用gva本库创建自动化代码(global.GVA_DB),填写后则会创建指定库的代码(global.MustGetGlobalDBByDBName(dbname))\"\n                  placement=\"bottom\"\n                  effect=\"light\"\n                >\n                  <div>\n                    业务库 <el-icon><QuestionFilled /></el-icon>\n                  </div>\n                </el-tooltip>\n              </template>\n              <el-select\n                v-model=\"form.businessDB\"\n                clearable\n                placeholder=\"选择业务库\"\n                class=\"w-full\"\n              >\n                <el-option\n                  v-for=\"item in dbList\"\n                  :key=\"item.aliasName\"\n                  :value=\"item.aliasName\"\n                  :label=\"item.aliasName\"\n                  :disabled=\"item.disable\"\n                >\n                  <div>\n                    <span>{{ item.aliasName }}</span>\n                    <span\n                      style=\"float: right; color: #8492a6; font-size: 13px\"\n                      >{{ item.dbName }}</span\n                    >\n                  </div>\n                </el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n    </div>\n    <div class=\"gva-search-box\">\n      <el-collapse class=\"no-border-collapse\">\n        <el-collapse-item>\n          <template #title>\n            <div class=\"text-lg text-gray-600 font-normal\">\n              专家模式\n            </div>\n          </template>\n          <template #icon=\"{ isActive }\">\n          <span class=\"text-lg ml-auto mr-4 font-normal\">\n            {{ isActive ? '收起' : '展开' }}\n          </span>\n          </template>\n          <div class=\"p-4\">\n            <!-- 基础设置组 -->\n            <div class=\"border-b border-gray-200 last:border-0\">\n              <h3 class=\"text-lg font-medium mb-4 text-gray-700\">基础设置</h3>\n              <el-row :gutter=\"20\">\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：会自动在结构体global.Model其中包含主键和软删除相关操作配置\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"使用GVA结构\">\n                      <el-checkbox v-model=\"form.gvaModel\" @change=\"useGva\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：会自动产生页面内的按钮权限配置，若不在角色管理中进行按钮分配则按钮不可见\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"创建按钮权限\">\n                      <el-checkbox :disabled=\"!form.generateWeb\" v-model=\"form.autoCreateBtnAuth\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n                <el-col :span=\"3\">\n                  <el-form-item label=\"生成前端\">\n                    <el-checkbox v-model=\"form.generateWeb\" />\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"3\">\n                  <el-form-item label=\"生成后端\">\n                    <el-checkbox disabled v-model=\"form.generateServer\" />\n                  </el-form-item>\n                </el-col>\n              </el-row>\n            </div>\n\n            <!-- 自动化设置组 -->\n            <div class=\"border-b border-gray-200 last:border-0\">\n              <h3 class=\"text-lg font-medium mb-4 text-gray-700\">自动化设置</h3>\n              <el-row :gutter=\"20\">\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：把自动生成的API注册进数据库\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"自动创建API\">\n                      <el-checkbox  :disabled=\"!form.generateServer\" v-model=\"form.autoCreateApiToSql\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：把自动生成的菜单注册进数据库\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"自动创建菜单\">\n                      <el-checkbox :disabled=\"!form.generateWeb\" v-model=\"form.autoCreateMenuToSql\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：自动同步数据库表结构，如果不需要可以选择关闭\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"同步表结构\">\n                      <el-checkbox  :disabled=\"!form.generateServer\" v-model=\"form.autoMigrate\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n              </el-row>\n            </div>\n\n            <!-- 高级设置组 -->\n            <div class=\"border-b border-gray-200 last:border-0\">\n              <h3 class=\"text-lg font-medium mb-4 text-gray-700\">高级设置</h3>\n              <el-row :gutter=\"20\">\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：会自动在结构体添加 created_by updated_by deleted_by，方便用户进行资源权限控制\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"创建资源标识\">\n                      <el-checkbox v-model=\"form.autoCreateResource\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n                <el-col :span=\"3\">\n                  <el-tooltip\n                      content=\"注：使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑\"\n                      placement=\"top\"\n                      effect=\"light\"\n                  >\n                    <el-form-item label=\"基础模板\">\n                      <el-checkbox v-model=\"form.onlyTemplate\" />\n                    </el-form-item>\n                  </el-tooltip>\n                </el-col>\n              </el-row>\n            </div>\n\n            <!-- 树形结构设置 -->\n            <div class=\"last:pb-0\">\n              <h3 class=\"text-lg font-medium mb-4 text-gray-700\">树形结构设置</h3>\n              <el-row :gutter=\"20\" align=\"middle\">\n                <el-col :span=\"24\">\n                    <el-form-item label=\"树型结构\">\n                      <div class=\"flex items-center gap-4\">\n                        <el-tooltip\n                            content=\"注：会自动创建parentID来进行父子关系关联,仅支持主键为int类型\"\n                            placement=\"top\"\n                            effect=\"light\"\n                        >\n                          <el-checkbox v-model=\"form.isTree\" />\n                        </el-tooltip>\n                        <el-input\n                            v-model=\"form.treeJson\"\n                            :disabled=\"!form.isTree\"\n                            placeholder=\"前端展示json属性\"\n                            class=\"flex-1\"\n                        />\n                      </div>\n                    </el-form-item>\n                </el-col>\n              </el-row>\n            </div>\n          </div>\n        </el-collapse-item>\n      </el-collapse>\n    </div>\n    <!-- 组件列表 -->\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button\n          type=\"primary\"\n          @click=\"editAndAddField()\"\n          :disabled=\"form.onlyTemplate\"\n        >\n          新增字段\n        </el-button>\n      </div>\n      <div class=\"draggable\">\n        <el-table :data=\"form.fields\" row-key=\"fieldName\">\n          <el-table-column\n            v-if=\"!isAdd\"\n            fixed=\"left\"\n            align=\"left\"\n            type=\"index\"\n            width=\"60\"\n          >\n            <template #default>\n              <el-icon class=\"cursor-grab drag-column\">\n                <MoreFilled />\n              </el-icon>\n            </template>\n          </el-table-column>\n          <el-table-column\n            fixed=\"left\"\n            align=\"left\"\n            type=\"index\"\n            label=\"序列\"\n            width=\"60\"\n          />\n          <el-table-column\n            fixed=\"left\"\n            align=\"left\"\n            type=\"index\"\n            label=\"主键\"\n            width=\"60\"\n          >\n            <template #default=\"{ row }\">\n              <el-checkbox :disabled=\"row.disabled\" v-model=\"row.primaryKey\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            fixed=\"left\"\n            align=\"left\"\n            prop=\"fieldName\"\n            label=\"字段名称\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-input disabled v-model=\"row.fieldName\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"fieldDesc\"\n            label=\"中文名\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-input :disabled=\"row.disabled\" v-model=\"row.fieldDesc\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"defaultValue\"\n            label=\"默认值\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-input :disabled=\"row.disabled\" v-model=\"row.defaultValue\" />\n            </template>\n          </el-table-column>\n          <el-table-column align=\"left\" prop=\"require\" label=\"必填\">\n            <template #default=\"{ row }\">\n              <el-checkbox :disabled=\"row.disabled\" v-model=\"row.require\" />\n            </template>\n          </el-table-column>\n          <el-table-column align=\"left\" prop=\"sort\" label=\"排序\">\n            <template #default=\"{ row }\">\n              <el-checkbox :disabled=\"row.disabled\" v-model=\"row.sort\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"form\"\n            width=\"100\"\n            label=\"新建/编辑\"\n          >\n            <template #default=\"{ row }\">\n              <el-checkbox :disabled=\"row.disabled\" v-model=\"row.form\" />\n            </template>\n          </el-table-column>\n          <el-table-column align=\"left\" prop=\"table\" label=\"表格\">\n            <template #default=\"{ row }\">\n              <el-checkbox :disabled=\"row.disabled\" v-model=\"row.table\" />\n            </template>\n          </el-table-column>\n          <el-table-column align=\"left\" prop=\"desc\" label=\"详情\">\n            <template #default=\"{ row }\">\n              <el-checkbox :disabled=\"row.disabled\" v-model=\"row.desc\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"excel\"\n            width=\"100\"\n            label=\"导入/导出\"\n            v-if=\"!isAdd\"\n          >\n            <template #default=\"{ row }\">\n              <el-checkbox v-model=\"row.excel\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"fieldJson\"\n            width=\"160px\"\n            label=\"字段Json\"\n          >\n            <template #default=\"{ row }\">\n              <el-input :disabled=\"row.disabled\" v-model=\"row.fieldJson\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"fieldType\"\n            label=\"字段类型\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-select\n                v-model=\"row.fieldType\"\n                style=\"width: 100%\"\n                placeholder=\"请选择字段类型\"\n                :disabled=\"row.disabled\"\n                clearable\n              >\n                <el-option\n                  v-for=\"item in typeOptions\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"fieldIndexType\"\n            label=\"索引类型\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-select\n                v-model=\"row.fieldIndexType\"\n                style=\"width: 100%\"\n                placeholder=\"请选择字段索引类型\"\n                :disabled=\"row.disabled\"\n                clearable\n              >\n                <el-option\n                  v-for=\"item in typeIndexOptions\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"dataTypeLong\"\n            label=\"字段长度/枚举值\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-input :disabled=\"row.disabled\" v-model=\"row.dataTypeLong\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"columnName\"\n            label=\"数据库字段\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-input :disabled=\"row.disabled\" v-model=\"row.columnName\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"comment\"\n            label=\"数据库字段描述\"\n            width=\"160\"\n          >\n            <template #default=\"{ row }\">\n              <el-input :disabled=\"row.disabled\" v-model=\"row.comment\" />\n            </template>\n          </el-table-column>\n          <el-table-column\n            align=\"left\"\n            prop=\"fieldSearchType\"\n            label=\"搜索条件\"\n            width=\"130\"\n          >\n            <template #default=\"{ row }\">\n              <el-select\n                v-model=\"row.fieldSearchType\"\n                style=\"width: 100%\"\n                placeholder=\"请选择字段查询条件\"\n                clearable\n                :disabled=\"row.fieldType === 'json' || row.disabled\"\n              >\n                <el-option\n                  v-for=\"item in typeSearchOptions\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                  :disabled=\"canSelect(row.fieldType,item.value)\"\n                />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column align=\"left\" label=\"操作\" width=\"300\" fixed=\"right\">\n            <template #default=\"scope\">\n              <el-button\n                v-if=\"!scope.row.disabled\"\n                type=\"primary\"\n                link\n                icon=\"edit\"\n                @click=\"editAndAddField(scope.row)\"\n              >\n                高级编辑\n              </el-button>\n              <el-button\n                v-if=\"!scope.row.disabled\"\n                type=\"primary\"\n                link\n                icon=\"delete\"\n                @click=\"deleteField(scope.$index)\"\n              >\n                删除\n              </el-button>\n            </template>\n          </el-table-column>\n        </el-table>\n      </div>\n      <!-- 组件列表 -->\n      <div class=\"gva-btn-list justify-end mt-4\">\n        <el-button type=\"primary\" :disabled=\"isAdd\" @click=\"exportJson()\">\n          导出json\n        </el-button>\n        <el-upload\n          class=\"flex items-center\"\n          :before-upload=\"importJson\"\n          :show-file-list=\"false\"\n          :headers=\"{'x-token': token}\"\n          accept=\".json\"\n        >\n          <el-button type=\"primary\" class=\"mx-2\" :disabled=\"isAdd\"\n            >导入json</el-button\n          >\n        </el-upload>\n        <el-button type=\"primary\" :disabled=\"isAdd\" @click=\"clearCatch()\">\n          清除暂存\n        </el-button>\n        <el-button type=\"primary\" :disabled=\"isAdd\" @click=\"catchData()\">\n          暂存\n        </el-button>\n        <el-button type=\"primary\" :disabled=\"isAdd\" @click=\"enterForm(false)\">\n          生成代码\n        </el-button>\n        <el-button type=\"primary\" @click=\"enterForm(true)\">\n          {{ isAdd ? '查看代码' : '预览代码' }}\n        </el-button>\n      </div>\n    </div>\n    <!-- 组件弹窗 -->\n    <el-drawer v-model=\"dialogFlag\" size=\"70%\" :show-close=\"false\">\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">组件内容</span>\n          <div>\n            <el-button @click=\"closeDialog\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"enterDialog\"> 确 定 </el-button>\n          </div>\n        </div>\n      </template>\n\n      <FieldDialog\n        v-if=\"dialogFlag\"\n        ref=\"fieldDialogNode\"\n        :dialog-middle=\"dialogMiddle\"\n        :type-options=\"typeOptions\"\n        :type-search-options=\"typeSearchOptions\"\n        :type-index-options=\"typeIndexOptions\"\n      />\n    </el-drawer>\n\n    <el-drawer v-model=\"previewFlag\" size=\"80%\" :show-close=\"false\">\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">操作栏</span>\n          <div>\n            <el-button type=\"primary\" @click=\"selectText\"> 全选 </el-button>\n            <el-button type=\"primary\" @click=\"copy\"> 复制 </el-button>\n          </div>\n        </div>\n      </template>\n      <PreviewCodeDialog\n        v-if=\"previewFlag\"\n        :is-add=\"isAdd\"\n        ref=\"previewNode\"\n        :preview-code=\"preViewCode\"\n      />\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import FieldDialog from '@/view/systemTools/autoCode/component/fieldDialog.vue'\n  import PreviewCodeDialog from '@/view/systemTools/autoCode/component/previewCodeDialog.vue'\n  import {\n    toUpperCase,\n    toHump,\n    toSQLLine,\n    toLowerCase\n  } from '@/utils/stringFun'\n  import {\n    createTemp,\n    getDB,\n    getTable,\n    getColumn,\n    preview,\n    getMeta,\n    getPackageApi,\n    llmAuto\n  } from '@/api/autoCode'\n  import { getDict } from '@/utils/dictionary'\n  import { ref, watch, toRaw, onMounted, nextTick } from 'vue'\n  import { useRoute, useRouter } from 'vue-router'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import Sortable from 'sortablejs'\n  import { useUserStore } from \"@/pinia\";\n\n  const userStore = useUserStore()\n\n  const token = userStore.token\n\n  const handleFocus = () => {\n    document.addEventListener('keydown', handleKeydown);\n    document.addEventListener('paste', handlePaste);\n  }\n\n  const handleBlur = () => {\n    document.removeEventListener('keydown', handleKeydown);\n    document.removeEventListener('paste', handlePaste);\n  }\n\n  const handleKeydown = (event) => {\n    if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {\n      llmAutoFunc()\n    }\n  }\n\n  const handlePaste = (event) => {\n    const items = event.clipboardData.items;\n    for (let i = 0; i < items.length; i++) {\n      if (items[i].type.indexOf('image') !== -1) {\n        const file = items[i].getAsFile();\n        const reader = new FileReader();\n        reader.onload =async (e) => {\n          const base64String = e.target.result;\n          const res = await llmAuto({ _file_path: base64String,mode:\"eye\" })\n          if (res.code === 0) {\n            prompt.value = res.data.text\n            llmAutoFunc()\n          }\n        };\n        reader.readAsDataURL(file);\n      }\n    }\n  };\n\n  const getOnlyNumber = () => {\n    let randomNumber = ''\n    while (randomNumber.length < 16) {\n      randomNumber += Math.random().toString(16).substring(2)\n    }\n    return randomNumber.substring(0, 16)\n  }\n\n  const prompt = ref('')\n\n  const eyeFunc = async () => {\n    const input = document.createElement('input');\n    input.type = 'file';\n    input.accept = 'image/*';\n\n    input.onchange = (event) => {\n      const file = event.target.files[0];\n      if (file) {\n        const reader = new FileReader();\n        reader.onload = async (e) => {\n          const base64String = e.target.result;\n\n          const res = await llmAuto({ _file_path: base64String,mode:'eye' })\n          if (res.code === 0) {\n            prompt.value = res.data.text\n            llmAutoFunc()\n          }\n        };\n        reader.readAsDataURL(file);\n      }\n    };\n\n    input.click();\n  }\n\n\n  const llmAutoFunc = async (flag) => {\n    if (flag && !form.value.structName) {\n      ElMessage.error('请输入结构体名称')\n      return\n    }\n    if (!flag && !prompt.value) {\n      ElMessage.error('请输入描述')\n      return\n    }\n\n    if (form.value.fields.length > 0) {\n      const res = await ElMessageBox.confirm(\n        'AI生成会清空当前数据，是否继续?',\n        '提示',\n        {\n          confirmButtonText: '确定',\n          cancelButtonText: '取消',\n          type: 'warning'\n        }\n      )\n      if (res !== 'confirm') {\n        return\n      }\n    }\n\n    const res = await llmAuto({\n      prompt: flag ? '结构体名称为' + form.value.structName : prompt.value,\n      mode: \"ai\"\n    })\n    if (res.code === 0) {\n      form.value.fields = []\n      const json = JSON.parse(res.data.text)\n      json.fields?.forEach((item) => {\n        item.fieldName = toUpperCase(item.fieldName)\n      })\n\n      for (let key in json) {\n        form.value[key] = json[key]\n      }\n\n      form.value.generateServer = true\n      form.value.generateWeb = true\n\n    }\n  }\n\n  const isAdd = ref(false)\n\n  // 行拖拽\n  const rowDrop = () => {\n    // 要拖拽元素的父容器\n    const tbody = document.querySelector(\n      '.draggable .el-table__body-wrapper tbody'\n    )\n    Sortable.create(tbody, {\n      //  可被拖拽的子元素\n      draggable: '.draggable .el-table__row',\n      handle: '.drag-column',\n      onEnd: async ({ newIndex, oldIndex }) => {\n        await nextTick()\n        const currRow = form.value.fields.splice(oldIndex, 1)[0]\n        form.value.fields.splice(newIndex, 0, currRow)\n      }\n    })\n  }\n\n  onMounted(() => {\n    rowDrop()\n  })\n\n  defineOptions({\n    name: 'AutoCode'\n  })\n  const gormModelList = ['id', 'created_at', 'updated_at', 'deleted_at']\n\n  const dataModelList = ['created_by', 'updated_by', 'deleted_by']\n\n  const typeOptions = ref([\n    {\n      label: '字符串',\n      value: 'string'\n    },\n    {\n      label: '富文本',\n      value: 'richtext'\n    },\n    {\n      label: '整型',\n      value: 'int'\n    },\n    {\n      label: '布尔值',\n      value: 'bool'\n    },\n    {\n      label: '浮点型',\n      value: 'float64'\n    },\n    {\n      label: '时间',\n      value: 'time.Time'\n    },\n    {\n      label: '枚举',\n      value: 'enum'\n    },\n    {\n      label: '单图片（字符串）',\n      value: 'picture'\n    },\n    {\n      label: '多图片（json字符串）',\n      value: 'pictures'\n    },\n    {\n      label: '视频（字符串）',\n      value: 'video'\n    },\n    {\n      label: '文件（json字符串）',\n      value: 'file'\n    },\n    {\n      label: 'JSON',\n      value: 'json'\n    },\n    {\n      label: '数组',\n      value: 'array'\n    }\n  ])\n\n  const typeSearchOptions = ref([\n    {\n      label: '=',\n      value: '='\n    },\n    {\n      label: '<>',\n      value: '<>'\n    },\n    {\n      label: '>',\n      value: '>'\n    },\n    {\n      label: '<',\n      value: '<'\n    },\n    {\n      label: 'LIKE',\n      value: 'LIKE'\n    },\n    {\n      label: 'BETWEEN',\n      value: 'BETWEEN'\n    },\n    {\n      label: 'NOT BETWEEN',\n      value: 'NOT BETWEEN'\n    }\n  ])\n\n  const typeIndexOptions = ref([\n    {\n      label: 'index',\n      value: 'index'\n    },\n    {\n      label: 'uniqueIndex',\n      value: 'uniqueIndex'\n    }\n  ])\n\n  const fieldTemplate = {\n    fieldName: '',\n    fieldDesc: '',\n    fieldType: '',\n    dataType: '',\n    fieldJson: '',\n    columnName: '',\n    dataTypeLong: '',\n    comment: '',\n    defaultValue: '',\n    require: false,\n    sort: false,\n    form: true,\n    desc: true,\n    table: true,\n    excel: false,\n    errorText: '',\n    primaryKey: false,\n    clearable: true,\n    fieldSearchType: '',\n    fieldIndexType: '',\n    dictType: '',\n    dataSource: {\n      dbName: '',\n      association: 1,\n      table: '',\n      label: '',\n      value: '',\n      hasDeletedAt: false\n    }\n  }\n  const route = useRoute()\n  const router = useRouter()\n  const preViewCode = ref({})\n  const dbform = ref({\n    businessDB: '',\n    dbName: '',\n    tableName: ''\n  })\n  const tableOptions = ref([])\n  const addFlag = ref('')\n  const fdMap = ref({})\n  const form = ref({\n    structName: '',\n    tableName: '',\n    packageName: '',\n    package: '',\n    abbreviation: '',\n    description: '',\n    businessDB: '',\n    autoCreateApiToSql: true,\n    autoCreateMenuToSql: true,\n    autoCreateBtnAuth: false,\n    autoMigrate: true,\n    gvaModel: true,\n    autoCreateResource: false,\n    onlyTemplate: false,\n    isTree: false,\n    generateWeb:true,\n    generateServer:true,\n    treeJson: \"\",\n    fields: []\n  })\n  const rules = ref({\n    structName: [\n      { required: true, message: '请输入结构体名称', trigger: 'blur' }\n    ],\n    abbreviation: [\n      { required: true, message: '请输入结构体简称', trigger: 'blur' }\n    ],\n    description: [\n      { required: true, message: '请输入结构体描述', trigger: 'blur' }\n    ],\n    packageName: [\n      {\n        required: true,\n        message: '文件名称：sysXxxxXxxx',\n        trigger: 'blur'\n      }\n    ],\n    package: [{ required: true, message: '请选择package', trigger: 'blur' }]\n  })\n  const dialogMiddle = ref({})\n  const bk = ref({})\n  const dialogFlag = ref(false)\n  const previewFlag = ref(false)\n\n  const useGva = (e) => {\n    if (e && form.value.fields.length) {\n      ElMessageBox.confirm(\n        '如果您开启GVA默认结构，会自动添加ID,CreatedAt,UpdatedAt,DeletedAt字段，此行为将自动清除您目前在下方创建的重名字段，是否继续？',\n        '注意',\n        {\n          confirmButtonText: '继续',\n          cancelButtonText: '取消',\n          type: 'warning'\n        }\n      )\n        .then(() => {\n          form.value.fields = form.value.fields.filter(\n            (item) =>\n              !gormModelList.some((gormfd) => gormfd === item.columnName)\n          )\n        })\n        .catch(() => {\n          form.value.gvaModel = false\n        })\n    }\n  }\n\n  const toLowerCaseFunc = (form, key) => {\n    form[key] = toLowerCase(form[key])\n  }\n  const previewNode = ref(null)\n  const selectText = () => {\n    previewNode.value.selectText()\n  }\n  const copy = () => {\n    previewNode.value.copy()\n  }\n  const editAndAddField = (item) => {\n    dialogFlag.value = true\n    if (item) {\n      addFlag.value = 'edit'\n      if (!item.dataSource) {\n        item.dataSource = {\n          dbName: '',\n          association: 1,\n          table: '',\n          label: '',\n          value: '',\n          hasDeletedAt: false\n        }\n      }\n      bk.value = JSON.parse(JSON.stringify(item))\n      dialogMiddle.value = item\n    } else {\n      addFlag.value = 'add'\n      fieldTemplate.onlyNumber = getOnlyNumber()\n      dialogMiddle.value = JSON.parse(JSON.stringify(fieldTemplate))\n    }\n  }\n\n  const fieldDialogNode = ref(null)\n  const enterDialog = () => {\n    fieldDialogNode.value.fieldDialogForm.validate((valid) => {\n      if (valid) {\n        dialogMiddle.value.fieldName = toUpperCase(dialogMiddle.value.fieldName)\n        if (addFlag.value === 'add') {\n          form.value.fields.push(dialogMiddle.value)\n        }\n        dialogFlag.value = false\n      } else {\n        return false\n      }\n    })\n  }\n  const closeDialog = () => {\n    if (addFlag.value === 'edit') {\n      dialogMiddle.value = bk.value\n    }\n    dialogFlag.value = false\n  }\n  const deleteField = (index) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      form.value.fields.splice(index, 1)\n    })\n  }\n  const autoCodeForm = ref(null)\n  const enterForm = async (isPreview) => {\n    if (form.value.isTree && !form.value.treeJson){\n      ElMessage({\n        type: 'error',\n        message: '请填写树型结构的前端展示json属性'\n      })\n      return false\n    }\n    if(!form.value.generateWeb && !form.value.generateServer){\n      ElMessage({\n        type: 'error',\n        message: '请至少选择一个生成项'\n      })\n      return false\n    }\n    if (!form.value.onlyTemplate) {\n      if (form.value.fields.length <= 0) {\n        ElMessage({\n          type: 'error',\n          message: '请填写至少一个field'\n        })\n        return false\n      }\n\n      if (\n        !form.value.gvaModel &&\n        form.value.fields.every((item) => !item.primaryKey)\n      ) {\n        ElMessage({\n          type: 'error',\n          message: '您至少需要创建一个主键才能保证自动化代码的可行性'\n        })\n        return false\n      }\n\n      if (\n        form.value.fields.some(\n          (item) => item.fieldName === form.value.structName\n        )\n      ) {\n        ElMessage({\n          type: 'error',\n          message: '存在与结构体同名的字段'\n        })\n        return false\n      }\n\n      if (\n        form.value.fields.some((item) => item.fieldJson === form.value.package)\n      ) {\n        ElMessage({\n          type: 'error',\n          message: '存在与模板同名的的字段JSON'\n        })\n        return false\n      }\n\n      if (form.value.fields.some((item) => !item.fieldType)) {\n        ElMessage({\n          type: 'error',\n          message: '请填写所有字段类型后进行提交'\n        })\n        return false\n      }\n\n      if (form.value.package === form.value.abbreviation) {\n        ElMessage({\n          type: 'error',\n          message: 'package和结构体简称不可同名'\n        })\n        return false\n      }\n    }\n\n    autoCodeForm.value.validate(async (valid) => {\n      if (valid) {\n        for (const key in form.value) {\n          if (typeof form.value[key] === 'string') {\n            form.value[key] = form.value[key].trim()\n          }\n        }\n        form.value.structName = toUpperCase(form.value.structName)\n        form.value.tableName = form.value.tableName.replace(' ', '')\n        if (!form.value.tableName) {\n          form.value.tableName = toSQLLine(toLowerCase(form.value.structName))\n        }\n        if (form.value.structName === form.value.abbreviation) {\n          ElMessage({\n            type: 'error',\n            message: 'structName和struct简称不能相同'\n          })\n          return false\n        }\n        form.value.humpPackageName = toSQLLine(form.value.packageName)\n\n        form.value.fields?.forEach((item) => {\n          item.fieldName = toUpperCase(item.fieldName)\n          if (item.fieldType === 'enum') {\n            // 判断一下 item.dataTypeLong 按照,切割后的每个元素是否都使用 '' 包裹，如果没包 则修改为包裹起来的 然后再转为字符串赋值给 item.dataTypeLong\n            item.dataTypeLong = item.dataTypeLong.replace(/[\\[\\]{}()]/g, '')\n            const arr = item.dataTypeLong.split(',')\n            arr.forEach((ele, index) => {\n              if (ele.indexOf(\"'\") === -1) {\n                arr[index] = `'${ele}'`\n              }\n            })\n            item.dataTypeLong = arr.join(',')\n          }\n        })\n\n        delete form.value.primaryField\n        if (isPreview) {\n          const res = await preview({\n            ...form.value,\n            isAdd: !!isAdd.value,\n            fields: form.value.fields.filter((item) => !item.disabled)\n          })\n          if(res.code !== 0){\n            return\n          }\n          preViewCode.value = res.data.autoCode\n          previewFlag.value = true\n        } else {\n          const res = await createTemp(form.value)\n          if (res.code !== 0) {\n            return\n          }\n          ElMessage({\n            type: 'success',\n            message: '自动化代码创建成功，自动移动成功'\n          })\n          clearCatch()\n        }\n      }\n    })\n  }\n\n  const dbList = ref([])\n  const dbOptions = ref([])\n\n  const getDbFunc = async () => {\n    dbform.value.dbName = ''\n    dbform.value.tableName = ''\n    const res = await getDB({ businessDB: dbform.value.businessDB })\n    if (res.code === 0) {\n      dbOptions.value = res.data.dbs\n      dbList.value = res.data.dbList\n    }\n  }\n  const getTableFunc = async () => {\n    const res = await getTable({\n      businessDB: dbform.value.businessDB,\n      dbName: dbform.value.dbName\n    })\n    if (res.code === 0) {\n      tableOptions.value = res.data.tables\n    }\n    dbform.value.tableName = ''\n  }\n\n  const getColumnFunc = async () => {\n    const res = await getColumn(dbform.value)\n    if (res.code === 0) {\n      let dbtype = ''\n      if (dbform.value.businessDB !== '') {\n        const dbtmp = dbList.value.find(\n          (item) => item.aliasName === dbform.value.businessDB\n        )\n        const dbraw = toRaw(dbtmp)\n        dbtype = dbraw.dbtype\n      }\n      form.value.gvaModel = false\n      const tbHump = toHump(dbform.value.tableName)\n      form.value.structName = toUpperCase(tbHump)\n      form.value.tableName = dbform.value.tableName\n      form.value.packageName = toLowerCase(tbHump)\n      form.value.abbreviation = toLowerCase(tbHump)\n      form.value.description = tbHump + '表'\n      form.value.autoCreateApiToSql = true\n      form.value.generateServer = true\n      form.value.generateWeb = true\n      form.value.fields = []\n      res.data.columns &&\n        res.data.columns.forEach((item) => {\n          if (needAppend(item)) {\n            const fbHump = toHump(item.columnName)\n            form.value.fields.push({\n              onlyNumber: getOnlyNumber(),\n              fieldName: toUpperCase(fbHump),\n              fieldDesc: item.columnComment || fbHump + '字段',\n              fieldType: fdMap.value[item.dataType],\n              dataType: item.dataType,\n              fieldJson: fbHump,\n              primaryKey: item.primaryKey,\n              dataTypeLong:\n                item.dataTypeLong && item.dataTypeLong.split(',')[0],\n              columnName:\n                dbtype === 'oracle'\n                  ? item.columnName.toUpperCase()\n                  : item.columnName,\n              comment: item.columnComment,\n              require: false,\n              errorText: '',\n              clearable: true,\n              fieldSearchType: '',\n              fieldIndexType: '',\n              dictType: '',\n              form: true,\n              table: true,\n              excel: false,\n              desc: true,\n              dataSource: {\n                dbName: '',\n                association: 1,\n                table: '',\n                label: '',\n                value: '',\n                hasDeletedAt: false\n              }\n            })\n          }\n        })\n    }\n  }\n\n  const needAppend = (item) => {\n    let isAppend = true\n    if (\n      form.value.gvaModel &&\n      gormModelList.some((gormfd) => gormfd === item.columnName)\n    ) {\n      isAppend = false\n    }\n    if (\n      form.value.autoCreateResource &&\n      dataModelList.some((datafd) => datafd === item.columnName)\n    ) {\n      isAppend = false\n    }\n    return isAppend\n  }\n\n  const setFdMap = async () => {\n    const fdTypes = ['string', 'int', 'bool', 'float64', 'time.Time']\n    fdTypes.forEach(async (fdtype) => {\n      const res = await getDict(fdtype)\n      res &&\n        res.forEach((item) => {\n          fdMap.value[item.label] = fdtype\n        })\n    })\n  }\n  const getAutoCodeJson = async (id) => {\n    const res = await getMeta({ id: Number(id) })\n    if (res.code === 0) {\n      const add = route.query.isAdd\n      isAdd.value = add\n      form.value = JSON.parse(res.data.meta)\n      if (isAdd.value) {\n        form.value.fields.forEach((item) => {\n          item.disabled = true\n        })\n      }\n    }\n  }\n\n  const pkgs = ref([])\n  const getPkgs = async () => {\n    const res = await getPackageApi()\n    if (res.code === 0) {\n      pkgs.value = res.data.pkgs\n    }\n  }\n\n  const goPkgs = () => {\n    router.push({ name: 'autoPkg' })\n  }\n\n  const init = () => {\n    getDbFunc()\n    setFdMap()\n    getPkgs()\n    const id = route.params.id\n    if (id) {\n      getAutoCodeJson(id)\n    }\n  }\n  init()\n\n  watch(()=>form.value.generateServer,()=>{\n    if(!form.value.generateServer){\n      form.value.autoCreateApiToSql = false\n      form.value.autoMigrate = false\n    }\n  })\n\n  watch(()=>form.value.generateWeb,()=>{\n    if(!form.value.generateWeb){\n      form.value.autoCreateMenuToSql = false\n      form.value.autoCreateBtnAuth = false\n    }\n  })\n\n  const catchData = () => {\n    window.sessionStorage.setItem('autoCode', JSON.stringify(form.value))\n    ElMessage.success('暂存成功')\n  }\n\n  const getCatch = () => {\n    const data = window.sessionStorage.getItem('autoCode')\n    if (data) {\n      form.value = JSON.parse(data)\n    }\n  }\n\n  const clearCatch = async () => {\n    form.value = {\n      structName: '',\n      tableName: '',\n      packageName: '',\n      package: '',\n      abbreviation: '',\n      description: '',\n      businessDB: '',\n      autoCreateApiToSql: true,\n      autoCreateMenuToSql: true,\n      autoCreateBtnAuth: false,\n      autoMigrate: true,\n      gvaModel: true,\n      autoCreateResource: false,\n      onlyTemplate: false,\n      isTree: false,\n      treeJson: \"\",\n      fields: []\n    }\n    await nextTick()\n    window.sessionStorage.removeItem('autoCode')\n  }\n\n  getCatch()\n\n  const exportJson = () => {\n    const dataStr = JSON.stringify(form.value, null, 2)\n    const blob = new Blob([dataStr], { type: 'application/json' })\n    const url = URL.createObjectURL(blob)\n    const a = document.createElement('a')\n    a.href = url\n    a.download = 'form_data.json'\n    document.body.appendChild(a)\n    a.click()\n    document.body.removeChild(a)\n    URL.revokeObjectURL(url)\n  }\n\n  const importJson = (file) => {\n    const reader = new FileReader()\n    reader.onload = (e) => {\n      try {\n        form.value = JSON.parse(e.target.result)\n        form.value.generateServer = true\n        form.value.generateWeb = true\n        ElMessage.success('JSON 文件导入成功')\n      } catch (_) {\n        ElMessage.error('无效的 JSON 文件')\n      }\n    }\n    reader.readAsText(file)\n    return false\n  }\n\n  watch(\n    () => form.value.onlyTemplate,\n    (val) => {\n      if (val) {\n        ElMessageBox.confirm(\n          '使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑',\n          '注意',\n          {\n            confirmButtonText: '继续',\n            cancelButtonText: '取消',\n            type: 'warning'\n          }\n        )\n          .then(() => {\n            form.value.fields = []\n          })\n          .catch(() => {\n            form.value.onlyTemplate = false\n          })\n      }\n    }\n  )\n\n  const canSelect = (fieldType,item) => {\n    if (fieldType === 'richtext') {\n      return item !== 'LIKE';\n    }\n\n    if (fieldType !== 'string' && item === 'LIKE') {\n      return true;\n    }\n\n    const nonNumericTypes = ['int', 'time.Time', 'float64'];\n    if (!nonNumericTypes.includes(fieldType) && ['BETWEEN', 'NOT BETWEEN'].includes(item)) {\n      return true;\n    }\n\n    return false;\n  }\n</script>\n\n<style>\n.no-border-collapse{\n  @apply border-none;\n  .el-collapse-item__header{\n    @apply border-none;\n  }\n  .el-collapse-item__wrap{\n    @apply border-none;\n  }\n  .el-collapse-item__content{\n    @apply pb-0;\n  }\n}\n</style>\n"
  },
  {
    "path": "web/src/view/systemTools/autoCode/mcp.vue",
    "content": "<template>\n  <div class=\"gva-form-box\">\n    <el-form :model=\"form\" ref=\"formRef\" label-width=\"100px\" :rules=\"rules\">\n      <el-form-item label=\"工具名称\" prop=\"name\">\n        <el-input v-model=\"form.name\" placeholder=\"例:CurrentTime\" />\n      </el-form-item>\n      <el-form-item label=\"工具描述\" prop=\"description\">\n        <el-input type=\"textarea\" v-model=\"form.description\" placeholder=\"请输入工具描述\" />\n      </el-form-item>\n      <el-form-item label=\"参数列表\">\n        <el-table :data=\"form.params\"  style=\"width: 100%\">\n          <el-table-column prop=\"name\" label=\"参数名\" width=\"120\">\n            <template #default=\"scope\">\n              <el-input v-model=\"scope.row.name\" placeholder=\"参数名\" />\n            </template>\n          </el-table-column>\n          <el-table-column prop=\"description\" label=\"描述\" min-width=\"180\">\n            <template #default=\"scope\">\n              <el-input v-model=\"scope.row.description\" placeholder=\"描述\" />\n            </template>\n          </el-table-column>\n          <el-table-column prop=\"type\" label=\"类型\" width=\"120\">\n            <template #default=\"scope\">\n              <el-select v-model=\"scope.row.type\" placeholder=\"类型\">\n                <el-option label=\"string\" value=\"string\" />\n                <el-option label=\"number\" value=\"number\" />\n                <el-option label=\"boolean\" value=\"boolean\" />\n                <el-option label=\"object\" value=\"object\" />\n                <el-option label=\"array\" value=\"array\" />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"默认值\" width=\"300\">\n            <template #default=\"scope\">\n              <el-input :disabled=\"scope.row.type === 'object'\" v-model=\"scope.row.default\" />\n            </template>\n          </el-table-column>\n          <el-table-column prop=\"required\" label=\"必填\" width=\"80\">\n            <template #default=\"scope\">\n              <el-checkbox v-model=\"scope.row.required\" />\n            </template>\n          </el-table-column>\n          <el-table-column label=\"操作\" width=\"80\">\n            <template #default=\"scope\">\n              <el-button type=\"text\" @click=\"removeParam(scope.$index)\">删除</el-button>\n            </template>\n          </el-table-column>\n        </el-table>\n      </el-form-item>\n      <div class=\"flex justify-end\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"addParam\" style=\"margin-top: 10px;\">添加参数</el-button>\n      </div>\n      <el-form-item label=\"返回参数\">\n        <el-table :data=\"form.response\" style=\"width: 100%\">\n          <el-table-column prop=\"type\" label=\"类型\" min-width=\"120\">\n            <template #default=\"scope\">\n              <el-select v-model=\"scope.row.type\" placeholder=\"类型\">\n                <el-option label=\"text\" value=\"text\" />\n                <el-option label=\"image\" value=\"image\" />\n              </el-select>\n            </template>\n          </el-table-column>\n          <el-table-column label=\"操作\" width=\"80\">\n            <template #default=\"scope\">\n              <el-button type=\"text\" @click=\"removeResponse(scope.$index)\">删除</el-button>\n            </template>\n          </el-table-column>\n        </el-table>\n      </el-form-item>\n      <div class=\"flex justify-end\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"addResponse\" style=\"margin-top: 10px;\">添加返回参数</el-button>\n      </div>\n\n      <div class=\"flex justify-end mt-8\">\n        <el-button type=\"primary\" @click=\"submit\">生成</el-button>\n      </div>\n    </el-form>\n  </div>\n</template>\n\n<script setup>\nimport { ref, reactive } from 'vue'\nimport { ElMessage } from 'element-plus'\nimport { mcp } from '@/api/autoCode'\n\ndefineOptions({\n  name: 'MCP'\n})\n\nconst formRef = ref(null)\nconst form = reactive({\n  name: '',\n  description: '',\n  type: '',\n  params: [],\n  response: []\n})\n\nconst rules = {\n  name: [{ required: true, message: '请输入工具名称', trigger: 'blur' }],\n  description: [{ required: true, message: '请输入工具描述', trigger: 'blur' }],\n  type: [{ required: true, message: '请选择类型', trigger: 'change' }]\n}\n\nfunction addParam() {\n  form.params.push({\n    name: '',\n    description: '',\n    type: '',\n    required: false\n  })\n}\n\nfunction removeParam(index) {\n  form.params.splice(index, 1)\n}\n\nfunction addResponse() {\n  form.response.push({\n    type: ''\n  })\n}\n\nfunction removeResponse(index) {\n  form.response.splice(index, 1)\n}\n\nfunction submit() {\n  formRef.value.validate(async (valid) => {\n    if (!valid) return\n    // 简单校验参数\n    for (const p of form.params) {\n      if (!p.name || !p.description || !p.type) {\n        ElMessage.error('请完善所有参数信息')\n        return\n      }\n    }\n    // 校验返回参数\n    for (const r of form.response) {\n      if (!r.type) {\n        ElMessage.error('请完善所有返回参数类型')\n        return\n      }\n    }\n      const res = await mcp(form)\n      if (res.code === 0) {\n        ElMessage.success(res.msg)\n      }\n  })\n}\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/autoCode/mcpTest.vue",
    "content": "<template>\n  <div class=\"p-2\">\n\n    <el-card class=\"mb-2\">\n      <template #header>\n        <div class=\"flex justify-between items-center font-bold\">\n          <span>MCP 服务器配置示例</span>\n          <el-tooltip content=\"复制配置\" placement=\"top\">\n            <el-button :icon=\"DocumentCopy\" circle @click=\"copyMcpConfig\" />\n          </el-tooltip>\n        </div>\n      </template>\n      <pre class=\"font-mono whitespace-pre-wrap break-words bg-gray-100 p-2.5 rounded text-gray-700\">{{ mcpServerConfig }}</pre>\n    </el-card>\n\n    \n    <el-row :gutter=\"8\">\n      <el-col v-for=\"tool in mcpTools\" :key=\"tool.name\" :xs=\"24\" :sm=\"12\" :md=\"12\" :lg=\"8\">\n        <el-card class=\"mb-5 min-h-[150px] flex flex-col overflow-hidden\">\n          <template #header>\n            <div class=\"flex justify-between items-center font-bold\">\n              <span>{{ tool.name }}</span>\n              <el-tooltip content=\"测试工具\" placement=\"top\">\n                <el-button :icon=\"VideoPlay\" circle @click=\"openTestDialog(tool)\" />\n              </el-tooltip>\n            </div>\n          </template>\n          <div class=\"text-sm mb-1\">{{ tool.description }}</div>\n          <div v-if=\"tool.inputSchema && tool.inputSchema.properties && Object.keys(tool.inputSchema.properties).length > 0\" class=\"mt-1 text-xs overflow-y-auto max-h-[100px] p-2 border-t border-gray-200 bg-gray-50 rounded-b\">\n            <p class=\"font-semibold mb-1 text-gray-700 flex items-center\">\n              <span class=\"mr-1 my-2\">参数列表</span>\n              <span class=\"text-xs text-gray-500\">({{ Object.keys(tool.inputSchema.properties).length }})</span>\n            </p>\n            <div class=\"space-y-2\">\n              <div v-for=\"(propDetails, propName) in tool.inputSchema.properties\" :key=\"propName\" class=\"flex flex-col p-1.5 bg-white rounded border border-gray-100 hover:border-gray-300 transition-colors\">\n                <div class=\"flex items-center justify-between\">\n                  <div class=\"flex items-center\">\n                    <span class=\"font-medium text-gray-800\">{{ propName }}</span>\n                    <span v-if=\"tool.inputSchema.required && tool.inputSchema.required.includes(propName)\" class=\"ml-1 text-red-500 text-xs\">*</span>\n                  </div>\n                  <span class=\"text-xs px-1.5 py-0.5 bg-blue-100 text-blue-700 rounded\">{{ propDetails.type }}</span>\n                </div>\n                <div class=\"text-gray-500 mt-0.5 text-xs line-clamp-2\" :title=\"propDetails.description || '无描述'\">\n                  {{ propDetails.description || '无描述' }}\n                </div>\n              </div>\n            </div>\n          </div>\n          <div v-else class=\"mt-1 text-xs p-2 border-t border-gray-200 bg-gray-50 rounded-b flex items-center justify-center\">\n            <span class=\"text-gray-500 italic py-3\">无输入参数</span>\n          </div>\n        </el-card>\n      </el-col>\n    </el-row>\n\n\n    <el-dialog\n      v-model=\"testDialogVisible\"\n      :title=\"currentTestingTool ? `${currentTestingTool.name} - 参数测试` : '参数测试'\"\n      width=\"60%\"\n      :before-close=\"handleCloseDialog\"\n    >\n      <el-form v-if=\"currentTestingTool\" :model=\"testParamsForm\" ref=\"testParamsFormRef\" label-width=\"120px\" label-position=\"top\" class=\"max-h-[calc(60vh-120px)] overflow-y-auto\">\n        <el-form-item\n          v-for=\"(propDetails, propName) in currentTestingTool.inputSchema.properties\"\n          :key=\"propName\"\n          :label=\"propDetails.description || propName\"\n          :prop=\"propName\"\n          :rules=\"currentTestingTool.inputSchema.required && currentTestingTool.inputSchema.required.includes(propName) ? [{ required: true, message: '请输入 ' + (propDetails.description || propName), trigger: 'blur' }] : []\"\n        >\n          <el-input \n            v-if=\"propDetails.type === 'string' && !propDetails.enum\" \n            v-model=\"testParamsForm[propName]\" \n            :placeholder=\"propDetails.description || '请输入' + propName\" \n          />\n          <el-input \n            v-else-if=\"propDetails.type === 'number'\" \n            v-model.number=\"testParamsForm[propName]\" \n            type=\"number\" \n            :placeholder=\"propDetails.description || '请输入数字' + propName\" \n          />\n          <el-select \n            v-else-if=\"propDetails.type === 'boolean'\" \n            v-model=\"testParamsForm[propName]\" \n            :placeholder=\"propDetails.description || '请选择'\"\n          >\n            <el-option label=\"True\" :value=\"true\" />\n            <el-option label=\"False\" :value=\"false\" />\n          </el-select>\n          <el-select \n            v-else-if=\"propDetails.type === 'string' && propDetails.enum\" \n            v-model=\"testParamsForm[propName]\" \n            :placeholder=\"propDetails.description || '请选择' + propName\"\n          >\n            <el-option v-for=\"enumValue in propDetails.enum\" :key=\"enumValue\" :label=\"enumValue\" :value=\"enumValue\" />\n          </el-select>\n          <el-input \n            v-else \n            type=\"textarea\" \n            v-model=\"testParamsForm[propName]\" \n            :placeholder=\"(propDetails.description || propName) + ' (请输入JSON格式)'\" \n            :autosize=\"{ minRows: 2, maxRows: 6 }\"\n          />\n        </el-form-item>\n      </el-form>\n      <div v-if=\"apiDialogResponse\" class=\"mt-5 p-[15px] border border-gray-200 rounded bg-gray-50\">\n        <h4 class=\"mt-0 mb-2.5 text-base\">API 返回结果:</h4>\n        <div v-if=\"typeof apiDialogResponse === 'string'\">\n          <pre class=\"bg-gray-100 p-2.5 rounded whitespace-pre-wrap break-words overflow-y-auto\">{{ apiDialogResponse }}</pre>\n        </div>\n        <div v-else-if=\"apiDialogResponse.type === 'image' && apiDialogResponse.content\">\n           <el-image\n              class=\"max-w-full max-h-[300px]\"\n              :src=\"apiDialogResponse.content\"\n              :preview-src-list=\"[apiDialogResponse.content]\"\n              fit=\"contain\"\n            />\n        </div>\n         <div v-else-if=\"apiDialogResponse.type === 'text' && apiDialogResponse.content\">\n          <pre class=\"bg-gray-100 p-2.5 rounded whitespace-pre-wrap break-words overflow-y-auto\">{{ apiDialogResponse.content }}</pre>\n        </div>\n        <div v-else>\n          <pre class=\"bg-gray-100 p-2.5 rounded whitespace-pre-wrap break-words overflow-y-auto\">{{ JSON.stringify(apiDialogResponse, null, 2) }}</pre>\n        </div>\n      </div>\n      <template #footer>\n        <span class=\"dialog-footer\">\n          <el-button @click=\"testDialogVisible = false\">取消</el-button>\n          <el-button type=\"primary\" @click=\"handleDialogTestTool\">测试</el-button>\n        </span>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\nimport { ref, reactive, onMounted } from 'vue'\nimport { ElMessage } from 'element-plus'\nimport { VideoPlay, DocumentCopy } from '@element-plus/icons-vue' // Added DocumentCopy\nimport { mcpList, mcpTest } from '@/api/autoCode'\n\ndefineOptions({\n  name: 'MCPTest'\n})\n\n\nconst mcpTools = ref([])\nconst testDialogVisible = ref(false)\nconst currentTestingTool = ref(null)\nconst testParamsForm = reactive({})\nconst testParamsFormRef = ref(null)\nconst apiDialogResponse = ref(null)\n\nconst mcpServerConfig = ref(JSON.stringify({\n  \"mcpServers\": {\n    \"gva\": {\n      \"url\": \"https://127.0.0.1/sse\"\n    }\n  }\n}, null, 2))\n\nconst fetchMcpTools = async () => {\n    const res = await mcpList()\n    if (res.code === 0 && res.data && res.data.list.tools) {\n      mcpTools.value = res.data.list.tools\n      mcpServerConfig.value = JSON.stringify(res.data.mcpServerConfig, null, 2)\n    } else {\n      ElMessage.error(res.msg || '获取工具列表失败或数据格式不正确')\n    }\n}\n\nonMounted(() => {\n  fetchMcpTools()\n})\n\nconst copyMcpConfig = async () => {\n  try {\n    await navigator.clipboard.writeText(mcpServerConfig.value)\n    ElMessage.success('配置已复制到剪贴板')\n  } catch (err) {\n    ElMessage.error('复制失败: ' + err)\n  }\n}\n\nconst openTestDialog = (tool) => {\n  currentTestingTool.value = tool\n  apiDialogResponse.value = null // 清空之前的API响应\n\n  // 重置并初始化表单数据\n  for (const key in testParamsForm) {\n    delete testParamsForm[key]\n  }\n  if (tool.inputSchema && tool.inputSchema.properties) {\n    Object.keys(tool.inputSchema.properties).forEach(propName => {\n      const propDetails = tool.inputSchema.properties[propName]\n      // 设置默认值，优先使用 schema 中的 default，否则根据类型给初始值\n      if (propDetails.default !== undefined) {\n        testParamsForm[propName] = propDetails.default\n      } else if (propDetails.type === 'boolean') {\n        testParamsForm[propName] = false\n      } else if (propDetails.type === 'number') {\n        testParamsForm[propName] = null // 或者 0\n      } else if (propDetails.type === 'object' || propDetails.type === 'array') {\n        testParamsForm[propName] = '' // 对象和数组类型，默认为空字符串，提示用户输入JSON\n      } else {\n        testParamsForm[propName] = ''\n      }\n    })\n  }\n  testDialogVisible.value = true\n  // 清除表单校验状态\n  if (testParamsFormRef.value) {\n    testParamsFormRef.value.clearValidate()\n  }\n}\n\nconst handleCloseDialog = (done) => {\n  apiDialogResponse.value = null\n  done()\n}\n\nconst handleDialogTestTool = async () => {\n  if (!currentTestingTool.value) {\n    ElMessage.warning('没有选中的测试工具')\n    return\n  }\n  if (testParamsFormRef.value) {\n    testParamsFormRef.value.validate(async (valid) => {\n      if (valid) {\n     \n          const toolName = currentTestingTool.value.name\n          const payload = { ...testParamsForm }\n          \n          if (currentTestingTool.value.inputSchema && currentTestingTool.value.inputSchema.properties) {\n            Object.keys(currentTestingTool.value.inputSchema.properties).forEach(propName => {\n              const propDetails = currentTestingTool.value.inputSchema.properties[propName]\n              if ((propDetails.type === 'object' || propDetails.type === 'array') && payload[propName] && typeof payload[propName] === 'string') {\n                try {\n                  payload[propName] = JSON.parse(payload[propName])\n                } catch (e) {\n                  ElMessage.error(`参数 ${propName} 的JSON格式无效: ${e.message}`)\n                  throw new Error(`参数 ${propName} JSON无效`); \n                }\n              }\n            })\n          }\n\n          const res = await mcpTest({\n            name:toolName,\n            arguments:payload\n          })\n          apiDialogResponse.value = res.data\n          if (res.code === 0) {\n            ElMessage.success('API调用成功')\n          }\n      }\n    })\n  }\n}\n\n</script>"
  },
  {
    "path": "web/src/view/systemTools/autoCode/picture.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n        href=\"https://plugin.gin-vue-admin.com/license\"\n        title=\"此功能只针对授权用户开放，点我【购买授权】\"\n    />\n    <div class=\"gva-search-box\">\n      <div class=\"text-xl mb-2 text-gray-600\">\n        AI前端工程师<a\n          class=\"text-blue-600 text-sm ml-4\"\n          href=\"https://plugin.gin-vue-admin.com/#/layout/userInfo/center\"\n          target=\"_blank\"\n      >获取AiPath</a\n      >\n      </div>\n      \n      <!-- 选项模式 -->\n      <div class=\"mb-4\">\n        <div class=\"mb-3\">\n          <div class=\"text-base font-medium mb-2\">页面用途</div>\n          <el-radio-group v-model=\"pageType\" class=\"mb-2\" @change=\"handlePageTypeChange\">\n            <el-radio label=\"企业官网\">企业官网</el-radio>\n            <el-radio label=\"电商页面\">电商页面</el-radio>\n            <el-radio label=\"个人博客\">个人博客</el-radio>\n            <el-radio label=\"产品介绍\">产品介绍</el-radio>\n            <el-radio label=\"活动落地页\">活动落地页</el-radio>\n            <el-radio label=\"其他\">其他</el-radio>\n          </el-radio-group>\n          <el-input v-if=\"pageType === '其他'\" v-model=\"pageTypeCustom\" placeholder=\"请输入页面用途\" class=\"w-full\" />\n        </div>\n        \n        <div class=\"mb-3\">\n          <div class=\"text-base font-medium mb-2\">主要内容板块</div>\n          <el-checkbox-group v-model=\"contentBlocks\" class=\"flex flex-wrap gap-2 mb-2\">\n            <el-checkbox label=\"Banner轮播图\">Banner轮播图</el-checkbox>\n            <el-checkbox label=\"产品/服务介绍\">产品/服务介绍</el-checkbox>\n            <el-checkbox label=\"功能特点展示\">功能特点展示</el-checkbox>\n            <el-checkbox label=\"客户案例\">客户案例</el-checkbox>\n            <el-checkbox label=\"团队介绍\">团队介绍</el-checkbox>\n            <el-checkbox label=\"联系表单\">联系表单</el-checkbox>\n            <el-checkbox label=\"新闻/博客列表\">新闻/博客列表</el-checkbox>\n            <el-checkbox label=\"价格表\">价格表</el-checkbox>\n            <el-checkbox label=\"FAQ/常见问题\">FAQ/常见问题</el-checkbox>\n            <el-checkbox label=\"用户评价\">用户评价</el-checkbox>\n            <el-checkbox label=\"数据统计\">数据统计</el-checkbox>\n            <el-checkbox label=\"商品列表\">商品列表</el-checkbox>\n            <el-checkbox label=\"商品卡片\">商品卡片</el-checkbox>\n            <el-checkbox label=\"购物车\">购物车</el-checkbox>\n            <el-checkbox label=\"结算页面\">结算页面</el-checkbox>\n            <el-checkbox label=\"订单跟踪\">订单跟踪</el-checkbox>\n            <el-checkbox label=\"商品分类\">商品分类</el-checkbox>\n            <el-checkbox label=\"热门推荐\">热门推荐</el-checkbox>\n            <el-checkbox label=\"限时特惠\">限时特惠</el-checkbox>\n            <el-checkbox label=\"其他\">其他</el-checkbox>\n          </el-checkbox-group>\n          <el-input v-if=\"contentBlocks.includes('其他')\" v-model=\"contentBlocksCustom\" placeholder=\"请输入其他内容板块\" class=\"w-full\" />\n        </div>\n        \n        <div class=\"mb-3\">\n          <div class=\"text-base font-medium mb-2\">风格偏好</div>\n          <el-radio-group v-model=\"stylePreference\" class=\"mb-2\">\n            <el-radio label=\"简约\">简约</el-radio>\n            <el-radio label=\"科技感\">科技感</el-radio>\n            <el-radio label=\"温馨\">温馨</el-radio>\n            <el-radio label=\"专业\">专业</el-radio>\n            <el-radio label=\"创意\">创意</el-radio>\n            <el-radio label=\"复古\">复古</el-radio>\n            <el-radio label=\"奢华\">奢华</el-radio>\n            <el-radio label=\"其他\">其他</el-radio>\n          </el-radio-group>\n          <el-input v-if=\"stylePreference === '其他'\" v-model=\"stylePreferenceCustom\" placeholder=\"请输入风格偏好\" class=\"w-full\" />\n        </div>\n        \n        <div class=\"mb-3\">\n          <div class=\"text-base font-medium mb-2\">设计布局</div>\n          <el-radio-group v-model=\"layoutDesign\" class=\"mb-2\">\n            <el-radio label=\"单栏布局\">单栏布局</el-radio>\n            <el-radio label=\"双栏布局\">双栏布局</el-radio>\n            <el-radio label=\"三栏布局\">三栏布局</el-radio>\n            <el-radio label=\"网格布局\">网格布局</el-radio>\n            <el-radio label=\"画廊布局\">画廊布局</el-radio>\n            <el-radio label=\"瀑布流\">瀑布流</el-radio>\n            <el-radio label=\"卡片式\">卡片式</el-radio>\n            <el-radio label=\"侧边栏+内容布局\">侧边栏+内容布局</el-radio>\n            <el-radio label=\"分屏布局\">分屏布局</el-radio>\n            <el-radio label=\"全屏滚动布局\">全屏滚动布局</el-radio>\n            <el-radio label=\"混合布局\">混合布局</el-radio>\n            <el-radio label=\"响应式\">响应式</el-radio>\n            <el-radio label=\"其他\">其他</el-radio>\n          </el-radio-group>\n          <el-input v-if=\"layoutDesign === '其他'\" v-model=\"layoutDesignCustom\" placeholder=\"请输入设计布局\" class=\"w-full\" />\n        </div>\n        \n        <div class=\"mb-3\">\n          <div class=\"text-base font-medium mb-2\">配色方案</div>\n          <el-radio-group v-model=\"colorScheme\" class=\"mb-2\">\n            <el-radio label=\"蓝色系\">蓝色系</el-radio>\n            <el-radio label=\"绿色系\">绿色系</el-radio>\n            <el-radio label=\"红色系\">红色系</el-radio>\n            <el-radio label=\"黑白灰\">黑白灰</el-radio>\n            <el-radio label=\"纯黑白\">纯黑白</el-radio>\n            <el-radio label=\"暖色调\">暖色调</el-radio>\n            <el-radio label=\"冷色调\">冷色调</el-radio>\n            <el-radio label=\"其他\">其他</el-radio>\n          </el-radio-group>\n          <el-input v-if=\"colorScheme === '其他'\" v-model=\"colorSchemeCustom\" placeholder=\"请输入配色方案\" class=\"w-full\" />\n        </div>\n      </div>\n      \n      <!-- 详细描述输入框 -->\n      <div class=\"relative\">\n        <div class=\"text-base font-medium mb-2\">详细描述（可选）</div>\n        <el-input\n            v-model=\"prompt\"\n            :maxlength=\"2000\"\n            :placeholder=\"placeholder\"\n            :rows=\"5\"\n            resize=\"none\"\n            type=\"textarea\"\n            @blur=\"handleBlur\"\n            @focus=\"handleFocus\"\n        />\n        <div class=\"flex absolute right-2 bottom-2\">\n          <el-tooltip effect=\"light\">\n            <template #content>\n              <div>\n                此功能仅针对授权用户开放，前往<a\n                  class=\"text-blue-600\"\n                  href=\"https://plugin.gin-vue-admin.com/license\"\n                  target=\"_blank\"\n              >购买授权</a\n              >\n              </div>\n            </template>\n            <el-button\n                type=\"primary\"\n                @click=\"llmAutoFunc()\"\n            >\n              <el-icon size=\"18\">\n                <ai-gva/>\n              </el-icon>\n              生成\n            </el-button>\n          </el-tooltip>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div v-if=\"!outPut\">\n        <el-empty :image-size=\"200\"/>\n      </div>\n      <div v-if=\"outPut && htmlFromLLM\">\n        <el-tabs type=\"border-card\">\n          <el-tab-pane label=\"页面预览\">\n            <div class=\"h-[500px] overflow-auto bg-gray-50 p-4 rounded\">\n              <div v-if=\"!loadedComponents\" class=\"text-gray-500 text-center py-4\">\n                组件加载中...\n              </div>\n              <component\n                v-else\n                :is=\"loadedComponents\" \n                class=\"vue-component-container w-full\"\n              />\n            </div>\n          </el-tab-pane>\n          <el-tab-pane label=\"源代码\">\n            <div class=\"relative h-[500px] overflow-auto bg-gray-50 p-4 rounded\">\n              <el-button \n                type=\"primary\" \n                :icon=\"DocumentCopy\" \n                class=\"absolute top-2 right-2 px-2 py-1\" \n                @click=\"copySnippet(htmlFromLLM)\" \n                plain\n              >\n                复制\n              </el-button>\n              <pre class=\"mt-10 whitespace-pre-wrap\">{{ htmlFromLLM }}</pre>\n            </div>\n          </el-tab-pane>\n        </el-tabs>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport { llmAuto } from '@/api/autoCode'\nimport { ref, reactive, markRaw } from 'vue'\nimport * as Vue from \"vue\";\nimport WarningBar from '@/components/warningBar/warningBar.vue'\nimport { ElMessage } from 'element-plus'\nimport { defineAsyncComponent } from 'vue'\nimport { DocumentCopy } from '@element-plus/icons-vue'\nimport { loadModule } from \"vue3-sfc-loader\";\n\ndefineOptions({\n  name: 'Picture'\n})\n\nconst handleFocus = () => {\n  document.addEventListener('keydown', handleKeydown);\n}\n\nconst handleBlur = () => {\n  document.removeEventListener('keydown', handleKeydown);\n}\n\nconst handleKeydown = (event) => {\n  if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {\n    llmAutoFunc()\n  }\n}\n\n// 复制方法：把某个字符串写进剪贴板\nconst copySnippet = (vueString) => {\n  navigator.clipboard.writeText(vueString)\n      .then(() => {\n        ElMessage({\n          message: '复制成功',\n          type: 'success',\n        })\n      })\n      .catch(err => {\n        ElMessage({\n          message: '复制失败',\n          type: 'warning',\n        })\n      })\n}\n\n// 选项模式相关变量\nconst pageType = ref('企业官网')\nconst pageTypeCustom = ref('')\nconst contentBlocks = ref(['Banner轮播图', '产品/服务介绍'])\nconst contentBlocksCustom = ref('')\nconst stylePreference = ref('简约')\nconst stylePreferenceCustom = ref('')\nconst layoutDesign = ref('响应式')\nconst layoutDesignCustom = ref('')\nconst colorScheme = ref('蓝色系')\nconst colorSchemeCustom = ref('')\n\n// 页面用途与内容板块的推荐映射关系\nconst pageTypeContentMap = {\n  '企业官网': ['Banner轮播图', '产品/服务介绍', '功能特点展示', '客户案例', '联系表单'],\n  '电商页面': ['Banner轮播图', '商品列表', '商品卡片', '购物车', '商品分类', '热门推荐', '限时特惠', '结算页面', '用户评价'],\n  '个人博客': ['Banner轮播图', '新闻/博客列表', '用户评价', '联系表单'],\n  '产品介绍': ['Banner轮播图', '产品/服务介绍', '功能特点展示', '价格表', 'FAQ/常见问题'],\n  '活动落地页': ['Banner轮播图', '功能特点展示', '联系表单', '数据统计']\n}\n\nconst prompt = ref('')\n\n// 判断是否返回的标志\nconst outPut = ref(false)\n// 容纳llm返回的vue组件代码\nconst htmlFromLLM = ref(\"\")\n\n// 存储加载的组件\nconst loadedComponents = ref(null)\n\nconst loadVueComponent = async (vueCode) => {\n  try {\n    // 使用内存中的虚拟路径\n    const fakePath = `virtual:component-0.vue`\n    \n    const component = defineAsyncComponent({\n      loader: async () => {\n        try {\n          const options = {\n            moduleCache: {\n              vue: Vue,\n            },\n            getFile(url) {\n              // 处理所有可能的URL格式，包括相对路径、绝对路径等\n              // 提取路径的最后部分，忽略查询参数\n              const fileName = url.split('/').pop().split('?')[0]\n              const componentFileName = fakePath.split('/').pop()\n              \n              // 如果文件名包含我们的组件名称，或者url完全匹配fakePath\n              if (fileName === componentFileName || url === fakePath || \n                  url === `./component/0.vue`) {\n                return Promise.resolve({\n                  type: '.vue',\n                  getContentData: () => vueCode\n                })\n              }\n              \n              console.warn('请求未知文件:', url)\n              return Promise.reject(new Error(`找不到文件: ${url}`))\n            },\n            addStyle(textContent) {\n              // 不再将样式添加到document.head，而是返回样式内容\n              // 稍后会将样式添加到Shadow DOM中\n              return textContent\n            },\n            handleModule(type, source, path, options) {\n              // 默认处理器\n              return undefined\n            },\n            log(type, ...args) {\n              console.log(`[vue3-sfc-loader] [${type}]`, ...args)\n            }\n          }\n          \n          // 尝试加载组件\n          const comp = await loadModule(fakePath, options)\n          return comp.default || comp\n        } catch (error) {\n          console.error('组件加载详细错误:', error)\n          throw error\n        }\n      },\n      loadingComponent: {\n        template: '<div>加载中...</div>'\n      },\n      errorComponent: {\n        props: ['error'],\n        template: '<div>组件加载失败: {{ error && error.message }}</div>',\n        setup(props) {\n          console.error('错误组件收到的错误:', props.error)\n          return {}\n        }\n      },\n      // 添加超时和重试选项\n      timeout: 30000,\n      delay: 200,\n      suspensible: false,\n      onError(error, retry, fail) {\n        console.error('加载错误，细节:', error)\n        fail()\n      }\n    })\n\n    // 创建一个包装组件，使用Shadow DOM隔离样式\n    const ShadowWrapper = {\n      name: 'ShadowWrapper',\n      setup() {\n        return {}\n      },\n      render() {\n        return Vue.h('div', { class: 'shadow-wrapper' })\n      },\n      mounted() {\n        // 创建Shadow DOM\n        const shadowRoot = this.$el.attachShadow({ mode: 'open' })\n        \n        // 创建一个容器元素\n        const container = document.createElement('div')\n        container.className = 'shadow-container'\n        shadowRoot.appendChild(container)\n        \n        // 提取组件中的样式\n        const styleContent = vueCode.match(/<style[^>]*>([\\s\\S]*?)<\\/style>/i)?.[1] || ''\n        \n        // 创建样式元素并添加到Shadow DOM\n        if (styleContent) {\n          const style = document.createElement('style')\n          style.textContent = styleContent\n          shadowRoot.appendChild(style)\n        }\n        \n        // 创建Vue应用并挂载到Shadow DOM容器中\n        const app = Vue.createApp({\n          render: () => Vue.h(component)\n        })\n        app.mount(container)\n      }\n    }\n\n    loadedComponents.value = markRaw(ShadowWrapper)\n    return ShadowWrapper\n  } catch (error) {\n    console.error('组件创建总错误:', error)\n    return null\n  }\n}\n\n// 当页面用途改变时，更新内容板块的选择\nconst handlePageTypeChange = (value) => {\n  if (value !== '其他' && pageTypeContentMap[value]) {\n    contentBlocks.value = [...pageTypeContentMap[value]]\n  }\n}\n\nconst llmAutoFunc = async () => {\n  // 构建完整的描述，包含选项模式的选择\n  let fullPrompt = ''\n  \n  // 添加页面用途\n  fullPrompt += `页面用途: ${pageType.value === '其他' ? pageTypeCustom.value : pageType.value}\\n`\n  \n  // 添加内容板块\n  fullPrompt += '主要内容板块: '\n  const blocks = contentBlocks.value.filter(block => block !== '其他')\n  if (contentBlocksCustom.value) {\n    blocks.push(contentBlocksCustom.value)\n  }\n  fullPrompt += blocks.join(', ') + '\\n'\n  \n  // 添加风格偏好\n  fullPrompt += `风格偏好: ${stylePreference.value === '其他' ? stylePreferenceCustom.value : stylePreference.value}\\n`\n  \n  // 添加设计布局\n  fullPrompt += `设计布局: ${layoutDesign.value === '其他' ? layoutDesignCustom.value : layoutDesign.value}\\n`\n  \n  // 添加配色方案\n  fullPrompt += `配色方案: ${colorScheme.value === '其他' ? colorSchemeCustom.value : colorScheme.value}\\n`\n  \n  // 添加用户的详细描述\n  if (prompt.value) {\n    fullPrompt += `\\n详细描述: ${prompt.value}`\n  }\n  \n  const res = await llmAuto({web: fullPrompt, mode: 'createWeb'})\n  if (res.code === 0) {\n    outPut.value = true\n    // 添加返回的Vue组件代码到数组\n    htmlFromLLM.value = res.data.text\n    // 加载新生成的组件\n    await loadVueComponent(res.data.text)\n  }\n}\n\nconst placeholder = ref(`补充您对页面的其他要求或特殊需求，例如：特别强调的元素、参考网站、交互效果等。`)\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/autoCodeAdmin/index.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"goAutoCode(null)\">\n          新增\n        </el-button>\n      </div>\n      <el-table :data=\"tableData\">\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column align=\"left\" label=\"id\" width=\"60\" prop=\"ID\" />\n        <el-table-column align=\"left\" label=\"日期\" width=\"180\">\n          <template #default=\"scope\">\n            {{ formatDate(scope.row.CreatedAt) }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"结构体名\"\n          min-width=\"150\"\n          prop=\"structName\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"结构体描述\"\n          min-width=\"150\"\n          prop=\"description\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"表名称\"\n          min-width=\"150\"\n          prop=\"tableName\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"回滚标记\"\n          min-width=\"150\"\n          prop=\"flag\"\n        >\n          <template #default=\"scope\">\n            <el-tag v-if=\"scope.row.flag\" type=\"danger\" effect=\"dark\">\n              已回滚\n            </el-tag>\n            <el-tag v-else type=\"success\" effect=\"dark\"> 未回滚 </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"操作\" min-width=\"240\">\n          <template #default=\"scope\">\n            <div>\n              <el-button\n                type=\"primary\"\n                link\n                :disabled=\"scope.row.flag === 1\"\n                @click=\"addFuncBtn(scope.row)\"\n              >\n                增加方法\n              </el-button>\n              <el-button type=\"primary\" link @click=\"goAutoCode(scope.row, 1)\">\n                增加字段\n              </el-button>\n              <el-button\n                type=\"primary\"\n                link\n                :disabled=\"scope.row.flag === 1\"\n                @click=\"openDialog(scope.row)\"\n              >\n                回滚\n              </el-button>\n              <el-button type=\"primary\" link @click=\"goAutoCode(scope.row)\">\n                复用\n              </el-button>\n              <el-button type=\"primary\" link @click=\"deleteRow(scope.row)\">\n                删除\n              </el-button>\n            </div>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n    <el-dialog\n      v-model=\"dialogFormVisible\"\n      :title=\"dialogFormTitle\"\n      :before-close=\"closeDialog\"\n      width=\"600px\"\n    >\n      <el-form :inline=\"true\" :model=\"formData\" label-width=\"80px\">\n        <el-form-item label=\"选项：\">\n          <el-checkbox v-model=\"formData.deleteApi\" label=\"删除接口\" />\n          <el-checkbox v-model=\"formData.deleteMenu\" label=\"删除菜单\" />\n          <el-checkbox\n            v-model=\"formData.deleteTable\"\n            label=\"删除表\"\n            @change=\"deleteTableCheck\"\n          />\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"closeDialog\"> 取 消 </el-button>\n          <el-popconfirm\n            title=\"此操作将回滚生成文件和勾选项目, 是否继续?\"\n            @confirm=\"enterDialog\"\n          >\n            <template #reference>\n              <el-button type=\"primary\"> 确 定 </el-button>\n            </template>\n          </el-popconfirm>\n        </div>\n      </template>\n    </el-dialog>\n\n    <el-drawer\n      v-model=\"funcFlag\"\n      size=\"60%\"\n      :show-close=\"false\"\n      :close-on-click-modal=\"false\"\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">操作栏</span>\n          <div>\n            <el-button type=\"primary\" @click=\"runFunc\" :loading=\"aiLoading\">\n              生成\n            </el-button>\n            <el-button type=\"primary\" @click=\"closeFunc\" :loading=\"aiLoading\">\n              取消\n            </el-button>\n          </div>\n        </div>\n      </template>\n      <div class=\"\">\n        <el-form\n          v-loading=\"aiLoading\"\n          label-position=\"top\"\n          element-loading-text=\"小淼正在思考，请稍候...\"\n          :model=\"autoFunc\"\n          label-width=\"80px\"\n        >\n          <el-row :gutter=\"12\">\n            <el-col :span=\"8\">\n              <el-form-item label=\"包名：\">\n                <el-input\n                    v-model=\"autoFunc.package\"\n                    placeholder=\"请输入包名\"\n                    disabled\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"8\">\n              <el-form-item label=\"结构体名：\">\n                <el-input\n                    v-model=\"autoFunc.structName\"\n                    placeholder=\"请输入结构体名\"\n                    disabled\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"8\">\n              <el-form-item label=\"前端文件名：\">\n                <el-input\n                    v-model=\"autoFunc.packageName\"\n                    placeholder=\"请输入文件名\"\n                    disabled\n                />\n              </el-form-item>\n            </el-col>\n          </el-row>\n          <el-row :gutter=\"12\">\n            <el-col :span=\"8\">\n              <el-form-item label=\"后端文件名：\">\n                <el-input\n                    v-model=\"autoFunc.humpPackageName\"\n                    placeholder=\"请输入文件名\"\n                    disabled\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"8\">\n              <el-form-item label=\"描述：\">\n                <el-input\n                    v-model=\"autoFunc.description\"\n                    placeholder=\"请输入描述\"\n                    disabled\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"8\">\n              <el-form-item label=\"缩写：\">\n                <el-input\n                    v-model=\"autoFunc.abbreviation\"\n                    placeholder=\"请输入缩写\"\n                    disabled\n                />\n              </el-form-item>\n            </el-col>\n          </el-row>\n          <el-form-item label=\"是否AI填充：\">\n            <el-switch v-model=\"autoFunc.isAi\" />\n            <span class=\"text-sm text-red-600 p-2\"\n              >当前ai帮写存在不稳定因素，生成代码后请注意手动调整部分内容</span\n            >\n          </el-form-item>\n          <template v-if=\"autoFunc.isAi\">\n            <el-form-item label=\"Ai帮写:\">\n              <div class=\"relative w-full\">\n                <el-input\n                  type=\"textarea\"\n                  placeholder=\"AI帮写功能，输入提示信息，自动生成代码\"\n                  v-model=\"autoFunc.prompt\"\n                  :rows=\"5\"\n                  @input=\"autoFunc.router = autoFunc.router.replace(/\\//g, '')\"\n                />\n                <el-button\n                  @click=\"aiAddFunc\"\n                  type=\"primary\"\n                  class=\"absolute right-2 bottom-2\"\n                  ><ai-gva />帮写</el-button\n                >\n              </div>\n            </el-form-item>\n            <el-form-item label=\"Api方法:\">\n              <v-ace-editor\n                v-model:value=\"autoFunc.apiFunc\"\n                lang=\"golang\"\n                theme=\"github_dark\"\n                class=\"h-80 w-full\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Server方法:\">\n              <v-ace-editor\n                v-model:value=\"autoFunc.serverFunc\"\n                lang=\"golang\"\n                theme=\"github_dark\"\n                class=\"h-80 w-full\"\n              />\n            </el-form-item>\n            <el-form-item label=\"前端JSAPI方法:\">\n              <v-ace-editor\n                v-model:value=\"autoFunc.jsFunc\"\n                lang=\"javascript\"\n                theme=\"github_dark\"\n                class=\"h-80 w-full\"\n              />\n            </el-form-item>\n          </template>\n\n          <el-form-item label=\"方法介绍：\">\n            <div class=\"flex w-full gap-2\">\n              <el-input\n                class=\"flex-1\"\n                v-model=\"autoFunc.funcDesc\"\n                placeholder=\"请输入方法介绍\"\n              />\n              <el-button type=\"primary\" @click=\"autoComplete\"\n                ><ai-gva />补全</el-button\n              >\n            </div>\n          </el-form-item>\n          <el-form-item label=\"方法名：\">\n            <el-input\n              @blur=\"autoFunc.funcName = toUpperCase(autoFunc.funcName)\"\n              v-model=\"autoFunc.funcName\"\n              placeholder=\"请输入方法名\"\n            />\n          </el-form-item>\n          <el-form-item label=\"方法：\">\n            <el-select v-model=\"autoFunc.method\" placeholder=\"请选择方法\">\n              <el-option\n                v-for=\"item in ['GET', 'POST', 'PUT', 'DELETE']\"\n                :key=\"item\"\n                :label=\"item\"\n                :value=\"item\"\n              />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"是否鉴权：\">\n            <el-switch\n              v-model=\"autoFunc.isAuth\"\n              active-text=\"是\"\n              inactive-text=\"否\"\n            />\n          </el-form-item>\n          <el-form-item label=\"路由path:\">\n            <el-input\n              v-model=\"autoFunc.router\"\n              placeholder=\"路由path\"\n              @input=\"autoFunc.router = autoFunc.router.replace(/\\//g, '')\"\n            />\n            <div>\n              API路径: [{{ autoFunc.method }}] /{{ autoFunc.abbreviation }}/{{\n                autoFunc.router\n              }}\n            </div>\n          </el-form-item>\n        </el-form>\n      </div>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    getSysHistory,\n    rollback,\n    delSysHistory,\n    addFunc,\n    llmAuto\n  } from '@/api/autoCode.js'\n  import { useRouter } from 'vue-router'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { ref } from 'vue'\n  import { formatDate } from '@/utils/format'\n  import { toUpperCase } from '@/utils/stringFun'\n\n  import { VAceEditor } from 'vue3-ace-editor'\n  import 'ace-builds/src-noconflict/mode-javascript'\n  import 'ace-builds/src-noconflict/mode-golang'\n  import 'ace-builds/src-noconflict/theme-github_dark'\n\n  defineOptions({\n    name: 'AutoCodeAdmin'\n  })\n\n  const aiLoading = ref(false)\n\n  const formData = ref({\n    id: undefined,\n    deleteApi: true,\n    deleteMenu: true,\n    deleteTable: false\n  })\n\n  const router = useRouter()\n  const dialogFormVisible = ref(false)\n  const dialogFormTitle = ref('')\n\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n\n  const activeInfo = ref('')\n\n  const autoFunc = ref({\n    package: '',\n    funcName: '',\n    structName: '',\n    packageName: '',\n    description: '',\n    abbreviation: '',\n    humpPackageName: '',\n    businessDB: '',\n    method: '',\n    funcDesc: '',\n    isAuth: false,\n    isAi: false,\n    apiFunc: '',\n    serverFunc: '',\n    jsFunc: ''\n  })\n\n  const addFuncBtn = (row) => {\n    const req = JSON.parse(row.request)\n    activeInfo.value = row.request\n    autoFunc.value.package = req.package\n    autoFunc.value.structName = req.structName\n    autoFunc.value.packageName = req.packageName\n    autoFunc.value.description = req.description\n    autoFunc.value.abbreviation = req.abbreviation\n    autoFunc.value.humpPackageName = req.humpPackageName\n    autoFunc.value.businessDB = req.businessDB\n    autoFunc.value.method = ''\n    autoFunc.value.funcName = ''\n    autoFunc.value.router = ''\n    autoFunc.value.funcDesc = ''\n    autoFunc.value.isAuth = false\n    autoFunc.value.isAi = false\n    autoFunc.value.apiFunc = ''\n    autoFunc.value.serverFunc = ''\n    autoFunc.value.jsFunc = ''\n    funcFlag.value = true\n  }\n\n  const funcFlag = ref(false)\n\n  const closeFunc = () => {\n    funcFlag.value = false\n  }\n\n  const runFunc = async () => {\n    // 首字母自动转换为大写\n    autoFunc.value.funcName = toUpperCase(autoFunc.value.funcName)\n\n    if (!autoFunc.value.funcName) {\n      ElMessage.error('请输入方法名')\n      return\n    }\n    if (!autoFunc.value.method) {\n      ElMessage.error('请选择方法')\n      return\n    }\n    if (!autoFunc.value.router) {\n      ElMessage.error('请输入路由')\n      return\n    }\n    if (!autoFunc.value.funcDesc) {\n      ElMessage.error('请输入方法介绍')\n      return\n    }\n\n    if (autoFunc.value.isAi) {\n      if (\n        !autoFunc.value.apiFunc ||\n        !autoFunc.value.serverFunc ||\n        !autoFunc.value.jsFunc\n      ) {\n        ElMessage.error('请先使用AI帮写完成基础代码，如果生成失败请重新调用')\n        return\n      }\n    }\n\n    const res = await addFunc(autoFunc.value)\n    if (res.code === 0) {\n      ElMessage.success('增加方法成功')\n      closeFunc()\n    }\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getSysHistory({\n      page: page.value,\n      pageSize: pageSize.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  const deleteRow = async (row) => {\n    ElMessageBox.confirm('此操作将删除本历史, 是否继续?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const res = await delSysHistory({ id: Number(row.ID) })\n      if (res.code === 0) {\n        ElMessage.success('删除成功')\n        getTableData()\n      }\n    })\n  }\n\n  // 打开弹窗\n  const openDialog = (row) => {\n    dialogFormTitle.value = '回滚：' + row.structName\n    formData.value.id = row.ID\n    dialogFormVisible.value = true\n  }\n\n  // 关闭弹窗\n  const closeDialog = () => {\n    dialogFormVisible.value = false\n    formData.value = {\n      id: undefined,\n      deleteApi: true,\n      deleteMenu: true,\n      deleteTable: false\n    }\n  }\n\n  // 确认删除表\n  const deleteTableCheck = (flag) => {\n    if (flag) {\n      ElMessageBox.confirm(\n        `此操作将删除自动创建的文件和api（会删除表！！！）, 是否继续?`,\n        '提示',\n        {\n          closeOnClickModal: false,\n          distinguishCancelAndClose: true,\n          confirmButtonText: '确定',\n          cancelButtonText: '取消',\n          type: 'warning'\n        }\n      )\n        .then(() => {\n          ElMessageBox.confirm(\n            `此操作将删除自动创建的文件和api（会删除表！！！）, 请继续确认！！！`,\n            '会删除表',\n            {\n              closeOnClickModal: false,\n              distinguishCancelAndClose: true,\n              confirmButtonText: '确定',\n              cancelButtonText: '取消',\n              type: 'warning'\n            }\n          ).catch(() => {\n            formData.value.deleteTable = false\n          })\n        })\n        .catch(() => {\n          formData.value.deleteTable = false\n        })\n    }\n  }\n\n  const enterDialog = async () => {\n    const res = await rollback(formData.value)\n    if (res.code === 0) {\n      ElMessage.success('回滚成功')\n      getTableData()\n    }\n  }\n\n  const goAutoCode = (row, isAdd) => {\n    if (row) {\n      router.push({\n        name: 'autoCodeEdit',\n        params: {\n          id: row.ID\n        },\n        query: {\n          isAdd: isAdd\n        }\n      })\n    } else {\n      router.push({ name: 'autoCode' })\n    }\n  }\n\n  const aiAddFunc = async () => {\n    aiLoading.value = true\n    autoFunc.value.apiFunc = ''\n    autoFunc.value.serverFunc = ''\n    autoFunc.value.jsFunc = ''\n\n    if (!autoFunc.value.prompt) {\n      ElMessage.error('请输入提示信息')\n      return\n    }\n\n    const res = await addFunc({ ...autoFunc.value, isPreview: true })\n    if (res.code !== 0) {\n      aiLoading.value = false\n      ElMessage.error(res.msg)\n      return\n    }\n\n    const aiRes = await llmAuto({\n      structInfo: activeInfo.value,\n      template: JSON.stringify(res.data),\n      prompt: autoFunc.value.prompt,\n      mode: 'addFunc'\n    })\n    aiLoading.value = false\n    if (aiRes.code === 0) {\n      try {\n        const aiData = JSON.parse(aiRes.data)\n        autoFunc.value.apiFunc = aiData.api\n        autoFunc.value.serverFunc = aiData.server\n        autoFunc.value.jsFunc = aiData.js\n        autoFunc.value.method = aiData.method\n        autoFunc.value.funcName = aiData.funcName\n        const routerArr = aiData.router.split('/')\n        autoFunc.value.router = routerArr[routerArr.length - 1]\n        autoFunc.value.funcDesc = autoFunc.value.prompt\n      } catch (_) {\n        ElMessage.error('小淼忙碌，请重新调用')\n      }\n    }\n  }\n\n  const autoComplete = async () => {\n    aiLoading.value = true\n    const aiRes = await llmAuto({\n      prompt: autoFunc.value.funcDesc,\n      mode: 'autoCompleteFunc'\n    })\n    aiLoading.value = false\n    if (aiRes.code === 0) {\n      try {\n        const aiData = JSON.parse(aiRes.data)\n        autoFunc.value.method = aiData.method\n        autoFunc.value.funcName = aiData.funcName\n        autoFunc.value.router = aiData.router\n        autoFunc.value.prompt = autoFunc.value.funcDesc\n      } catch (_) {\n        ElMessage.error('小淼开小差了，请重新调用')\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/autoPkg/autoPkg.vue",
    "content": "<template>\n  <div>\n    <warning-bar\n      href=\"https://www.bilibili.com/video/BV1kv4y1g7nT?p=3\"\n      title=\"此功能为开发环境使用，不建议发布到生产，具体使用效果请看视频https://www.bilibili.com/video/BV1kv4y1g7nT?p=3\"\n    />\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list gap-3 flex items-center\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDialog('addApi')\">\n          新增\n        </el-button>\n      </div>\n      <el-table :data=\"tableData\">\n        <el-table-column align=\"left\" label=\"id\" width=\"120\" prop=\"ID\" />\n        <el-table-column\n          align=\"left\"\n          label=\"包名\"\n          width=\"150\"\n          prop=\"packageName\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"模板\"\n          width=\"150\"\n          prop=\"template\"\n        />\n        <el-table-column align=\"left\" label=\"展示名\" width=\"150\" prop=\"label\" />\n        <el-table-column\n          align=\"left\"\n          label=\"描述\"\n          min-width=\"150\"\n          prop=\"desc\"\n        />\n\n        <el-table-column align=\"left\" label=\"操作\" width=\"200\">\n          <template #default=\"scope\">\n            <el-button\n              icon=\"delete\"\n              type=\"primary\"\n              link\n              @click=\"deleteApiFunc(scope.row)\"\n            >\n              删除\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n    </div>\n\n    <el-drawer v-model=\"dialogFormVisible\" size=\"40%\" :show-close=\"false\">\n      <warning-bar\n        title=\"模板package会创建集成于项目本体中的代码包，模板plugin会创建插件包\"\n      />\n      <el-form ref=\"pkgForm\" :model=\"form\" :rules=\"rules\" label-width=\"80px\">\n        <el-form-item label=\"包名\" prop=\"packageName\">\n          <el-input v-model=\"form.packageName\" autocomplete=\"off\" />\n        </el-form-item>\n        <el-form-item label=\"模板\" prop=\"template\">\n          <el-select v-model=\"form.template\">\n            <el-option\n              v-for=\"template in templatesOptions\"\n              :label=\"template\"\n              :value=\"template\"\n              :key=\"template\"\n            />\n          </el-select>\n        </el-form-item>\n\n        <el-form-item label=\"展示名\" prop=\"label\">\n          <el-input v-model=\"form.label\" autocomplete=\"off\" />\n        </el-form-item>\n        <el-form-item label=\"描述\" prop=\"desc\">\n          <el-input v-model=\"form.desc\" autocomplete=\"off\" />\n        </el-form-item>\n      </el-form>\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">创建Package</span>\n          <div>\n            <el-button @click=\"closeDialog\"> 取 消 </el-button>\n            <el-button type=\"primary\" @click=\"enterDialog\"> 确 定 </el-button>\n          </div>\n        </div>\n      </template>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    createPackageApi,\n    getPackageApi,\n    deletePackageApi,\n    getTemplatesApi\n  } from '@/api/autoCode'\n  import { ref } from 'vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n\n  defineOptions({\n    name: 'AutoPkg'\n  })\n\n  const form = ref({\n    packageName: '',\n    template: '',\n    label: '',\n    desc: ''\n  })\n  const templatesOptions = ref([])\n\n  const getTemplates = async () => {\n    const res = await getTemplatesApi()\n    if (res.code === 0) {\n      templatesOptions.value = res.data\n    }\n  }\n\n  getTemplates()\n\n  const validateData = (rule, value, callback) => {\n    if (/[\\u4E00-\\u9FA5]/g.test(value)) {\n      callback(new Error('不能为中文'))\n    } else if (/^\\d+$/.test(value[0])) {\n      callback(new Error('不能够以数字开头'))\n    } else if (!/^[a-zA-Z0-9_]+$/.test(value)) {\n      callback(new Error('只能包含英文字母、数字和下划线'))\n    } else {\n      callback()\n    }\n  }\n\n  const rules = ref({\n    packageName: [\n      { required: true, message: '请输入包名', trigger: 'blur' },\n      { validator: validateData, trigger: 'blur' }\n    ],\n    template: [\n      { required: true, message: '请选择模板', trigger: 'change' },\n      { validator: validateData, trigger: 'blur' }\n    ]\n  })\n\n  const dialogFormVisible = ref(false)\n  const openDialog = () => {\n    dialogFormVisible.value = true\n  }\n\n  const closeDialog = () => {\n    dialogFormVisible.value = false\n    form.value = {\n      packageName: '',\n      template: '',\n      label: '',\n      desc: ''\n    }\n  }\n\n  const pkgForm = ref(null)\n  const enterDialog = async () => {\n    pkgForm.value.validate(async (valid) => {\n      if (valid) {\n        const res = await createPackageApi(form.value)\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '添加成功',\n            showClose: true\n          })\n        }\n        getTableData()\n        closeDialog()\n      }\n    })\n  }\n\n  const tableData = ref([])\n  const getTableData = async () => {\n    const table = await getPackageApi()\n    if (table.code === 0) {\n      tableData.value = table.data.pkgs\n    }\n  }\n\n  const deleteApiFunc = async (row) => {\n    ElMessageBox.confirm(\n      '此操作仅删除数据库中的pkg存储，后端相应目录结构请自行删除与数据库保持一致！',\n      '提示',\n      {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    ).then(async () => {\n      const res = await deletePackageApi(row)\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功!'\n        })\n        getTableData()\n      }\n    })\n  }\n\n  getTableData()\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/exportTemplate/code.js",
    "content": "export const getCode = (templateID) => {\n  return `<template>\n  <!-- 导出组件 -->\n  <ExportExcel templateId=\"${templateID}\" :condition=\"condition\" :limit=\"limit\" :offset=\"offset\" :order=\"order\" />\n\n  <!-- 导入组件 handleSuccess为导入成功后的回调函数 -->\n  <ImportExcel templateId=\"${templateID}\" @on-success=\"handleSuccess\" />\n\n  <!-- 导出模板 -->\n  <ExportTemplate templateId=\"${templateID}\" />\n</template>\n\n<script setup>\nimport { ref } from 'vue';\n// 导出组件\nimport ExportExcel from '@/components/exportExcel/exportExcel.vue';\n// 导入组件\nimport ImportExcel from '@/components/exportExcel/importExcel.vue';\n// 导出模板组件\nimport ExportTemplate from '@/components/exportExcel/exportTemplate.vue';\n\nconst condition = ref({}); // 查询条件\nconst limit = ref(10); // 最大条数限制\nconst offset = ref(0); // 偏移量\nconst order = ref('id desc'); // 排序条件\n\nconst handleSuccess = (res) => {\n  console.log(res);\n  // 导入成功的回调函数\n};\n</script>`\n}\n"
  },
  {
    "path": "web/src/view/systemTools/exportTemplate/exportTemplate.vue",
    "content": "<template>\n  <div>\n    <WarningBar\n      title=\"本功能提供同步的表格导出功能，大数据量的异步表格导出功能，可以选择点我定制\"\n      href=\"https://flipped-aurora.feishu.cn/docx/KwjxdnvatozgwIxGV0rcpkZSn4d\"\n    />\n    <div class=\"gva-search-box\">\n      <el-form\n        ref=\"elSearchFormRef\"\n        :inline=\"true\"\n        :model=\"searchInfo\"\n        class=\"demo-form-inline\"\n        :rules=\"searchRule\"\n        @keyup.enter=\"onSubmit\"\n      >\n        <el-form-item label=\"创建日期\" prop=\"createdAt\">\n          <template #label>\n            <span>\n              创建日期\n              <el-tooltip\n                content=\"搜索范围是开始日期（包含）至结束日期（不包含）\"\n              >\n                <el-icon><QuestionFilled /></el-icon>\n              </el-tooltip>\n            </span>\n          </template>\n          <el-date-picker\n            v-model=\"searchInfo.startCreatedAt\"\n            type=\"datetime\"\n            placeholder=\"开始日期\"\n            :disabled-date=\"\n              (time) =>\n                searchInfo.endCreatedAt\n                  ? time.getTime() > searchInfo.endCreatedAt.getTime()\n                  : false\n            \"\n          />\n          —\n          <el-date-picker\n            v-model=\"searchInfo.endCreatedAt\"\n            type=\"datetime\"\n            placeholder=\"结束日期\"\n            :disabled-date=\"\n              (time) =>\n                searchInfo.startCreatedAt\n                  ? time.getTime() < searchInfo.startCreatedAt.getTime()\n                  : false\n            \"\n          />\n        </el-form-item>\n        <el-form-item label=\"模板名称\" prop=\"name\">\n          <el-input v-model=\"searchInfo.name\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item label=\"表名称\" prop=\"tableName\">\n          <el-input v-model=\"searchInfo.tableName\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item label=\"模板标识\" prop=\"templateID\">\n          <el-input v-model=\"searchInfo.templateID\" placeholder=\"搜索条件\" />\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\"\n            >查询</el-button\n          >\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"primary\" icon=\"plus\" @click=\"openDialog\"\n          >新增</el-button\n        >\n\n        <el-button\n          icon=\"delete\"\n          style=\"margin-left: 10px\"\n          :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\"\n          >删除</el-button\n        >\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :data=\"tableData\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column align=\"left\" label=\"日期\" width=\"180\">\n          <template #default=\"scope\">{{\n            formatDate(scope.row.CreatedAt)\n          }}</template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"数据库\" width=\"120\">\n          <template #default=\"scope\">\n            <span>{{ scope.row.dbName || 'GVA库' }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column\n          align=\"left\"\n          label=\"模板标识\"\n          prop=\"templateID\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"模板名称\"\n          prop=\"name\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"表名称\"\n          prop=\"tableName\"\n          width=\"120\"\n        />\n        <el-table-column\n          align=\"left\"\n          label=\"模板信息\"\n          prop=\"templateInfo\"\n          min-width=\"120\"\n          show-overflow-tooltip\n        />\n        <el-table-column align=\"left\" label=\"操作\" min-width=\"280\">\n          <template #default=\"scope\">\n            <el-button\n                type=\"primary\"\n                link\n                icon=\"documentCopy\"\n                class=\"table-button\"\n                @click=\"copyFunc(scope.row)\"\n            >复制</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit-pen\"\n              class=\"table-button\"\n              @click=\"showCode(scope.row)\"\n              >代码和SQL预览</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"edit\"\n              class=\"table-button\"\n              @click=\"updateSysExportTemplateFunc(scope.row)\"\n              >变更</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteRow(scope.row)\"\n              >删除</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n    <el-drawer\n      v-model=\"dialogFormVisible\"\n      size=\"60%\"\n      :before-close=\"closeDialog\"\n      :title=\"type === 'create' ? '添加' : '修改'\"\n      :show-close=\"false\"\n      destroy-on-close\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">{{ type === 'create' ? '添加' : '修改' }}</span>\n          <div>\n            <el-button @click=\"closeDialog\">取 消</el-button>\n            <el-button type=\"primary\" @click=\"enterDialog\">确 定</el-button>\n          </div>\n        </div>\n      </template>\n\n      <el-form\n        ref=\"elFormRef\"\n        :model=\"formData\"\n        label-position=\"right\"\n        :rules=\"rule\"\n        label-width=\"100px\"\n        v-loading=\"aiLoading\"\n        element-loading-text=\"小淼正在思考...\"\n      >\n        <el-form-item label=\"业务库\" prop=\"dbName\">\n          <template #label>\n            <el-tooltip\n              content=\"注：需要提前到db-list自行配置多数据库，如未配置需配置后重启服务方可使用。若无法选择，请到config.yaml中设置disabled:false，选择导入导出的目标库。\"\n              placement=\"bottom\"\n              effect=\"light\"\n            >\n              <div>\n                业务库 <el-icon><QuestionFilled /></el-icon>\n              </div>\n            </el-tooltip>\n          </template>\n          <el-select\n            v-model=\"formData.dbName\"\n            clearable\n            @change=\"dbNameChange\"\n            placeholder=\"选择业务库\"\n          >\n            <el-option\n              v-for=\"item in dbList\"\n              :key=\"item.aliasName\"\n              :value=\"item.aliasName\"\n              :label=\"item.aliasName\"\n              :disabled=\"item.disable\"\n            >\n              <div>\n                <span>{{ item.aliasName }}</span>\n                <span style=\"float: right; color: #8492a6; font-size: 13px\">{{\n                  item.dbName\n                }}</span>\n              </div>\n            </el-option>\n          </el-select>\n        </el-form-item>\n\n        <el-form-item label=\"需用到的表\" prop=\"tables\">\n          <el-select\n            multiple\n            v-model=\"tables\"\n            clearable\n            placeholder=\"使用AI的情况下请选择\"\n          >\n            <el-option\n              v-for=\"item in tableOptions\"\n              :key=\"item.tableName\"\n              :label=\"item.tableName\"\n              :value=\"item.tableName\"\n            />\n          </el-select>\n        </el-form-item>\n\n        <el-form-item label=\"AI帮写:\" prop=\"ai\">\n          <div class=\"relative w-full\">\n            <el-input\n              type=\"textarea\"\n              v-model=\"prompt\"\n              :clearable=\"true\"\n              :rows=\"5\"\n              placeholder=\"试试描述你要做的导出功能让AI帮你完成，在此之前请选择你需要导出的表所在的业务库，如不做选择，则默认使用gva库\"\n            />\n            <el-button\n              class=\"absolute bottom-2 right-2\"\n              type=\"primary\"\n              @click=\"autoExport\"\n              ><el-icon><ai-gva /></el-icon>帮写</el-button\n            >\n          </div>\n        </el-form-item>\n\n        <el-form-item label=\"表名称:\" clearable prop=\"tableName\">\n          <div class=\"w-full flex gap-4\">\n            <el-select\n              v-model=\"formData.tableName\"\n              class=\"flex-1\"\n              filterable\n              placeholder=\"请选择表\"\n            >\n              <el-option\n                v-for=\"item in tableOptions\"\n                :key=\"item.tableName\"\n                :label=\"item.tableName\"\n                :value=\"item.tableName\"\n              />\n            </el-select>\n            <el-button\n              :disabled=\"!formData.tableName\"\n              type=\"primary\"\n              @click=\"getColumnFunc(true)\"\n              ><el-icon><ai-gva /></el-icon>自动补全</el-button\n            >\n            <el-button\n              :disabled=\"!formData.tableName\"\n              type=\"primary\"\n              @click=\"getColumnFunc(false)\"\n              >自动生成模板</el-button\n            >\n          </div>\n        </el-form-item>\n\n        <el-form-item label=\"模板名称:\" prop=\"name\">\n          <el-input\n            v-model=\"formData.name\"\n            :clearable=\"true\"\n            placeholder=\"请输入模板名称\"\n          />\n        </el-form-item>\n\n        <el-form-item label=\"模板标识:\" prop=\"templateID\">\n          <el-input\n            v-model=\"formData.templateID\"\n            :clearable=\"true\"\n            placeholder=\"模板标识为前端组件需要挂在的标识属性\"\n          />\n        </el-form-item>\n\n        <el-tabs v-model=\"activeName\">\n          <el-tab-pane label=\"自动构建\" name=\"auto\" class=\"pt-2\">\n            <el-form-item label=\"关联条件:\">\n              <div\n                v-for=\"(join, key) in formData.joinTemplate\"\n                :key=\"key\"\n                class=\"flex gap-4 w-full mb-2\"\n              >\n                <el-select v-model=\"join.joins\" placeholder=\"请选择关联方式\">\n                  <el-option label=\"LEFT JOIN\" value=\"LEFT JOIN\" />\n                  <el-option label=\"INNER JOIN\" value=\"INNER JOIN\" />\n                  <el-option label=\"RIGHT JOIN\" value=\"RIGHT JOIN\" />\n                </el-select>\n                <el-input v-model=\"join.table\" placeholder=\"请输入关联表\" />\n                <el-input\n                  v-model=\"join.on\"\n                  placeholder=\"关联条件 table1.a = table2.b\"\n                />\n                <el-button\n                  type=\"danger\"\n                  icon=\"delete\"\n                  @click=\"() => formData.joinTemplate.splice(key, 1)\"\n                  >删除</el-button\n                >\n              </div>\n              <div class=\"flex justify-end w-full\">\n                <el-button type=\"primary\" icon=\"plus\" @click=\"addJoin\"\n                  >添加条件</el-button\n                >\n              </div>\n            </el-form-item>\n\n            <el-form-item label=\"默认导出条数:\">\n              <el-input-number\n                v-model=\"formData.limit\"\n                :step=\"1\"\n                :step-strictly=\"true\"\n                :precision=\"0\"\n              />\n            </el-form-item>\n            <el-form-item label=\"默认排序条件:\">\n              <el-input v-model=\"formData.order\" placeholder=\"例:id desc\" />\n            </el-form-item>\n            <el-form-item label=\"导出条件:\">\n              <div\n                v-for=\"(condition, key) in formData.conditions\"\n                :key=\"key\"\n                class=\"flex gap-4 w-full mb-2\"\n              >\n                <el-input\n                  v-model=\"condition.from\"\n                  placeholder=\"需要从查询条件取的json key\"\n                />\n                <el-input v-model=\"condition.column\" placeholder=\"表对应的column\" />\n                <el-select\n                  v-model=\"condition.operator\"\n                  placeholder=\"请选择查询条件\"\n                >\n                  <el-option\n                    v-for=\"item in typeSearchOptions\"\n                    :key=\"item.value\"\n                    :label=\"item.label\"\n                    :value=\"item.value\"\n                  />\n                </el-select>\n                <el-button\n                  type=\"danger\"\n                  icon=\"delete\"\n                  @click=\"() => formData.conditions.splice(key, 1)\"\n                  >删除</el-button\n                >\n              </div>\n              <div class=\"flex justify-end w-full\">\n                <el-button type=\"primary\" icon=\"plus\" @click=\"addCondition\"\n                  >添加条件</el-button\n                >\n              </div>\n            </el-form-item>\n          </el-tab-pane>\n          <el-tab-pane label=\"自定义SQL\" name=\"sql\"  class=\"pt-2\">\n            <el-form-item label=\"导出SQL:\" prop=\"sql\">\n              <el-input\n                v-model=\"formData.sql\"\n                type=\"textarea\"\n                :rows=\"10\"\n                placeholder=\"请输入导出SQL语句，支持GORM命名参数模式，例如：SELECT * FROM sys_apis WHERE id = @id\"\n              />\n            </el-form-item>\n            <el-form-item label=\"导入SQL:\" prop=\"importSql\">\n              <el-input\n                v-model=\"formData.importSql\"\n                type=\"textarea\"\n                :rows=\"10\"\n                placeholder=\"请输入导入SQL语句，支持GORM命名参数模式，例如：INSERT INTO sys_apis (path, description ,api_group, method) VALUES (@path, @description, @api_group, @method)。参数名对应模板信息中的key。\"\n              />\n            </el-form-item>\n            <el-form-item label=\"导出条件:\">\n              此时导出条件的key必然为 condition = {key1:\"value1\",key2:\"value2\"}，这里需要和你传入sql语句@key占位符的key一致。\n            </el-form-item>\n          </el-tab-pane>\n        </el-tabs>\n\n        <el-form-item label=\"模板信息:\" prop=\"templateInfo\">\n          <el-input\n            v-model=\"formData.templateInfo\"\n            type=\"textarea\"\n            :rows=\"12\"\n            :clearable=\"true\"\n            :placeholder=\"templatePlaceholder\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n\n    <!-- 合并：代码模板 + SQL预览 抽屉 -->\n    <el-drawer\n      v-model=\"drawerVisible\"\n      size=\"70%\"\n      :title=\"'模板与预览'\"\n      :show-close=\"true\"\n      destroy-on-close\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">模板与预览</span>\n          <div>\n            <el-button @click=\"drawerVisible = false\">关 闭</el-button>\n            <el-button v-if=\"activeTab === 'sql'\" type=\"primary\" @click=\"runPreview\">生 成</el-button>\n          </div>\n        </div>\n      </template>\n      <el-tabs v-model=\"activeTab\" type=\"border-card\">\n        <el-tab-pane label=\"代码模板\" name=\"code\">\n          <v-ace-editor\n            v-model:value=\"webCode\"\n            lang=\"vue\"\n            theme=\"github_dark\"\n            class=\"w-full h-96\"\n            :options=\"{ showPrintMargin: false, fontSize: 14 }\"\n          />\n        </el-tab-pane>\n        <el-tab-pane label=\"SQL预览\" name=\"sql\">\n          <div class=\"flex flex-col gap-4\">\n            <div class=\"w-full\">\n              <el-form :model=\"previewForm\" label-width=\"120px\">\n                <el-form-item label=\"过滤已删除\">\n                  <el-switch v-model=\"previewForm.filterDeleted\" />\n                </el-form-item>\n                <el-form-item label=\"默认排序\">\n                  <el-input v-model=\"previewForm.order\" placeholder=\"例如: id desc\" />\n                </el-form-item>\n                <el-form-item label=\"限制条数\">\n                  <el-input-number v-model=\"previewForm.limit\" :min=\"0\" />\n                </el-form-item>\n                <el-form-item label=\"偏移量\">\n                  <el-input-number v-model=\"previewForm.offset\" :min=\"0\" />\n                </el-form-item>\n\n                <el-divider content-position=\"left\">查询条件</el-divider>\n                <div v-if=\"previewConditions.length === 0\" class=\"text-gray\">该模板暂无条件</div>\n                <template v-for=\"(cond, idx) in previewConditions\" :key=\"idx\">\n                  <el-form-item :label=\"cond.column + ' ' + cond.operator\">\n                    <template v-if=\"cond.operator === 'BETWEEN'\">\n                      <div class=\"flex gap-2 w-full\">\n                        <el-input v-model=\"previewForm['start' + cond.from]\" placeholder=\"开始值: start{{cond.from}}\" />\n                        <el-input v-model=\"previewForm['end' + cond.from]\" placeholder=\"结束值: end{{cond.from}}\" />\n                      </div>\n                    </template>\n                    <template v-else>\n                      <el-input v-model=\"previewForm[cond.from]\" :placeholder=\"'变量: ' + cond.from\" />\n                    </template>\n                  </el-form-item>\n                </template>\n              </el-form>\n            </div>\n            <div class=\"w-full\">\n              <v-ace-editor\n                v-model:value=\"previewSQLCode\"\n                lang=\"sql\"\n                theme=\"github_dark\"\n                class=\"w-full h-96\"\n                :options=\"aceOptions\"\n              />\n            </div>\n          </div>\n        </el-tab-pane>\n      </el-tabs>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    createSysExportTemplate,\n    deleteSysExportTemplate,\n    deleteSysExportTemplateByIds,\n    updateSysExportTemplate,\n    findSysExportTemplate,\n    getSysExportTemplateList\n  } from '@/api/exportTemplate.js'\n  import { previewSQL } from '@/api/exportTemplate.js'\n\n  // 全量引入格式化工具 请按需保留\n  import { formatDate } from '@/utils/format'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { ref, reactive } from 'vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { getDB, getTable, getColumn, llmAuto } from '@/api/autoCode'\n  import { getCode } from './code'\n  import { VAceEditor } from 'vue3-ace-editor'\n\n  import 'ace-builds/src-noconflict/mode-vue'\n  import 'ace-builds/src-noconflict/theme-github_dark'\n  import 'ace-builds/src-noconflict/mode-sql'\n\n  defineOptions({\n    name: 'ExportTemplate'\n  })\n\n  const templatePlaceholder = `模板信息格式：key标识数据库column列名称（在join模式下需要写为 table.column），value标识导出excel列名称，如key为数据库关键字或函数，请按照关键字的处理模式处理，当前以mysql为例，如下：\n{\n  \"table_column1\":\"第一列\",\n  \"table_column3\":\"第三列\",\n  \"table_column4\":\"第四列\",\n  \"\\`rows\\`\":\"我属于数据库关键字或函数\",\n}\n如果使用是sql模式，您自行构建的sql的key就是需要写在json的key，例如您写了xxx as k1，那么模板信息中就写{\"k1\":\"对应列名称\"}\n如果增加了JOINS导出key应该列为 {table_name1.table_column1:\"第一列\",table_name2.table_column2:\"第二列\"}\n如果有重复的列名导出格式应为 {table_name1.table_column1 as key:\"第一列\",table_name2.table_column2 as key2:\"第二列\"}\nJOINS模式下不支持导入\n`\n\n  // 自动化生成的字典（可能为空）以及字段\n  const formData = ref({\n    name: '',\n    tableName: '',\n    dbName: '',\n    templateID: '',\n    templateInfo: '',\n    limit: 0,\n    order: '',\n    conditions: [],\n    joinTemplate: [],\n    sql: '',\n    importSql: ''\n  })\n\n  const activeName = ref('auto')\n\n  const prompt = ref('')\n  const tables = ref([])\n\n  const typeSearchOptions = ref([\n    {\n      label: '=',\n      value: '='\n    },\n    {\n      label: '<>',\n      value: '<>'\n    },\n    {\n      label: '>',\n      value: '>'\n    },\n    {\n      label: '<',\n      value: '<'\n    },\n    {\n      label: 'LIKE',\n      value: 'LIKE'\n    },\n    {\n      label: 'BETWEEN',\n      value: 'BETWEEN'\n    },\n    {\n      label: 'NOT BETWEEN',\n      value: 'NOT BETWEEN'\n    },\n    {\n      label: 'IN',\n      value: 'IN'\n    },\n    {\n      label: 'NOT IN',\n      value: 'NOT IN'\n    },\n  ])\n\n  const addCondition = () => {\n    formData.value.conditions.push({\n      from: '',\n      column: '',\n      operator: ''\n    })\n  }\n\n  const addJoin = () => {\n    formData.value.joinTemplate.push({\n      joins: 'LEFT JOIN',\n      table: '',\n      on: ''\n    })\n  }\n\n  // 验证规则\n  const rule = reactive({\n    name: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ],\n    tableName: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ],\n    templateID: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ],\n    templateInfo: [\n      {\n        required: true,\n        message: '',\n        trigger: ['input', 'blur']\n      },\n      {\n        whitespace: true,\n        message: '不能只输入空格',\n        trigger: ['input', 'blur']\n      }\n    ]\n  })\n\n  const searchRule = reactive({\n    createdAt: [\n      {\n        validator: (rule, value, callback) => {\n          if (\n            searchInfo.value.startCreatedAt &&\n            !searchInfo.value.endCreatedAt\n          ) {\n            callback(new Error('请填写结束日期'))\n          } else if (\n            !searchInfo.value.startCreatedAt &&\n            searchInfo.value.endCreatedAt\n          ) {\n            callback(new Error('请填写开始日期'))\n          } else if (\n            searchInfo.value.startCreatedAt &&\n            searchInfo.value.endCreatedAt &&\n            (searchInfo.value.startCreatedAt.getTime() ===\n              searchInfo.value.endCreatedAt.getTime() ||\n              searchInfo.value.startCreatedAt.getTime() >\n                searchInfo.value.endCreatedAt.getTime())\n          ) {\n            callback(new Error('开始日期应当早于结束日期'))\n          } else {\n            callback()\n          }\n        },\n        trigger: 'change'\n      }\n    ]\n  })\n\n  const elFormRef = ref()\n  const elSearchFormRef = ref()\n\n  // =========== 表格控制部分 ===========\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const searchInfo = ref({})\n\n  const dbList = ref([])\n  const tableOptions = ref([])\n  const aiLoading = ref(false)\n\n  const getTablesCloumn = async () => {\n    const tablesMap = {}\n    const promises = tables.value.map(async (item) => {\n      const res = await getColumn({\n        businessDB: formData.value.dbName,\n        tableName: item\n      })\n      if (res.code === 0) {\n        tablesMap[item] = res.data.columns\n      }\n    })\n    await Promise.all(promises)\n    return tablesMap\n  }\n\n  const autoExport = async () => {\n    if (tables.value.length === 0) {\n      ElMessage({\n        type: 'error',\n        message: '请先选择需要参与导出的表'\n      })\n      return\n    }\n    aiLoading.value = true\n    const tableMap = await getTablesCloumn()\n    const aiRes = await llmAuto({\n      prompt: prompt.value,\n      tableMap: JSON.stringify(tableMap),\n      mode: 'autoExportTemplate'\n    })\n    aiLoading.value = false\n    if (aiRes.code === 0) {\n      const aiData = JSON.parse(aiRes.data)\n      formData.value.name = aiData.name\n      formData.value.tableName = aiData.tableName\n      formData.value.templateID = aiData.templateID\n      formData.value.templateInfo = JSON.stringify(aiData.templateInfo, null, 2)\n      formData.value.joinTemplate = aiData.joinTemplate\n    }\n  }\n\n  const getDbFunc = async () => {\n    const res = await getDB()\n    if (res.code === 0) {\n      dbList.value = res.data.dbList\n    }\n  }\n\n  getDbFunc()\n\n  const dbNameChange = () => {\n    formData.value.tableName = ''\n    formData.value.templateInfo = ''\n    tables.value = []\n    getTableFunc()\n  }\n\n  const getTableFunc = async () => {\n    const res = await getTable({ businessDB: formData.value.dbName })\n    if (res.code === 0) {\n      tableOptions.value = res.data.tables\n    }\n    formData.value.tableName = ''\n  }\n  getTableFunc()\n  const getColumnFunc = async (aiFLag) => {\n    if (!formData.value.tableName) {\n      ElMessage({\n        type: 'error',\n        message: '请先选择业务库及选择表后再进行操作'\n      })\n      return\n    }\n    formData.value.templateInfo = ''\n    aiLoading.value = true\n    const res = await getColumn({\n      businessDB: formData.value.dbName,\n      tableName: formData.value.tableName\n    })\n    if (res.code === 0) {\n      if (aiFLag) {\n        const aiRes = await llmAuto({\n          data: JSON.stringify(res.data.columns),\n          mode: 'exportCompletion'\n        })\n        if (aiRes.code === 0) {\n          const aiData = JSON.parse(aiRes.data)\n          aiLoading.value = false\n          formData.value.templateInfo = JSON.stringify(\n            aiData.templateInfo,\n            null,\n            2\n          )\n          formData.value.name = aiData.name\n          formData.value.templateID = aiData.templateID\n          return\n        }\n        ElMessage.warning('AI自动补全失败，已调整为逻辑填写')\n      }\n\n      // 把返回值的data.columns做尊换，制作一组JSON数据，columnName做key，columnComment做value\n      const templateInfo = {}\n      res.data.columns.forEach((item) => {\n        templateInfo[item.columnName] = item.columnComment || item.columnName\n      })\n      formData.value.templateInfo = JSON.stringify(templateInfo, null, 2)\n    }\n    aiLoading.value = false\n  }\n\n  // 重置\n  const onReset = () => {\n    searchInfo.value = {}\n    getTableData()\n  }\n\n  // 搜索\n  const onSubmit = () => {\n    elSearchFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      page.value = 1\n      getTableData()\n    })\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  // 修改页面容量\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getSysExportTemplateList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  // ============== 表格控制部分结束 ===============\n\n  // 获取需要的字典 可能为空 按需保留\n  const setOptions = async () => {}\n\n  // 获取需要的字典 可能为空 按需保留\n  setOptions()\n\n  // 多选数据\n  const multipleSelection = ref([])\n  // 多选\n  const handleSelectionChange = (val) => {\n    multipleSelection.value = val\n  }\n\n  // 删除行\n  const deleteRow = (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(() => {\n      deleteSysExportTemplateFunc(row)\n    })\n  }\n\n  // 多选删除\n  const onDelete = async () => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const ids = []\n      if (multipleSelection.value.length === 0) {\n        ElMessage({\n          type: 'warning',\n          message: '请选择要删除的数据'\n        })\n        return\n      }\n      multipleSelection.value &&\n        multipleSelection.value.map((item) => {\n          ids.push(item.ID)\n        })\n      const res = await deleteSysExportTemplateByIds({ ids })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === ids.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n\n  // 行为控制标记（弹窗内部需要增还是改）\n  const type = ref('')\n\n  // 复制\n  const copyFunc = async (row) => {\n    let copyData\n    const res = await findSysExportTemplate({ ID: row.ID })\n    if (res.code === 0) {\n      copyData = JSON.parse(JSON.stringify(res.data.resysExportTemplate))\n      if (!copyData.conditions) {\n        copyData.conditions = []\n      }\n      if (!copyData.joinTemplate) {\n        copyData.joinTemplate = []\n      }\n      if (!copyData.sql) {\n        copyData.sql = ''\n      }\n      if (!copyData.importSql) {\n        copyData.importSql = ''\n      }\n      delete copyData.ID\n      delete copyData.CreatedAt\n      delete copyData.UpdatedAt\n      copyData.templateID = copyData.templateID + '_copy'\n      copyData.name = copyData.name + '_copy'\n      formData.value = copyData\n      dialogFormVisible.value = true\n    }\n  }\n\n  // 更新行\n  const updateSysExportTemplateFunc = async (row) => {\n    const res = await findSysExportTemplate({ ID: row.ID })\n    type.value = 'update'\n    if (res.code === 0) {\n      formData.value = res.data.resysExportTemplate\n      if (!formData.value.conditions) {\n        formData.value.conditions = []\n      }\n      if (!formData.value.joinTemplate) {\n        formData.value.joinTemplate = []\n      }\n      if (!formData.value.sql) {\n        formData.value.sql = ''\n      }\n      if (!formData.value.importSql) {\n        formData.value.importSql = ''\n      }\n      if (formData.value.sql || formData.value.importSql) {\n        activeName.value = 'sql'\n      } else {\n        activeName.value = 'auto'\n      }\n      dialogFormVisible.value = true\n    }\n  }\n\n  // 删除行\n  const deleteSysExportTemplateFunc = async (row) => {\n    const res = await deleteSysExportTemplate({ ID: row.ID })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '删除成功'\n      })\n      if (tableData.value.length === 1 && page.value > 1) {\n        page.value--\n      }\n      getTableData()\n    }\n  }\n  const drawerVisible = ref(false)\n  const activeTab = ref('code')\n  // 弹窗控制标记\n  const dialogFormVisible = ref(false)\n\n  const webCode = ref('')\n\n  const showCode = (row) => {\n    webCode.value = getCode(row.templateID)\n    activeTab.value = 'code'\n    drawerVisible.value = true\n  }\n\n  // 预览 SQL\n  const previewForm = ref({ filterDeleted: true, order: '', limit: 0, offset: 0 })\n  const previewSQLCode = ref('')\n  const previewTemplate = ref(null)\n  const previewConditions = ref([])\n  const aceOptions = { wrap: true, showPrintMargin: false, fontSize: 14 }\n\n  const openPreview = async (row) => {\n    // 获取模板完整信息以展示条件输入项\n    const res = await findSysExportTemplate({ ID: row.ID })\n    if (res.code === 0) {\n      previewTemplate.value = res.data.resysExportTemplate\n      previewConditions.value = (previewTemplate.value.conditions || []).map((c) => ({\n        from: c.from,\n        column: c.column,\n        operator: c.operator\n      }))\n      // 预填默认的排序与限制\n      previewForm.value.order = previewTemplate.value.order || ''\n      previewForm.value.limit = previewTemplate.value.limit || 0\n      previewForm.value.offset = 0\n      previewSQLCode.value = ''\n      activeTab.value = 'sql'\n      drawerVisible.value = true\n    }\n  }\n\n  const runPreview = async () => {\n    if (!previewTemplate.value) return\n    // 组装 params，与导出组件保持一致\n    const paramsCopy = JSON.parse(JSON.stringify(previewForm.value))\n    // 将布尔与数值等按照导出组件规则编码\n    if (paramsCopy.filterDeleted) paramsCopy.filterDeleted = 'true'\n    const entries = Object.entries(paramsCopy).filter(([key, v]) => {\n      if (v === '' || v === null || v === undefined) return false\n      if ((key === 'limit' || key === 'offset') && Number(v) === 0) return false\n      return true\n    })\n    const params = entries\n      .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n      .join('&')\n\n    const res = await previewSQL({ templateID: previewTemplate.value.templateID, params })\n    if (res.code === 0) {\n      previewSQLCode.value = res.data.sql || ''\n    }\n  }\n\n  // 打开弹窗\n  const openDialog = () => {\n    type.value = 'create'\n    dialogFormVisible.value = true\n  }\n\n  // 关闭弹窗\n  const closeDialog = () => {\n    dialogFormVisible.value = false\n    formData.value = {\n      name: '',\n      tableName: '',\n      templateID: '',\n      templateInfo: '',\n      limit: 0,\n      order: '',\n      conditions: [],\n      joinTemplate: [],\n      sql: '',\n      importSql: ''\n    }\n    activeName.value = 'auto'\n  }\n  // 弹窗确定\n  const enterDialog = async () => {\n    // 判断 formData.templateInfo 是否为标准json格式 如果不是标准json 则辅助调整\n    try {\n      JSON.parse(formData.value.templateInfo)\n    } catch (_) {\n      ElMessage({\n        type: 'error',\n        message: '模板信息格式不正确，请检查'\n      })\n      return\n    }\n\n    const reqData = JSON.parse(JSON.stringify(formData.value))\n    if (activeName.value === 'sql') {\n      reqData.conditions = []\n      reqData.joinTemplate = []\n      reqData.limit = 0\n      reqData.order = ''\n    } else {\n      reqData.sql = ''\n      reqData.importSql = ''\n    }\n\n    for (let i = 0; i < reqData.conditions.length; i++) {\n      if (\n        !reqData.conditions[i].from ||\n        !reqData.conditions[i].column ||\n        !reqData.conditions[i].operator\n      ) {\n        ElMessage({\n          type: 'error',\n          message: '请填写完整的导出条件'\n        })\n        return\n      }\n      reqData.conditions[i].templateID = reqData.templateID\n    }\n\n    for (let i = 0; i < reqData.joinTemplate.length; i++) {\n      if (!reqData.joinTemplate[i].joins || !reqData.joinTemplate[i].on) {\n        ElMessage({\n          type: 'error',\n          message: '请填写完整的关联'\n        })\n        return\n      }\n      reqData.joinTemplate[i].templateID = reqData.templateID\n    }\n\n    elFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      let res\n      switch (type.value) {\n        case 'create':\n          res = await createSysExportTemplate(reqData)\n          break\n        case 'update':\n          res = await updateSysExportTemplate(reqData)\n          break\n        default:\n          res = await createSysExportTemplate(reqData)\n          break\n      }\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '创建/更改成功'\n        })\n        closeDialog()\n        getTableData()\n      }\n    })\n  }\n</script>\n\n<style></style>\n"
  },
  {
    "path": "web/src/view/systemTools/formCreate/index.vue",
    "content": "<template>\n  <div class=\"form-designer-container\">\n    <fc-designer ref=\"designer\" :config=\"config\" height=\"calc(100vh - 160px)\">\n      <template #handle>\n        <el-button type=\"primary\" size=\"small\" plain @click=\"exportVueTemplate\">\n          解析为 Vue 原生标签\n        </el-button>\n      </template>\n    </fc-designer>\n\n    <el-dialog v-model=\"dialogVisible\" title=\"生成的 Vue 模板代码\" width=\"70%\" top=\"5vh\">\n      <el-input \n        type=\"textarea\" \n        :rows=\"25\" \n        v-model=\"vueCode\" \n        readonly \n        class=\"code-input\"\n        resize=\"none\"\n      />\n      <template #footer>\n        <span class=\"dialog-footer\">\n          <el-button @click=\"dialogVisible = false\">关闭</el-button>\n          <el-button type=\"primary\" @click=\"copyCode\">一键复制</el-button>\n        </span>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup>\n  import { ref } from 'vue'\n  import { ElMessage } from 'element-plus'\n  import FcDesigner from '@form-create/designer'\n\n  defineOptions({\n    name: 'FormGenerator'\n  })\n\n  const designer = ref(null)\n  const dialogVisible = ref(false)\n  const vueCode = ref('')\n\n  const config = {\n    fieldReadonly: false,\n    useTemplate: true\n  }\n\n  const kebabCase = (str) => {\n    return str.replace(/([A-Z])/g, '-$1').toLowerCase()\n  }\n\n  const generateVueCode = (rules, options) => {\n    let formDataInit = []\n    let formRules = []\n\n    const parseRule = (rule) => {\n      if (rule.type === 'row') {\n        const propsStr = rule.props ? Object.entries(rule.props).map(([k, v]) => `:${k}=\"${v}\"`).join(' ') : ''\n        let childrenStr = rule.children ? rule.children.map(c => parseRule(c)).join('\\n') : ''\n        return `\\n    <el-row ${propsStr}>${childrenStr}\\n    </el-row>`\n      }\n      if (rule.type === 'col') {\n        const propsStr = rule.props ? Object.entries(rule.props).map(([k, v]) => `:${k}=\"${v}\"`).join(' ') : ''\n        let childrenStr = rule.children ? rule.children.map(c => parseRule(c)).join('\\n') : ''\n        return `\\n      <el-col ${propsStr}>${childrenStr}\\n      </el-col>`\n      }\n\n      if (!rule.field) return ''\n\n      let tag = rule.type\n      \n      const typeMap = {\n        input: 'el-input',\n        inputNumber: 'el-input-number',\n        select: 'el-select',\n        radio: 'el-radio-group',\n        checkbox: 'el-checkbox-group',\n        switch: 'el-switch',\n        timePicker: 'el-time-picker',\n        datePicker: 'el-date-picker',\n        slider: 'el-slider',\n        rate: 'el-rate',\n        colorPicker: 'el-color-picker',\n        cascader: 'el-cascader',\n        upload: 'el-upload'\n      }\n\n      const elTag = typeMap[tag] || (tag.startsWith('el-') ? tag : `el-${tag}`)\n\n      let propsStr = ''\n      if (rule.props) {\n        for (const [key, value] of Object.entries(rule.props)) {\n          if (value === null || value === undefined) continue\n          if (typeof value === 'boolean') {\n            propsStr += value ? ` ${kebabCase(key)}` : ` :${kebabCase(key)}=\"false\"`\n          } else if (typeof value === 'string') {\n            propsStr += ` ${kebabCase(key)}=\"${value}\"`\n          } else {\n            propsStr += ` :${kebabCase(key)}='${JSON.stringify(value)}'`\n          }\n        }\n      }\n\n      let innerContent = ''\n      if (rule.options && Array.isArray(rule.options)) {\n        if (tag === 'select') {\n          innerContent = rule.options.map(opt => `\\n        <el-option label=\"${opt.label}\" value=\"${opt.value}\" />`).join('') + '\\n      '\n        } else if (tag === 'radio') {\n          innerContent = rule.options.map(opt => `\\n        <el-radio label=\"${opt.value}\">${opt.label}</el-radio>`).join('') + '\\n      '\n        } else if (tag === 'checkbox') {\n          innerContent = rule.options.map(opt => `\\n        <el-checkbox label=\"${opt.value}\">${opt.label}</el-checkbox>`).join('') + '\\n      '\n        }\n      }\n\n      let initVal = rule.value !== undefined ? rule.value : (tag === 'checkbox' ? [] : null)\n      formDataInit.push(`  ${rule.field}: ${JSON.stringify(initVal)}`)\n\n      if (rule.$required || (rule.effect && rule.effect.required)) {\n        formRules.push(`  ${rule.field}: [{ required: true, message: '${rule.title}不能为空', trigger: 'blur' }]`)\n      } else if (rule.validate) {\n        formRules.push(`  ${rule.field}: ${JSON.stringify(rule.validate)}`)\n      }\n\n      return `\n    <el-form-item label=\"${rule.title}\" prop=\"${rule.field}\">\n      <${elTag} v-model=\"formData.${rule.field}\"${propsStr}>${innerContent}</${elTag}>\n    </el-form-item>`\n    }\n\n    const formItems = rules.map(parseRule).join('')\n\n    const formConfig = options.form || {}\n    let formPropsStr = []\n    if (formConfig.labelWidth) formPropsStr.push(`label-width=\"${formConfig.labelWidth}\"`)\n    if (formConfig.size) formPropsStr.push(`size=\"${formConfig.size}\"`)\n    if (formConfig.labelPosition) formPropsStr.push(`label-position=\"${formConfig.labelPosition}\"`)\n    if (formConfig.hideRequiredAsterisk) formPropsStr.push(`hide-required-asterisk`)\n\n    // 8. 拼装成标准的 <template> 和 <script setup> 闭环代码\n    return `<template>\n  <div>\n    <el-form ref=\"formRef\" :model=\"formData\" :rules=\"rules\" ${formPropsStr.join(' ')}>\n${formItems}\n      <el-form-item>\n        <el-button type=\"primary\" @click=\"submitForm\">提交</el-button>\n        <el-button @click=\"resetForm\">重置</el-button>\n      </el-form-item>\n    </el-form>\n  </div>\n</template>\n\n<script setup>\nimport { reactive, ref } from 'vue'\nimport { ElMessage } from 'element-plus'\n\nconst formRef = ref(null)\n\nconst formData = reactive({\n${formDataInit.join(',\\n')}\n})\n\nconst rules = reactive({\n${formRules.join(',\\n')}\n})\n\nconst submitForm = async () => {\n  if (!formRef.value) return\n  await formRef.value.validate((valid) => {\n    if (valid) {\n      ElMessage.success('表单校验通过，准备提交')\n      console.log('提交的数据: ', formData)\n    } else {\n      ElMessage.error('表单校验失败')\n    }\n  })\n}\n\nconst resetForm = () => {\n  if (!formRef.value) return\n  formRef.value.resetFields()\n}\n<\\/script>\n`\n  }\n\n  const exportVueTemplate = () => {\n    const rules = designer.value.getRule()\n    const options = designer.value.getOption()\n    \n    vueCode.value = generateVueCode(rules, options)\n    dialogVisible.value = true\n  }\n\n  const copyCode = async () => {\n    try {\n      await navigator.clipboard.writeText(vueCode.value)\n      ElMessage.success('代码已成功复制到剪贴板！')\n      dialogVisible.value = false\n    } catch (err) {\n      ElMessage.error('复制失败，请手动选择复制')\n    }\n  }\n</script>\n\n<style scoped>\n\n</style>\n\n"
  },
  {
    "path": "web/src/view/systemTools/index.vue",
    "content": "<template>\n  <div>\n    <router-view v-slot=\"{ Component }\">\n      <transition mode=\"out-in\" name=\"el-fade-in-linear\">\n        <keep-alive :include=\"routerStore.keepAliveRouters\">\n          <component :is=\"Component\" />\n        </keep-alive>\n      </transition>\n    </router-view>\n  </div>\n</template>\n\n<script setup>\n  import { useRouterStore } from '@/pinia/modules/router'\n  const routerStore = useRouterStore()\n\n  defineOptions({\n    name: 'System'\n  })\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/installPlugin/index.vue",
    "content": "<template>\n  <div class=\"gva-form-box\">\n    <el-upload\n      drag\n      :action=\"`${getBaseUrl()}/autoCode/installPlugin`\"\n      :show-file-list=\"false\"\n      :on-success=\"handleSuccess\"\n      :on-error=\"handleSuccess\"\n      :headers=\"{'x-token': token}\"\n      name=\"plug\"\n    >\n      <el-icon class=\"el-icon--upload\"><upload-filled /></el-icon>\n      <div class=\"el-upload__text\">拖拽或<em>点击上传</em></div>\n      <template #tip>\n        <div class=\"el-upload__tip\">请把安装包的zip拖拽至此处上传</div>\n      </template>\n    </el-upload>\n\n    <!-- Plugin List Table -->\n    <div style=\"margin-top: 20px;\">\n      <el-table :data=\"pluginList\" style=\"width: 100%\">\n        <el-table-column type=\"expand\">\n            <template #default=\"props\">\n                <div style=\"padding: 20px;\">\n                    <h3>API 列表</h3>\n                    <el-table :data=\"props.row.apis\" border>\n                        <el-table-column prop=\"path\" label=\"路径\" />\n                        <el-table-column prop=\"method\" label=\"方法\" />\n                        <el-table-column prop=\"description\" label=\"描述\" />\n                        <el-table-column prop=\"apiGroup\" label=\"APIGROUP\" />\n                    </el-table>\n                    <h3>菜单列表</h3>\n                    <el-table :data=\"props.row.menus\" row-key=\"name\" :tree-props=\"{children: 'children', hasChildren: 'hasChildren'}\" border>\n                        <el-table-column prop=\"meta.title\" label=\"标题\" />\n                        <el-table-column prop=\"name\" label=\"Name\" />\n                        <el-table-column prop=\"path\" label=\"Path\" />\n                    </el-table>\n                     <h3>字典列表</h3>\n                     <el-table :data=\"props.row.dictionaries\" border>\n                         <el-table-column prop=\"name\" label=\"字典名\" />\n                         <el-table-column prop=\"type\" label=\"字典类型\" />\n                         <el-table-column prop=\"desc\" label=\"描述\" />\n                     </el-table>\n                </div>\n            </template>\n        </el-table-column>\n        <el-table-column prop=\"pluginName\" label=\"插件名称\" />\n        <el-table-column prop=\"pluginType\" label=\"插件类型\">\n          <template #default=\"scope\">\n              {{ typeMap[scope.row.pluginType] || '未知类型' }}\n          </template>\n        </el-table-column>\n        <el-table-column label=\"操作\">\n          <template #default=\"scope\">\n            <el-button type=\"primary\" link icon=\"delete\" @click=\"deletePlugin(scope.row)\">删除</el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { ref, onMounted } from 'vue'\n  import { ElMessage } from 'element-plus'\n  import { getBaseUrl } from '@/utils/format'\n  import { useUserStore } from \"@/pinia\";\n  import { getPluginList, removePlugin } from '@/api/autoCode'\n  import { ElMessageBox } from 'element-plus'\n\n  const userStore = useUserStore()\n  const token = userStore.token\n  const pluginList = ref([])\n\n  const getTableData = async () => {\n    const res = await getPluginList()\n    if (res.code === 0) {\n      pluginList.value = res.data\n    }\n  }\n\n  const typeMap = {\n    \"server\": \"后端插件\",\n    \"web\": \"前端插件\",\n    \"full\": \"全栈插件\"\n  }\n\n  const deletePlugin = (row) => {\n    ElMessageBox.confirm(\n    '此操作将永久删除该插件及其关联的API、菜单和字典数据, 是否继续?',\n    '提示',\n    {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning',\n    }\n  )\n    .then(async () => {\n      const res = await removePlugin({ pluginName: row.pluginName, pluginType: row.pluginType })\n      if (res.code === 0) {\n        ElMessage.success('删除成功')\n        getTableData()\n      }\n    })\n    .catch(() => {\n    })\n  }\n\n  onMounted(() => {\n    getTableData()\n  })\n\n  const handleSuccess = (res) => {\n    if (res.code === 0) {\n      let msg = ``\n      res.data &&\n        res.data.forEach((item, index) => {\n          msg += `${index + 1}.${item.msg}\\n`\n        })\n      alert(msg)\n      getTableData() // Refresh list on success\n    } else {\n      ElMessage.error(res.msg)\n    }\n  }\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/loginLog/index.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form :inline=\"true\" :model=\"searchInfo\">\n        <el-form-item label=\"用户名\">\n          <el-input v-model=\"searchInfo.username\" placeholder=\"搜索用户名\" />\n        </el-form-item>\n        <el-form-item label=\"状态\">\n             <el-select v-model=\"searchInfo.status\" placeholder=\"请选择\" clearable>\n                 <el-option label=\"成功\" :value=\"true\" />\n                 <el-option label=\"失败\" :value=\"false\" />\n             </el-select>\n        </el-form-item>\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">查询</el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button\n          icon=\"delete\"\n          style=\"margin-left: 10px;\"\n          :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\"\n        >删除</el-button>\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        :data=\"tableData\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column align=\"left\" label=\"ID\" prop=\"ID\" width=\"80\" />\n        <el-table-column align=\"left\" label=\"用户名\" prop=\"username\" width=\"150\" />\n        <el-table-column align=\"left\" label=\"登录IP\" prop=\"ip\" width=\"150\" />\n        <el-table-column align=\"left\" label=\"状态\" width=\"100\">\n          <template #default=\"scope\">\n            <el-tag :type=\"scope.row.status ? 'success' : 'danger'\">\n              {{ scope.row.status ? '成功' : '失败' }}\n            </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"详情\" show-overflow-tooltip>\n             <template #default=\"scope\">\n                 {{ scope.row.status ? '登录成功' : scope.row.errorMessage }}\n             </template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"浏览器/设备\" prop=\"agent\" show-overflow-tooltip />\n        <el-table-column align=\"left\" label=\"登录时间\" width=\"180\">\n          <template #default=\"scope\">{{ formatDate(scope.row.CreatedAt) }}</template>\n        </el-table-column>\n        <el-table-column align=\"left\" label=\"操作\" width=\"120\">\n          <template #default=\"scope\">\n            <el-popover v-model:visible=\"scope.row.visible\" placement=\"top\" width=\"160\">\n              <p>确定要删除吗？</p>\n              <div style=\"text-align: right; margin: 0\">\n                <el-button size=\"small\" type=\"primary\" link @click=\"scope.row.visible = false\">取消</el-button>\n                <el-button size=\"small\" type=\"primary\" @click=\"deleteRow(scope.row)\">确定</el-button>\n              </div>\n              <template #reference>\n                <el-button icon=\"delete\" type=\"primary\" link @click=\"scope.row.visible = true\">删除</el-button>\n              </template>\n            </el-popover>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup>\nimport {\n  getLoginLogList,\n  deleteLoginLog,\n  deleteLoginLogByIds\n} from '@/api/sysLoginLog'\nimport { ref } from 'vue'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport { formatDate } from '@/utils/format'\n\nconst page = ref(1)\nconst total = ref(0)\nconst pageSize = ref(10)\nconst tableData = ref([])\nconst searchInfo = ref({})\nconst multipleSelection = ref([])\n\nconst handleSelectionChange = (val) => {\n  multipleSelection.value = val\n}\n\nconst getTableData = async () => {\n  const table = await getLoginLogList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })\n  if (table.code === 0) {\n    tableData.value = table.data.list\n    total.value = table.data.total\n    page.value = table.data.page\n    pageSize.value = table.data.pageSize\n  }\n}\n\nconst deleteRow = async (row) => {\n  row.visible = false\n  const res = await deleteLoginLog(row)\n  if (res.code === 0) {\n    ElMessage({\n      type: 'success',\n      message: '删除成功'\n    })\n    if (tableData.value.length === 1 && page.value > 1) {\n      page.value--\n    }\n    getTableData()\n  }\n}\n\nconst onDelete = async() => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n        confirmButtonText: '确定',\n        cancelButtonText: '取消',\n        type: 'warning'\n    }).then(async() => {\n        const ids = multipleSelection.value.map(item => item.ID)\n        const res = await deleteLoginLogByIds({ ids })\n        if (res.code === 0) {\n            ElMessage({\n                type: 'success',\n                message: '删除成功'\n            })\n            if (tableData.value.length === ids.length && page.value > 1) {\n                page.value--\n            }\n            getTableData()\n        }\n    })\n}\n\nconst onSubmit = () => {\n  page.value = 1\n  pageSize.value = 10\n  getTableData()\n}\n\nconst onReset = () => {\n  searchInfo.value = {}\n  getTableData()\n}\n\nconst handleSizeChange = (val) => {\n  pageSize.value = val\n  getTableData()\n}\n\nconst handleCurrentChange = (val) => {\n  page.value = val\n  getTableData()\n}\n\n// 首次加载\ngetTableData()\n</script>\n\n<style scoped>\n</style>\n"
  },
  {
    "path": "web/src/view/systemTools/pubPlug/pubPlug.vue",
    "content": "<template>\n  <div class=\"gva-form-box\">\n    <div class=\"p-4 bg-white dark:bg-slate-900\">\n      <WarningBar\n        title=\"目前只支持标准插件（通过插件模板生成的标准目录插件），非标准插件请自行打包\"\n      />\n      <div class=\"flex items-center gap-3\">\n        <el-input v-model=\"plugName\" placeholder=\"插件模板处填写的【插件名】\" />\n      </div>\n      <el-card class=\"mt-2 text-center\">\n        <WarningBar title=\"穿梭框请只选择子级菜单即可\" />\n        <el-input\n          v-model=\"parentMenu\"\n          placeholder=\"请输入菜单组名，例：公告管理\"\n          class=\"mb-2\"\n        ></el-input>\n        <el-transfer\n          v-model=\"menus\"\n          :props=\"{\n            key: 'ID'\n          }\"\n          class=\"plugin-transfer\"\n          :data=\"menusData\"\n          filterable\n          :filter-method=\"filterMenuMethod\"\n          filter-placeholder=\"请输入菜单名称/路径\"\n          :titles=\"['可选菜单', '使用菜单']\"\n          :button-texts=\"['移除', '选中']\"\n        >\n          <template #default=\"{ option }\">\n            {{ option.meta.title }} {{ option.component }}\n          </template>\n        </el-transfer>\n        <div class=\"flex justify-end mt-2\">\n          <el-button type=\"primary\" @click=\"fmtInitMenu\">\n            定义安装菜单\n          </el-button>\n        </div>\n      </el-card>\n      <el-card class=\"mt-2 text-center\">\n        <el-transfer\n          v-model=\"apis\"\n          :props=\"{\n            key: 'ID'\n          }\"\n          class=\"plugin-transfer\"\n          :data=\"apisData\"\n          filterable\n          :filter-method=\"filterApiMethod\"\n          filter-placeholder=\"请输入API描述/PATH\"\n          :titles=\"['可选API', '使用API']\"\n          :button-texts=\"['移除', '选中']\"\n        >\n          <template #default=\"{ option }\">\n            {{ option.description }} {{ option.path }}\n          </template>\n        </el-transfer>\n        <div class=\"flex justify-end mt-2\">\n          <el-button type=\"primary\" @click=\"fmtInitAPI\">\n            定义安装API\n          </el-button>\n        </div>\n      </el-card>\n      <el-card class=\"mt-2 text-center\">\n        <el-transfer\n          v-model=\"dictionaries\"\n          :props=\"{\n            key: 'ID'\n          }\"\n          class=\"plugin-transfer\"\n          :data=\"dictionariesData\"\n          filterable\n          :filter-method=\"filterDictionaryMethod\"\n          filter-placeholder=\"请输入字典名称/Type\"\n          :titles=\"['可选字典', '使用字典']\"\n          :button-texts=\"['移除', '选中']\"\n        >\n          <template #default=\"{ option }\">\n            {{ option.name }} {{ option.type }}\n          </template>\n        </el-transfer>\n        <div class=\"flex justify-end mt-2\">\n          <el-button type=\"primary\" @click=\"fmtInitDictionary\">\n            定义安装字典\n          </el-button>\n        </div>\n      </el-card>\n    </div>\n    <div class=\"flex justify-end\">\n      <el-button type=\"primary\" @click=\"pubPlugin\"> 打包插件 </el-button>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { ref } from 'vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import { pubPlug, initMenu, initAPI, initDictionary } from '@/api/autoCode.js'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { getAllApis } from '@/api/api'\n  import { getMenuList } from '@/api/menu'\n  import { getSysDictionaryList } from '@/api/sysDictionary'\n\n  const plugName = ref('')\n\n  const menus = ref([])\n  const menusData = ref([])\n  const apis = ref([])\n  const apisData = ref([])\n  const dictionaries = ref([])\n  const dictionariesData = ref([])\n  const parentMenu = ref('')\n\n  const fmtMenu = (menus) => {\n    // 如果menu存在children，递归展开到一级\n    const res = []\n    menus.forEach((item) => {\n      if (item.children) {\n        res.push(...fmtMenu(item.children))\n      } else {\n        res.push(item)\n      }\n    })\n    return res\n  }\n\n  const initData = async () => {\n    const menuRes = await getMenuList()\n    if (menuRes.code === 0) {\n      menusData.value = fmtMenu(menuRes.data)\n    }\n    const apiRes = await getAllApis()\n    if (apiRes.code === 0) {\n      apisData.value = apiRes.data.apis\n    }\n    const dictionaryRes = await getSysDictionaryList({\n      page: 1,\n      pageSize: 9999\n    })\n    if (dictionaryRes.code === 0) {\n      dictionariesData.value = dictionaryRes.data\n    }\n  }\n\n  const filterMenuMethod = (query, item) => {\n    return (\n      item.meta.title.indexOf(query) > -1 || item.component.indexOf(query) > -1\n    )\n  }\n\n  const filterApiMethod = (query, item) => {\n    return item.description.indexOf(query) > -1 || item.path.indexOf(query) > -1\n  }\n\n  const filterDictionaryMethod = (query, item) => {\n    return item.name.indexOf(query) > -1 || item.type.indexOf(query) > -1\n  }\n\n  initData()\n\n\n  const pubPlugin = async () => {\n    ElMessageBox.confirm(\n      `请检查server下的/plugin/${plugName.value}/plugin.go是否已放开需要的 initialize.Api(ctx), initialize.Menu(ctx) 和 initialize.Dictionary(ctx)?`,\n      '打包',\n      {\n        confirmButtonText: '打包',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    )\n      .then(async () => {\n        const res = await pubPlug({ plugName: plugName.value })\n        if (res.code === 0) {\n          ElMessage.success(res.msg)\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '关闭打包'\n        })\n      })\n  }\n\n  const fmtInitMenu = () => {\n    if (!parentMenu.value) {\n      ElMessage.error('请填写菜单组名')\n      return\n    }\n    if (menus.value.length === 0) {\n      ElMessage.error('请至少选择一个菜单')\n      return\n    }\n    if (plugName.value === '') {\n      ElMessage.error('请填写插件名')\n      return\n    }\n    ElMessageBox.confirm(\n      `点击后将会覆盖server下的/plugin/${plugName.value}/initialize/menu. 是否继续?`,\n      '生成初始菜单',\n      {\n        confirmButtonText: '生成',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    )\n      .then(async () => {\n        const req = {\n          plugName: plugName.value,\n          parentMenu: parentMenu.value,\n          menus: menus.value\n        }\n        const res = await initMenu(req)\n        if (res.code === 0) {\n          ElMessage.success('菜单注入成功')\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '关闭生成菜单'\n        })\n      })\n  }\n  const fmtInitAPI = () => {\n    if (apis.value.length === 0) {\n      ElMessage.error('请至少选择一个API')\n      return\n    }\n    if (plugName.value === '') {\n      ElMessage.error('请填写插件名')\n      return\n    }\n    ElMessageBox.confirm(\n      `点击后将会覆盖server下的/plugin/${plugName.value}/initialize/api. 是否继续?`,\n      '生成初始API',\n      {\n        confirmButtonText: '生成',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    )\n      .then(async () => {\n        const req = {\n          plugName: plugName.value,\n          apis: apis.value\n        }\n        const res = await initAPI(req)\n        if (res.code === 0) {\n          ElMessage.success('API注入成功')\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '关闭生成API'\n        })\n      })\n  }\n\n  const fmtInitDictionary = () => {\n    if (dictionaries.value.length === 0) {\n      ElMessage.error('请至少选择一个字典')\n      return\n    }\n    if (plugName.value === '') {\n      ElMessage.error('请填写插件名')\n      return\n    }\n    ElMessageBox.confirm(\n      `点击后将会覆盖server下的/plugin/${plugName.value}/initialize/dictionary. 是否继续?`,\n      '生成初始字典',\n      {\n        confirmButtonText: '生成',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    )\n      .then(async () => {\n        const req = {\n          plugName: plugName.value,\n          dictionaries: dictionaries.value\n        }\n        const res = await initDictionary(req)\n        if (res.code === 0) {\n          ElMessage.success('字典注入成功')\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '关闭生成字典'\n        })\n      })\n  }\n</script>\n\n<style lang=\"scss\">\n  .plugin-transfer {\n    .el-transfer-panel {\n      width: 400px !important;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/systemTools/skills/index.vue",
    "content": "﻿<template>\n  <div class=\"h-full\">\n    <warning-bar\n        href=\"https://plugin.gin-vue-admin.com/license\"\n        title=\"此功能仅在开发阶段使用，用户构建本项目内的skills技能库。\"\n    />\n    <el-row :gutter=\"12\" class=\"h-full\">\n      <el-col :xs=\"24\" :sm=\"8\" :md=\"6\" :lg=\"5\" class=\"flex flex-col gap-4 h-full\">\n        <el-card shadow=\"never\" class=\"!border-none shrink-0\">\n          <div class=\"font-bold mb-2\">AI 工具</div>\n          <div class=\"flex flex-wrap gap-2\">\n            <div\n              v-for=\"tool in tools\"\n              :key=\"tool.key\"\n              class=\"px-3 py-1.5 rounded-md text-sm cursor-pointer transition-all border select-none\"\n              :class=\"activeTool === tool.key\n                ? 'bg-[var(--el-color-primary)] text-white border-[var(--el-color-primary)] shadow-sm'\n                : 'bg-white hover:bg-gray-50 text-gray-700 border-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700 dark:hover:bg-gray-700'\"\n              @click=\"handleToolSelect(tool.key)\"\n            >\n              {{ tool.label }}\n            </div>\n          </div>\n        </el-card>\n\n        <el-card shadow=\"never\" class=\"!border-none shrink-0\">\n          <div class=\"flex justify-between items-center mb-2\">\n            <span class=\"font-bold\">全局约束</span>\n            <el-button type=\"primary\" link icon=\"Edit\" @click=\"openGlobalConstraint\">编辑</el-button>\n          </div>\n          <div class=\"text-xs text-gray-500\">路径: {{ globalConstraintPath }}</div>\n        </el-card>\n\n        <el-card shadow=\"never\" class=\"!border-none flex-1 mt-2 flex flex-col min-h-0\">\n          <div class=\"flex justify-between items-center mb-2\">\n            <span class=\"font-bold\">Skills</span>\n            <div class=\"flex gap-1\">\n              <el-button type=\"primary\" link icon=\"Download\" @click=\"openOnlineDrawer\">在线</el-button>\n              <el-button type=\"primary\" link icon=\"Plus\" @click=\"openCreateDialog\">新增</el-button>\n            </div>\n          </div>\n          <el-input\n            v-model=\"skillFilter\"\n            size=\"small\"\n            clearable\n            placeholder=\"搜索技能\"\n            class=\"mb-2\"\n            prefix-icon=\"Search\"\n          />\n          <el-scrollbar class=\"h-[calc(100vh-380px)]\">\n            <el-menu :default-active=\"activeSkill\" class=\"!border-none\" @select=\"handleSkillSelect\">\n              <el-menu-item\n                v-for=\"skill in filteredSkills\"\n                :key=\"skill\"\n                :index=\"skill\"\n                class=\"!h-10 !leading-10 !my-1 !mx-1 !rounded-[4px]\"\n              >\n                <div class=\"w-full flex items-center justify-between min-w-0\">\n                  <div class=\"flex items-center min-w-0 gap-1\">\n                    <el-icon><Document /></el-icon>\n                    <span class=\"truncate\" :title=\"skill\">{{ skill }}</span>\n                  </div>\n                  <el-button\n                    type=\"danger\"\n                    link\n                    icon=\"Delete\"\n                    @click.stop=\"handleDeleteSkill(skill)\"\n                  >\n                    删除\n                  </el-button>\n                </div>\n              </el-menu-item>\n            </el-menu>\n          </el-scrollbar>\n        </el-card>\n      </el-col>\n\n      <el-col :xs=\"24\" :sm=\"16\" :md=\"18\" :lg=\"19\" class=\"h-full\">\n        <el-card shadow=\"never\" class=\"!border-none h-full flex flex-col\">\n          <template v-if=\"!activeSkill\">\n            <div class=\"h-full flex items-center justify-center\">\n              <el-empty description=\"请选择或新建一个技能\" />\n            </div>\n          </template>\n          <template v-else>\n            <div class=\"flex justify-between items-center mb-4 pb-4 border-b border-gray-100 dark:border-gray-800\">\n              <div class=\"text-lg font-bold flex items-center gap-2\">\n                <span>{{ activeSkill }}</span>\n                <el-tag size=\"small\" type=\"info\">Skill</el-tag>\n              </div>\n              <div class=\"flex items-center gap-2\">\n                <el-button icon=\"Download\" @click=\"packageCurrentSkill\">打包</el-button>\n                <el-button type=\"primary\" icon=\"Check\" @click=\"saveCurrentSkill\">保存配置</el-button>\n              </div>\n            </div>\n\n            <el-tabs v-model=\"activeTab\" class=\"h-full\">\n              <el-tab-pane label=\"技能配置\" name=\"config\">\n                <div\n                  class=\"mt-4 mb-4 rounded-md border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/30 p-3 text-xs text-gray-600 dark:text-gray-300\"\n                >\n                  <div class=\"font-medium text-gray-700 dark:text-gray-200 mb-2\">填写指引</div>\n                  <ul class=\"list-disc pl-4 space-y-1\">\n                    <li>Name 用小写+连字符（kebab-case），作为技能目录名与 /slash 命令。</li>\n                    <li>Description 写清触发条件与关键词，这是自动匹配的核心。</li>\n                    <li>正文建议按 Instructions / Examples / Guidelines 组织，写清输入、输出与约束。</li>\n                    <li>需要模板/规范/脚本时，在正文中引用 templates/...、references/...、scripts/...</li>\n                  </ul>\n                </div>\n                <el-form :model=\"form\" label-width=\"160px\">\n                  <el-form-item>\n                    <template #label>\n                      <div class=\"flex items-center\">\n                        Name\n                        <el-tooltip content=\"唯一标识，建议小写+连字符（kebab-case）\" placement=\"top\">\n                          <el-icon class=\"ml-1 cursor-pointer\"><QuestionFilled /></el-icon>\n                        </el-tooltip>\n                      </div>\n                    </template>\n                    <el-input v-model=\"form.name\" placeholder=\"例如: code-comment-expert\" />\n                    <div class=\"text-xs text-gray-400 mt-1\">建议 2-4 个单词，避免空格与中文。</div>\n                  </el-form-item>\n                  <el-form-item>\n                    <template #label>\n                      <div class=\"flex items-center\">\n                        Description\n                        <el-tooltip content=\"写清使用时机/触发条件与关键字，这是最重要的一行\" placement=\"top\">\n                          <el-icon class=\"ml-1 cursor-pointer\"><QuestionFilled /></el-icon>\n                        </el-tooltip>\n                      </div>\n                    </template>\n                    <el-input\n                      v-model=\"form.description\"\n                      placeholder=\"例如: 为代码添加双语注释，适合代码审查/重构/可读性改进\"\n                    />\n                    <div class=\"text-xs text-gray-400 mt-1\">建议包含：任务类型、触发场景、关键词。</div>\n                  </el-form-item>\n                  <el-form-item>\n                    <template #label>\n                      <div class=\"flex items-center\">\n                        Allowed Tools\n                        <el-tooltip content=\"限制可用工具范围，例如: Bash(gh *), Read, Write\" placement=\"top\">\n                          <el-icon class=\"ml-1 cursor-pointer\"><QuestionFilled /></el-icon>\n                        </el-tooltip>\n                      </div>\n                    </template>\n                    <el-input v-model=\"form.allowedTools\" placeholder=\"可选，例如: Bash(gh *), Read, Write\" />\n                    <div class=\"text-xs text-gray-400 mt-1\">可选字段，留空后保存会移除</div>\n                  </el-form-item>\n                  <el-form-item>\n                    <template #label>\n                      <div class=\"flex items-center\">\n                        Context\n                        <el-tooltip content=\"fork 表示独立上下文，适合复杂任务\" placement=\"top\">\n                          <el-icon class=\"ml-1 cursor-pointer\"><QuestionFilled /></el-icon>\n                        </el-tooltip>\n                      </div>\n                    </template>\n                    <el-input v-model=\"form.context\" placeholder=\"可选，例如: fork\" />\n                    <div class=\"text-xs text-gray-400 mt-1\">可选字段，留空后保存会移除</div>\n                  </el-form-item>\n                  <el-form-item>\n                    <template #label>\n                      <div class=\"flex items-center\">\n                        Agent\n                        <el-tooltip content=\"context=fork 时可指定子代理，例如: Explore\" placement=\"top\">\n                          <el-icon class=\"ml-1 cursor-pointer\"><QuestionFilled /></el-icon>\n                        </el-tooltip>\n                      </div>\n                    </template>\n                    <el-input v-model=\"form.agent\" placeholder=\"可选，例如: Explore / Build\" />\n                    <div class=\"text-xs text-gray-400 mt-1\">可选字段，留空后保存会移除</div>\n                  </el-form-item>\n                  <el-form-item>\n                    <template #label>\n                      <div class=\"flex items-center\">\n                        Markdown 内容\n                        <el-tooltip content=\"正文建议精简，复杂细节可拆到 templates/references/scripts\" placement=\"top\">\n                          <el-icon class=\"ml-1 cursor-pointer\"><QuestionFilled /></el-icon>\n                        </el-tooltip>\n                      </div>\n                    </template>\n                    <div class=\"mb-2 flex flex-wrap gap-2\">\n                      <el-button\n                        v-for=\"block in quickBlocks\"\n                        :key=\"block.label\"\n                        size=\"small\"\n                        @click=\"appendMarkdown(block.content)\"\n                      >\n                        {{ block.label }}\n                      </el-button>\n                      <el-button size=\"small\" @click=\"insertFullTemplate\">插入完整模板</el-button>\n                    </div>\n                    <el-input\n                      v-model=\"form.markdown\"\n                      type=\"textarea\"\n                      :rows=\"20\"\n                      :placeholder=\"markdownPlaceholder\"\n                    />\n                    <div class=\"text-xs text-gray-400 mt-1\">\n                      建议精简正文，把细节放到 templates/references/scripts，并在正文中通过相对路径引用。\n                    </div>\n                  </el-form-item>\n                </el-form>\n              </el-tab-pane>\n\n              <el-tab-pane label=\"脚本\" name=\"scripts\" class=\"mt-4\">\n                <div class=\"flex justify-between items-center mb-4\">\n                  <div class=\"text-sm text-gray-500 bg-gray-50 dark:bg-gray-800 px-3 py-1 rounded\">路径: scripts/</div>\n                  <el-button type=\"primary\" icon=\"Plus\" size=\"small\" @click=\"openScriptDialog\">创建脚本</el-button>\n                </div>\n                <div class=\"text-xs text-gray-500 mb-3\">\n                  适合放可执行逻辑或校验流程，在正文中引用 <span class=\"font-mono\">scripts/文件名</span> 使用（运行需启用 code execution）。\n                </div>\n                <el-table :data=\"scriptRows\" style=\"width: 100%\">\n                  <el-table-column prop=\"name\" label=\"文件名\">\n                    <template #default=\"scope\">\n                      <div class=\"flex items-center gap-2\">\n                        <el-icon><Document /></el-icon>\n                        <span>{{ scope.row.name }}</span>\n                      </div>\n                    </template>\n                  </el-table-column>\n                  <el-table-column label=\"操作\" width=\"180\">\n                    <template #default=\"scope\">\n                      <el-button type=\"primary\" link icon=\"Edit\" @click=\"openScriptEditor(scope.row.name)\">编辑</el-button>\n                      <el-button type=\"primary\" link @click=\"insertFileSnippet('script', scope.row.name)\">调用</el-button>\n                    </template>\n                  </el-table-column>\n                </el-table>\n                <el-empty v-if=\"scriptRows.length === 0\" description=\"暂无脚本\" />\n              </el-tab-pane>\n\n              <el-tab-pane label=\"资源\" name=\"resources\">\n                <div class=\"flex justify-between items-center mb-4 mt-4\">\n                  <div class=\"text-sm text-gray-500 bg-gray-50 dark:bg-gray-800 px-3 py-1 rounded\">路径: resources/</div>\n                  <el-button type=\"primary\" icon=\"Plus\" size=\"small\" @click=\"openResourceDialog\">创建资源</el-button>\n                </div>\n                <div class=\"text-xs text-gray-500 mb-3\">\n                  适合补充背景资料或术语表，在正文中引用 <span class=\"font-mono\">resources/文件名</span> 提示模型按需查阅。\n                </div>\n                <el-table :data=\"resourceRows\" style=\"width: 100%\">\n                  <el-table-column prop=\"name\" label=\"文件名\">\n                    <template #default=\"scope\">\n                      <div class=\"flex items-center gap-2\">\n                        <el-icon><Document /></el-icon>\n                        <span>{{ scope.row.name }}</span>\n                      </div>\n                    </template>\n                  </el-table-column>\n                  <el-table-column label=\"操作\" width=\"180\">\n                    <template #default=\"scope\">\n                      <el-button type=\"primary\" link icon=\"Edit\" @click=\"openResourceEditor(scope.row.name)\">编辑</el-button>\n                      <el-button type=\"primary\" link @click=\"insertFileSnippet('resource', scope.row.name)\">引用</el-button>\n                    </template>\n                  </el-table-column>\n                </el-table>\n                <el-empty v-if=\"resourceRows.length === 0\" description=\"暂无资源\" />\n              </el-tab-pane>\n\n              <el-tab-pane label=\"References\" name=\"references\">\n                <div class=\"flex justify-between items-center mb-4 mt-4\">\n                  <div class=\"text-sm text-gray-500 bg-gray-50 dark:bg-gray-800 px-3 py-1 rounded\">路径: references/</div>\n                  <el-button type=\"primary\" icon=\"Plus\" size=\"small\" @click=\"openReferenceDialog\">创建参考</el-button>\n                </div>\n                <div class=\"text-xs text-gray-500 mb-3\">\n                  适合放规范、规则或权威资料，在正文中引用 <span class=\"font-mono\">references/文件名</span> 指定遵循来源。\n                </div>\n                <el-table :data=\"referenceRows\" style=\"width: 100%\">\n                  <el-table-column prop=\"name\" label=\"文件名\">\n                    <template #default=\"scope\">\n                      <div class=\"flex items-center gap-2\">\n                        <el-icon><Document /></el-icon>\n                        <span>{{ scope.row.name }}</span>\n                      </div>\n                    </template>\n                  </el-table-column>\n                  <el-table-column label=\"操作\" width=\"180\">\n                    <template #default=\"scope\">\n                      <el-button type=\"primary\" link icon=\"Edit\" @click=\"openReferenceEditor(scope.row.name)\">编辑</el-button>\n                      <el-button type=\"primary\" link @click=\"insertFileSnippet('reference', scope.row.name)\">引用</el-button>\n                    </template>\n                  </el-table-column>\n                </el-table>\n                <el-empty v-if=\"referenceRows.length === 0\" description=\"暂无参考\" />\n              </el-tab-pane>\n\n              <el-tab-pane label=\"Templates\" name=\"templates\">\n                <div class=\"flex justify-between items-center mb-4 mt-4\">\n                  <div class=\"text-sm text-gray-500 bg-gray-50 dark:bg-gray-800 px-3 py-1 rounded\">路径: templates/</div>\n                  <el-button type=\"primary\" icon=\"Plus\" size=\"small\" @click=\"openTemplateDialog\">创建模板</el-button>\n                </div>\n                <div class=\"text-xs text-gray-500 mb-3\">\n                  适合放输出结构或代码骨架，在正文中引用 <span class=\"font-mono\">templates/文件名</span> 作为格式约束。\n                </div>\n                <el-table :data=\"templateRows\" style=\"width: 100%\">\n                  <el-table-column prop=\"name\" label=\"文件名\">\n                    <template #default=\"scope\">\n                      <div class=\"flex items-center gap-2\">\n                        <el-icon><Document /></el-icon>\n                        <span>{{ scope.row.name }}</span>\n                      </div>\n                    </template>\n                  </el-table-column>\n                  <el-table-column label=\"操作\" width=\"180\">\n                    <template #default=\"scope\">\n                      <el-button type=\"primary\" link icon=\"Edit\" @click=\"openTemplateEditor(scope.row.name)\">编辑</el-button>\n                      <el-button type=\"primary\" link @click=\"insertFileSnippet('template', scope.row.name)\">引用</el-button>\n                    </template>\n                  </el-table-column>\n                </el-table>\n                <el-empty v-if=\"templateRows.length === 0\" description=\"暂无模板\" />\n              </el-tab-pane>\n            </el-tabs>\n          </template>\n        </el-card>\n      </el-col>\n    </el-row>\n\n    <el-dialog v-model=\"createDialogVisible\" title=\"新增 Skill\" width=\"420px\">\n      <el-form :model=\"newSkill\" label-width=\"100px\">\n        <el-form-item label=\"Skill 名称\">\n          <el-input v-model=\"newSkill.name\" placeholder=\"例如: code-comment-expert\" />\n          <div class=\"text-xs text-gray-400 mt-1\">仅小写字母/数字/连字符（kebab-case）。</div>\n        </el-form-item>\n        <el-form-item label=\"描述\">\n          <el-input v-model=\"newSkill.description\" placeholder=\"例如: 代码注释与可读性优化，适合审查/重构\" />\n          <div class=\"text-xs text-gray-400 mt-1\">写清触发条件与关键词，越具体越好。</div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"createDialogVisible = false\">取消</el-button>\n        <el-button type=\"primary\" @click=\"createSkill\">创建</el-button>\n      </template>\n    </el-dialog>\n\n    <el-dialog v-model=\"scriptDialogVisible\" title=\"创建脚本\" width=\"420px\">\n      <el-form :model=\"newScript\" label-width=\"100px\">\n        <el-form-item label=\"脚本类型\">\n          <el-select v-model=\"newScript.type\" placeholder=\"选择类型\">\n            <el-option label=\"Python (.py)\" value=\"py\" />\n            <el-option label=\"JavaScript (.js)\" value=\"js\" />\n            <el-option label=\"Shell (.sh)\" value=\"sh\" />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"文件名\">\n          <el-input v-model=\"newScript.name\" placeholder=\"例如: lint\" />\n          <div class=\"text-xs text-gray-400 mt-1\">无需扩展名，会按类型自动补全。</div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"scriptDialogVisible = false\">取消</el-button>\n        <el-button type=\"primary\" @click=\"createScript\">创建</el-button>\n      </template>\n    </el-dialog>\n\n    <el-dialog v-model=\"resourceDialogVisible\" title=\"创建资源\" width=\"420px\">\n      <el-form :model=\"newResource\" label-width=\"100px\">\n        <el-form-item label=\"文件名\">\n          <el-input v-model=\"newResource.name\" placeholder=\"例如: glossary\" />\n          <div class=\"text-xs text-gray-400 mt-1\">自动补全 .md。</div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"resourceDialogVisible = false\">取消</el-button>\n        <el-button type=\"primary\" @click=\"createResource\">创建</el-button>\n      </template>\n    </el-dialog>\n\n    <el-dialog v-model=\"referenceDialogVisible\" title=\"创建参考\" width=\"420px\">\n      <el-form :model=\"newReference\" label-width=\"100px\">\n        <el-form-item label=\"文件名\">\n          <el-input v-model=\"newReference.name\" placeholder=\"例如: style-guide\" />\n          <div class=\"text-xs text-gray-400 mt-1\">自动补全 .md。</div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"referenceDialogVisible = false\">取消</el-button>\n        <el-button type=\"primary\" @click=\"createReference\">创建</el-button>\n      </template>\n    </el-dialog>\n\n    <el-dialog v-model=\"templateDialogVisible\" title=\"创建模板\" width=\"420px\">\n      <el-form :model=\"newTemplate\" label-width=\"100px\">\n        <el-form-item label=\"文件名\">\n          <el-input v-model=\"newTemplate.name\" placeholder=\"例如: output-structure\" />\n          <div class=\"text-xs text-gray-400 mt-1\">自动补全 .md。</div>\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"templateDialogVisible = false\">取消</el-button>\n        <el-button type=\"primary\" @click=\"createTemplate\">创建</el-button>\n      </template>\n    </el-dialog>\n\n    <el-dialog v-model=\"downloadTargetDialogVisible\" title=\"选择下载目标\" width=\"420px\">\n      <el-form label-width=\"90px\">\n        <el-form-item label=\"下载到\">\n          <el-select v-model=\"downloadTarget\" placeholder=\"请选择工具\" class=\"w-full\">\n            <el-option\n              v-for=\"item in downloadTargetOptions\"\n              :key=\"item.value\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n            />\n          </el-select>\n        </el-form-item>\n        <div class=\"text-xs text-gray-500\">\n          可下载到单个 AI 工具，也可选择“全部工具”。\n        </div>\n      </el-form>\n      <template #footer>\n        <el-button @click=\"closeDownloadTargetDialog\">取消</el-button>\n        <el-button type=\"primary\" @click=\"confirmDownloadSkill\">开始下载</el-button>\n      </template>\n    </el-dialog>\n\n    <el-drawer v-model=\"editorVisible\" size=\"70%\" destroy-on-close :with-header=\"false\">\n      <div class=\"h-full flex flex-col p-4\">\n        <div class=\"flex justify-between items-center mb-4\">\n          <div class=\"text-lg font-bold flex items-center gap-2\">\n            <el-icon><Edit /></el-icon>\n            {{ editorTitle }}\n          </div>\n          <div class=\"flex gap-2\">\n            <el-button @click=\"editorVisible = false\">取消</el-button>\n            <el-button type=\"primary\" icon=\"Check\" @click=\"saveEditor\">保存内容</el-button>\n          </div>\n        </div>\n        <div class=\"flex-1 overflow-hidden border border-gray-200 dark:border-gray-700 rounded-md shadow-inner\">\n          <v-ace-editor\n            v-model:value=\"editorContent\"\n            :lang=\"editorLang\"\n            theme=\"github_dark\"\n            class=\"w-full h-full\"\n            :options=\"{ showPrintMargin: false, fontSize: 14 }\"\n          />\n        </div>\n      </div>\n    </el-drawer>\n\n    <!-- 在线 Skills 抽屉 -->\n    <el-drawer\n      v-model=\"onlineDrawerVisible\"\n      size=\"90%\"\n      :show-close=\"false\"\n      destroy-on-close\n    >\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">在线 Skills</span>\n          <el-button @click=\"onlineDrawerVisible = false\">关 闭</el-button>\n        </div>\n      </template>\n      <div class=\"mb-4\">\n        <el-form :inline=\"true\" :model=\"onlineSearchInfo\">\n          <el-form-item label=\"名称\">\n            <el-input v-model=\"onlineSearchInfo.name\" placeholder=\"搜索技能名称\" clearable @keyup.enter=\"searchOnlineSkills\" />\n          </el-form-item>\n          <el-form-item>\n            <el-button type=\"primary\" icon=\"Search\" @click=\"searchOnlineSkills\">查 询</el-button>\n            <el-button icon=\"Refresh\" @click=\"resetOnlineSearch\">重 置</el-button>\n          </el-form-item>\n        </el-form>\n      </div>\n      <el-table v-loading=\"onlineLoading\" :data=\"onlineSkillList\" stripe>\n        <el-table-column label=\"封面\" width=\"80\">\n          <template #default=\"{ row }\">\n            <el-image\n              v-if=\"row.picture\"\n              :src=\"row.picture\"\n              style=\"width: 50px; height: 50px\"\n              fit=\"cover\"\n              class=\"rounded\"\n            />\n          </template>\n        </el-table-column>\n        <el-table-column label=\"名称\" prop=\"name\" min-width=\"160\" show-overflow-tooltip>\n          <template #default=\"{ row }\">\n            <a\n              class=\"text-blue-500 hover:text-blue-700 cursor-pointer\"\n              :href=\"`https://plugin.gin-vue-admin.com/details/${row.ID}`\"\n              target=\"_blank\"\n            >{{ row.name }}</a>\n          </template>\n        </el-table-column>\n        <el-table-column label=\"简介\" prop=\"resume\" min-width=\"240\" show-overflow-tooltip />\n        <el-table-column label=\"版本\" prop=\"actVersion\" width=\"100\" />\n        <el-table-column label=\"下载量\" prop=\"downloadCount\" width=\"90\" />\n        <el-table-column label=\"操作\" width=\"120\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <el-button\n              v-if=\"row.money === 0\"\n              type=\"primary\"\n              link\n              icon=\"Download\"\n              :loading=\"downloadingIds.has(row.ID)\"\n              @click=\"handleDownloadSkill(row)\"\n            >下载</el-button>\n            <a\n              v-else\n              class=\"text-blue-500 hover:text-blue-700 text-sm\"\n              :href=\"`https://plugin.gin-vue-admin.com/details/${row.ID}`\"\n              target=\"_blank\"\n            >去购买</a>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"flex justify-center mt-4\">\n        <el-pagination\n          :current-page=\"onlineSearchInfo.page\"\n          :page-size=\"onlineSearchInfo.pageSize\"\n          :page-sizes=\"[10, 20, 50]\"\n          :total=\"onlineTotal\"\n          layout=\"total, sizes, prev, pager, next\"\n          @current-change=\"handleOnlinePageChange\"\n          @size-change=\"handleOnlineSizeChange\"\n        />\n      </div>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import { computed, onMounted, reactive, ref } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { QuestionFilled, Document, Plus, Search, Check, Edit } from '@element-plus/icons-vue'\n  import WarningBar from '@/components/warningBar/warningBar.vue'\n  import {\n    getSkillTools,\n    getSkillList,\n    getSkillDetail,\n    saveSkill,\n    deleteSkill,\n    createSkillScript,\n    getSkillScript,\n    saveSkillScript,\n    createSkillResource,\n    getSkillResource,\n    saveSkillResource,\n    createSkillReference,\n    getSkillReference,\n    saveSkillReference,\n    createSkillTemplate,\n    getSkillTemplate,\n    saveSkillTemplate,\n    getGlobalConstraint,\n    saveGlobalConstraint,\n    packageSkill,\n    downloadOnlineSkill\n  } from '@/api/skills'\n  import { getShopPluginList } from '@/api/plugin/api'\n  import { VAceEditor } from 'vue3-ace-editor'\n  import 'ace-builds/src-noconflict/mode-javascript'\n  import 'ace-builds/src-noconflict/mode-python'\n  import 'ace-builds/src-noconflict/mode-sh'\n  import 'ace-builds/src-noconflict/mode-markdown'\n  import 'ace-builds/src-noconflict/theme-github_dark'\n\n  defineOptions({\n    name: 'Skills'\n  })\n\n  const tools = ref([\n    { key: 'copilot', label: 'Copilot' },\n    { key: 'claude', label: 'Claude' },\n    { key: 'cursor', label: 'Cursor' },\n    { key: 'trae', label: 'Trae' },\n    { key: 'codex', label: 'Codex' }\n  ])\n  const activeTool = ref('claude')\n  const skills = ref([])\n  const activeSkill = ref('')\n  const skillFilter = ref('')\n  const activeTab = ref('config')\n  const globalConstraintExists = ref(false)\n\n  const toolDirMap = {\n    copilot: '.aone_copilot',\n    claude: '.claude',\n    cursor: '.cursor',\n    trae: '.trae',\n    codex: '.codex'\n  }\n\n  const globalConstraintPath = computed(() => {\n    if (!activeTool.value) return 'skills/README.md'\n    const toolDir = toolDirMap[activeTool.value] || `.${activeTool.value}`\n    return `${toolDir}/skills/README.md`\n  })\n\n  const form = reactive({\n    name: '',\n    description: '',\n    allowedTools: '',\n    context: '',\n    agent: '',\n    markdown: ''\n  })\n\n  const markdownPlaceholder =\n    '建议结构：# Skill Title -> ## Instructions -> ## Examples -> ## Guidelines。\\n' +\n    '在正文中引用 templates/...、references/...、scripts/... 可按需加载。\\n\\n' +\n    '示例：\\n# Code Comment Expert\\n## Instructions\\n- 说明目标、输入、输出与步骤。\\n\\n## Examples\\n- 输入: ...\\n- 输出: ...\\n\\n## Guidelines\\n- 约束、格式与质量标准。\\n'\n\n  const quickBlocks = [\n    { label: '标题', content: '\\n# Skill Title\\n' },\n    { label: '指令', content: '\\n## Instructions\\n- 描述该技能要做什么、如何做。\\n' },\n    { label: '示例', content: '\\n## Examples\\n- 输入: ...\\n- 输出: ...\\n' },\n    { label: '指南', content: '\\n## Guidelines\\n- 约束、质量标准与注意事项。\\n' },\n    { label: '输出格式', content: '\\n## Output Format\\n1. ...\\n2. ...\\n' },\n    { label: '引用模板', content: '\\n需要结构时参考 templates/your-template.md。\\n' },\n    { label: '引用参考', content: '\\n如需规范/术语，参考 references/your-reference.md。\\n' },\n    { label: '调用脚本', content: '\\n如需自动化，执行 scripts/your-script.py \"{输入}\"。\\n' }\n  ]\n\n  const scripts = ref([])\n  const resources = ref([])\n  const references = ref([])\n  const templates = ref([])\n\n  const scriptRows = computed(() => skillsFilesToRows(scripts.value))\n  const resourceRows = computed(() => skillsFilesToRows(resources.value))\n  const referenceRows = computed(() => skillsFilesToRows(references.value))\n  const templateRows = computed(() => skillsFilesToRows(templates.value))\n\n  const createDialogVisible = ref(false)\n  const scriptDialogVisible = ref(false)\n  const resourceDialogVisible = ref(false)\n  const referenceDialogVisible = ref(false)\n  const templateDialogVisible = ref(false)\n\n  const newSkill = reactive({\n    name: '',\n    description: ''\n  })\n\n  const newScript = reactive({\n    name: '',\n    type: 'py'\n  })\n\n  const newResource = reactive({\n    name: ''\n  })\n\n  const newReference = reactive({\n    name: ''\n  })\n\n  const newTemplate = reactive({\n    name: ''\n  })\n\n  const editorVisible = ref(false)\n  const editorContent = ref('')\n  const editorFileName = ref('')\n  const editorType = ref('script')\n  const editorLang = ref('text')\n\n  const editorTitle = computed(() => {\n    if (!editorFileName.value) {\n      return editorType.value === 'constraint' ? '全局约束' : '文件编辑'\n    }\n    if (editorType.value === 'script') return `脚本：${editorFileName.value}`\n    if (editorType.value === 'resource') return `资源：${editorFileName.value}`\n    if (editorType.value === 'reference') return `参考：${editorFileName.value}`\n    if (editorType.value === 'template') return `模板：${editorFileName.value}`\n    if (editorType.value === 'constraint') return `全局约束：${editorFileName.value}`\n    return `文件编辑：${editorFileName.value}`\n  })\n\n  const filteredSkills = computed(() => {\n    if (!skillFilter.value) return skills.value\n    return skills.value.filter((item) => item.toLowerCase().includes(skillFilter.value.toLowerCase()))\n  })\n\n  onMounted(async () => {\n    await loadTools()\n    await loadSkills()\n  })\n\n  async function loadTools() {\n    try {\n      const res = await getSkillTools()\n      if (res.code === 0 && res.data?.tools?.length) {\n        tools.value = res.data.tools\n        if (!tools.value.find((item) => item.key === activeTool.value)) {\n          activeTool.value = tools.value[0]?.key || 'claude'\n        }\n      }\n    } catch (e) {\n      ElMessage.warning('获取工具列表失败，使用默认列表')\n    }\n  }\n\n  async function loadSkills() {\n    if (!activeTool.value) return\n    try {\n      const res = await getSkillList({ tool: activeTool.value })\n      if (res.code === 0) {\n        skills.value = res.data?.skills || []\n      }\n    } catch (e) {\n      ElMessage.error('获取技能列表失败')\n    }\n  }\n\n  async function loadSkillDetail(skillName) {\n    if (!activeTool.value || !skillName) return\n    try {\n      const res = await getSkillDetail({ tool: activeTool.value, skill: skillName })\n      if (res.code === 0) {\n        const detail = res.data?.detail\n        activeSkill.value = detail?.skill || skillName\n        form.name = detail?.meta?.name || skillName\n        form.description = detail?.meta?.description || ''\n        form.allowedTools = detail?.meta?.allowedTools || ''\n        form.context = detail?.meta?.context || ''\n        form.agent = detail?.meta?.agent || ''\n        form.markdown = detail?.markdown || ''\n        scripts.value = detail?.scripts || []\n        resources.value = detail?.resources || []\n        references.value = detail?.references || []\n        templates.value = detail?.templates || []\n      }\n    } catch (e) {\n      ElMessage.error('获取技能详情失败')\n    }\n  }\n\n  async function openGlobalConstraint() {\n    if (!activeTool.value) {\n      ElMessage.warning('请先选择工具')\n      return\n    }\n    try {\n      const res = await getGlobalConstraint({ tool: activeTool.value })\n      if (res.code === 0) {\n        globalConstraintExists.value = !!res.data?.exists\n        if (!globalConstraintExists.value) {\n          ElMessage.info('未检测到 README.md，保存后将创建该文件')\n        }\n        openEditor('constraint', 'README.md', res.data?.content || '')\n      }\n    } catch (e) {\n      ElMessage.error('读取全局约束失败')\n    }\n  }\n\n  function resetDetail() {\n    activeSkill.value = ''\n    form.name = ''\n    form.description = ''\n    form.allowedTools = ''\n    form.context = ''\n    form.agent = ''\n    form.markdown = ''\n    scripts.value = []\n    resources.value = []\n    references.value = []\n    templates.value = []\n    activeTab.value = 'config'\n  }\n\n  function handleToolSelect(key) {\n    activeTool.value = key\n    resetDetail()\n    globalConstraintExists.value = false\n    loadSkills()\n  }\n\n  function handleSkillSelect(skillName) {\n    loadSkillDetail(skillName)\n  }\n\n  async function handleDeleteSkill(skillName) {\n    if (!activeTool.value || !skillName) return\n    try {\n      await ElMessageBox.confirm(\n        `确认删除技能「${skillName}」吗？将同时删除其 scripts/resources/references/templates 文件。`,\n        '删除确认',\n        {\n          confirmButtonText: '删除',\n          cancelButtonText: '取消',\n          type: 'warning'\n        }\n      )\n    } catch (e) {\n      return\n    }\n\n    try {\n      const res = await deleteSkill({ tool: activeTool.value, skill: skillName })\n      if (res.code !== 0) {\n        return\n      }\n      if (activeSkill.value === skillName) {\n        resetDetail()\n      }\n      await loadSkills()\n      ElMessage.success('删除成功')\n    } catch (e) {\n      ElMessage.error('删除失败')\n    }\n  }\n\n  function openCreateDialog() {\n    newSkill.name = ''\n    newSkill.description = ''\n    createDialogVisible.value = true\n  }\n\n  async function createSkill() {\n    if (!newSkill.name.trim()) {\n      ElMessage.warning('请输入 Skill 名称')\n      return\n    }\n    const payload = {\n      tool: activeTool.value,\n      skill: newSkill.name.trim(),\n      meta: {\n        name: newSkill.name.trim(),\n        description: newSkill.description.trim() || '请补充技能描述',\n        allowedTools: 'Bash(gh *)',\n        context: 'fork',\n        agent: 'Explore'\n      },\n      markdown: defaultSkillTemplate()\n    }\n    try {\n      const res = await saveSkill(payload)\n      if (res.code === 0) {\n        ElMessage.success('创建成功')\n        createDialogVisible.value = false\n        await loadSkills()\n        await loadSkillDetail(payload.skill)\n      }\n    } catch (e) {\n      ElMessage.error('创建失败')\n    }\n  }\n\n  async function saveCurrentSkill() {\n    if (!activeSkill.value) return\n    if (!form.name.trim()) {\n      ElMessage.warning('Name 不能为空')\n      return\n    }\n    const payload = {\n      tool: activeTool.value,\n      skill: activeSkill.value,\n      meta: {\n        name: form.name.trim(),\n        description: form.description.trim(),\n        allowedTools: form.allowedTools.trim(),\n        context: form.context.trim(),\n        agent: form.agent.trim()\n      },\n      markdown: form.markdown\n    }\n\n    let syncTools = []\n    try {\n      await ElMessageBox.confirm('是否同步到其他 AI 客户端工具？', '同步提示', {\n        confirmButtonText: '同步',\n        cancelButtonText: '仅当前',\n        type: 'warning'\n      })\n      syncTools = tools.value\n        .map((item) => item.key)\n        .filter((key) => key && key !== activeTool.value)\n    } catch (e) {\n      syncTools = []\n    }\n\n    if (syncTools.length) {\n      payload.syncTools = syncTools\n    }\n\n    try {\n      const res = await saveSkill(payload)\n      if (res.code === 0) {\n        ElMessage.success('保存成功')\n      }\n    } catch (e) {\n      ElMessage.error('保存失败')\n    }\n  }\n\n  function extractFileNameFromDisposition(disposition) {\n    if (!disposition) return ''\n    const utf8Match = disposition.match(/filename\\*=UTF-8''([^;]+)/i)\n    if (utf8Match?.[1]) {\n      try {\n        return decodeURIComponent(utf8Match[1])\n      } catch (e) {\n        return utf8Match[1]\n      }\n    }\n    const normalMatch = disposition.match(/filename=\"?([^\";]+)\"?/i)\n    return normalMatch?.[1] || ''\n  }\n\n  async function packageCurrentSkill() {\n    if (!activeTool.value || !activeSkill.value) {\n      ElMessage.warning('请先选择技能')\n      return\n    }\n    try {\n      const res = await packageSkill({ tool: activeTool.value, skill: activeSkill.value })\n      const blob = res instanceof Blob ? res : (res?.data instanceof Blob ? res.data : null)\n      if (!blob) {\n        ElMessage.error('打包失败')\n        return\n      }\n      const contentType = String(res?.headers?.['content-type'] || blob.type || '').toLowerCase()\n      const disposition = String(res?.headers?.['content-disposition'] || '')\n      const isZipResponse = contentType.includes('application/zip') || disposition.toLowerCase().includes('filename=')\n      const isErrorBlob = contentType.includes('application/json') || contentType.includes('text/plain')\n      if (!isZipResponse || isErrorBlob) {\n        let msg = '打包失败'\n        try {\n          const text = await blob.text()\n          if (text) {\n            try {\n              const json = JSON.parse(text)\n              msg = json?.msg || msg\n            } catch (e) {\n              msg = text\n            }\n          }\n        } catch (e) {\n          // ignore parse error\n        }\n        ElMessage.error(msg)\n        return\n      }\n      const fileName = extractFileNameFromDisposition(disposition) || `${activeSkill.value}.zip`\n      const url = window.URL.createObjectURL(blob)\n      const link = document.createElement('a')\n      link.href = url\n      link.download = fileName\n      document.body.appendChild(link)\n      link.click()\n      document.body.removeChild(link)\n      window.URL.revokeObjectURL(url)\n      ElMessage.success('打包成功')\n    } catch (e) {\n      ElMessage.error('打包失败')\n    }\n  }\n\n  function appendMarkdown(content) {\n    form.markdown = `${form.markdown || ''}${content}`\n  }\n\n  function insertFileSnippet(kind, fileName) {\n    if (!fileName) return\n    let snippet = ''\n    switch (kind) {\n      case 'script':\n        snippet = `如需自动化处理，可执行 scripts/${fileName} \"{输入}\"。`\n        break\n      case 'resource':\n        snippet = `背景资料见 resources/${fileName}。`\n        break\n      case 'reference':\n        snippet = `请遵循 references/${fileName} 的规范。`\n        break\n      case 'template':\n        snippet = `输出结构参考 templates/${fileName}。`\n        break\n      default:\n        snippet = ''\n    }\n    if (!snippet) return\n    appendMarkdown(`\\n${snippet}\\n`)\n    ElMessage.success('已插入到 SKILL.md')\n    activeTab.value = 'config'\n  }\n\n  function insertFullTemplate() {\n    if (!form.markdown.trim()) {\n      form.markdown = defaultSkillTemplate()\n      return\n    }\n    form.markdown = `${form.markdown}\\n${defaultSkillTemplate()}`\n  }\n\n  function openScriptDialog() {\n    if (!activeSkill.value) {\n      ElMessage.warning('请先选择技能')\n      return\n    }\n    newScript.name = ''\n    newScript.type = 'py'\n    scriptDialogVisible.value = true\n  }\n\n  async function createScript() {\n    if (!newScript.name.trim()) {\n      ElMessage.warning('请输入脚本文件名')\n      return\n    }\n    try {\n      const res = await createSkillScript({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName: newScript.name.trim(),\n        scriptType: newScript.type\n      })\n      if (res.code === 0) {\n        scriptDialogVisible.value = false\n        await loadSkillDetail(activeSkill.value)\n        openEditor('script', res.data.fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('创建脚本失败')\n    }\n  }\n\n  async function openScriptEditor(fileName) {\n    if (!fileName) return\n    try {\n      const res = await getSkillScript({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName\n      })\n      if (res.code === 0) {\n        openEditor('script', fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('读取脚本失败')\n    }\n  }\n\n  function openResourceDialog() {\n    if (!activeSkill.value) {\n      ElMessage.warning('请先选择技能')\n      return\n    }\n    newResource.name = ''\n    resourceDialogVisible.value = true\n  }\n\n  async function createResource() {\n    if (!newResource.name.trim()) {\n      ElMessage.warning('请输入资源文件名')\n      return\n    }\n    try {\n      const res = await createSkillResource({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName: newResource.name.trim()\n      })\n      if (res.code === 0) {\n        resourceDialogVisible.value = false\n        await loadSkillDetail(activeSkill.value)\n        openEditor('resource', res.data.fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('创建资源失败')\n    }\n  }\n\n  async function openResourceEditor(fileName) {\n    if (!fileName) return\n    try {\n      const res = await getSkillResource({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName\n      })\n      if (res.code === 0) {\n        openEditor('resource', fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('读取资源失败')\n    }\n  }\n\n  function openReferenceDialog() {\n    if (!activeSkill.value) {\n      ElMessage.warning('请先选择技能')\n      return\n    }\n    newReference.name = ''\n    referenceDialogVisible.value = true\n  }\n\n  async function createReference() {\n    if (!newReference.name.trim()) {\n      ElMessage.warning('请输入参考文件名')\n      return\n    }\n    try {\n      const res = await createSkillReference({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName: newReference.name.trim()\n      })\n      if (res.code === 0) {\n        referenceDialogVisible.value = false\n        await loadSkillDetail(activeSkill.value)\n        openEditor('reference', res.data.fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('创建参考失败')\n    }\n  }\n\n  async function openReferenceEditor(fileName) {\n    if (!fileName) return\n    try {\n      const res = await getSkillReference({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName\n      })\n      if (res.code === 0) {\n        openEditor('reference', fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('读取参考失败')\n    }\n  }\n\n  function openTemplateDialog() {\n    if (!activeSkill.value) {\n      ElMessage.warning('请先选择技能')\n      return\n    }\n    newTemplate.name = ''\n    templateDialogVisible.value = true\n  }\n\n  async function createTemplate() {\n    if (!newTemplate.name.trim()) {\n      ElMessage.warning('请输入模板文件名')\n      return\n    }\n    try {\n      const res = await createSkillTemplate({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName: newTemplate.name.trim()\n      })\n      if (res.code === 0) {\n        templateDialogVisible.value = false\n        await loadSkillDetail(activeSkill.value)\n        openEditor('template', res.data.fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('创建模板失败')\n    }\n  }\n\n  async function openTemplateEditor(fileName) {\n    if (!fileName) return\n    try {\n      const res = await getSkillTemplate({\n        tool: activeTool.value,\n        skill: activeSkill.value,\n        fileName\n      })\n      if (res.code === 0) {\n        openEditor('template', fileName, res.data.content)\n      }\n    } catch (e) {\n      ElMessage.error('读取模板失败')\n    }\n  }\n\n  function openEditor(type, fileName, content) {\n    editorType.value = type\n    editorFileName.value = fileName\n    editorContent.value = content || ''\n    editorLang.value = detectLang(fileName)\n    editorVisible.value = true\n  }\n\n  async function saveEditor() {\n    if (!editorFileName.value) return\n    try {\n      if (editorType.value === 'script') {\n        const res = await saveSkillScript({\n          tool: activeTool.value,\n          skill: activeSkill.value,\n          fileName: editorFileName.value,\n          content: editorContent.value\n        })\n        if (res.code === 0) {\n          ElMessage.success('保存成功')\n        }\n      } else if (editorType.value === 'resource') {\n        const res = await saveSkillResource({\n          tool: activeTool.value,\n          skill: activeSkill.value,\n          fileName: editorFileName.value,\n          content: editorContent.value\n        })\n        if (res.code === 0) {\n          ElMessage.success('保存成功')\n        }\n      } else if (editorType.value === 'reference') {\n        const res = await saveSkillReference({\n          tool: activeTool.value,\n          skill: activeSkill.value,\n          fileName: editorFileName.value,\n          content: editorContent.value\n        })\n        if (res.code === 0) {\n          ElMessage.success('保存成功')\n        }\n      } else if (editorType.value === 'template') {\n        const res = await saveSkillTemplate({\n          tool: activeTool.value,\n          skill: activeSkill.value,\n          fileName: editorFileName.value,\n          content: editorContent.value\n        })\n        if (res.code === 0) {\n          ElMessage.success('保存成功')\n        }\n      } else if (editorType.value === 'constraint') {\n        let syncTools = []\n        if (tools.value.length > 1) {\n          try {\n            await ElMessageBox.confirm('是否同步到其他 AI 客户端工具？', '同步提示', {\n              confirmButtonText: '同步',\n              cancelButtonText: '仅当前',\n              type: 'warning'\n            })\n            syncTools = tools.value\n              .map((item) => item.key)\n              .filter((key) => key && key !== activeTool.value)\n          } catch (e) {\n            syncTools = []\n          }\n        }\n\n        const res = await saveGlobalConstraint({\n          tool: activeTool.value,\n          content: editorContent.value,\n          syncTools\n        })\n        if (res.code !== 0) {\n          ElMessage.error('保存失败')\n          return\n        }\n        globalConstraintExists.value = true\n        ElMessage.success(syncTools.length ? '保存并同步成功' : '保存成功')\n      }\n    } catch (e) {\n      ElMessage.error('保存失败')\n    }\n  }\n\n  function detectLang(fileName) {\n    if (!fileName) return 'text'\n    const lower = fileName.toLowerCase()\n    if (lower.endsWith('.py')) return 'python'\n    if (lower.endsWith('.js')) return 'javascript'\n    if (lower.endsWith('.sh')) return 'sh'\n    if (lower.endsWith('.md')) return 'markdown'\n    return 'text'\n  }\n\n  function defaultSkillTemplate() {\n    return (\n      '# Skill Title\\n' +\n      '## Instructions\\n- 说明目标、输入、输出与步骤。\\n\\n' +\n      '## Examples\\n- 输入: ...\\n- 输出: ...\\n\\n' +\n      '## Guidelines\\n- 约束、格式与质量标准。\\n\\n' +\n      '## Output Format\\n1. ...\\n2. ...\\n'\n    )\n  }\n\n  function skillsFilesToRows(list) {\n    return (list || []).map((name) => ({ name }))\n  }\n\n  // ===== 在线 Skills =====\n  const onlineDrawerVisible = ref(false)\n  const onlineSkillList = ref([])\n  const onlineTotal = ref(0)\n  const onlineSearchInfo = reactive({ page: 1, pageSize: 10, name: '' })\n  const onlineLoading = ref(false)\n  const downloadingIds = reactive(new Set())\n  const downloadTargetDialogVisible = ref(false)\n  const downloadTarget = ref('')\n  const downloadRow = ref(null)\n\n  const ALL_TOOLS_DOWNLOAD_TARGET = '__all__'\n\n  const downloadTargetOptions = computed(() => {\n    const options = tools.value.map((item) => ({\n      label: item.label || item.key,\n      value: item.key\n    }))\n    options.push({\n      label: '全部工具',\n      value: ALL_TOOLS_DOWNLOAD_TARGET\n    })\n    return options\n  })\n\n  const pluginMarketLoginURL = 'https://plugin.gin-vue-admin.com'\n\n  const isPluginMarketAuthError = (message) => {\n    const msg = (message || '').toString()\n    return msg.includes('插件市场登录') || msg.includes('401')\n  }\n\n  const promptPluginMarketLogin = async () => {\n    try {\n      await ElMessageBox.confirm('请先登录插件市场后再下载技能，是否现在前往登录？', '提示', {\n        confirmButtonText: '前往插件市场',\n        cancelButtonText: '取消',\n        type: 'warning'\n      })\n      window.open(pluginMarketLoginURL, '_blank')\n    } catch (e) {\n      // 用户取消时不需要额外提示\n    }\n  }\n\n  const openOnlineDrawer = () => {\n    onlineSearchInfo.page = 1\n    onlineSearchInfo.pageSize = 10\n    onlineSearchInfo.name = ''\n    onlineDrawerVisible.value = true\n    getOnlineSkills()\n  }\n\n  const getOnlineSkills = async () => {\n    onlineLoading.value = true\n    const res = await getShopPluginList({\n      page: onlineSearchInfo.page,\n      pageSize: onlineSearchInfo.pageSize,\n      category: 6,\n      name: onlineSearchInfo.name || undefined,\n      updateTime: 1\n    })\n    if (res.code === 0) {\n      onlineSkillList.value = res.data.list\n      onlineTotal.value = res.data.total\n    }\n    onlineLoading.value = false\n  }\n\n  const searchOnlineSkills = () => {\n    onlineSearchInfo.page = 1\n    getOnlineSkills()\n  }\n\n  const resetOnlineSearch = () => {\n    onlineSearchInfo.name = ''\n    onlineSearchInfo.page = 1\n    getOnlineSkills()\n  }\n\n  const handleOnlinePageChange = (page) => {\n    onlineSearchInfo.page = page\n    getOnlineSkills()\n  }\n\n  const handleOnlineSizeChange = (size) => {\n    onlineSearchInfo.pageSize = size\n    onlineSearchInfo.page = 1\n    getOnlineSkills()\n  }\n\n  const getToolLabel = (key) => {\n    return tools.value.find((item) => item.key === key)?.label || key\n  }\n\n  const closeDownloadTargetDialog = () => {\n    downloadTargetDialogVisible.value = false\n    downloadRow.value = null\n  }\n\n  const handleDownloadSkill = (row) => {\n    downloadRow.value = row\n    downloadTarget.value = activeTool.value || tools.value[0]?.key || ''\n    downloadTargetDialogVisible.value = true\n  }\n\n  const confirmDownloadSkill = async () => {\n    if (!downloadRow.value) {\n      ElMessage.warning('未找到待下载技能')\n      return\n    }\n    const targetTools = downloadTarget.value === ALL_TOOLS_DOWNLOAD_TARGET\n      ? tools.value.map((item) => item.key).filter(Boolean)\n      : [downloadTarget.value].filter(Boolean)\n    if (!targetTools.length) {\n      ElMessage.warning('请选择下载目标')\n      return\n    }\n\n    const row = downloadRow.value\n    closeDownloadTargetDialog()\n    downloadingIds.add(row.ID)\n    const successTools = []\n    const failedTools = []\n    try {\n      for (const tool of targetTools) {\n        try {\n          const res = await downloadOnlineSkill({ tool, id: row.ID, version: row.actVersion })\n          if (res.code === 0) {\n            successTools.push(tool)\n            continue\n          }\n          if (isPluginMarketAuthError(res.msg)) {\n            await promptPluginMarketLogin()\n            return\n          }\n          failedTools.push(`${getToolLabel(tool)}: ${res.msg || '下载失败'}`)\n        } catch (e) {\n          const msg = e?.response?.data?.msg || e?.message || ''\n          if (e?.response?.status === 401 || isPluginMarketAuthError(msg)) {\n            await promptPluginMarketLogin()\n            return\n          }\n          failedTools.push(`${getToolLabel(tool)}: 下载失败`)\n        }\n      }\n\n      if (successTools.includes(activeTool.value)) {\n        await loadSkills()\n      }\n\n      if (failedTools.length === 0) {\n        const successLabels = successTools.map((tool) => getToolLabel(tool)).join('、')\n        ElMessage({\n          type: 'success',\n          message: targetTools.length > 1 ? `${row.name} 已下载到：${successLabels}` : `${row.name} 下载成功`\n        })\n        return\n      }\n\n      if (successTools.length === 0) {\n        ElMessage({ type: 'error', message: failedTools[0] || '下载失败，请重试' })\n        return\n      }\n      const successLabels = successTools.map((tool) => getToolLabel(tool)).join('、')\n      ElMessage({\n        type: 'warning',\n        message: `${row.name} 部分下载成功。成功：${successLabels}；失败：${failedTools.join('；')}`\n      })\n    } finally {\n      downloadingIds.delete(row.ID)\n    }\n  }\n</script>\n\n"
  },
  {
    "path": "web/src/view/systemTools/sysError/sysError.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form\n        ref=\"elSearchFormRef\"\n        :inline=\"true\"\n        :model=\"searchInfo\"\n        class=\"demo-form-inline\"\n        @keyup.enter=\"onSubmit\"\n      >\n        <el-form-item label=\"创建日期\" prop=\"createdAtRange\">\n          <template #label>\n            <span>\n              创建日期\n              <el-tooltip\n                content=\"搜索范围是开始日期（包含）至结束日期（不包含）\"\n              >\n                <el-icon><QuestionFilled /></el-icon>\n              </el-tooltip>\n            </span>\n          </template>\n\n          <el-date-picker\n            v-model=\"searchInfo.createdAtRange\"\n            class=\"!w-380px\"\n            type=\"datetimerange\"\n            range-separator=\"至\"\n            start-placeholder=\"开始时间\"\n            end-placeholder=\"结束时间\"\n          />\n        </el-form-item>\n\n        <el-form-item label=\"错误来源\" prop=\"form\">\n          <el-input v-model=\"searchInfo.form\" placeholder=\"搜索条件\" />\n        </el-form-item>\n\n        <el-form-item label=\"错误内容\" prop=\"info\">\n          <el-input v-model=\"searchInfo.info\" placeholder=\"搜索条件\" />\n        </el-form-item>\n\n        <template v-if=\"showAllQuery\">\n          <!-- 将需要控制显示状态的查询条件添加到此范围内 -->\n        </template>\n\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\"\n            >查询</el-button\n          >\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n          <el-button\n            link\n            type=\"primary\"\n            icon=\"arrow-down\"\n            @click=\"showAllQuery = true\"\n            v-if=\"!showAllQuery\"\n            >展开</el-button\n          >\n          <el-button\n            link\n            type=\"primary\"\n            icon=\"arrow-up\"\n            @click=\"showAllQuery = false\"\n            v-else\n            >收起</el-button\n          >\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button\n          icon=\"delete\"\n          style=\"margin-left: 10px\"\n          :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\"\n          >删除</el-button\n        >\n      </div>\n      <el-table\n        ref=\"multipleTable\"\n        style=\"width: 100%\"\n        tooltip-effect=\"dark\"\n        :data=\"tableData\"\n        row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n\n        <el-table-column\n          sortable\n          align=\"left\"\n          label=\"日期\"\n          prop=\"CreatedAt\"\n          width=\"180\"\n        >\n          <template #default=\"scope\">{{\n            formatDate(scope.row.CreatedAt)\n          }}</template>\n        </el-table-column>\n\n        <el-table-column\n          align=\"left\"\n          label=\"错误来源\"\n          prop=\"form\"\n          width=\"120\"\n        />\n\n        <el-table-column\n          align=\"left\"\n          label=\"错误等级\"\n          prop=\"level\"\n          width=\"120\"\n        >\n          <template #default=\"scope\">\n            <el-tag\n              effect=\"dark\"\n              :type=\"levelTagMap[scope.row.level] || 'info'\"\n            >\n              {{ levelLabelMap[scope.row.level] || defaultLevelLabel }}\n            </el-tag>\n          </template>\n        </el-table-column>\n\n        <el-table-column\n          align=\"left\"\n          label=\"处理状态\"\n          prop=\"status\"\n          width=\"140\"\n        >\n          <template #default=\"scope\">\n            <el-tag\n              effect=\"light\"\n              :type=\"statusTagMap[scope.row.status] || 'info'\"\n            >\n              {{ statusLabelMap[scope.row.status] || defaultStatusLabel }}\n            </el-tag>\n          </template>\n        </el-table-column>\n\n        <el-table-column\n          align=\"left\"\n          label=\"错误内容\"\n          prop=\"info\"\n          show-overflow-tooltip\n          width=\"240\"\n        />\n\n        <el-table-column\n          align=\"left\"\n          label=\"解决方案\"\n          show-overflow-tooltip\n          prop=\"solution\"\n          width=\"120\"\n        />\n\n        <el-table-column\n          align=\"left\"\n          label=\"操作\"\n          fixed=\"right\"\n          :min-width=\"appStore.operateMinWith\"\n        >\n          <template #default=\"scope\">\n            <el-button\n              v-if=\"scope.row.status !== '处理中'\"\n              type=\"primary\"\n              link\n              class=\"table-button\"\n              @click=\"getSolution(scope.row.ID)\"\n            >\n              <el-icon><ai-gva /></el-icon>方案\n            </el-button>\n            <el-button\n              type=\"primary\"\n              link\n              class=\"table-button\"\n              @click=\"getDetails(scope.row)\"\n              ><el-icon style=\"margin-right: 5px\"><InfoFilled /></el-icon\n              >查看</el-button\n            >\n            <el-button\n              type=\"primary\"\n              link\n              icon=\"delete\"\n              @click=\"deleteRow(scope.row)\"\n              >删除</el-button\n            >\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination\n          layout=\"total, sizes, prev, pager, next, jumper\"\n          :current-page=\"page\"\n          :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\"\n          :total=\"total\"\n          @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\"\n        />\n      </div>\n    </div>\n\n    <el-drawer\n      destroy-on-close\n      :size=\"appStore.drawerSize\"\n      v-model=\"detailShow\"\n      :show-close=\"true\"\n      :before-close=\"closeDetailShow\"\n      title=\"查看\"\n    >\n      <el-descriptions :column=\"2\" border direction=\"vertical\">\n        <el-descriptions-item label=\"错误来源\">\n          {{ detailForm.form }}\n        </el-descriptions-item>\n        <el-descriptions-item label=\"错误等级\">\n          <el-tag\n            effect=\"dark\"\n            :type=\"levelTagMap[detailForm.level] || 'info'\"\n          >\n            {{ levelLabelMap[detailForm.level] || defaultLevelLabel }}\n          </el-tag>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"处理状态\">\n          <el-tag\n            effect=\"light\"\n            :type=\"statusTagMap[detailForm.status] || 'info'\"\n          >\n            {{ statusLabelMap[detailForm.status] || defaultStatusLabel }}\n          </el-tag>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"错误内容\" :span=\"2\">\n          <pre class=\"whitespace-pre-wrap break-words\">{{ detailForm.info }}</pre>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"解决方案\" :span=\"2\">\n          <pre class=\"whitespace-pre-wrap break-words\">{{ detailForm.solution }}</pre>\n        </el-descriptions-item>\n      </el-descriptions>\n    </el-drawer>\n  </div>\n</template>\n\n<script setup>\n  import {\n    deleteSysError,\n    deleteSysErrorByIds,\n    findSysError,\n    getSysErrorList,\n    getSysErrorSolution\n  } from '@/api/system/sysError'\n\n  import { formatDate } from '@/utils/format'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { ref } from 'vue'\n  import { useAppStore } from '@/pinia'\n\n  defineOptions({\n    name: 'SysError'\n  })\n\n  const appStore = useAppStore()\n\n  // 控制更多查询条件显示/隐藏状态\n  const showAllQuery = ref(false)\n\n  const elSearchFormRef = ref()\n\n  // =========== 表格控制部分 ===========\n  const page = ref(1)\n  const total = ref(0)\n  const pageSize = ref(10)\n  const tableData = ref([])\n  const searchInfo = ref({})\n  // 重置\n  const onReset = () => {\n    searchInfo.value = {}\n    getTableData()\n  }\n\n  const getSolution = async (id) => {\n    const confirmed = await ElMessageBox.confirm(\n      '日志将通过 AI-PATH 传输至 GVA AI 用于错误分析，并在 GVA 官方平台短暂存储作为 AI 上下文。是否确认进行 AI 处理？（此功能仅向授权用户开放）',\n      '提示(Beta)',\n      {\n        confirmButtonText: '确认',\n        cancelButtonText: '取消',\n        type: 'warning'\n      }\n    ).catch(() => false)\n    if (!confirmed) return\n    const res = await getSysErrorSolution({ id })\n    if (res.code === 0) {\n      ElMessage({ type: 'success', message: res.msg || '处理已提交，1分钟后完成' })\n      getTableData()\n    }\n  }\n  // 搜索\n  const onSubmit = () => {\n    elSearchFormRef.value?.validate(async (valid) => {\n      if (!valid) return\n      page.value = 1\n      getTableData()\n    })\n  }\n\n  // 分页\n  const handleSizeChange = (val) => {\n    pageSize.value = val\n    getTableData()\n  }\n\n  // 修改页面容量\n  const handleCurrentChange = (val) => {\n    page.value = val\n    getTableData()\n  }\n\n  // 查询\n  const getTableData = async () => {\n    const table = await getSysErrorList({\n      page: page.value,\n      pageSize: pageSize.value,\n      ...searchInfo.value\n    })\n    if (table.code === 0) {\n      tableData.value = table.data.list\n      total.value = table.data.total\n      page.value = table.data.page\n      pageSize.value = table.data.pageSize\n    }\n  }\n\n  getTableData()\n\n  // ============== 表格控制部分结束 ===============\n\n  // 获取需要的字典 可能为空 按需保留\n  const setOptions = async () => {}\n\n  // 获取需要的字典 可能为空 按需保留\n  setOptions()\n\n  // 多选数据\n  const multipleSelection = ref([])\n  // 多选\n  const handleSelectionChange = (val) => {\n    multipleSelection.value = val\n  }\n\n  // 删除行\n  const deleteRow = (row) => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(() => {\n      deleteSysErrorFunc(row)\n    })\n  }\n\n  // 多选删除\n  const onDelete = async () => {\n    ElMessageBox.confirm('确定要删除吗?', '提示', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    }).then(async () => {\n      const IDs = []\n      if (multipleSelection.value.length === 0) {\n        ElMessage({\n          type: 'warning',\n          message: '请选择要删除的数据'\n        })\n        return\n      }\n      multipleSelection.value &&\n        multipleSelection.value.map((item) => {\n          IDs.push(item.ID)\n        })\n      const res = await deleteSysErrorByIds({ IDs })\n      if (res.code === 0) {\n        ElMessage({\n          type: 'success',\n          message: '删除成功'\n        })\n        if (tableData.value.length === IDs.length && page.value > 1) {\n          page.value--\n        }\n        getTableData()\n      }\n    })\n  }\n\n  // 删除行\n  const deleteSysErrorFunc = async (row) => {\n    const res = await deleteSysError({ ID: row.ID })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '删除成功'\n      })\n      if (tableData.value.length === 1 && page.value > 1) {\n        page.value--\n      }\n      getTableData()\n    }\n  }\n\n  const detailForm = ref({})\n\n  // 查看详情控制标记\n  const detailShow = ref(false)\n\n  // 打开详情弹窗\n  const openDetailShow = () => {\n    detailShow.value = true\n  }\n\n  // 打开详情\n  const getDetails = async (row) => {\n    // 打开弹窗\n    const res = await findSysError({ ID: row.ID })\n    if (res.code === 0) {\n      detailForm.value = res.data\n      openDetailShow()\n    }\n  }\n\n  // 关闭详情弹窗\n  const closeDetailShow = () => {\n    detailShow.value = false\n    detailForm.value = {}\n  }\n\n  const statusLabelMap = {\n    未处理: '未处理',\n    处理中: '处理中',\n    处理完成: '处理完成',\n    处理失败: '处理失败'\n  }\n  const statusTagMap = {\n    未处理: 'info',\n    处理中: 'warning',\n    处理完成: 'success',\n    处理失败: 'danger'\n  }\n  const defaultStatusLabel = '未处理'\n\n  const levelLabelMap = {\n    fatal: '致命错误',\n    error: '一般错误'\n  }\n  const levelTagMap = {\n    fatal: 'danger',\n    error: 'warning'\n  }\n  const defaultLevelLabel = '一般错误'\n</script>\n"
  },
  {
    "path": "web/src/view/systemTools/system/system.vue",
    "content": "<template>\n  <div class=\"system\">\n    <el-form ref=\"form\" :model=\"config\" label-width=\"240px\">\n      <!--  System start  -->\n      <el-tabs v-model=\"activeNames\">\n        <el-tab-pane label=\"系统配置\" name=\"1\" class=\"mt-3.5\">\n          <el-form-item label=\"端口值\">\n            <el-input-number\n              v-model=\"config.system.addr\"\n              placeholder=\"请输入端口值\"\n            />\n          </el-form-item>\n          <el-form-item label=\"数据库类型\">\n            <el-select v-model=\"config.system['db-type']\" class=\"w-full\">\n              <el-option value=\"mysql\" />\n              <el-option value=\"pgsql\" />\n              <el-option value=\"mssql\" />\n              <el-option value=\"sqlite\" />\n              <el-option value=\"oracle\" />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"Oss类型\">\n            <el-select v-model=\"config.system['oss-type']\" class=\"w-full\">\n              <el-option value=\"local\" label=\"本地\" />\n              <el-option value=\"qiniu\" label=\"七牛\" />\n              <el-option value=\"tencent-cos\" label=\"腾讯云COS\" />\n              <el-option value=\"aliyun-oss\" label=\"阿里云OSS\" />\n              <el-option value=\"huawei-obs\" label=\"华为云OBS\" />\n              <el-option value=\"cloudflare-r2\" label=\"cloudflare R2\" />\n              <el-option value=\"minio\">MinIO</el-option>\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"多点登录拦截\">\n            <el-switch v-model=\"config.system['use-multipoint']\" />\n          </el-form-item>\n          <el-form-item label=\"开启redis\">\n            <el-switch v-model=\"config.system['use-redis']\" />\n          </el-form-item>\n          <el-form-item label=\"开启Mongo\">\n            <el-switch v-model=\"config.system['use-mongo']\" />\n          </el-form-item>\n          <el-form-item label=\"严格角色模式\">\n            <el-switch v-model=\"config.system['use-strict-auth']\" />\n          </el-form-item>\n          <el-form-item label=\"限流次数\">\n            <el-input-number v-model.number=\"config.system['iplimit-count']\" />\n          </el-form-item>\n          <el-form-item label=\"限流时间\">\n            <el-input-number v-model.number=\"config.system['iplimit-time']\" />\n          </el-form-item>\n          <el-form-item label=\"禁用自动迁移数据库表结构\">\n            <el-switch v-model=\"config.system['disable-auto-migrate']\" />\n          </el-form-item>\n          <el-tooltip\n            content=\"请修改完成后，注意一并修改前端env环境下的VITE_BASE_PATH\"\n            placement=\"top-start\"\n          >\n            <el-form-item label=\"全局路由前缀\">\n              <el-input\n                v-model.trim=\"config.system['router-prefix']\"\n                placeholder=\"请输入全局路由前缀\"\n              />\n            </el-form-item>\n          </el-tooltip>\n        </el-tab-pane>\n        <el-tab-pane label=\"jwt签名\" name=\"2\" class=\"mt-3.5\">\n          <el-form-item label=\"jwt签名\">\n            <el-input\n              v-model.trim=\"config.jwt['signing-key']\"\n              placeholder=\"请输入jwt签名\"\n            >\n              <template #append>\n                <el-button @click=\"getUUID\">生成</el-button>\n              </template>\n            </el-input>\n          </el-form-item>\n          <el-form-item label=\"有效期\">\n            <el-input\n              v-model.trim=\"config.jwt['expires-time']\"\n              placeholder=\"请输入有效期\"\n            />\n          </el-form-item>\n          <el-form-item label=\"缓冲期\">\n            <el-input\n              v-model.trim=\"config.jwt['buffer-time']\"\n              placeholder=\"请输入缓冲期\"\n            />\n          </el-form-item>\n          <el-form-item label=\"签发者\">\n            <el-input\n              v-model.trim=\"config.jwt.issuer\"\n              placeholder=\"请输入签发者\"\n            />\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane label=\"Zap日志配置\" name=\"3\" class=\"mt-3.5\">\n          <el-form-item label=\"级别\">\n            <el-select v-model=\"config.zap.level\">\n              <el-option value=\"off\" label=\"关闭\" />\n              <el-option value=\"fatal\" label=\"致命\" />\n              <el-option value=\"error\" label=\"错误\" />\n              <el-option value=\"warn\" label=\"警告\" />\n              <el-option value=\"info\" label=\"信息\" />\n              <el-option value=\"debug\" label=\"调试\" />\n              <el-option value=\"trace\" label=\"跟踪\" />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"输出\">\n            <el-select v-model=\"config.zap.format\">\n              <el-option value=\"console\" label=\"console\" />\n              <el-option value=\"json\" label=\"json\" />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"日志前缀\">\n            <el-input\n              v-model.trim=\"config.zap.prefix\"\n              placeholder=\"请输入日志前缀\"\n            />\n          </el-form-item>\n          <el-form-item label=\"日志文件夹\">\n            <el-input\n              v-model.trim=\"config.zap.director\"\n              placeholder=\"请输入日志文件夹\"\n            />\n          </el-form-item>\n          <el-form-item label=\"编码级\">\n            <el-select v-model=\"config.zap['encode-level']\" class=\"w-6/12\">\n              <el-option\n                value=\"LowercaseLevelEncoder\"\n                label=\"LowercaseLevelEncoder\"\n              />\n              <el-option\n                value=\"LowercaseColorLevelEncoder\"\n                label=\"LowercaseColorLevelEncoder\"\n              />\n              <el-option\n                value=\"CapitalLevelEncoder\"\n                label=\"CapitalLevelEncoder\"\n              />\n              <el-option\n                value=\"CapitalColorLevelEncoder\"\n                label=\"CapitalColorLevelEncoder\"\n              />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"栈名\">\n            <el-input\n              v-model.trim=\"config.zap['stacktrace-key']\"\n              placeholder=\"请输入栈名\"\n            />\n          </el-form-item>\n          <el-form-item label=\"日志留存时间(默认以天为单位)\">\n            <el-input-number v-model=\"config.zap['retention-day']\" />\n          </el-form-item>\n          <el-form-item label=\"显示行\">\n            <el-switch v-model=\"config.zap['show-line']\" />\n          </el-form-item>\n          <el-form-item label=\"输出控制台\">\n            <el-switch v-model=\"config.zap['log-in-console']\" />\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane\n          label=\"Redis\"\n          name=\"4\"\n          class=\"mt-3.5\"\n          v-if=\"config.system['use-redis']\"\n        >\n          <el-form-item label=\"库\">\n            <el-input-number v-model=\"config.redis.db\" min=\"0\" max=\"16\" />\n          </el-form-item>\n          <el-form-item label=\"地址\">\n            <el-input\n              v-model.trim=\"config.redis.addr\"\n              placeholder=\"请输入地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"密码\">\n            <el-input\n              v-model.trim=\"config.redis.password\"\n              placeholder=\"请输入密码\"\n            />\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane label=\"邮箱配置\" name=\"5\" class=\"mt-3.5\">\n          <el-form-item label=\"接收者邮箱\">\n            <el-input\n              v-model=\"config.email.to\"\n              placeholder=\"可多个，以逗号分隔\"\n            />\n          </el-form-item>\n          <el-form-item label=\"端口\">\n            <el-input-number v-model=\"config.email.port\" />\n          </el-form-item>\n          <el-form-item label=\"发送者邮箱\">\n            <el-input\n              v-model.trim=\"config.email.from\"\n              placeholder=\"请输入发送者邮箱\"\n            />\n          </el-form-item>\n          <el-form-item label=\"host\">\n            <el-input\n              v-model.trim=\"config.email.host\"\n              placeholder=\"请输入host\"\n            />\n          </el-form-item>\n          <el-form-item label=\"是否为ssl\">\n            <el-switch v-model=\"config.email['is-ssl']\" />\n          </el-form-item>\n          <el-form-item label=\"是否LoginAuth认证\">\n            <el-switch v-model=\"config.email['is-loginauth']\" />\n          </el-form-item>\n          <el-form-item label=\"secret\">\n            <el-input\n              v-model.trim=\"config.email.secret\"\n              placeholder=\"请输入secret\"\n            />\n          </el-form-item>\n          <el-form-item label=\"测试邮件\">\n            <el-button @click=\"email\">测试邮件</el-button>\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane\n          label=\"Mongo 数据库配置\"\n          name=\"14\"\n          class=\"mt-3.5\"\n          v-if=\"config.system['use-mongo']\"\n        >\n          <el-form-item label=\"collection name(表名,一般不写)\">\n            <el-input\n              v-model.trim=\"config.mongo.coll\"\n              placeholder=\"请输入collection name\"\n            />\n          </el-form-item>\n          <el-form-item label=\"mongodb 选项\">\n            <el-input\n              v-model.trim=\"config.mongo.options\"\n              placeholder=\"请输入mongodb 选项\"\n            />\n          </el-form-item>\n          <el-form-item label=\"database name(数据库名)\">\n            <el-input\n              v-model.trim=\"config.mongo.database\"\n              placeholder=\"请输入数据库名\"\n            />\n          </el-form-item>\n          <el-form-item label=\"用户名\">\n            <el-input\n              v-model.trim=\"config.mongo.username\"\n              placeholder=\"请输入用户名\"\n            />\n          </el-form-item>\n          <el-form-item label=\"密码\">\n            <el-input\n              v-model.trim=\"config.mongo.password\"\n              placeholder=\"请输入密码\"\n            />\n          </el-form-item>\n          <el-form-item label=\"最小连接池\">\n            <el-input-number v-model=\"config.mongo['min-pool-size']\" min=\"0\" />\n          </el-form-item>\n          <el-form-item label=\"最大连接池\">\n            <el-input-number\n              v-model=\"config.mongo['max-pool-size']\"\n              min=\"100\"\n            />\n          </el-form-item>\n          <el-form-item label=\"socket超时时间\">\n            <el-input-number\n              v-model=\"config.mongo['socket-timeout-ms']\"\n              min=\"0\"\n            />\n          </el-form-item>\n          <el-form-item label=\"连接超时时间\">\n            <el-input-number\n              v-model=\"config.mongo['socket-timeout-ms']\"\n              min=\"0\"\n            />\n          </el-form-item>\n          <el-form-item label=\"是否开启zap日志\">\n            <el-switch v-model=\"config.mongo['is-zap']\" />\n          </el-form-item>\n          <el-form-item\n            v-for=\"(item, k) in config.mongo.hosts\"\n            :key=\"k\"\n            :label=\"`节点 ${k + 1}`\"\n          >\n            <div v-for=\"(_, k2) in item\" :key=\"k2\">\n              <el-form-item :key=\"k + k2\" :label=\"k2\" label-width=\"60\">\n                <el-input\n                  v-model.trim=\"item[k2]\"\n                  :placeholder=\"k2 === 'host' ? '请输入地址' : '请输入端口'\"\n                />\n              </el-form-item>\n            </div>\n            <el-form-item v-if=\"k > 0\">\n              <el-button\n                type=\"danger\"\n                size=\"small\"\n                plain\n                :icon=\"Minus\"\n                @click=\"removeNode(k)\"\n                class=\"ml-3\"\n              />\n            </el-form-item>\n          </el-form-item>\n          <el-form-item>\n            <el-button\n              type=\"primary\"\n              size=\"small\"\n              plain\n              :icon=\"Plus\"\n              @click=\"addNode\"\n            />\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane label=\"验证码配置\" name=\"7\" class=\"mt-3.5\">\n          <el-form-item label=\"字符长度\">\n            <el-input-number\n              v-model=\"config.captcha['key-long']\"\n              :min=\"4\"\n              :max=\"6\"\n            />\n          </el-form-item>\n          <el-form-item label=\"图片宽度\">\n            <el-input-number v-model.number=\"config.captcha['img-width']\" />\n          </el-form-item>\n          <el-form-item label=\"图片高度\">\n            <el-input-number v-model.number=\"config.captcha['img-height']\" />\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane label=\"数据库配置\" name=\"9\" class=\"mt-3.5\">\n          <template v-if=\"config.system['db-type'] === 'mysql'\">\n            <el-form-item label=\"\">\n              <h3>MySQL</h3>\n            </el-form-item>\n            <el-form-item label=\"用户名\">\n              <el-input\n                v-model.trim=\"config.mysql.username\"\n                placeholder=\"请输入用户名\"\n              />\n            </el-form-item>\n            <el-form-item label=\"密码\">\n              <el-input\n                v-model.trim=\"config.mysql.password\"\n                placeholder=\"请输入密码\"\n              />\n            </el-form-item>\n            <el-form-item label=\"地址\">\n              <el-input\n                v-model.trim=\"config.mysql.path\"\n                placeholder=\"请输入地址\"\n              />\n            </el-form-item>\n            <el-form-item label=\"数据库名称\">\n              <el-input\n                v-model.trim=\"config.mysql['db-name']\"\n                placeholder=\"请输入数据库名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"前缀\">\n              <el-input\n                v-model.trim=\"config.mysql['prefix']\"\n                placeholder=\"默认为空\"\n              />\n            </el-form-item>\n            <el-form-item label=\"复数表\">\n              <el-switch v-model=\"config.mysql['singular']\" />\n            </el-form-item>\n            <el-form-item label=\"引擎\">\n              <el-input\n                v-model.trim=\"config.mysql['engine']\"\n                placeholder=\"默认为InnoDB\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxIdleConns\">\n              <el-input-number\n                v-model=\"config.mysql['max-idle-conns']\"\n                :min=\"1\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxOpenConns\">\n              <el-input-number\n                v-model=\"config.mysql['max-open-conns']\"\n                :min=\"1\"\n              />\n            </el-form-item>\n            <el-form-item label=\"写入日志\">\n              <el-switch v-model=\"config.mysql['log-zap']\" />\n            </el-form-item>\n            <el-form-item label=\"日志模式\">\n              <el-select v-model=\"config.mysql['log-mode']\">\n                <el-option value=\"off\" label=\"关闭\" />\n                <el-option value=\"fatal\" label=\"致命\" />\n                <el-option value=\"error\" label=\"错误\" />\n                <el-option value=\"warn\" label=\"警告\" />\n                <el-option value=\"info\" label=\"信息\" />\n                <el-option value=\"debug\" label=\"调试\" />\n                <el-option value=\"trace\" label=\"跟踪\" />\n              </el-select>\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['db-type'] === 'pgsql'\">\n            <el-form-item label=\"\">\n              <h3>PostgreSQL</h3>\n            </el-form-item>\n            <el-form-item label=\"用户名\">\n              <el-input\n                v-model=\"config.pgsql.username\"\n                placeholder=\"请输入用户名\"\n              />\n            </el-form-item>\n            <el-form-item label=\"密码\">\n              <el-input\n                v-model=\"config.pgsql.password\"\n                placeholder=\"请输入密码\"\n              />\n            </el-form-item>\n            <el-form-item label=\"地址\">\n              <el-input\n                v-model.trim=\"config.pgsql.path\"\n                placeholder=\"请输入地址\"\n              />\n            </el-form-item>\n            <el-form-item label=\"数据库\">\n              <el-input\n                v-model.trim=\"config.pgsql['db-name']\"\n                placeholder=\"请输入数据库\"\n              />\n            </el-form-item>\n            <el-form-item label=\"前缀\">\n              <el-input\n                v-model.trim=\"config.pgsql['prefix']\"\n                placeholder=\"请输入前缀\"\n              />\n            </el-form-item>\n            <el-form-item label=\"复数表\">\n              <el-switch v-model=\"config.pgsql['singular']\" />\n            </el-form-item>\n            <el-form-item label=\"引擎\">\n              <el-input\n                v-model.trim=\"config.pgsql['engine']\"\n                placeholder=\"请输入引擎\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxIdleConns\">\n              <el-input-number v-model=\"config.pgsql['max-idle-conns']\" />\n            </el-form-item>\n            <el-form-item label=\"maxOpenConns\">\n              <el-input-number v-model=\"config.pgsql['max-open-conns']\" />\n            </el-form-item>\n            <el-form-item label=\"写入日志\">\n              <el-switch v-model=\"config.pgsql['log-zap']\" />\n            </el-form-item>\n            <el-form-item label=\"日志模式\">\n              <el-select v-model=\"config.pgsql['log-mode']\">\n                <el-option value=\"off\" label=\"关闭\" />\n                <el-option value=\"fatal\" label=\"致命\" />\n                <el-option value=\"error\" label=\"错误\" />\n                <el-option value=\"warn\" label=\"警告\" />\n                <el-option value=\"info\" label=\"信息\" />\n                <el-option value=\"debug\" label=\"调试\" />\n                <el-option value=\"trace\" label=\"跟踪\" />\n              </el-select>\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['db-type'] === 'mssql'\">\n            <el-form-item label=\"\">\n              <h3>MsSQL</h3>\n            </el-form-item>\n            <el-form-item label=\"用户名\">\n              <el-input\n                v-model.trim=\"config.mssql.username\"\n                placeholder=\"请输入用户名\"\n              />\n            </el-form-item>\n            <el-form-item label.trim=\"密码\">\n              <el-input\n                v-model.trim=\"config.mssql.password\"\n                placeholder=\"请输入密码\"\n              />\n            </el-form-item>\n            <el-form-item label=\"地址\">\n              <el-input\n                v-model.trim=\"config.mssql.path\"\n                placeholder=\"请输入地址\"\n              />\n            </el-form-item>\n            <el-form-item label=\"端口\">\n              <el-input\n                v-model.trim=\"config.mssql.port\"\n                placeholder=\"请输入端口\"\n              />\n            </el-form-item>\n            <el-form-item label=\"数据库\">\n              <el-input\n                v-model.trim=\"config.mssql['db-name']\"\n                placeholder=\"请输入数据库\"\n              />\n            </el-form-item>\n            <el-form-item label=\"前缀\">\n              <el-input\n                v-model.trim=\"config.mssql['prefix']\"\n                placeholder=\"请输入前缀\"\n              />\n            </el-form-item>\n            <el-form-item label=\"复数表\">\n              <el-switch v-model=\"config.mssql['singular']\" />\n            </el-form-item>\n            <el-form-item label=\"引擎\">\n              <el-input\n                v-model.trim=\"config.mssql['engine']\"\n                placeholder=\"请输入引擎\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxIdleConns\">\n              <el-input-number v-model=\"config.mssql['max-idle-conns']\" />\n            </el-form-item>\n            <el-form-item label=\"maxOpenConns\">\n              <el-input-number v-model=\"config.mssql['max-open-conns']\" />\n            </el-form-item>\n            <el-form-item label=\"写入日志\">\n              <el-switch v-model=\"config.mssql['log-zap']\" />\n            </el-form-item>\n            <el-form-item label=\"日志模式\">\n              <el-select v-model=\"config.mssql['log-mode']\">\n                <el-option value=\"off\" label=\"关闭\" />\n                <el-option value=\"fatal\" label=\"致命\" />\n                <el-option value=\"error\" label=\"错误\" />\n                <el-option value=\"warn\" label=\"警告\" />\n                <el-option value=\"info\" label=\"信息\" />\n                <el-option value=\"debug\" label=\"调试\" />\n                <el-option value=\"trace\" label=\"跟踪\" />\n              </el-select>\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['db-type'] === 'sqlite'\">\n            <el-form-item label=\"\">\n              <h3>sqlite</h3>\n            </el-form-item>\n            <el-form-item label=\"用户名\">\n              <el-input\n                v-model.trim=\"config.sqlite.username\"\n                placeholder=\"请输入用户名\"\n              />\n            </el-form-item>\n            <el-form-item label=\"密码\">\n              <el-input\n                v-model.trim=\"config.sqlite.password\"\n                placeholder=\"请输入密码\"\n              />\n            </el-form-item>\n            <el-form-item label=\"地址\">\n              <el-input\n                v-model.trim=\"config.sqlite.path\"\n                placeholder=\"请输入地址\"\n              />\n            </el-form-item>\n            <el-form-item label=\"端口\">\n              <el-input\n                v-model.trim=\"config.sqlite.port\"\n                placeholder=\"请输入端口\"\n              />\n            </el-form-item>\n            <el-form-item label=\"数据库\">\n              <el-input\n                v-model.trim=\"config.sqlite['db-name']\"\n                placeholder=\"请输入数据库\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxIdleConns\">\n              <el-input-number v-model=\"config.sqlite['max-idle-conns']\" />\n            </el-form-item>\n            <el-form-item label=\"maxOpenConns\">\n              <el-input-number v-model=\"config.sqlite['max-open-conns']\" />\n            </el-form-item>\n            <el-form-item label=\"写入日志\">\n              <el-switch v-model=\"config.sqlite['log-zap']\" />\n            </el-form-item>\n            <el-form-item label=\"日志模式\">\n              <el-select v-model=\"config.sqlite['log-mode']\">\n                <el-option value=\"off\" label=\"关闭\" />\n                <el-option value=\"fatal\" label=\"致命\" />\n                <el-option value=\"error\" label=\"错误\" />\n                <el-option value=\"warn\" label=\"警告\" />\n                <el-option value=\"info\" label=\"信息\" />\n                <el-option value=\"debug\" label=\"调试\" />\n                <el-option value=\"trace\" label=\"跟踪\" />\n              </el-select>\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['db-type'] === 'oracle'\">\n            <el-form-item label=\"\">\n              <h3>oracle</h3>\n            </el-form-item>\n            <el-form-item label=\"用户名\">\n              <el-input\n                v-model.trim=\"config.oracle.username\"\n                placeholder=\"请输入用户名\"\n              />\n            </el-form-item>\n            <el-form-item label=\"密码\">\n              <el-input\n                v-model.trim=\"config.oracle.password\"\n                placeholder=\"请输入密码\"\n              />\n            </el-form-item>\n            <el-form-item label=\"地址\">\n              <el-input\n                v-model.trim=\"config.oracle.path\"\n                placeholder=\"请输入地址\"\n              />\n            </el-form-item>\n            <el-form-item label=\"数据库名称\">\n              <el-input\n                v-model.trim=\"config.oracle['db-name']\"\n                placeholder=\"请输入数据库名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"前缀\">\n              <el-input\n                v-model.trim=\"config.oracle['prefix']\"\n                placeholder=\"默认为空\"\n              />\n            </el-form-item>\n            <el-form-item label=\"复数表\">\n              <el-switch v-model=\"config.oracle['singular']\" />\n            </el-form-item>\n            <el-form-item label=\"引擎\">\n              <el-input\n                v-model.trim=\"config.oracle['engine']\"\n                placeholder=\"默认为InnoDB\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxIdleConns\">\n              <el-input-number\n                v-model=\"config.oracle['max-idle-conns']\"\n                :min=\"1\"\n              />\n            </el-form-item>\n            <el-form-item label=\"maxOpenConns\">\n              <el-input-number\n                v-model=\"config.oracle['max-open-conns']\"\n                :min=\"1\"\n              />\n            </el-form-item>\n            <el-form-item label=\"写入日志\">\n              <el-switch v-model=\"config.oracle['log-zap']\" />\n            </el-form-item>\n            <el-form-item label=\"日志模式\">\n              <el-select v-model=\"config.oracle['log-mode']\">\n                <el-option value=\"off\" label=\"关闭\" />\n                <el-option value=\"fatal\" label=\"致命\" />\n                <el-option value=\"error\" label=\"错误\" />\n                <el-option value=\"warn\" label=\"警告\" />\n                <el-option value=\"info\" label=\"信息\" />\n                <el-option value=\"debug\" label=\"调试\" />\n                <el-option value=\"trace\" label=\"跟踪\" />\n              </el-select>\n            </el-form-item>\n          </template>\n        </el-tab-pane>\n        <el-tab-pane label=\"oss配置\" name=\"10\" class=\"mt-3.5\">\n          <template v-if=\"config.system['oss-type'] === 'local'\">\n            <h2>本地配置</h2>\n            <el-form-item label=\"本地文件访问路径\">\n              <el-input\n                v-model.trim=\"config.local.path\"\n                placeholder=\"请输入本地文件访问路径\"\n              />\n            </el-form-item>\n            <el-form-item label=\"本地文件存储路径\">\n              <el-input\n                v-model.trim=\"config.local['store-path']\"\n                placeholder=\"请输入本地文件存储路径\"\n              />\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['oss-type'] === 'qiniu'\">\n            <h2>七牛上传配置</h2>\n            <el-form-item label=\"存储区域\">\n              <el-input\n                v-model.trim=\"config.qiniu.zone\"\n                placeholder=\"请输入存储区域\"\n              />\n            </el-form-item>\n            <el-form-item label=\"空间名称\">\n              <el-input\n                v-model.trim=\"config.qiniu.bucket\"\n                placeholder=\"请输入空间名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"CDN加速域名\">\n              <el-input\n                v-model.trim=\"config.qiniu['img-path']\"\n                placeholder=\"请输入CDN加速域名\"\n              />\n            </el-form-item>\n            <el-form-item label=\"是否使用https\">\n              <el-switch v-model=\"config.qiniu['use-https']\">开启</el-switch>\n            </el-form-item>\n            <el-form-item label=\"accessKey\">\n              <el-input\n                v-model.trim=\"config.qiniu['access-key']\"\n                placeholder=\"请输入accessKey\"\n              />\n            </el-form-item>\n            <el-form-item label=\"secretKey\">\n              <el-input\n                v-model.trim=\"config.qiniu['secret-key']\"\n                placeholder=\"请输入secretKey\"\n              />\n            </el-form-item>\n            <el-form-item label=\"上传是否使用CDN上传加速\">\n              <el-switch v-model=\"config.qiniu['use-cdn-domains']\" />\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['oss-type'] === 'tencent-cos'\">\n            <h2>腾讯云COS上传配置</h2>\n            <el-form-item label=\"存储桶名称\">\n              <el-input\n                v-model.trim=\"config['tencent-cos']['bucket']\"\n                placeholder=\"请输入存储桶名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"所属地域\">\n              <el-input\n                v-model.trim=\"config['tencent-cos'].region\"\n                placeholder=\"请输入所属地域\"\n              />\n            </el-form-item>\n            <el-form-item label=\"secretID\">\n              <el-input\n                v-model.trim=\"config['tencent-cos']['secret-id']\"\n                placeholder=\"请输入secretID\"\n              />\n            </el-form-item>\n            <el-form-item label=\"secretKey\">\n              <el-input\n                v-model.trim=\"config['tencent-cos']['secret-key']\"\n                placeholder=\"请输入secretKey\"\n              />\n            </el-form-item>\n            <el-form-item label=\"路径前缀\">\n              <el-input\n                v-model.trim=\"config['tencent-cos']['path-prefix']\"\n                placeholder=\"请输入路径前缀\"\n              />\n            </el-form-item>\n            <el-form-item label=\"访问域名\">\n              <el-input\n                v-model.trim=\"config['tencent-cos']['base-url']\"\n                placeholder=\"请输入访问域名\"\n              />\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['oss-type'] === 'aliyun-oss'\">\n            <h2>阿里云OSS上传配置</h2>\n            <el-form-item label=\"区域\">\n              <el-input\n                v-model.trim=\"config['aliyun-oss'].endpoint\"\n                placeholder=\"请输入区域\"\n              />\n            </el-form-item>\n            <el-form-item label=\"accessKeyId\">\n              <el-input\n                v-model.trim=\"config['aliyun-oss']['access-key-id']\"\n                placeholder=\"请输入accessKeyId\"\n              />\n            </el-form-item>\n            <el-form-item label=\"accessKeySecret\">\n              <el-input\n                v-model.trim=\"config['aliyun-oss']['access-key-secret']\"\n                placeholder=\"请输入accessKeySecret\"\n              />\n            </el-form-item>\n            <el-form-item label=\"存储桶名称\">\n              <el-input\n                v-model.trim=\"config['aliyun-oss']['bucket-name']\"\n                placeholder=\"请输入存储桶名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"访问域名\">\n              <el-input\n                v-model.trim=\"config['aliyun-oss']['bucket-url']\"\n                placeholder=\"请输入访问域名\"\n              />\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['oss-type'] === 'huawei-obs'\">\n            <h2>华为云OBS上传配置</h2>\n            <el-form-item label=\"路径\">\n              <el-input\n                v-model.trim=\"config['hua-wei-obs'].path\"\n                placeholder=\"请输入路径\"\n              />\n            </el-form-item>\n            <el-form-item label=\"存储桶名称\">\n              <el-input\n                v-model.trim=\"config['hua-wei-obs'].bucket\"\n                placeholder=\"请输入存储桶名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"区域\">\n              <el-input\n                v-model.trim=\"config['hua-wei-obs'].endpoint\"\n                placeholder=\"请输入区域\"\n              />\n            </el-form-item>\n            <el-form-item label=\"accessKey\">\n              <el-input\n                v-model.trim=\"config['hua-wei-obs']['access-key']\"\n                placeholder=\"请输入accessKey\"\n              />\n            </el-form-item>\n            <el-form-item label=\"secretKey\">\n              <el-input\n                v-model.trim=\"config['hua-wei-obs']['secret-key']\"\n                placeholder=\"请输入secretKey\"\n              />\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['oss-type'] === 'cloudflare-r2'\">\n            <h2>Cloudflare R2上传配置</h2>\n            <el-form-item label=\"路径\">\n              <el-input\n                v-model.trim=\"config['cloudflare-r2'].path\"\n                placeholder=\"请输入路径\"\n              />\n            </el-form-item>\n            <el-form-item label=\"存储桶名称\">\n              <el-input\n                v-model.trim=\"config['cloudflare-r2'].bucket\"\n                placeholder=\"请输入存储桶名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Base URL\">\n              <el-input\n                v-model.trim=\"config['cloudflare-r2']['base-url']\"\n                placeholder=\"请输入Base URL\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Account ID\">\n              <el-input\n                v-model.trim=\"config['cloudflare-r2']['account-id']\"\n                placeholder=\"请输入secretKey\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Access Key ID\">\n              <el-input\n                v-model.trim=\"config['cloudflare-r2']['access-key-id']\"\n                placeholder=\"请输入secretKey\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Secret Access Key\">\n              <el-input\n                v-model.trim=\"config['cloudflare-r2']['secret-access-key']\"\n                placeholder=\"请输入secretKey\"\n              />\n            </el-form-item>\n          </template>\n          <template v-if=\"config.system['oss-type'] === 'minio'\">\n            <h2>MinIO上传配置</h2>\n            <el-form-item label=\"Endpoint\">\n              <el-input\n                v-model.trim=\"config.minio.endpoint\"\n                placeholder=\"请输入Endpoint，如 127.0.0.1:9000\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Access Key ID\">\n              <el-input\n                v-model.trim=\"config.minio['access-key-id']\"\n                placeholder=\"请输入Access Key ID\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Access Key Secret\">\n              <el-input\n                v-model.trim=\"config.minio['access-key-secret']\"\n                placeholder=\"请输入Access Key Secret\"\n              />\n            </el-form-item>\n            <el-form-item label=\"存储桶名称\">\n              <el-input\n                v-model.trim=\"config.minio['bucket-name']\"\n                placeholder=\"请输入存储桶名称\"\n              />\n            </el-form-item>\n            <el-form-item label=\"访问域名\">\n              <el-input\n                v-model.trim=\"config.minio['bucket-url']\"\n                placeholder=\"请输入访问域名\"\n              />\n            </el-form-item>\n            <el-form-item label=\"Base Path\">\n              <el-input\n                v-model.trim=\"config.minio['base-path']\"\n                placeholder=\"请输入Base Path\"\n              />\n            </el-form-item>\n            <el-form-item label=\"开启SSL\">\n              <el-switch v-model=\"config.minio['use-ssl']\" />\n            </el-form-item>\n          </template>\n        </el-tab-pane>\n        <el-tab-pane label=\"Excel上传配置\" name=\"11\" class=\"mt-3.5\">\n          <el-form-item label=\"合成目标地址\">\n            <el-input\n              v-model.trim=\"config.excel.dir\"\n              placeholder=\"请输入合成目标地址\"\n            />\n          </el-form-item>\n        </el-tab-pane>\n        <el-tab-pane label=\"自动化代码配置\" name=\"12\" class=\"mt-3.5\">\n          <el-form-item label=\"是否自动重启(linux)\">\n            <el-switch v-model=\"config.autocode['transfer-restart']\" />\n          </el-form-item>\n          <el-form-item label=\"root(项目根路径)\">\n            <el-input v-model=\"config.autocode.root\" disabled />\n          </el-form-item>\n          <el-form-item label=\"Server(后端代码地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['server']\"\n              placeholder=\"请输入后端代码地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"SApi(后端api文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['server-api']\"\n              placeholder=\"请输入后端api文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"SInitialize(后端Initialize文件夹)\">\n            <el-input\n              v-model.trim=\"config.autocode['server-initialize']\"\n              placeholder=\"请输入后端Initialize文件夹\"\n            />\n          </el-form-item>\n          <el-form-item label=\"SModel(后端Model文件地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['server-model']\"\n              placeholder=\"请输入后端Model文件地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"SRequest(后端Request文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['server-request']\"\n              placeholder=\"请输入后端Request文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"SRouter(后端Router文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['server-router']\"\n              placeholder=\"请输入后端Router文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"SService(后端Service文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['server-service']\"\n              placeholder=\"请输入后端Service文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"Web(前端文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode.web\"\n              placeholder=\"请输入前端文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"WApi(后端WApi文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['web-api']\"\n              placeholder=\"请输入后端WApi文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"WForm(后端WForm文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['web-form']\"\n              placeholder=\"请输入后端WForm文件夹地址\"\n            />\n          </el-form-item>\n          <el-form-item label=\"WTable(后端WTable文件夹地址)\">\n            <el-input\n              v-model.trim=\"config.autocode['web-table']\"\n              placeholder=\"请输入后端WTable文件夹地址\"\n            />\n          </el-form-item>\n        </el-tab-pane>\n      </el-tabs>\n    </el-form>\n    <div class=\"mt-4\">\n      <el-button type=\"primary\" @click=\"update\">立即更新 </el-button>\n      <el-button type=\"primary\" @click=\"reload\">重载服务 </el-button>\n    </div>\n  </div>\n</template>\n\n<script setup>\n  import { getSystemConfig, reloadSystem, setSystemConfig } from '@/api/system'\n  import { ref } from 'vue'\n  import { ElMessage, ElMessageBox } from 'element-plus'\n  import { Minus, Plus } from '@element-plus/icons-vue'\n  import { emailTest } from '@/api/email'\n  import { CreateUUID } from '@/utils/format'\n\n  defineOptions({\n    name: 'Config'\n  })\n\n  const activeNames = ref('1')\n  const config = ref({\n    system: {\n      'iplimit-count': 0,\n      'iplimit-time': 0\n    },\n    jwt: {},\n    mysql: {},\n    mssql: {},\n    sqlite: {},\n    pgsql: {},\n    oracle: {},\n    excel: {},\n    autocode: {},\n    redis: {},\n    mongo: {\n      coll: '',\n      options: '',\n      database: '',\n      username: '',\n      password: '',\n      'min-pool-size': '',\n      'max-pool-size': '',\n      'socket-timeout-ms': '',\n      'connect-timeout-ms': '',\n      'is-zap': false,\n      hosts: [\n        {\n          host: '',\n          port: ''\n        }\n      ]\n    },\n    qiniu: {},\n    'tencent-cos': {},\n    'aliyun-oss': {},\n    'hua-wei-obs': {},\n    'cloudflare-r2': {},\n    minio: {},\n    captcha: {},\n    zap: {},\n    local: {},\n    email: {},\n    timer: {\n      detail: {}\n    }\n  })\n\n  const initForm = async () => {\n    const res = await getSystemConfig()\n    if (res.code === 0) {\n      config.value = res.data.config\n    }\n  }\n  initForm()\n  const reload = () => {\n    ElMessageBox.confirm('确定要重载服务?', '警告', {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      type: 'warning'\n    })\n      .then(async () => {\n        const res = await reloadSystem()\n        if (res.code === 0) {\n          ElMessage({\n            type: 'success',\n            message: '操作成功'\n          })\n        }\n      })\n      .catch(() => {\n        ElMessage({\n          type: 'info',\n          message: '取消重启'\n        })\n      })\n  }\n\n  const update = async () => {\n    const res = await setSystemConfig({ config: config.value })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '配置文件设置成功'\n      })\n      await initForm()\n    }\n  }\n\n  const email = async () => {\n    const res = await emailTest()\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '邮件发送成功'\n      })\n      await initForm()\n    } else {\n      ElMessage({\n        type: 'error',\n        message: '邮件发送失败'\n      })\n    }\n  }\n\n  const getUUID = () => {\n    config.value.jwt['signing-key'] = CreateUUID()\n  }\n\n  const addNode = () => {\n    config.value.mongo.hosts.push({\n      host: '',\n      port: ''\n    })\n  }\n\n  const removeNode = (index) => {\n    config.value.mongo.hosts.splice(index, 1)\n  }\n</script>\n\n<style lang=\"scss\" scoped>\n  .system {\n    @apply bg-white p-9 rounded dark:bg-slate-900;\n    h2 {\n      @apply p-2.5 my-2.5 text-lg shadow;\n    }\n  }\n</style>\n"
  },
  {
    "path": "web/src/view/systemTools/version/version.vue",
    "content": "<template>\n  <div>\n    <div class=\"gva-search-box\">\n      <el-form ref=\"elSearchFormRef\" :inline=\"true\" :model=\"searchInfo\" class=\"demo-form-inline\"\n        @keyup.enter=\"onSubmit\">\n        <el-form-item label=\"创建日期\" prop=\"createdAtRange\">\n          <template #label>\n            <span>\n              创建日期\n              <el-tooltip content=\"搜索范围是开始日期（包含）至结束日期（不包含）\">\n                <el-icon>\n                  <QuestionFilled />\n                </el-icon>\n              </el-tooltip>\n            </span>\n          </template>\n\n          <el-date-picker v-model=\"searchInfo.createdAtRange\" class=\"w-[380px]\" type=\"datetimerange\" range-separator=\"至\"\n            start-placeholder=\"开始时间\" end-placeholder=\"结束时间\" />\n        </el-form-item>\n\n        <el-form-item label=\"版本名称\" prop=\"versionName\">\n          <el-input v-model=\"searchInfo.versionName\" placeholder=\"搜索条件\" />\n        </el-form-item>\n\n        <el-form-item label=\"版本号\" prop=\"versionCode\">\n          <el-input v-model=\"searchInfo.versionCode\" placeholder=\"搜索条件\" />\n        </el-form-item>\n\n\n\n        <template v-if=\"showAllQuery\">\n          <!-- 将需要控制显示状态的查询条件添加到此范围内 -->\n        </template>\n\n        <el-form-item>\n          <el-button type=\"primary\" icon=\"search\" @click=\"onSubmit\">查询</el-button>\n          <el-button icon=\"refresh\" @click=\"onReset\">重置</el-button>\n          <el-button link type=\"primary\" icon=\"arrow-down\" @click=\"showAllQuery = true\"\n            v-if=\"!showAllQuery\">展开</el-button>\n          <el-button link type=\"primary\" icon=\"arrow-up\" @click=\"showAllQuery = false\" v-else>收起</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n    <div class=\"gva-table-box\">\n      <div class=\"gva-btn-list\">\n        <el-button type=\"success\" icon=\"download\" @click=\"openExportDialog\">创建发版</el-button>\n        <el-button type=\"warning\" icon=\"upload\" @click=\"openImportDialog\">导入版本</el-button>\n        <el-button icon=\"delete\" style=\"margin-left: 10px;\" :disabled=\"!multipleSelection.length\"\n          @click=\"onDelete\">删除</el-button>\n      </div>\n      <el-table ref=\"multipleTable\" style=\"width: 100%\" tooltip-effect=\"dark\" :data=\"tableData\" row-key=\"ID\"\n        @selection-change=\"handleSelectionChange\">\n        <el-table-column type=\"selection\" width=\"55\" />\n\n        <el-table-column sortable align=\"left\" label=\"日期\" prop=\"CreatedAt\" width=\"180\">\n          <template #default=\"scope\">{{ formatDate(scope.row.CreatedAt) }}</template>\n        </el-table-column>\n\n        <el-table-column align=\"left\" label=\"版本名称\" prop=\"versionName\" width=\"120\" />\n\n        <el-table-column align=\"left\" label=\"版本号\" prop=\"versionCode\" width=\"120\" />\n\n        <el-table-column align=\"left\" label=\"操作\" fixed=\"right\" min-width=\"320\">\n          <template #default=\"scope\">\n            <el-button type=\"primary\" link class=\"table-button\" @click=\"getDetails(scope.row)\"><el-icon\n                style=\"margin-right: 5px\">\n                <InfoFilled />\n              </el-icon>查看</el-button>\n            <el-button type=\"success\" link icon=\"download\" class=\"table-button\"\n              @click=\"downloadJson(scope.row)\">下载发版包</el-button>\n            <el-button type=\"primary\" link icon=\"delete\" @click=\"deleteRow(scope.row)\">删除</el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"gva-pagination\">\n        <el-pagination layout=\"total, sizes, prev, pager, next, jumper\" :current-page=\"page\" :page-size=\"pageSize\"\n          :page-sizes=\"[10, 30, 50, 100]\" :total=\"total\" @current-change=\"handleCurrentChange\"\n          @size-change=\"handleSizeChange\" />\n      </div>\n    </div>\n\n    <el-drawer destroy-on-close :size=\"appStore.drawerSize\" v-model=\"detailShow\" :show-close=\"true\"\n      :before-close=\"closeDetailShow\" title=\"查看\">\n      <el-descriptions :column=\"1\" border>\n        <el-descriptions-item label=\"版本名称\">\n          {{ detailForm.versionName }}\n        </el-descriptions-item>\n        <el-descriptions-item label=\"版本号\">\n          {{ detailForm.versionCode }}\n        </el-descriptions-item>\n        <el-descriptions-item label=\"版本描述\">\n          {{ detailForm.description }}\n        </el-descriptions-item>\n      </el-descriptions>\n    </el-drawer>\n\n    <!-- 导出版本抽屉 -->\n    <el-drawer v-model=\"exportDialogVisible\" title=\"创建发版\" direction=\"rtl\" size=\"80%\" :before-close=\"closeExportDialog\"\n      :show-close=\"false\">\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">创建发版</span>\n          <div>\n            <el-button @click=\"closeExportDialog\">取消</el-button>\n            <el-button type=\"primary\" @click=\"handleExport\" :loading=\"exportLoading\">创建发版</el-button>\n          </div>\n        </div>\n      </template>\n      <el-form :model=\"exportForm\" label-width=\"100px\">\n        <el-form-item label=\"版本名称\" required>\n          <el-input v-model=\"exportForm.versionName\" placeholder=\"请输入版本名称\" />\n        </el-form-item>\n        <el-form-item label=\"版本号\" required>\n          <el-input v-model=\"exportForm.versionCode\" placeholder=\"请输入版本号\" />\n        </el-form-item>\n        <el-form-item label=\"版本描述\">\n          <el-input v-model=\"exportForm.description\" type=\"textarea\" placeholder=\"请输入版本描述\" />\n        </el-form-item>\n        <el-form-item label=\"发版信息\">\n          <div class=\"flex gap-3 w-full\">\n            <!-- 菜单选择 -->\n            <div class=\"card-col card-vertical\">\n              <div class=\"card-header\">\n                <span class=\"card-title\">选择菜单</span>\n              </div>\n              <div class=\"card-filter\">\n                <el-input v-model=\"menuFilterText\" placeholder=\"输入关键字进行过滤\" clearable size=\"small\" />\n              </div>\n              <div class=\"card-body\">\n                <el-tree ref=\"menuTreeRef\" :data=\"menuTreeData\" :default-checked-keys=\"selectedMenuIds\"\n                  :props=\"menuTreeProps\" default-expand-all highlight-current node-key=\"ID\" show-checkbox\n                  :filter-node-method=\"filterMenuNode\" @check=\"onMenuCheck\" class=\"menu-tree\">\n                  <template #default=\"{ node }\">\n                    <span class=\"flex-1 flex items-center justify-between text-sm pr-2\">\n                      <span>{{ node.label }}</span>\n                    </span>\n                  </template>\n                </el-tree>\n              </div>\n            </div>\n\n            <!-- API选择 -->\n            <div class=\"card-col card-vertical\">\n              <div class=\"card-header\">\n                <span class=\"card-title\">选择API</span>\n              </div>\n              <div class=\"card-filter\">\n                <el-input v-model=\"apiFilterTextName\" placeholder=\"按名称过滤\" clearable size=\"small\"\n                  style=\"margin-bottom: 8px\" />\n                <el-input v-model=\"apiFilterTextPath\" placeholder=\"按路径过滤\" clearable size=\"small\" />\n              </div>\n              <div class=\"card-body\">\n                <el-tree ref=\"apiTreeRef\" :data=\"apiTreeData\" :default-checked-keys=\"selectedApiIds\"\n                  :props=\"apiTreeProps\" default-expand-all highlight-current node-key=\"onlyId\" show-checkbox\n                  :filter-node-method=\"filterApiNode\" @check=\"onApiCheck\" class=\"api-tree\">\n                  <template #default=\"{ data }\">\n                    <div class=\"flex items-center justify-between w-full pr-1\">\n                      <span>{{ data.description }}</span>\n                      <el-tooltip :content=\"data.path\">\n                        <span\n                          class=\"max-w-[240px] break-all overflow-ellipsis overflow-hidden text-gray-500 dark:text-gray-400\">\n                          {{ data.path }}\n                        </span>\n                      </el-tooltip>\n                    </div>\n                  </template>\n                </el-tree>\n              </div>\n            </div>\n\n            <!-- 字典选择 -->\n            <div class=\"card-col card-vertical\">\n              <div class=\"card-header\">\n                <span class=\"card-title\">选择字典</span>\n              </div>\n              <div class=\"card-filter\">\n                <el-input v-model=\"dictFilterText\" placeholder=\"输入关键字进行过滤\" clearable size=\"small\" />\n              </div>\n              <div class=\"card-body\">\n                <el-tree ref=\"dictTreeRef\" :data=\"dictTreeData\" :default-checked-keys=\"selectedDictIds\"\n                  :props=\"dictTreeProps\" default-expand-all highlight-current node-key=\"ID\" show-checkbox\n                  :filter-node-method=\"filterDictNode\" @check=\"onDictCheck\" class=\"dict-tree\">\n                  <template #default=\"{ data }\">\n                    <div class=\"flex items-center justify-between w-full pr-1\">\n                      <span>{{ data.name || data.label }}</span>\n                      <el-tooltip :content=\"data.desc || (data.value ? `值: ${data.value}` : '')\">\n                        <span class=\"text-gray-500 dark:text-gray-400 text-xs ml-2\">\n                          {{ data.type || (data.value ? `值: ${data.value}` : '') }}\n                        </span>\n                      </el-tooltip>\n                    </div>\n                  </template>\n                </el-tree>\n              </div>\n            </div>\n          </div>\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n\n    <!-- 导入版本抽屉 -->\n    <el-drawer v-model=\"importDialogVisible\" title=\"导入版本\" direction=\"rtl\" size=\"80%\" :before-close=\"closeImportDialog\"\n      :show-close=\"false\">\n      <template #header>\n        <div class=\"flex justify-between items-center\">\n          <span class=\"text-lg\">导入版本</span>\n          <div>\n            <el-button @click=\"closeImportDialog\">取消</el-button>\n            <el-button type=\"primary\" @click=\"handleImport\" :loading=\"importLoading\"\n              :disabled=\"!importJsonContent.trim()\">导入</el-button>\n          </div>\n        </div>\n      </template>\n      <el-form label-width=\"100px\">\n        <el-form-item label=\"上传文件\">\n          <el-upload ref=\"uploadRef\" :auto-upload=\"false\" :show-file-list=\"true\" :limit=\"1\" accept=\".json\"\n            :on-change=\"handleFileChange\" :on-remove=\"handleFileRemove\" drag>\n            <el-icon class=\"el-icon--upload\"><upload-filled /></el-icon>\n            <div class=\"el-upload__text\">\n              将JSON文件拖到此处，或<em>点击上传</em>\n            </div>\n            <template #tip>\n              <div class=\"el-upload__tip\">\n                只能上传JSON文件\n              </div>\n            </template>\n          </el-upload>\n        </el-form-item>\n        <el-form-item label=\"版本JSON\">\n          <el-input v-model=\"importJsonContent\" type=\"textarea\" :rows=\"20\" placeholder=\"请粘贴版本JSON\"\n            @input=\"handleJsonContentChange\" />\n        </el-form-item>\n        <el-form-item label=\"预览内容\" v-if=\"importPreviewData\">\n          <div class=\"preview-wrap\">\n            <div class=\"flex gap-3 w-full\">\n              <div class=\"card-col\">\n                <div class=\"card-vertical\">\n                  <div class=\"card-header\">\n                    <h3 class=\"card-title\">菜单 ({{ getTotalMenuCount() }}项)</h3>\n                  </div>\n                  <div class=\"card-body\">\n                    <el-tree :data=\"previewMenuTreeData\" :props=\"menuTreeProps\" node-key=\"name\"\n                      :expand-on-click-node=\"false\" :check-on-click-node=\"false\" :show-checkbox=\"false\"\n                      default-expand-all>\n                      <template #default=\"{ data }\">\n                        <div class=\"flex-1 flex items-center justify-between text-sm pr-2\">\n                          <span>{{ data.meta?.title || data.title }}</span>\n                          <span class=\"text-gray-500 dark:text-gray-400 text-xs ml-2\">{{ data.path }}</span>\n                        </div>\n                      </template>\n                    </el-tree>\n                  </div>\n                </div>\n              </div>\n              <div class=\"card-col\">\n                <div class=\"card-vertical\">\n                  <div class=\"card-header\">\n                    <h3 class=\"card-title\">API ({{ importPreviewData.apis?.length || 0 }}项)</h3>\n                  </div>\n                  <div class=\"card-body\">\n                    <el-tree :data=\"previewApiTreeData\" :props=\"apiTreeProps\" node-key=\"ID\"\n                      :expand-on-click-node=\"false\" :check-on-click-node=\"false\" :show-checkbox=\"false\"\n                      default-expand-all>\n                      <template #default=\"{ data }\">\n                        <div class=\"flex-1 flex items-center justify-between text-sm pr-2\">\n                          <span>{{ data.description }}</span>\n                          <span class=\"text-gray-500 dark:text-gray-400 text-xs ml-2\">{{ data.path }} [{{ data.method\n                            }}]</span>\n                        </div>\n                      </template>\n                    </el-tree>\n                  </div>\n                </div>\n              </div>\n              <div class=\"card-col\">\n                <div class=\"card-vertical\">\n                  <div class=\"card-header\">\n                    <h3 class=\"card-title\">字典 ({{ importPreviewData.dictionaries?.length || 0 }}项)</h3>\n                  </div>\n                  <div class=\"card-body\">\n                    <el-tree :data=\"previewDictTreeData\" :props=\"dictTreeProps\" node-key=\"ID\"\n                      :expand-on-click-node=\"false\" :check-on-click-node=\"false\" :show-checkbox=\"false\"\n                      default-expand-all>\n                      <template #default=\"{ data }\">\n                        <div class=\"flex-1 flex items-center justify-between text-sm pr-2\">\n                          <span>{{ data.name || data.label }}</span>\n                          <span class=\"text-gray-500 dark:text-gray-400 text-xs ml-2\">\n                            {{ data.type || (data.value ? `值: ${data.value}` : '') }}\n                          </span>\n                        </div>\n                      </template>\n                    </el-tree>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </el-form-item>\n      </el-form>\n    </el-drawer>\n\n  </div>\n</template>\n\n<script setup>\nimport {\n  deleteSysVersion,\n  deleteSysVersionByIds,\n  findSysVersion,\n  getSysVersionList,\n  exportVersion,\n  importVersion,\n  downloadVersionJson\n} from '@/api/version'\n\n// 导入菜单和API相关接口\nimport { getMenuList } from '@/api/menu'\nimport { getApiList } from '@/api/api'\nimport { getSysDictionaryList } from '@/api/sysDictionary'\n\n// 全量引入格式化工具 请按需保留\nimport { formatDate } from '@/utils/format'\nimport { ElMessage, ElMessageBox } from 'element-plus'\nimport { UploadFilled } from '@element-plus/icons-vue'\nimport { ref, watch } from 'vue'\nimport { useAppStore } from \"@/pinia\"\n\ndefineOptions({\n  name: 'SysVersion'\n})\n\nconst appStore = useAppStore()\n\n// 控制更多查询条件显示/隐藏状态\nconst showAllQuery = ref(false)\n\n// 导出相关数据\nconst exportDialogVisible = ref(false)\nconst exportLoading = ref(false)\nconst exportForm = ref({\n  versionName: '',\n  versionCode: '',\n  description: '',\n  menuIds: [],\n  apiIds: [],\n  dictIds: []\n})\n\n// 树形结构相关数据\nconst menuTreeData = ref([])\nconst apiTreeData = ref([])\nconst dictTreeData = ref([])\nconst selectedMenuIds = ref([])\nconst selectedApiIds = ref([])\nconst selectedDictIds = ref([])\nconst menuFilterText = ref('')\nconst apiFilterTextName = ref('')\nconst apiFilterTextPath = ref('')\nconst dictFilterText = ref('')\n\n// 树形组件引用\nconst menuTreeRef = ref(null)\nconst apiTreeRef = ref(null)\nconst dictTreeRef = ref(null)\n\n// 树形属性配置\nconst menuTreeProps = ref({\n  children: 'children',\n  label: function (data) {\n    return data.meta?.title || data.title\n  }\n})\n\nconst apiTreeProps = ref({\n  children: 'children',\n  label: 'description'\n})\n\nconst dictTreeProps = ref({\n  children: 'sysDictionaryDetails',\n  label: function (data) {\n    // 如果是字典主项，显示字典名称\n    if (data.name) {\n      return data.name\n    }\n    // 如果是字典详情项，显示标签\n    if (data.label) {\n      return data.label\n    }\n    return '未知项'\n  }\n})\n\n// 导入相关数据\nconst importDialogVisible = ref(false)\nconst importLoading = ref(false)\nconst importJsonContent = ref('')\nconst importPreviewData = ref(null)\nconst uploadRef = ref(null)\nconst previewMenuTreeData = ref([])\nconst previewApiTreeData = ref([])\nconst previewDictTreeData = ref([])\n\n\n\nconst elSearchFormRef = ref()\n\n// =========== 表格控制部分 ===========\nconst page = ref(1)\nconst total = ref(0)\nconst pageSize = ref(10)\nconst tableData = ref([])\nconst searchInfo = ref({})\n// 重置\nconst onReset = () => {\n  searchInfo.value = {}\n  getTableData()\n}\n\n// 搜索\nconst onSubmit = () => {\n  elSearchFormRef.value?.validate(async (valid) => {\n    if (!valid) return\n    page.value = 1\n    getTableData()\n  })\n}\n\n// 分页\nconst handleSizeChange = (val) => {\n  pageSize.value = val\n  getTableData()\n}\n\n// 修改页面容量\nconst handleCurrentChange = (val) => {\n  page.value = val\n  getTableData()\n}\n\n// 查询\nconst getTableData = async () => {\n  const table = await getSysVersionList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })\n  if (table.code === 0) {\n    tableData.value = table.data.list\n    total.value = table.data.total\n    page.value = table.data.page\n    pageSize.value = table.data.pageSize\n  }\n}\n\ngetTableData()\n\n// ============== 表格控制部分结束 ===============\n\n// 多选数据\nconst multipleSelection = ref([])\n// 多选\nconst handleSelectionChange = (val) => {\n  multipleSelection.value = val\n}\n\n// 删除行\nconst deleteRow = (row) => {\n  ElMessageBox.confirm('确定要删除吗?', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(() => {\n    deleteSysVersionFunc(row)\n  })\n}\n\n// 多选删除\nconst onDelete = async () => {\n  ElMessageBox.confirm('确定要删除吗?', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(async () => {\n    const IDs = []\n    if (multipleSelection.value.length === 0) {\n      ElMessage({\n        type: 'warning',\n        message: '请选择要删除的数据'\n      })\n      return\n    }\n    multipleSelection.value &&\n      multipleSelection.value.map(item => {\n        IDs.push(item.ID)\n      })\n    const res = await deleteSysVersionByIds({ IDs })\n    if (res.code === 0) {\n      ElMessage({\n        type: 'success',\n        message: '删除成功'\n      })\n      if (tableData.value.length === IDs.length && page.value > 1) {\n        page.value--\n      }\n      getTableData()\n    }\n  })\n}\n\n// 删除行\nconst deleteSysVersionFunc = async (row) => {\n  const res = await deleteSysVersion({ ID: row.ID })\n  if (res.code === 0) {\n    ElMessage({\n      type: 'success',\n      message: '删除成功'\n    })\n    if (tableData.value.length === 1 && page.value > 1) {\n      page.value--\n    }\n    getTableData()\n  }\n}\n\nconst detailForm = ref({})\n\n// 查看详情控制标记\nconst detailShow = ref(false)\n\n\n// 打开详情弹窗\nconst openDetailShow = () => {\n  detailShow.value = true\n}\n\n\n// 打开详情\nconst getDetails = async (row) => {\n  // 打开弹窗\n  const res = await findSysVersion({ ID: row.ID })\n  if (res.code === 0) {\n    detailForm.value = res.data\n    openDetailShow()\n  }\n}\n\n\n// 关闭详情弹窗\nconst closeDetailShow = () => {\n  detailShow.value = false\n  detailForm.value = {}\n}\n\n\n\n// 获取菜单和API列表\nconst getMenuAndApiList = async () => {\n  try {\n    // 获取菜单列表\n    const menuRes = await getMenuList()\n    if (menuRes.code === 0) {\n      menuTreeData.value = menuRes.data || []\n    }\n\n    // 获取API列表\n    const apiRes = await getApiList({ page: 1, pageSize: 9999 })\n    if (apiRes.code === 0) {\n      console.log('原始API数据:', apiRes.data)\n      const apis = apiRes.data.list || []\n      apiTreeData.value = buildApiTree(apis)\n    }\n  } catch (error) {\n    console.error('获取数据失败:', error)\n    ElMessage.error('获取菜单或API数据失败')\n  }\n}\n\n// 获取字典列表\nconst getDictList = async () => {\n  try {\n    const dictRes = await getSysDictionaryList({ page: 1, pageSize: 9999 })\n    if (dictRes.code === 0) {\n      dictTreeData.value = dictRes.data || []\n    }\n  } catch (error) {\n    console.error('获取字典数据失败:', error)\n    ElMessage.error('获取字典数据失败')\n  }\n}\n\n// 构建API树形结构\nconst buildApiTree = (apis) => {\n  const apiObj = {}\n  apis.forEach((item) => {\n    item.onlyId = 'p:' + item.path + 'm:' + item.method\n    if (Object.prototype.hasOwnProperty.call(apiObj, item.apiGroup)) {\n      apiObj[item.apiGroup].push(item)\n    } else {\n      Object.assign(apiObj, { [item.apiGroup]: [item] })\n    }\n  })\n  const apiTree = []\n  for (const key in apiObj) {\n    const treeNode = {\n      ID: key,\n      description: key + '组',\n      children: apiObj[key]\n    }\n    apiTree.push(treeNode)\n  }\n  return apiTree\n}\n\n// 树形组件事件处理方法\nconst filterMenuNode = (value, data) => {\n  if (!value) return true\n  const title = data.meta?.title || data.title || ''\n  return title.indexOf(value) !== -1\n}\n\nconst filterApiNode = (value, data) => {\n  if (!apiFilterTextName.value && !apiFilterTextPath.value) return true\n  let matchesName, matchesPath\n  if (!apiFilterTextName.value) {\n    matchesName = true\n  } else {\n    matchesName = data.description && data.description.includes(apiFilterTextName.value)\n  }\n  if (!apiFilterTextPath.value) {\n    matchesPath = true\n  } else {\n    matchesPath = data.path && data.path.includes(apiFilterTextPath.value)\n  }\n  return matchesName && matchesPath\n}\n\nconst filterDictNode = (value, data) => {\n  if (!value) return true\n  const name = data.name || ''\n  const type = data.type || ''\n  const desc = data.desc || ''\n  const label = data.label || ''\n  const dataValue = data.value || ''\n  return name.indexOf(value) !== -1 ||\n    type.indexOf(value) !== -1 ||\n    desc.indexOf(value) !== -1 ||\n    label.indexOf(value) !== -1 ||\n    dataValue.indexOf(value) !== -1\n}\n\nconst onMenuCheck = (data, checked) => {\n  if (checked.checkedKeys) {\n    selectedMenuIds.value = checked.checkedKeys\n  }\n}\n\nconst onApiCheck = (data, checked) => {\n  if (checked.checkedKeys) {\n    selectedApiIds.value = checked.checkedKeys\n  }\n}\n\nconst onDictCheck = (data, checked) => {\n  if (checked.checkedKeys) {\n    selectedDictIds.value = checked.checkedKeys\n  }\n}\n\n// 监听过滤文本变化\nwatch(menuFilterText, (val) => {\n  if (menuTreeRef.value) {\n    menuTreeRef.value.filter(val)\n  }\n})\n\nwatch([apiFilterTextName, apiFilterTextPath], () => {\n  if (apiTreeRef.value) {\n    apiTreeRef.value.filter('')\n  }\n})\n\nwatch(dictFilterText, (val) => {\n  if (dictTreeRef.value) {\n    dictTreeRef.value.filter(val)\n  }\n})\n\n// 导出相关方法\nconst openExportDialog = async () => {\n  exportDialogVisible.value = true\n  await getMenuAndApiList()\n  await getDictList()\n}\n\nconst closeExportDialog = () => {\n  exportDialogVisible.value = false\n  exportForm.value = {\n    versionName: '',\n    versionCode: '',\n    description: '',\n    menuIds: [],\n    apiIds: [],\n    dictIds: []\n  }\n  selectedMenuIds.value = []\n  selectedApiIds.value = []\n  selectedDictIds.value = []\n  menuFilterText.value = ''\n  apiFilterTextName.value = ''\n  apiFilterTextPath.value = ''\n  dictFilterText.value = ''\n}\n\nconst handleExport = async () => {\n  if (!exportForm.value.versionName || !exportForm.value.versionCode) {\n    ElMessage.warning('请填写版本名称和版本号')\n    return\n  }\n\n  exportLoading.value = true\n  try {\n    // 获取选中的菜单、API和字典\n    const checkedMenus = menuTreeRef.value ? menuTreeRef.value.getCheckedNodes(false, true) : []\n    const checkedApis = apiTreeRef.value ? apiTreeRef.value.getCheckedNodes(true) : []\n    const checkedDicts = dictTreeRef.value ? dictTreeRef.value.getCheckedNodes(true) : []\n\n    const menuIds = checkedMenus.map(menu => menu.ID)\n    const apiIds = checkedApis.map(api => api.ID)\n    const dictIds = checkedDicts.map(dict => dict.ID)\n\n    exportForm.value.menuIds = menuIds\n    exportForm.value.apiIds = apiIds\n    exportForm.value.dictIds = dictIds\n\n    const res = await exportVersion(exportForm.value)\n    if (res.code !== 0) {\n      ElMessage.error(res.msg || '创建发版失败')\n      return\n    }\n\n    ElMessage.success('创建发版成功')\n    closeExportDialog()\n    getTableData() // 刷新表格数据\n  } catch (error) {\n    console.error('创建发版失败:', error)\n    ElMessage.error('创建发版失败')\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n// 导入相关方法\nconst openImportDialog = () => {\n  importDialogVisible.value = true\n}\n\nconst closeImportDialog = () => {\n  importDialogVisible.value = false\n  importJsonContent.value = ''\n  importPreviewData.value = null\n  previewMenuTreeData.value = []\n  previewApiTreeData.value = []\n  // 清理上传文件\n  if (uploadRef.value) {\n    uploadRef.value.clearFiles()\n  }\n}\n\n// 文件上传处理函数\nconst handleFileChange = (file) => {\n  if (!file.raw) return\n\n  // 验证文件类型\n  if (!file.name.toLowerCase().endsWith('.json')) {\n    ElMessage.error('只能上传JSON文件')\n    uploadRef.value.clearFiles()\n    return\n  }\n\n  // 读取文件内容\n  const reader = new FileReader()\n  reader.onload = (e) => {\n    try {\n      const content = e.target.result\n      // 验证JSON格式\n      JSON.parse(content)\n      importJsonContent.value = content\n      handleJsonContentChange()\n      ElMessage.success('文件上传成功')\n    } catch (error) {\n      ElMessage.error('JSON文件格式错误')\n      uploadRef.value.clearFiles()\n    }\n  }\n  reader.readAsText(file.raw)\n}\n\nconst handleFileRemove = () => {\n  importJsonContent.value = ''\n  importPreviewData.value = null\n  previewMenuTreeData.value = []\n  previewApiTreeData.value = []\n}\n\n// 计算菜单总数（递归计算所有菜单项）\nconst getTotalMenuCount = () => {\n  if (!importPreviewData.value?.menus) return 0\n\n  const countMenus = (menus) => {\n    let count = 0\n    menus.forEach(menu => {\n      count += 1 // 当前菜单\n      if (menu.children && menu.children.length > 0) {\n        count += countMenus(menu.children) // 递归计算子菜单\n      }\n    })\n    return count\n  }\n\n  return countMenus(importPreviewData.value.menus)\n}\n\n\n\nconst handleJsonContentChange = () => {\n  if (!importJsonContent.value.trim()) {\n    importPreviewData.value = null\n    previewMenuTreeData.value = []\n    previewApiTreeData.value = []\n    previewDictTreeData.value = []\n    return\n  }\n\n  try {\n    const data = JSON.parse(importJsonContent.value)\n\n    // 构建预览数据\n    importPreviewData.value = {\n      menus: data.menus || [],\n      apis: data.apis || [],\n      dictionaries: data.dictionaries || []\n    }\n\n    // 直接使用菜单数据，因为它已经是树形结构（包含children字段）\n    if (data.menus && data.menus.length > 0) {\n      previewMenuTreeData.value = data.menus\n    } else {\n      previewMenuTreeData.value = []\n    }\n\n    // 构建API树形数据（按分组组织）\n    if (data.apis && data.apis.length > 0) {\n      const apiGroups = {}\n      data.apis.forEach(api => {\n        const group = api.apiGroup || '未分组'\n        if (!apiGroups[group]) {\n          apiGroups[group] = {\n            ID: `group_${group}`,\n            description: group,\n            path: '',\n            method: '',\n            children: []\n          }\n        }\n        apiGroups[group].children.push(api)\n      })\n      previewApiTreeData.value = Object.values(apiGroups)\n    } else {\n      previewApiTreeData.value = []\n    }\n\n    // 处理字典数据\n    if (data.dictionaries && data.dictionaries.length > 0) {\n      previewDictTreeData.value = data.dictionaries\n    } else {\n      previewDictTreeData.value = []\n    }\n  } catch (error) {\n    console.error('JSON解析失败:', error)\n    importPreviewData.value = null\n    previewMenuTreeData.value = []\n    previewApiTreeData.value = []\n    previewDictTreeData.value = []\n  }\n}\n\nconst handleImport = async () => {\n  if (!importJsonContent.value.trim()) {\n    ElMessage.warning('请输入版本JSON')\n    return\n  }\n\n  try {\n    JSON.parse(importJsonContent.value)\n  } catch (error) {\n    ElMessage.error('JSON格式错误，请检查输入内容')\n    return\n  }\n\n  importLoading.value = true\n  try {\n    const data = JSON.parse(importJsonContent.value)\n    const res = await importVersion(data)\n    if (res.code === 0) {\n      ElMessage.success('导入成功')\n      closeImportDialog()\n      getTableData() // 刷新表格数据\n    } else {\n      ElMessage.error(res.msg || '导入失败')\n    }\n  } catch (error) {\n    console.error('导入失败:', error)\n    ElMessage.error('导入失败')\n  } finally {\n    importLoading.value = false\n  }\n}\n\n// 下载版本JSON\nconst downloadJson = async (row) => {\n  try {\n    const res = await downloadVersionJson({ ID: row.ID })\n    // 处理axios响应，获取实际的blob数据\n    // 当responseType为blob时，axios拦截器会返回完整的response对象\n    let blob\n    if (res instanceof Blob) {\n      blob = res\n    } else if (res.data instanceof Blob) {\n      blob = res.data\n    } else {\n      // 如果不是blob，可能是错误响应，尝试从response中获取\n      blob = res\n    }\n\n    const url = window.URL.createObjectURL(blob)\n    const link = document.createElement('a')\n    link.href = url\n    link.download = `${row.versionName}_${row.versionCode}.json`\n    document.body.appendChild(link)\n    link.click()\n    document.body.removeChild(link)\n    window.URL.revokeObjectURL(url)\n\n    ElMessage.success('下载成功')\n  } catch (error) {\n    console.error('下载失败:', error)\n    ElMessage.error('下载失败')\n  }\n}\n\n</script>\n\n<style scoped>\n/* 复用卡片样式（支持暗色） */\n.card-col {\n  @apply border border-gray-300 dark:border-gray-600 rounded overflow-hidden flex-1 bg-white dark:bg-gray-900;\n}\n\n.card-vertical {\n  @apply flex flex-col h-full;\n}\n\n.card-header {\n  @apply flex justify-between items-center px-4 py-3 bg-gray-50 dark:bg-gray-800 border-b border-gray-300 dark:border-gray-600;\n}\n\n.card-title {\n  @apply m-0 text-gray-800 dark:text-gray-200 text-base font-medium;\n}\n\n.card-filter {\n  @apply px-4 py-3 border-b border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800;\n}\n\n.card-body {\n  @apply flex-1 p-2 min-h-[300px] max-h-[400px] overflow-y-auto;\n}\n\n.preview-wrap {\n  @apply flex flex-col flex-1 gap-4 border border-gray-300 dark:border-gray-600 rounded p-4 bg-gray-50 dark:bg-gray-900;\n}\n\n/* Element Plus 树形组件样式优化 */\n:deep(.el-tree) {\n  background-color: transparent;\n}\n\n:deep(.el-tree-node__content) {\n  height: 32px;\n  line-height: 32px;\n}\n\n:deep(.el-tree-node__label) {\n  font-size: 14px;\n}\n\n:deep(.el-scrollbar__view) {\n  padding: 0;\n}\n</style>\n"
  },
  {
    "path": "web/uno.config.js",
    "content": "import { defineConfig } from '@unocss/vite';\nimport presetWind3 from '@unocss/preset-wind3';\nimport transformerDirectives from '@unocss/transformer-directives'\n\nexport default defineConfig({\n  theme: {\n    backgroundColor: {\n      main: '#F5F5F5'\n    },\n    textColor: {\n      active: 'var(--el-color-primary)'\n    },\n    boxShadowColor: {\n      active: 'var(--el-color-primary)'\n    },\n    borderColor: {\n      'table-border': 'var(--el-border-color-lighter)'\n    }\n  },\n  presets: [\n    presetWind3({ dark: 'class' })\n  ],\n  transformers: [\n    transformerDirectives(),\n  ],\n})\n"
  },
  {
    "path": "web/vite.config.js",
    "content": "import legacyPlugin from '@vitejs/plugin-legacy'\nimport { viteLogo } from './src/core/config'\nimport Banner from 'vite-plugin-banner'\nimport * as path from 'path'\nimport { loadEnv } from 'vite'\nimport vuePlugin from '@vitejs/plugin-vue'\nimport vueDevTools from 'vite-plugin-vue-devtools'\nimport VueFilePathPlugin from './vitePlugin/componentName/index.js'\nimport { svgBuilder } from 'vite-auto-import-svg'\nimport vueRootValidator from 'vite-check-multiple-dom'\nimport { AddSecret } from './vitePlugin/secret'\nimport UnoCSS from '@unocss/vite'\n\n// @see https://cn.vitejs.dev/config/\nexport default ({ mode }) => {\n  AddSecret('')\n  const env = loadEnv(mode, process.cwd())\n  viteLogo(env)\n\n  const timestamp = Date.parse(new Date())\n\n  const optimizeDeps = {}\n\n  const alias = {\n    '@': path.resolve(__dirname, './src'),\n    vue$: 'vue/dist/vue.runtime.esm-bundler.js'\n  }\n\n  const esbuild = {}\n\n  const rollupOptions = {\n    output: {\n      entryFileNames: 'assets/087AC4D233B64EB0[name].[hash].js',\n      chunkFileNames: 'assets/087AC4D233B64EB0[name].[hash].js',\n      assetFileNames: 'assets/087AC4D233B64EB0[name].[hash].[ext]'\n    }\n  }\n\n  const base = '/'\n  const root = './'\n  const outDir = 'dist'\n\n  const config = {\n    base: base, // 编译后js导入的资源路径\n    root: root, // index.html文件所在位置\n    publicDir: 'public', // 静态资源文件夹\n    resolve: {\n      alias\n    },\n    css: {\n      preprocessorOptions: {\n        scss: {\n          api: 'modern-compiler' // or \"modern\"\n        }\n      }\n    },\n    server: {\n      // 如果使用docker-compose开发模式，设置为false\n      open: true,\n      port: Number(env.VITE_CLI_PORT),\n      proxy: {\n        // 把key的路径代理到target位置\n        // detail: https://cli.vuejs.org/config/#devserver-proxy\n        [env.VITE_BASE_API]: {\n          // 需要代理的路径   例如 '/api'\n          target: `${env.VITE_BASE_PATH}:${env.VITE_SERVER_PORT}/`, // 代理到 目标路径\n          changeOrigin: true,\n          rewrite: (path) =>\n            path.replace(new RegExp('^' + env.VITE_BASE_API), '')\n        },\n        '/plugin': {\n          // 需要代理的路径   例如 '/api'\n          target: `https://plugin.gin-vue-admin.com/api/`, // 代理到 目标路径\n          changeOrigin: true,\n          rewrite: (path) =>\n            path.replace(new RegExp('^/plugin'), '')\n        }\n      }\n    },\n    build: {\n      minify: 'terser', // 是否进行压缩,boolean | 'terser' | 'esbuild',默认使用terser\n      manifest: false, // 是否产出manifest.json\n      sourcemap: false, // 是否产出sourcemap.json\n      outDir: outDir, // 产出目录\n      terserOptions: {\n        compress: {\n          //生产环境时移除console\n          drop_console: true,\n          drop_debugger: true\n        }\n      },\n      rollupOptions\n    },\n    esbuild,\n    optimizeDeps,\n    plugins: [\n      env.VITE_POSITION === 'open' &&\n      vueDevTools({ launchEditor: env.VITE_EDITOR }),\n      legacyPlugin({\n        targets: [\n          'Android > 39',\n          'Chrome >= 60',\n          'Safari >= 10.1',\n          'iOS >= 10.3',\n          'Firefox >= 54',\n          'Edge >= 15'\n        ]\n      }),\n      vuePlugin(),\n      svgBuilder(['./src/plugin/', './src/assets/icons/'], base, outDir, 'assets', mode),\n      [Banner(`\\n Build based on gin-vue-admin \\n Time : ${timestamp}`)],\n      VueFilePathPlugin('./src/pathInfo.json'),\n      UnoCSS(),\n      vueRootValidator()\n    ]\n  }\n  return config\n}\n"
  },
  {
    "path": "web/vitePlugin/componentName/index.js",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport chokidar from 'chokidar'\n\nconst toPascalCase = (str) => {\n  return str.replace(/(^\\w|-\\w)/g, clearAndUpper)\n}\n\nconst clearAndUpper = (text) => {\n  return text.replace(/-/, '').toUpperCase()\n}\n\n// 递归获取目录下所有的 .vue 文件\nconst getAllVueFiles = (dir, fileList = []) => {\n  const files = fs.readdirSync(dir)\n  files.forEach((file) => {\n    const filePath = path.join(dir, file)\n    if (fs.statSync(filePath).isDirectory()) {\n      getAllVueFiles(filePath, fileList)\n    } else if (filePath.endsWith('.vue')) {\n      fileList.push(filePath)\n    }\n  })\n  return fileList\n}\n\n// 从 .vue 文件内容中提取组件名称\nconst extractComponentName = (fileContent) => {\n  const regex = /defineOptions\\(\\s*{\\s*name:\\s*[\"']([^\"']+)[\"']/\n  const match = fileContent.match(regex)\n  return match ? match[1] : null\n}\n\n// Vite 插件定义\nconst vueFilePathPlugin = (outputFilePath) => {\n  let root\n  let isDev = false\n  const generatePathNameMap = () => {\n    const vueFiles = [\n      ...getAllVueFiles(path.join(root, 'src/view')),\n      ...getAllVueFiles(path.join(root, 'src/plugin'))\n    ]\n    const pathNameMap = vueFiles.reduce((acc, filePath) => {\n      const content = fs.readFileSync(filePath, 'utf-8')\n      const componentName = extractComponentName(content)\n      let relativePath = '/' + path.relative(root, filePath).replace(/\\\\/g, '/')\n      acc[relativePath] =\n        componentName || toPascalCase(path.basename(filePath, '.vue'))\n      return acc\n    }, {})\n    const outputContent = JSON.stringify(pathNameMap, null, 2)\n    fs.writeFileSync(outputFilePath, outputContent)\n  }\n\n  const watchDirectoryChanges = () => {\n    const watchDirectories = [\n      path.join(root, 'src/view'),\n      path.join(root, 'src/plugin')\n    ]\n    const watcher = chokidar.watch(watchDirectories, {\n      persistent: true,\n      ignoreInitial: true\n    })\n    watcher.on('all', () => {\n      generatePathNameMap()\n    })\n  }\n\n  return {\n    name: 'vue-file-path-plugin',\n    configResolved(resolvedConfig) {\n      root = resolvedConfig.root\n      if (resolvedConfig.mode === 'development') {\n        isDev = true\n      }\n    },\n    buildStart() {\n      generatePathNameMap()\n    },\n    buildEnd() {\n      if (isDev) {\n        watchDirectoryChanges()\n      }\n    }\n  }\n}\n\nexport default vueFilePathPlugin\n"
  },
  {
    "path": "web/vitePlugin/secret/index.js",
    "content": "export function AddSecret(secret) {\n  if (!secret) {\n    secret = ''\n  }\n  global['gva-secret'] = secret\n}\n"
  }
]