Compare commits

...
156 Commits
Author SHA1 Message Date
PiexlMax(奇淼 e8d675c891 Merge pull request #2263 from xue-ding-e/feat/redis-username
feat: Redis 配置支持 username,兼容云 Redis 自定义 ACL 账号
2026-09-20 14:43:38 +08:00
xuedinge 6830fb6e0e server/config.yaml: redis 配置补充 username 示例项
配合 #2264 的修复(config.Redis 新增 username 字段),在配置模板的
redis 段补充对应配置项,方便使用云 Redis 自定义 ACL 账号的用户发现
该配置;默认留空,不影响现有部署。
2026-09-20 01:54:31 +08:00
xuedinge 98ca3dbc79 server/config/redis.go: Redis 支持配置 username,兼容云 Redis 自定义 ACL 账号
云厂商(阿里云等)的 Redis 实例使用自定义 ACL 账号时,要求客户端以
AUTH <username> <password> 双参数方式进行鉴权。当前代码在初始化
go-redis 客户端时从未设置 Username,只会发送单参数 AUTH <password>,
连接此类实例时 Ping 报错 WRONGPASS,导致服务启动失败(panic)。

- config.Redis 新增 username 配置项
- initialize/redis.go 单机与集群两种模式的客户端初始化均传入 Username

使用默认账号或本地 Redis 时 username 留空即可,行为与原有版本完全
一致(go-redis 在 Username 为空时只发送单参数 AUTH)。

Fix #2264
2026-09-20 01:49:31 +08:00
PiexlMax(奇淼 d6d4950c1b Merge pull request #2258 from cococoaa/fix/announcement-gen-gogenerate
fix: 修复 announcement/gen/gen.go 中 //go:generate 指令位置错误导致 go build ./... 失败
2026-09-14 15:12:40 +08:00
PiexlMax(奇淼 d2185187b4 Merge pull request #2259 from songzhibin97/songzhibin97/upgrade-gkit-v1.5.0
go.mod: Upgrade GKit to v1.5.0
2026-09-14 15:12:26 +08:00
PiexlMax(奇淼 5b041b5b38 Merge pull request #2256 from cococoaa/fix/autocode-service-withcontext
fix(autocode): service 模板 DB 操作补充 WithContext(ctx) 传播 context
2026-09-14 15:12:13 +08:00
songzhibin97 2d90c34ddf go.mod: Upgrade GKit to v1.5.0 for local cache fixes 2026-09-14 11:12:23 +08:00
cococoaa 897fb971da fix: 修复 announcement/gen/gen.go 中 //go:generate 指令位置错误导致 go build ./... 失败 2026-09-11 20:52:50 +08:00
cococoaaandClaude Opus 5 cb3ffce379 fix(autocode): service 模板 DB 操作补充 WithContext(ctx) 传播 context
自动化代码生成的 service 层方法签名带 ctx 参数,但函数体内 DB
操作未调用 WithContext(ctx),导致请求取消/超时/链路追踪无法传播
到数据库操作。本次给 package 和 plugin 两个 service 模板的所有
DB 操作补上 WithContext(ctx)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 19:11:27 +08:00
PiexlMax(奇淼 f408aea137 Merge pull request #2253 from cococoaa/fix/autocode-clearcatch-generateserver
fix: 代码生成器连续生成时「生成后端」复选框失效导致只生成前端代码
2026-09-09 15:24:04 +08:00
cococoaa 260ac90d7f fix: 代码生成器 clearCatch 重置后丢失 generateServer/generateWeb 导致只生成前端代码 2026-09-09 13:44:10 +08:00
PiexlMax(奇淼 81558bbec4 Merge pull request #2250 from 000px/fix/casbin-cache-expire-time
casbin_util.go: correct Enforcer cache expiration to 1 hour
2026-09-08 16:47:46 +08:00
wan c9e273176d casbin_util.go: correct Enforcer cache expiration to 1 hour
Previously set to 60*60, which is 3600 nanoseconds, causing the cache to expire immediately. Change to time.Hour to properly cache policies for 1 hour.
2026-09-07 00:19:27 +08:00
PiexlMax(奇淼 f01ce1b34f feat: 添加授权版体验链接到欢迎信息 2026-09-04 16:40:39 +08:00
PiexlMax(奇淼 c993a3a1a1 feat: 更新许可证信息,调整文档以符合 Apache License 2.0 要求 2026-09-04 16:39:25 +08:00
PiexlMax(奇淼 dbb43061d3 feat: 添加演示站和商业链接配置,更新仪表盘和头部组件以支持授权版演示 2026-09-03 01:53:25 +08:00
PiexlMax(奇淼 a6882210a8 Merge pull request #2235 from kobihikri/ci/pin-github-push-action
ci: pin ad-m/github-push-action to a full commit SHA
2026-07-14 15:56:15 +08:00
kobihikri 005a7b747f ci: pin ad-m/github-push-action to a full commit SHA
The release-pr job passes GITHUB_TOKEN to ad-m/github-push-action, which
is referenced by the mutable `@master` branch. A mutable ref means the
exact code that runs with the token can change without any change in this
repository. Pinning to a full commit SHA makes the action immutable while
keeping the current behaviour.

881a6320fdb16eb5318c5054f31c218aec2b324c is the commit `@master` resolves
to today, which is also tagged v1.3.0, so this changes nothing about what
runs now — it just fixes the version in place. The trailing `# v1.3.0`
comment keeps the human-readable version visible for future updates.

This mirrors GitHub's own hardening guidance for third-party actions.
2026-07-14 10:53:57 +03:00
PiexlMax(奇淼 4445d0e583 Merge pull request #2225 from estel-li/codex/fix-select-image-max-update-count
fix: respect SelectImage maxUpdateCount in media library
2026-06-30 17:34:09 +08:00
PiexlMax(奇淼 e04452c99e Merge pull request #2218 from AbbyJL/codex/fix-db-migrate-exit-code-clean
gorm.go: fix migrate failure exit code
2026-06-30 17:33:42 +08:00
李觉 06f859c13a fix select image max update count 2026-06-30 17:28:00 +08:00
PiexlMax(奇淼 87f6838a08 Merge pull request #2219 from 1220lanxi-glitch/main
fix:MCP生成代码创建字典失败bug
2026-06-30 15:42:28 +08:00
lanxi 463f6b7dfe fix:MCP生成代码创建字典失败bug 2026-06-16 13:46:37 +08:00
PiexlMax(奇淼 7d3f0c7564 clear: 清理对用户无意义的ai记忆 2026-06-15 10:35:05 +08:00
PiexlMax(奇淼 086fbdae04 clear: 清理无用的docs文件 2026-06-15 10:33:17 +08:00
Codex 07f1beb4b3 gorm.go: fix migrate failure exit code 2026-06-08 16:08:08 +08:00
PiexlMax(奇淼 78b3bd0c67 docs: 更新README.md,简化章节标题和图片描述 2026-05-28 11:43:04 +08:00
PiexlMax(奇淼 53e72c9660 Merge branch 'main' of github.com:flipped-aurora/gin-vue-admin 2026-05-28 11:42:36 +08:00
PiexlMax(奇淼andCopilot a333332e6c docs: 更新README.md,优化格式和内容,增加文档可读性
Co-authored-by: Copilot <copilot@github.com>
2026-05-28 11:41:43 +08:00
PiexlMax(奇淼 d1e6d9c642 Update README.md 2026-05-28 11:34:43 +08:00
PiexlMax(奇淼 7c8b623ab2 Update README.md 2026-05-28 10:46:00 +08:00
Zmzblink e4b76383ef Preload Authorities when fetching user by ID (#2215)
修复多角色时,签发其他角色token失败的问题
2026-05-28 09:31:56 +08:00
PiexlMax(奇淼 cdfd2a1901 docs: 更新许可证信息为BSL 1.1,明确非商用和商用使用要求 2026-05-28 09:29:41 +08:00
PiexlMax(奇淼 03f4daf5d2 docs: add BSL migration design 2026-05-27 16:13:43 +08:00
PiexlMax(奇淼 0094f677a1 fix: 更新记忆管理规则,确保长期记忆与业务记忆独立维护 2026-05-26 13:52:29 +08:00
PiexlMax(奇淼 3fda90acac Merge branch 'main' of github.com:flipped-aurora/gin-vue-admin 2026-05-26 12:32:19 +08:00
Robert SilénandCursor ee2de98fab docs: mention MariaDB in README (zh) (#2213)
Note MySQL or MariaDB 5.7+ in the tech stack database line; the server
already detects MariaDB versions in basetypes.go.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 12:03:14 +08:00
PiexlMax(奇淼andCopilot fcb10a9b6e feat: 添加构建设置项检查脚本,优化主题样式变量
Co-authored-by: Copilot <copilot@github.com>
2026-05-26 12:01:38 +08:00
PiexlMax(奇淼 f4fa0b7a40 fix: 更新预览脚本以支持开发模式 2026-05-26 11:22:25 +08:00
PiexlMax(奇淼 e77e0603db fix: 更新 @vueuse/core 和 @vueuse/integrations 依赖版本 2026-05-26 11:21:55 +08:00
15e6e7685c update:发布2.9.2版本 (#2211)
* feat: 调整AI工作模式,更符合harness基准

* feat: 添加BusinessDB字段到PluginInitializeGorm结构体,并增加相关测试用例

* [middleware/jwt.go]: fix #2192 issues bug

* docs: add auto plugin design spec

* chore: ignore worktrees directory

* feat: 调整代码辅助能力至插件

* feat: 添加警告条组件,提示授权用户访问限制

* fix: 修正商业用途版权声明链接

* feat: 调整agent.md 更加节省token

* fix: 修改casbin版本为v3

* feat: 更新JSONMap和JSONSlice类型,优化GORM数据类型处理

* feat: 添加数据库就绪通知机制,优化插件注册流程

* feat: 重构API路径和描述,优化代码生成器和模板配置分组

* feat: 优化 person 页面的 css 作用域限制

* feat: 更新 vite 至 vite8

* 新增:MCP工具为指定URL的角色ID授权

* fix: 增加文件名合法性检查,拒绝包含非法字符的文件写入

* chore: 更新CI配置,升级Node.js和Go版本,调整checkout和setup动作版本

* feat: 为数据库连接增加最大复用时间配置

* feat: 为各数据库连接配置增加最大连接生命周期设置

* feat: 更新插件注册逻辑并优化API和菜单组件的标签显示

* feat: 更新版本号至v2.9.2并添加新插件路径信息

---------

Co-authored-by: taincheng <zhangtc@gmail.com>
Co-authored-by: Azir-11 <2075125282@qq.com>
Co-authored-by: lanxi <1220lanxi@gmail.com>
2026-05-11 13:48:18 +08:00
PiexlMax(奇淼 699817becc Merge pull request #2204 from Azir-11/main
feat: 提高对深色主题的适配
2026-05-05 15:44:54 +08:00
Azir-11 fbd742ae03 feat: 提高对深色主题的适配 2026-04-30 14:20:05 +08:00
pixelmaxQM 0025a1a002 fix: 修正商业用途版权声明链接 2026-04-28 14:25:04 +08:00
pixelmaxQM 36c7f0bdd7 feat: 添加警告条组件,提示授权用户访问限制 2026-04-26 11:22:16 +08:00
pixelmaxQM 51b4ea284e feat: 添加获取 MCP 路由列表的接口 2026-04-16 10:08:21 +08:00
pixelmaxQM 60c1ec12b9 feat: 添加获取和设置API及菜单关联角色的接口 2026-04-16 10:05:11 +08:00
pixelmaxQM 61a3f8297c feat: 更新版本号至v2.9.1 2026-03-27 05:46:07 +01:00
PiexlMax(奇淼 375b21c222 发布2.9.1版本 (#2189)
* feat: mcp能力调整为 streamhttp的独立服务 不再依赖gva本体

* feat: 增加MCP启动能力,增加AI工作流需求整理能力。

* feat: 增加需求梳理能力

* feat: 清理无用日志

* feat: 移除无用的Dify Chatflow文本和代码生成器按钮
2026-03-27 12:14:13 +08:00
pixelmaxQM 324aaa3057 feat: 优化图片路径处理,使用getUrl函数替代硬编码路径 2026-03-21 07:32:55 +01:00
pixelmaxQM 24c2013caf feat: 修复错误的预览路径 2026-03-21 05:43:06 +01:00
pixelmaxQM 708a568427 feat:修复自动化代码导致注释位置错乱的bug 2026-03-21 05:25:17 +01:00
PiexlMax(奇淼 a75ab546ee Update README.md 2026-03-10 22:22:10 +08:00
PiexlMax(奇淼 f8c40b5b21 Update README.md 2026-03-10 22:21:21 +08:00
pixelmaxQM 98ccdc6295 feat: 更新版本号至2.9.0 2026-03-09 03:47:30 +01:00
PiexlMax(奇淼 d143b326b4 v2.9.0Beta版本发布
- 优化了AI规则,更利于大家规范化使用VibeCoding
- skills增加了在线板块,可以直接下载在线的skills。
- skill增加了一键打包能力
- 角色页面可以把角色可以直接分配给用户
- 菜单页面可以直接把菜单分配给角色
- API页面可以直接把API分配给角色
- 升级了AWS S3的版本
- 修复了富文本组件样式缺失的bug
- dashboard页面样式调整
- 优化了插件市场的UI,增加了分享和点赞能力
- 优化了vite环境变量的使用规则
2026-03-06 18:14:46 +08:00
piexlMax(奇淼 556b4431a2 fix: 修复获取x-token的方式,确保正确获取用户token 2026-02-10 16:15:40 +08:00
piexlMax(奇淼 9fea067931 feat: 更新富文本编辑器样式和结构,增强可读性 2026-02-10 11:44:10 +08:00
piexlMax(奇淼 fa9957c43c feat: 调整自动化插件的注册逻辑 2026-02-05 14:08:13 +08:00
piexlMax(奇淼 00224efd24 fix: 字典搜索bug 2026-02-05 13:50:33 +08:00
piexlMax(奇淼 506c9ea7a6 feat: 自动生成插件不再添加到注册功能 2026-02-05 13:44:26 +08:00
piexlMax(奇淼 05872354b8 feat: 补充丢失的自动迁移结构 2026-02-04 13:49:24 +08:00
piexlMax(奇淼 b71cc443c0 refactor: 调整ai工具rules 2026-02-03 10:18:34 +08:00
piexlMax(奇淼 1317e9504e feat: 增加技能配置界面的填写指引和模板功能 2026-02-02 18:06:19 +08:00
piexlMax(奇淼 670aff79cc feat: 增加技能参考和模板的创建、获取及保存功能 2026-02-02 18:06:11 +08:00
pixelmaxQM 52cd5e72ab feat: 更新版本号至v2.8.9 2026-02-01 23:42:20 +08:00
pixelmaxQM 68d6d3660b feat: 更新版本号至2.8.9 2026-02-01 22:26:03 +08:00
876017115c feat:发布2.8.9版本 (#2175)
* feat: ai相关调用调整

* feat: ai请求模式变更

* feat: ai自动化完善

* feat: 添加环境变量支持,条件渲染开发环境特定元素

* feat: 2.5版本插件,不再冗余进主项目,可以做到完全自动安装。

* feat: 添加插件管理功能,包括获取插件列表和删除插件的API接口

* feat: 同步测试插件的id调整

* feat: 增加插件相关初始化api

* feat: 增加登录日志

* feat: 增加签发token功能

* feat: 更安全的目录清理功能,确保仅在无Go文件时删除目录

* feat: 增加skills定义能力

* feat: 优化技能管理界面,增强用户体验和视觉效果

* feat: 更新授权链接,确保用户获取最新的授权信息

* feat: 修改技能定义器名称为“Skills管理”,并重命名文件为sys_skills.go

* feat: 增加全局约束的获取和保存功能,更新相关API和前端实现

* feat: 增加skills全局约束管理

* feat: 添加 vite-check-multiple-dom 插件,支持路由多根检测 (#2173)

---------

Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: Azir-11 <2075125282@qq.com>
Co-authored-by: 青菜白玉汤 <79054161+Azir-11@users.noreply.github.com>
2026-01-30 14:33:57 +08:00
piexlMax(奇淼 755373468c feat: 为mcp增加字段示例 2026-01-16 16:19:22 +08:00
piexlMax(奇淼 d2e64e618e fix: 重新实现组件缓存处理逻辑 2026-01-12 15:27:41 +08:00
piexlMax(奇淼 fd2a6f45fd fixed: 增加错误设置首页的补救措施 2026-01-12 15:11:59 +08:00
2242f5d6e1 public: 发布v2.8.8 (#2167)
* refactor: 移除冗余的工具名称和描述,简化代码生成器的文档

* Add Content-Type to S3 upload input

Add Content-Type to S3 upload input

* style: 优化仪表盘组件样式,调整各个子组件的布局和样式,提升视觉效果

* fix: 更新插件链接

* 优化角色配置-首页配置相关内容

* feat: 默认首页只允许选择已选中的菜单

* feat: 更新样式和优化组件,调整背景色及文本颜色

* feat: 清理无用的右侧边线

* feat: 增加了导入导出模板的自定义sql能力,方便灵活化扩展导入导出

* feat: 优化日志输出,增加插件字典定义

* feat: 增加插件字典定义前端相关

* feat: 增加插件自定义字典方法

* feat: 增加文件名和路径合法性检查

* feat: 修复暗黑模式和亮色模式logo路径定义

* feat: 移除未使用的defineEmits导入

* feat: 更新授权链接至新地址

* feat: 移除菜单组件背景色

* feat: 更新导出和导入SQL语句的示例占位符

* feat: 错误日志必须有数据库链接才会存储

* feat: 移除仪表板中公告卡片的高度限制

* feat: 添加插件列表接口,更新插件表格组件,移除不必要的注释

* feat: 更新版本号至v2.8.8,调整插件表格组件的请求参数

---------

Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: Yexk_M <yexk@yexk.cn>
Co-authored-by: Azir-11 <2075125282@qq.com>
2026-01-11 14:41:19 +08:00
PiexlMax(奇淼 567a8eb9bb fixed: 文件句柄泄露问题
文件句柄泄露问题
2026-01-11 14:34:21 +08:00
PiexlMax(奇淼 2ebeec8765 fixed: MD5 碰撞可能导致不同内容被误判为同一文件
MD5 碰撞可能导致不同内容被误判为同一文件
2026-01-11 14:33:47 +08:00
wike2019 70ee13e10c 文件句柄泄露问题 2026-01-11 13:13:59 +08:00
wike2019 ed65e31cd8 MD5 碰撞可能导致不同内容被误判为同一文件 2026-01-11 13:11:38 +08:00
PiexlMax(奇淼 f693f29326 feat: 优化unzip逻辑
io.Copy 后立即调用 Unzip,但 out 由 defer 关闭,可能在解压时尚未关闭
2026-01-11 12:40:34 +08:00
wike2019 2fda961643 io.Copy 后立即调用 Unzip,但 out 由 defer 关闭,可能在解压时尚未关闭 2026-01-10 16:00:10 +08:00
PiexlMax(奇淼 fd9883e035 Merge pull request #2158 from wike2019/pr/fix-cutter
fix bug 当日志文件删除失败时不阻塞日志写入
2026-01-06 11:18:58 +08:00
wike2019 f41f068b8b fix bug 当日志文件删除失败时不阻塞日志写入 2026-01-05 22:18:41 +08:00
PiexlMax(奇淼 799f411c73 Merge pull request #2150 from zayn-code/user-dialog
用户管理的侧边栏优化体验
2026-01-04 11:45:10 +08:00
PiexlMax(奇淼 97e7be9d3c Merge pull request #2157 from morya/main
FIX: code too complicated
2026-01-04 11:43:37 +08:00
morya 5cfe04e5f5 FIX: code too complicated 2026-01-03 23:56:05 +08:00
zayn 5f0d59a02b 其他页面都可以通过点击外层关闭侧边栏,用户界面不可以,养成习惯后体验不太好,故允许点击外层关闭侧边栏 2025-12-09 15:27:22 +08:00
pixelmaxQM 805f319578 fix: 优化文件上传时的数据库查询条件 2025-12-06 15:46:15 +08:00
piexlMax(奇淼 f23df92c18 feat: 调整获取logo的方式 2025-12-05 17:51:18 +08:00
piexlMax(奇淼 41d9453110 fix: 修复初始化没有创建SysError表的问题 2025-12-01 17:29:30 +08:00
piexlMax(奇淼 48d0cae58b Merge remote-tracking branch 'origin/main' 2025-12-01 17:26:55 +08:00
PiexlMax(奇淼 49af9439dc update: 2.8.7-beta发布
update: 2.8.7-beta发布
2025-12-01 17:21:02 +08:00
piexlMax(奇淼 1994497ad4 fix: 调整 gva-search-box 的内边距 2025-12-01 17:07:31 +08:00
piexlMax(奇淼 4ce191cdfa fix: 修复分片上传任意文件删除问题 2025-12-01 16:56:31 +08:00
piexlMax(奇淼 ee8d8d7e04 fix: 添加路径穿越拦截,禁止非法路径删除切片 2025-12-01 16:44:32 +08:00
PiexlMax(奇淼 cf022f492c Merge pull request #2146 from Azir-11/dev-287
添加全局组件大小配置和相关主题配置项,优化主题配置栏
2025-12-01 16:17:51 +08:00
piexlMax(奇淼 96019e8a9b feat: 版本号调整 2025-11-28 17:34:37 +08:00
piexlMax(奇淼 9ff1d4ca77 fix: 修复 vueDevTools 插件的缩进问题 2025-11-28 16:45:00 +08:00
Azir-11 8bc631ece9 feat: 重构优化系统配置相关 css 代码,优化了部分 UI 样式 2025-11-27 19:12:41 +08:00
Azir-11 0edb567ded feat: 添加全局组件大小配置和相关主题配置项 2025-11-27 18:40:22 +08:00
piexlMax(奇淼 d404fcf746 feat: 优化字典管理界面,增加按钮提示和调整按钮样式 2025-11-27 15:15:24 +08:00
piexlMax(奇淼 8af78e3af7 feat: 增加图片上传和粘贴识别功能,支持通过图片生成字典描述 2025-11-27 15:11:14 +08:00
piexlMax(奇淼 3722a8e7f3 feat: 增加AI生成字典功能,支持用户输入描述生成字典 2025-11-27 14:43:50 +08:00
piexlMax(奇淼 b86f7412d1 feat: 添加项目规则文档,详细描述功能、架构及开发规范 2025-11-27 14:04:13 +08:00
piexlMax(奇淼 ad52418f99 feat: 优化前端页面展示 2025-11-26 17:24:41 +08:00
piexlMax(奇淼 08ae44158f feat: 增加字典的json导出和导入 2025-11-26 16:40:48 +08:00
piexlMax(奇淼 a1f18e1386 feat: 增加字典的json导出和导入 2025-11-26 15:54:18 +08:00
PiexlMax(奇淼 c028ea3d15 Merge pull request #2145 from bypanghu/main
fix: 更新优化插件注册 api 和注册 menus 逻辑。
2025-11-24 18:34:54 +08:00
bypanghu f357c676bb fix: 更新优化插件注册 api 和注册 menus 逻辑。 2025-11-24 09:13:57 +08:00
piexlMax(奇淼 8022c662e0 fix(layout): 将Iframe路由名称更新为gvaLayoutIframe 2025-11-18 14:06:05 +08:00
piexlMax(奇淼 aa84605c4d fix(deps): 修复自动划代码字典查询条件失效问题 2025-11-17 17:49:50 +08:00
piexlMax(奇淼 287cb6ec9c feat: 优化error样式 2025-11-14 14:41:11 +08:00
piexlMax(奇淼 b9788e9b2a feat(错误日志): 添加异步处理错误日志功能 2025-11-12 18:02:50 +08:00
piexlMax(奇淼 a469ea23f4 refactor(zap): 重构日志信息格式以提高可读性 2025-11-11 18:21:14 +08:00
piexlMax(奇淼 c5f81d64c8 fix(错误处理): 统一前端错误类型显示为中文并优化错误详情布局 2025-11-11 18:18:23 +08:00
piexlMax(奇淼 78d135c6bf feat(日志): 增强日志功能以显示调用方源码 2025-11-11 18:11:32 +08:00
PiexlMax(奇淼 ff06aca146 Merge pull request #2138 from feitianbubu/pr/fix-cookie-duration
fix: correct cookie maxAge using seconds instead of minutes
2025-11-11 17:12:44 +08:00
PiexlMax(奇淼 849d5760da Merge pull request #2139 from feitianbubu/pr/fix-repeat-migrate-alter
fix: repeat alter gorm autoMigrate due to incorrect comments
2025-11-11 17:12:18 +08:00
piexlMax(奇淼 8cce9edbde feat(错误日志): 实现错误日志记录功能并修复拼写错误
添加错误日志记录功能,包括API、路由、服务和前端接口
修复sysError相关变量名的拼写错误
更新依赖版本至go 1.24
使用自定义Recovery中间件记录panic信息
添加zap日志钩子将错误日志写入数据库
2025-11-11 16:33:26 +08:00
piexlMax(奇淼 6bea7bc524 feat(导出模板): 添加SQL预览功能并优化模板展示界面 2025-11-10 11:05:03 +08:00
PiexlMax(奇淼 b6c0138dd2 feat: 统一配置命名 2025-11-09 16:15:22 +08:00
PiexlMax(奇淼 7658ff46c3 Update config.js 2025-11-09 16:10:40 +08:00
PiexlMax(奇淼 bd1aa1f559 Merge pull request #2140 from xue-ding-e/patch-7
bugfix:用户管理界面无法分配 / 查看角色
2025-11-09 16:09:29 +08:00
Qing Liang 27fb85f630 bugfix:用户管理界面无法分配 / 查看角色 2025-11-07 22:50:01 +08:00
feitianbubu 0be88bb442 fix: repeat alter gorm autoMigrate due to incorrect comments 2025-11-07 14:44:06 +08:00
feitianbubuandClaude a8642f4582 fix: correct cookie maxAge calculation by using seconds instead of minutes
The SetToken function expects maxAge in seconds (standard for HTTP cookies), but the code was incorrectly dividing by 60, causing cookies to expire 60 times faster than intended.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 10:54:11 +08:00
piexlMax(奇淼 5d67fc95d2 Merge branch 'main' into dev-287 2025-11-06 17:59:24 +08:00
piexlMax(奇淼 057ad2eeeb fix: 添加占位符字段防止json schema校验失败 2025-11-06 17:50:23 +08:00
piexlMax(奇淼 ca0a1250cd refactor(mcp): 修改execute定义 2025-11-06 17:50:23 +08:00
piexlMax(奇淼 6c82b34e8b fix: 添加占位符字段防止json schema校验失败 2025-11-06 17:43:09 +08:00
piexlMax(奇淼 fdc5e3cbf6 Merge branch 'dev-287' of github.com:flipped-aurora/gin-vue-admin into dev-287 2025-11-06 17:28:30 +08:00
piexlMax(奇淼 a0643d8ded refactor(mcp): 修改execute定义 2025-11-06 17:25:36 +08:00
PiexlMax(奇淼 83fb5d5921 Merge pull request #2107 from feitianbubu/pr/fix--dup-key-upload
feat: add check for existing file key before upload
2025-11-05 11:49:57 +08:00
PiexlMax(奇淼 a1070a64e7 Merge pull request #2132 from andrewbytecoder/main
fix(jwt):修正令牌过期时间计算错误
2025-11-03 13:28:47 +08:00
PiexlMax(奇淼 709cdef050 Merge pull request #2134 from frank-515/main
doc: 翻译了README-en.md 中的中文
2025-11-03 13:27:24 +08:00
PiexlMax(奇淼 c95ece3fe2 Merge pull request #2136 from Azir-11/dev-287
style(版本管理): 优化导出和导入版本抽屉的样式,支持暗色模式
2025-11-03 13:05:15 +08:00
Azir-11 d5befa1900 style(版本管理): 优化导出和导入版本抽屉的样式,支持暗色模式 2025-11-03 02:13:58 +08:00
frank515 89ea284cc6 doc: 翻译了README-en.md 中的中文 2025-10-30 21:46:10 +08:00
piexlMax(奇淼 ffe1844ba6 fix(mcp): 递归检查包文件夹中是否存在.go文件 2025-10-28 17:47:29 +08:00
piexlMax(奇淼 d36eeb5134 feat(错误日志): 实现前后端错误日志收集与管理功能 2025-10-28 16:19:25 +08:00
wangyazhou b9009bbd48 fix(jwt):修正令牌过期时间计算错误
- 调整了SetToken函数中的过期时间单位,从秒转换为分钟
- 确保新令牌的过期时间与配置保持一致- 修复了因时间单位不匹配导致的令牌提前失效问题
2025-10-28 15:14:26 +08:00
pixelmaxQM 5be32930f8 Merge branch 'main' of github.com:flipped-aurora/gin-vue-admin 2025-10-25 12:05:34 +08:00
pixelmaxQM 296c71290c fix(autocode): 修复插件包检测逻辑并清理无效数据库记录
改进插件包目录结构检测逻辑,当缺少必要目录时生成警告描述
添加清理数据库中不存在对应实体文件的包记录功能
2025-10-25 12:05:25 +08:00
PiexlMax(奇淼 2378971c8c Merge pull request #2129 from Azir-11/main
typo: 优化注释说明
2025-10-25 11:43:21 +08:00
piexlMax(奇淼 5a30df558c fix(登录): 修正验证码长度计算逻辑 2025-10-24 15:50:53 +08:00
青菜白玉汤 2c21f3cd75 typo: 优化注释说明 2025-10-24 10:52:16 +08:00
7d3e7b5b7a public:发布2.8.6版本 (#2126)
* feat(mcp): 新增gva_review工具并优化字典和代码生成逻辑

* fix: 调整mcp整体逻辑

* chore: 更新.gitignore,添加对本地配置文件的忽略

* feat(logo): 新增Logo组件并在多个页面中替换原有logo实现

* fix: 修复菜单 Logo 部分删除文本后显示异常的问题

* fix:添加字典列表搜索,支持中英文搜索.添加字典详情搜索

* style: 优化部分视觉样式

* feat: 增强错误预览组件的暗黑模式支持

* feat: 优化请求错误消息获取逻辑,增加状态文本优先级

* feat: 添加前端登录验证码静态验证逻辑

* feat: 添加开发环境启动脚本

* feat: 更新 SvgIcon 组件,支持本地图标和 Iconify 图标、移除未使用的 unocss 依赖

* fix:字典支持 tree 结构

* feat: 优化动态路由注册方式

* feat: 添加配置控制标签页keep-alive功能

* feat: 添加全局错误处理机制,捕获 Vue 和 JS 错误

* refactor: 移除API和菜单创建结果中的权限分配提醒,优化输出信息

* feat: 更新 reset.scss,优化全局样式重置,增强兼容性和可读性

* refactor(字典详情): 优化字典详情查询逻辑,移除预加载改为按需加载

* refactor(路由管理): 优化路由添加逻辑,增强路径处理和顶级路由注册

* refactor(系统配置): 将auto-migrate修改为disable-auto-migrate,保证用户升级的兼容性

* feat(utils): 优化字典数据递归查找功能并替换select为tree-select

* fix(deps): 修复在字段类型为file生成搜索条件无法运行的bug

* fix: 修复header的tools中icon不展示的问题

---------

Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: Azir-11 <2075125282@qq.com>
Co-authored-by: bypanghu <bypanghu@163.com>
Co-authored-by: feitianbubu <feitianbubu@qq.com>
Co-authored-by: 青菜白玉汤 <79054161+Azir-11@users.noreply.github.com>
Co-authored-by: krank <emosick@qq.com>
2025-10-19 13:27:48 +08:00
PiexlMax(奇淼 b01b27f1eb Merge pull request #2111 from zayn-code/dict
修复字典值分页bug
2025-10-09 13:59:00 +08:00
zayn d40b1ba823 修复字典值分页bug
当字典值有多页,且sort值一样时,分页返回的数据有问题,导致有些数据一直加载不出来
2025-09-30 16:38:55 +08:00
piexlMax(奇淼 a1dfc360fc feat(路由): 添加配置控制标签页keep-alive功能 2025-09-30 13:46:33 +08:00
青菜白玉汤 6da17433f2 优化动态路由注册方式
feat: 优化动态路由注册方式
2025-09-30 13:29:12 +08:00
feitianbubu 32f21d8555 feat: add check for existing file key before upload 2025-09-30 12:59:26 +08:00
PiexlMax(奇淼 c59a0504ad Merge pull request #2095 from xue-ding-e/patch-6
Update response.go
2025-09-21 17:32:07 +08:00
Qing Liang 182af9a1b6 Update response.go
删除多余注释
2025-09-11 11:20:52 +08:00
piexlMax(奇淼 424c5b23b4 style(layout): 修复绝对定位类名缺少重要修饰符的问题 2025-09-09 11:18:11 +08:00
piexlMax(奇淼 86b420ce67 docs: 更新项目规则文档和调整代码格式 2025-09-08 10:54:44 +08:00
piexlMax(奇淼 ea5709e5b6 style(菜单组件): 调整级联选择器宽度样式为全宽 2025-09-08 10:51:13 +08:00
PiexlMax(奇淼 4e9e3570ed fix: VersionData数据类型改为text 2025-09-07 23:58:05 +08:00
piexlMax(奇淼 58b123d316 fix: VersionData 默认使用text 考虑兼容性 2025-09-05 17:38:46 +08:00
362 changed files with 25321 additions and 8955 deletions
+902
View File
@@ -0,0 +1,902 @@
### 功能描述以及必要性描述
---
name: gin-vue-admin
description: |
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
前端技术栈:
- Vue 3.5.7 + Composition API
- Vite 6.2.3 构建工具
- Pinia 2.2.2 状态管理
- Element Plus 2.10.2 UI组件库
- UnoCSS 66.4.2 原子化CSS框架
- Vue Router 4.4.3 路由管理
- Axios 1.8.2 HTTP客户端
- ECharts 5.5.1 数据可视化
- @vueuse/core Vue组合式API工具集
后端技术栈:
- Go 1.23 + Gin 1.10.0 Web框架
- GORM 1.25.12 ORM框架
- Casbin 2.103.0 权限管理
- Viper 1.19.0 配置管理
- Zap 1.27.0 日志系统
- Redis 9.7.0 缓存
- JWT 5.2.2 认证授权
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
核心特性:
- 完整的RBAC权限控制系统
- 代码自动生成功能
- 丰富的中间件支持
- 插件化架构设计
- Swagger API文档
---
#### **角色与目标**
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
---
### **🚀 重要提示:GVA Helper MCP 支持**
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
3. **开发流程**:
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
4. **优势**: 通过GVA Helper可以获得:
- 最新的GVA框架特性和最佳实践
- 符合项目规范的代码模板
- 避免常见的开发陷阱和错误
- 确保代码质量和一致性
**请始终记住:GVA Helper → 获得支持 → 开始开发**
---
### **核心开发指令:绝不可违背的原则**
## **项目结构说明**
### **整体架构**
gin-vue-admin 采用前后端分离架构:
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
### **后端目录结构 (server/)**
```
server/
├── api/ # API控制器层
│ └── v1/ # API版本控制
│ ├── enter.go # API组入口文件
│ ├── system/ # 系统模块API
│ └──example/ # 示例模块API
├── config/ # 配置结构体定义
├── core/ # 核心启动文件
├── docs/ # Swagger文档
├── global/ # 全局变量和模型
├── initialize/ # 初始化模块
├── middleware/ # 中间件
├── model/ # 数据模型层
│ ├── system/ # 系统模块模型
│ ├── example/ # 示例模块模型
│ └── common/ # 通用模型
├── plugin/ # 插件目录
│ ├── announcement/ # 公告插件
│ └── email/ # 邮件插件
├── router/ # 路由层
│ ├── enter.go # 路由组入口
│ ├── system/ # 系统路由
│ └──example/ # 示例路由
├── service/ # 服务层
│ ├── enter.go # 服务组入口
│ ├── system/ # 系统服务
│ └── example/ # 示例服务
├── source/ # 数据初始化
├── utils/ # 工具包
├── config.yaml # 配置文件
└── main.go # 程序入口
```
### **前端目录结构 (web/)**
```
web/
├── public/ # 静态资源
├── src/
│ ├── api/ # API接口定义
│ │ ├── user.js # 用户相关API
│ │ ├── menu.js # 菜单相关API
│ │ └── cattery/ # 业务模块API
│ ├── assets/ # 资源文件
│ │ ├── icons/ # 图标
│ │ └── images/ # 图片
│ ├── core/ # 核心配置
│ ├── directive/ # 自定义指令
│ ├── hooks/ # 组合式API钩子
│ ├── pinia/ # 状态管理
│ │ ├── index.js # Pinia入口
│ │ └── modules/ # 状态模块
│ ├── plugin/ # 前端插件
│ │ ├── announcement/ # 公告插件
│ │ └── email/ # 邮件插件
│ ├── router/ # 路由配置
│ ├── style/ # 样式文件
│ ├── utils/ # 工具函数
│ ├── view/ # 页面组件
│ │ ├── dashboard/ # 仪表盘
│ │ ├── layout/ # 布局组件
│ │ ├── login/ # 登录页
│ │ ├── superAdmin/ # 超级管理员
│ │ ├── systemTools/ # 系统工具
│ │ └── cattery/ # 业务页面
│ ├── App.vue # 根组件
│ └── main.js # 程序入口
├── package.json # 依赖配置
├── vite.config.js # Vite配置
└── uno.config.js # UnoCSS配置
```
---
#### 后端规则
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的分层架构**:
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
2. **`enter.go` 组管理模式**:
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
3. **详尽的 Swagger 注释 (API层强制要求)**:
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
4. **统一的响应与错误处理**:
- Service 层函数遇到业务错误时,应返回 `error` 对象。
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
---
### **各层级代码实现规范**
#### **1. 模型层 (`model/`)**
- **数据模型 (`model/xxx.go`)**:
- 用于定义与数据库表映射的 GORM 结构体。
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
- 以上三个字段返回给前端并未做驼峰处理,json内依然是 `ID`, `CreatedAt`, `UpdatedAt`
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
- **⚠️ 重要提醒:数据类型一致性**
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
- **⚠️ 指针类型处理**:
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
- **请求模型 (`model/request/xxx.go`)**:
- 用于定义接收前端请求参数的结构体(DTOs)。
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
#### **2. 服务层 (`service/`)**
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
- **⚠️ 数据类型处理注意事项**:
- 在进行数据模型转换时,**必须确保**字段类型的一致性
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
#### **3. API层 (`api/`)**
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
- **Swagger 示例 (必须遵循)**:
Go
```
// CreateXxx 创建XXX
// @Tags XxxModule
// @Summary 创建一个新的XXX
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /xxx/createXxx [post]
func (a *XxxApi) CreateXxx(c *gin.Context) {
// ...
}
```
#### **4. 路由层 (`router/`)**
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
#### **5. 初始化层 (`initialize/`)**
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
- viper.go: 加载插件配置文件
- api.go: 注册API到系统
#### **6. 插件入口 (`plugin.go`)
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
- **插件注册**: **必须**调用 ```
func init() {
interfaces.Register(Plugin)
}
```
方法,让插件自动注册到本体中
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
### 模块间引用关系:
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
### 插件默认注册功能
`plugin/register.go` 文件下用 ` _ "github.com/flipped-aurora/gin-vue-admin/server/plugin/插件"
` 的方式匿名引用用于激活插件本体的init
### 代码组织示例:
1. Service入口 (service/enter.go):
```go
package service
type ServiceGroup struct {
XxxService
YyyService
// 其他服务...
}
var ServiceGroupApp = new(ServiceGroup)
```
2. API入口 (api/enter.go):
```go
package api
type ApiGroup struct {
XxxApi
YyyApi
// 其他API...
}
var ApiGroupApp = new(ApiGroup)
```
3. Router入口 (router/enter.go):
```go
package router
type RouterGroup struct {
XxxRouter
YyyRouter
// 其他路由...
}
var RouterGroupApp = new(RouterGroup)
```
### Swagger注释规范:
- @Tags: 接口所属的分组
- @Summary: 接口功能简述
- @Security: 安全认证方式(如需认证则添加)
- @accept/@Produce: 请求/响应格式
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
- @Success: 成功响应,包括状态码、返回类型、描述
- @Router: 接口路径和HTTP方法
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
---
### **开发工作流**
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
2. **【第一步】模型设计 (奠定基础)**:
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
3. **【第二步】自下而上,分层实现**:
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
4. **【第三步】插件初始化与注册**:
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
5. **【第四步】提供完整代码**:
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
---
## **前端开发规范**
### **角色与目标**
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
### **核心开发指令:绝不可违背的原则**
#### 前端规则
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的模块化架构**:
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
2. **统一的API调用模式**:
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
3. **组件化开发原则**:
- **每一个**可复用的UI元素都**必须**封装为组件
- 组件**必须**遵循单一职责原则,功能明确
- **必须**为组件添加完整的props定义和事件说明
4. **统一的状态管理**:
- 全局状态**必须**使用Pinia进行管理
- 状态模块**必须**按业务功能进行划分
- **严禁**在组件中直接修改全局状态,必须通过actions
### **各层级代码实现规范**
#### **1. API层 (`src/api/`)**
- **职责**: 封装所有后端API调用,提供统一的接口服务
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
- **规范**:
```javascript
import service from '@/utils/request'
/**
* 获取用户列表
* @param {Object} data 查询参数
* @param {number} data.page 页码
* @param {number} data.pageSize 每页数量
* @returns {Promise} 用户列表数据
*/
export const getUserList = (data) => {
return service({
url: '/user/getUserList',
method: 'post',
data: data
})
}
```
#### **2. 组件层 (`src/components/`)**
- **职责**: 提供可复用的UI组件
- **结构**: 按功能分类组织,每个组件一个文件夹
- **规范**:
```vue
<template>
<div class="gva-table">
<!-- 组件内容 -->
</div>
</template>
<script setup>
/**
* 通用表格组件
* @component GvaTable
* @description 提供统一的表格展示功能
*/
// Props定义
const props = defineProps({
data: {
type: Array,
required: true,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
// 事件定义
const emit = defineEmits(['refresh', 'edit', 'delete'])
</script>
```
#### **3. 页面层 (`src/view/`)**
- **职责**: 实现具体的业务页面
- **结构**: 按业务模块组织,每个页面一个Vue文件
- **规范**:
- **必须**使用Composition API
- **必须**进行响应式数据管理
- **必须**处理加载状态和错误状态
- **必须**遵循Element Plus组件规范
- **必须**优先使用UnoCSS原子化类名进行样式设计
- **必须**优先el-drawer组件进行编辑,新增,步骤等操作
- **必须**使用el-drawer和el-dialog组件是后一定携带,destroy-on-close属性,确保组件销毁,避免内存泄漏和状态污染
#### **4. 状态管理 (`src/pinia/`)**
- **职责**: 管理全局状态和业务逻辑
- **结构**: 按业务模块创建store文件
- **规范**:
```javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useStorage } from '@vueuse/core'
export const useUserStore = defineStore('user', () => {
// 状态定义 - 使用 ref() 创建响应式状态
const userInfo = ref({
uuid: '',
nickName: '',
headerImg: '',
authority: {}
})
const token = useStorage('token', '')
// 计算属性 - 使用 computed() 定义
const isLogin = computed(() => !!token.value)
// 方法定义 - 直接定义函数作为 actions
const setUserInfo = (val) => {
userInfo.value = val
}
const setToken = (val) => {
token.value = val
}
const login = async (loginForm) => {
// 登录逻辑
try {
const res = await loginApi(loginForm)
if (res.code === 0) {
setUserInfo(res.data.user)
setToken(res.data.token)
return true
}
return false
} catch (error) {
console.error('Login error:', error)
return false
}
}
const logout = async () => {
// 登出逻辑
token.value = ''
userInfo.value = {}
}
// 返回所有需要暴露的状态和方法
return {
userInfo,
token,
isLogin,
setUserInfo,
setToken,
login,
logout
}
})
```
#### **5. 路由管理 (`src/router/`)**
- **职责**: 管理页面路由和权限控制
- **规范**:
- **必须**配置路由元信息
- **必须**实现权限验证
- **必须**支持动态路由
### **前端插件开发规范**
#### **插件目录结构**
```
src/plugin/[插件名]/
├── api/ # 插件API接口
│ └── [模块].js
├── components/ # 插件组件(可选)
│ └── [组件名].vue
├── view/ # 插件页面
│ └── [页面名].vue
├── form/ # 插件表单(可选)
│ └── [表单名].vue
└── index.js # 插件入口文件(可选)
```
#### **插件开发原则**
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
2. **可配置性**: 插件应该支持配置化,便于定制
3. **可扩展性**: 插件应该预留扩展接口
4. **一致性**: 插件UI风格应与主系统保持一致
### **代码质量要求**
1. **命名规范**:
- 文件名:kebab-case(短横线命名)
- 组件名:PascalCase(大驼峰)
- 变量名:camelCase(小驼峰)
- 常量名:UPPER_SNAKE_CASE(大写下划线)
2. **注释规范**:
- **必须**为所有API函数添加JSDoc注释
- **必须**为复杂组件添加功能说明
- **必须**为关键业务逻辑添加行内注释
3. **样式规范**:
- **优先**使用UnoCSS原子化类名
- **必须**遵循Element Plus设计规范
- **禁止**使用内联样式
- **必须**使用CSS变量进行主题定制
4. **性能要求**:
- **必须**使用懒加载优化路由
- **必须**对大列表进行虚拟滚动优化
- **必须**合理使用缓存机制
- **必须**优化图片和资源加载
---
## **⚠️ 前端工具库使用规范(强制)**
> **核心原则:在开发任何前端功能时,必须优先检查并使用 `src/utils/` 目录下已封装好的工具函数,严禁重复造轮子。**
`src/utils/` 目录提供了项目级别的通用工具集,涵盖 HTTP 请求、日期处理、格式转换、字符串操作、图片处理等多个方面。以下是各工具文件的功能说明:
### **工具文件清单**
#### `request.js` — HTTP 请求封装(核心)
- 基于 Axios 封装的统一 HTTP 请求实例,内置全局 Loading 状态管理、JWT Token 自动注入、统一错误处理和响应拦截
- **所有 API 请求必须且只能通过此模块发送,禁止直接使用 axios**
- 用法:`import service from '@/utils/request'`
#### `date.js` — 日期格式化
- 扩展了 `Date.prototype.Format` 方法,支持自定义格式如 `yyyy-MM-dd hh:mm:ss`
- 导出 `formatTimeToStr(times, pattern)` 将时间戳或日期对象格式化为字符串
- **需要格式化日期时,优先使用此工具,禁止自行手写日期格式化逻辑**
- 用法:`import { formatTimeToStr } from '@/utils/date'`
#### `format.js` — 数据展示格式化(综合工具)
- `formatBoolean(bool)` — 将布尔值转为 "是"/"否" 中文展示
- `formatDate(time)` — 将时间转为 `yyyy-MM-dd hh:mm:ss` 格式字符串
- `filterDict(value, options)` — 在字典选项数组(支持多级树形)中根据 value 查找对应的 label
- `filterDataSource(dataSource, value)` — 在数据源(支持多级树形)中根据 value 查找 label,支持数组批量查找
- `getDictFunc(type)` — 异步获取指定类型的字典数据
- `ReturnArrImg(arr)` — 将图片路径(单个或数组)转为完整 URL,自动补全服务器前缀
- `onDownloadFile(url)` — 触发文件下载
- `setBodyPrimaryColor(primaryColor, darkMode)` — 动态设置主题色相关的 CSS 变量(支持亮/暗模式)
- `CreateUUID()` — 生成 UUID v4 字符串
- `getBaseUrl()` — 获取当前环境的 API BaseURL
- **以上所有格式化场景优先使用此文件中的工具函数**
- 用法:`import { formatBoolean, formatDate, filterDict, CreateUUID, ... } from '@/utils/format'`
#### `dictionary.js` — 字典数据获取
- `getDict(type, options)` — 异步获取字典数据,支持 `depth`(深度)和 `value`(指定节点)参数,内置 Pinia store 缓存,避免重复请求
- **凡是需要字典下拉数据、字典树形数据的场景,必须使用此工具**
- 用法:`import { getDict } from '@/utils/dictionary'`
#### `stringFun.js` — 字符串处理
- `toUpperCase(str)` — 首字母转大写
- `toLowerCase(str)` — 首字母转小写
- `toSQLLine(str)` — 驼峰命名转下划线(snake_case),如 `userName` → `user_name`
- `toHump(name)` — 下划线命名转驼峰,如 `user_name` → `userName`
- **进行命名格式转换时必须使用此工具,禁止使用正则手写**
- 用法:`import { toUpperCase, toSQLLine, toHump } from '@/utils/stringFun'`
#### `params.js` — 系统参数获取
- `getParams(key)` — 异步从 Pinia store 中获取系统参数,内置缓存
- **获取系统配置参数时,优先使用此工具**
- 用法:`import { getParams } from '@/utils/params'`
#### `bus.js` — 全局事件总线
- 基于 `mitt` 封装的全局事件总线实例 `emitter`,用于跨组件通信
- **跨层级组件通信优先使用此事件总线,避免滥用 Pinia**
- 用法:`import { emitter } from '@/utils/bus'`
#### `closeThisPage.js` — 关闭当前标签页
- `closeThisPage()` — 触发关闭当前多标签页的操作(通过事件总线发送 `closeThisPage` 事件)
- **在需要程序化关闭当前页面时,必须使用此工具**
- 用法:`import { closeThisPage } from '@/utils/closeThisPage'`
#### `downloadImg.js` — 图片下载
- `downloadImage(imgsrc, name)` — 通过 Canvas 将图片转为 base64 后触发下载,支持跨域
- **需要下载图片时,优先使用此工具**
- 用法:`import { downloadImage } from '@/utils/downloadImg'`
#### `image.js` — 图片压缩
- 导出 `ImageCompress` 类,支持图片等比压缩至指定最大宽高,并可限制文件大小
- **上传图片前需要做压缩处理时,使用此工具**
- 用法:`import ImageCompress from '@/utils/image'`
#### `event.js` — DOM 事件监听管理
- `addEventListen(target, event, handler, capture)` — 安全地添加 DOM 事件监听
- `removeEventListen(target, event, handler, capture)` — 安全地移除 DOM 事件监听
- **手动操作 DOM 事件时,使用此工具以确保安全性**
- 用法:`import { addEventListen, removeEventListen } from '@/utils/event'`
#### `env.js` — 环境判断
- `isDev` — 是否为开发环境(Boolean)
- `isProd` — 是否为生产环境(Boolean)
- **需要区分运行环境时,使用此工具,禁止直接读取 `import.meta.env`**
- 用法:`import { isDev, isProd } from '@/utils/env'`
#### `doc.js` — 外部文档跳转
- `toDoc(url)` — 在新标签页打开指定 URL
- 用法:`import { toDoc } from '@/utils/doc'`
#### `fmtRouterTitle.js` — 路由标题格式化
- `fmtTitle(title, route)` — 解析路由标题中的动态参数插值(如 `${id}` 替换为路由 params/query 值)
- 用法:`import { fmtTitle } from '@/utils/fmtRouterTitle'`
#### `page.js` — 页面标题生成
- `getPageTitle(pageTitle, route)` — 根据页面标题和路由生成完整的浏览器 Tab 标题(格式:`页面名 - 应用名`)
- 用法:`import getPageTitle from '@/utils/page'`
#### `asyncRouter.js` — 异步路由处理
- `asyncRouterHandle(asyncRouter)` — 将后端返回的路由配置(字符串 component 路径)动态转换为 Vue 组件的 import 函数,支持 `view/` 和 `plugin/` 目录
- **动态路由相关逻辑已由此工具处理,不需要也不应该手动实现**
- 用法:`import { asyncRouterHandle } from '@/utils/asyncRouter'`
#### `btnAuth.js` — 按钮权限
- `useBtnAuth()` — Composition API Hook,返回当前路由挂载的按钮权限对象(来自 `route.meta.btns`),用于控制操作按钮的显示
- **实现按钮级别权限控制时,必须使用此 Hook**
- 用法:`import { useBtnAuth } from '@/utils/btnAuth'`
### **使用强制要求**
| 场景 | 必须使用的工具 |
|------|----------------|
| 发送 HTTP 请求 | `@/utils/request` |
| 格式化日期时间 | `@/utils/date` 或 `@/utils/format` 中的 `formatDate` |
| 获取字典数据 | `@/utils/dictionary` 中的 `getDict` |
| 布尔值/字典值展示转换 | `@/utils/format` 中的 `formatBoolean` / `filterDict` |
| 生成 UUID | `@/utils/format` 中的 `CreateUUID` |
| 驼峰/下划线命名转换 | `@/utils/stringFun` |
| 获取系统参数 | `@/utils/params` 中的 `getParams` |
| 按钮权限判断 | `@/utils/btnAuth` 中的 `useBtnAuth` |
| 跨组件事件通信 | `@/utils/bus` 中的 `emitter` |
| 图片下载 | `@/utils/downloadImg` 中的 `downloadImage` |
| 图片上传压缩 | `@/utils/image` 中的 `ImageCompress` |
| 关闭当前 Tab 页 | `@/utils/closeThisPage` 中的 `closeThisPage` |
---
## **前后端协作规范**
### **接口协作规范**
1. **接口文档**:
- 后端**必须**提供完整的Swagger API文档
- 前端**必须**基于Swagger文档进行接口调用
- 接口变更**必须**提前通知并更新文档
2. **数据格式**:
- **统一**使用JSON格式进行数据交换
- **统一**响应格式:`{code, data, msg}`
- **统一**分页格式:`{page, pageSize, total, list}`
- **统一**时间格式:ISO 8601标准
- **⚠️ 数据类型一致性**:
- 前后端对于同一字段**必须**使用相同的数据类型
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
3. **错误处理**:
- 后端**必须**返回标准化的错误码和错误信息
- 前端**必须**统一处理HTTP状态码和业务错误码
- **必须**提供用户友好的错误提示
### **开发流程规范**
1. **需求分析阶段**:
- 确定功能需求和接口设计
- 定义数据模型和业务流程
- 制定前后端开发计划
2. **开发阶段**:
- 后端优先开发API接口
- 前端基于Mock数据进行并行开发
- 定期进行接口联调测试
3. **测试阶段**:
- 单元测试:前后端各自负责
- 集成测试:前后端协作完成
- 用户验收测试:产品团队主导
### **版本管理规范**
1. **分支策略**:
- `main`:生产环境分支
- `develop`:开发环境分支
- `feature/*`:功能开发分支
- `hotfix/*`:紧急修复分支
2. **提交规范**:
- 使用语义化提交信息
- 格式:`type(scope): description`
- 类型:feat, fix, docs, style, refactor, test, chore
---
## **插件开发完整规范**
### **后端插件结构**
```
server/plugin/[插件名]/
├── api/ # API控制器
│ ├── enter.go # API组入口
│ └── [模块].go # 具体API实现
├── config/ # 插件配置
│ └── config.go
├── initialize/ # 初始化模块
│ ├── api.go # API注册
│ ├── gorm.go # 数据库初始化
│ ├── menu.go # 菜单初始化
│ ├── router.go # 路由初始化
│ └── viper.go # 配置初始化
├── model/ # 数据模型
│ ├── [模型].go # 数据库模型
│ └── request/ # 请求模型
├── router/ # 路由定义
│ ├── enter.go # 路由组入口
│ └── [模块].go # 具体路由
├── service/ # 业务服务
│ ├── enter.go # 服务组入口
│ └── [模块].go # 具体服务
└── plugin.go # 插件入口
```
### **前端插件结构**
```
web/src/plugin/[插件名]/
├── api/ # API接口
│ └── [模块].js
├── components/ # 插件组件
│ └── [组件].vue
├── view/ # 插件页面
│ └── [页面].vue
├── form/ # 表单组件
│ └── [表单].vue
└── config.js # 插件配置
```
### **插件开发工作流**
1. **【第一步】需求分析**:
- 明确插件功能和业务需求
- 设计数据模型和接口规范
- 规划前端页面和交互流程
2. **【第二步】后端开发**:
- 创建数据模型和请求模型
- 实现服务层业务逻辑
- 开发API控制器和路由
- 编写初始化和配置代码
3. **【第三步】前端开发**:
- 创建API接口封装
- 开发页面组件和表单
- 实现业务逻辑和状态管理
- 集成到主系统菜单
4. **【第四步】测试集成**:
- 单元测试和集成测试
- 前后端联调测试
- 用户体验测试
- 性能和安全测试
### **插件质量标准**
1. **功能完整性**: 插件功能完整,满足业务需求
2. **代码质量**: 代码规范,注释完整,易于维护
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
4. **性能表现**: 响应速度快,资源占用合理
5. **用户体验**: 界面友好,操作流畅,错误处理完善
6. **兼容性**: 与主系统兼容,不影响其他功能
7. **安全性**: 数据安全,权限控制,防止安全漏洞
---
### **建议和方案**
基于以上规范,建议AI在开发gin-vue-admin项目时:
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
6. **重视安全性**:实现完善的权限控制和数据验证机制
+16 -752
View File
@@ -1,761 +1,25 @@
### 功能描述以及必要性描述
---
name: gin-vue-admin
description: |
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
前端技术栈:
- Vue 3.5.7 + Composition API
- Vite 6.2.3 构建工具
- Pinia 2.2.2 状态管理
- Element Plus 2.10.2 UI组件库
- UnoCSS 66.4.2 原子化CSS框架
- Vue Router 4.4.3 路由管理
- Axios 1.8.2 HTTP客户端
- ECharts 5.5.1 数据可视化
- @vueuse/core Vue组合式API工具集
后端技术栈:
- Go 1.23 + Gin 1.10.0 Web框架
- GORM 1.25.12 ORM框架
- Casbin 2.103.0 权限管理
- Viper 1.19.0 配置管理
- Zap 1.27.0 日志系统
- Redis 9.7.0 缓存
- JWT 5.2.2 认证授权
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
核心特性:
- 完整的RBAC权限控制系统
- 代码自动生成功能
- 丰富的中间件支持
- 插件化架构设计
- Swagger API文档
tool: claude
role: compatibility-adapter
canonical_source: /AGENT.MD
structured_context: /aiDoc
---
#### **角色与目标**
# Claude 规则适配层
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
本文件只用于兼容 Claude 现有的自动加载路径。
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
## 真实规则入口
---
请按下面顺序读取:
### **🚀 重要提示:GVA Helper MCP 支持**
1. `/AGENT.MD`
2. `/aiDoc/README.md`
3. `/aiDoc/relations/`、`/aiDoc/modules/`、`/aiDoc/frontend-backend/`、`/aiDoc/examples/`、`/aiDoc/memory/` 中与当前任务相关的文件
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
## 适配层约束
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
3. **开发流程**:
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
4. **优势**: 通过GVA Helper可以获得:
- 最新的GVA框架特性和最佳实践
- 符合项目规范的代码模板
- 避免常见的开发陷阱和错误
- 确保代码质量和一致性
**请始终记住:GVA Helper → 获得支持 → 开始开发**
---
### **核心开发指令:绝不可违背的原则**
## **项目结构说明**
### **整体架构**
gin-vue-admin 采用前后端分离架构:
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
### **后端目录结构 (server/)**
```
server/
├── api/ # API控制器层
│ └── v1/ # API版本控制
│ ├── enter.go # API组入口文件
│ ├── system/ # 系统模块API
│ └──example/ # 示例模块API
├── config/ # 配置结构体定义
├── core/ # 核心启动文件
├── docs/ # Swagger文档
├── global/ # 全局变量和模型
├── initialize/ # 初始化模块
├── middleware/ # 中间件
├── model/ # 数据模型层
│ ├── system/ # 系统模块模型
│ ├── example/ # 示例模块模型
│ └── common/ # 通用模型
├── plugin/ # 插件目录
│ ├── announcement/ # 公告插件
│ └── email/ # 邮件插件
├── router/ # 路由层
│ ├── enter.go # 路由组入口
│ ├── system/ # 系统路由
│ └──example/ # 示例路由
├── service/ # 服务层
│ ├── enter.go # 服务组入口
│ ├── system/ # 系统服务
│ └── example/ # 示例服务
├── source/ # 数据初始化
├── utils/ # 工具包
├── config.yaml # 配置文件
└── main.go # 程序入口
```
### **前端目录结构 (web/)**
```
web/
├── public/ # 静态资源
├── src/
│ ├── api/ # API接口定义
│ │ ├── user.js # 用户相关API
│ │ ├── menu.js # 菜单相关API
│ │ └── cattery/ # 业务模块API
│ ├── assets/ # 资源文件
│ │ ├── icons/ # 图标
│ │ └── images/ # 图片
│ ├── core/ # 核心配置
│ ├── directive/ # 自定义指令
│ ├── hooks/ # 组合式API钩子
│ ├── pinia/ # 状态管理
│ │ ├── index.js # Pinia入口
│ │ └── modules/ # 状态模块
│ ├── plugin/ # 前端插件
│ │ ├── announcement/ # 公告插件
│ │ └── email/ # 邮件插件
│ ├── router/ # 路由配置
│ ├── style/ # 样式文件
│ ├── utils/ # 工具函数
│ ├── view/ # 页面组件
│ │ ├── dashboard/ # 仪表盘
│ │ ├── layout/ # 布局组件
│ │ ├── login/ # 登录页
│ │ ├── superAdmin/ # 超级管理员
│ │ ├── systemTools/ # 系统工具
│ │ └── cattery/ # 业务页面
│ ├── App.vue # 根组件
│ └── main.js # 程序入口
├── package.json # 依赖配置
├── vite.config.js # Vite配置
└── uno.config.js # UnoCSS配置
```
---
#### 后端规则
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的分层架构**:
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
2. **`enter.go` 组管理模式**:
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
3. **详尽的 Swagger 注释 (API层强制要求)**:
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
4. **统一的响应与错误处理**:
- Service 层函数遇到业务错误时,应返回 `error` 对象。
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
---
### **各层级代码实现规范**
#### **1. 模型层 (`model/`)**
- **数据模型 (`model/xxx.go`)**:
- 用于定义与数据库表映射的 GORM 结构体。
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
- **⚠️ 重要提醒:数据类型一致性**
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
- **⚠️ 指针类型处理**:
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
- **请求模型 (`model/request/xxx.go`)**:
- 用于定义接收前端请求参数的结构体(DTOs)。
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
#### **2. 服务层 (`service/`)**
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
- **⚠️ 数据类型处理注意事项**:
- 在进行数据模型转换时,**必须确保**字段类型的一致性
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
#### **3. API层 (`api/`)**
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
- **Swagger 示例 (必须遵循)**:
Go
```
// CreateXxx 创建XXX
// @Tags XxxModule
// @Summary 创建一个新的XXX
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /xxx/createXxx [post]
func (a *XxxApi) CreateXxx(c *gin.Context) {
// ...
}
```
#### **4. 路由层 (`router/`)**
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
#### **5. 初始化层 (`initialize/`)**
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
- viper.go: 加载插件配置文件
- api.go: 注册API到系统
#### **6. 插件入口 (`plugin.go`)
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
### 模块间引用关系:
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
### 代码组织示例:
1. Service入口 (service/enter.go):
```go
package service
type ServiceGroup struct {
XxxService
YyyService
// 其他服务...
}
var ServiceGroupApp = new(ServiceGroup)
```
2. API入口 (api/enter.go):
```go
package api
type ApiGroup struct {
XxxApi
YyyApi
// 其他API...
}
var ApiGroupApp = new(ApiGroup)
```
3. Router入口 (router/enter.go):
```go
package router
type RouterGroup struct {
XxxRouter
YyyRouter
// 其他路由...
}
var RouterGroupApp = new(RouterGroup)
```
### Swagger注释规范:
- @Tags: 接口所属的分组
- @Summary: 接口功能简述
- @Security: 安全认证方式(如需认证则添加)
- @accept/@Produce: 请求/响应格式
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
- @Success: 成功响应,包括状态码、返回类型、描述
- @Router: 接口路径和HTTP方法
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
---
### **开发工作流**
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
2. **【第一步】模型设计 (奠定基础)**:
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
3. **【第二步】自下而上,分层实现**:
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
4. **【第三步】插件初始化与注册**:
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
5. **【第四步】提供完整代码**:
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
---
## **前端开发规范**
### **角色与目标**
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
### **核心开发指令:绝不可违背的原则**
#### 前端规则
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的模块化架构**:
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
2. **统一的API调用模式**:
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
3. **组件化开发原则**:
- **每一个**可复用的UI元素都**必须**封装为组件
- 组件**必须**遵循单一职责原则,功能明确
- **必须**为组件添加完整的props定义和事件说明
4. **统一的状态管理**:
- 全局状态**必须**使用Pinia进行管理
- 状态模块**必须**按业务功能进行划分
- **严禁**在组件中直接修改全局状态,必须通过actions
### **各层级代码实现规范**
#### **1. API层 (`src/api/`)**
- **职责**: 封装所有后端API调用,提供统一的接口服务
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
- **规范**:
```javascript
import service from '@/utils/request'
/**
* 获取用户列表
* @param {Object} data 查询参数
* @param {number} data.page 页码
* @param {number} data.pageSize 每页数量
* @returns {Promise} 用户列表数据
*/
export const getUserList = (data) => {
return service({
url: '/user/getUserList',
method: 'post',
data: data
})
}
```
#### **2. 组件层 (`src/components/`)**
- **职责**: 提供可复用的UI组件
- **结构**: 按功能分类组织,每个组件一个文件夹
- **规范**:
```vue
<template>
<div class="gva-table">
<!-- 组件内容 -->
</div>
</template>
<script setup>
/**
* 通用表格组件
* @component GvaTable
* @description 提供统一的表格展示功能
*/
// Props定义
const props = defineProps({
data: {
type: Array,
required: true,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
// 事件定义
const emit = defineEmits(['refresh', 'edit', 'delete'])
</script>
```
#### **3. 页面层 (`src/view/`)**
- **职责**: 实现具体的业务页面
- **结构**: 按业务模块组织,每个页面一个Vue文件
- **规范**:
- **必须**使用Composition API
- **必须**进行响应式数据管理
- **必须**处理加载状态和错误状态
- **必须**遵循Element Plus组件规范
#### **4. 状态管理 (`src/pinia/`)**
- **职责**: 管理全局状态和业务逻辑
- **结构**: 按业务模块创建store文件
- **规范**:
```javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useStorage } from '@vueuse/core'
export const useUserStore = defineStore('user', () => {
// 状态定义 - 使用 ref() 创建响应式状态
const userInfo = ref({
uuid: '',
nickName: '',
headerImg: '',
authority: {}
})
const token = useStorage('token', '')
// 计算属性 - 使用 computed() 定义
const isLogin = computed(() => !!token.value)
// 方法定义 - 直接定义函数作为 actions
const setUserInfo = (val) => {
userInfo.value = val
}
const setToken = (val) => {
token.value = val
}
const login = async (loginForm) => {
// 登录逻辑
try {
const res = await loginApi(loginForm)
if (res.code === 0) {
setUserInfo(res.data.user)
setToken(res.data.token)
return true
}
return false
} catch (error) {
console.error('Login error:', error)
return false
}
}
const logout = async () => {
// 登出逻辑
token.value = ''
userInfo.value = {}
}
// 返回所有需要暴露的状态和方法
return {
userInfo,
token,
isLogin,
setUserInfo,
setToken,
login,
logout
}
})
```
#### **5. 路由管理 (`src/router/`)**
- **职责**: 管理页面路由和权限控制
- **规范**:
- **必须**配置路由元信息
- **必须**实现权限验证
- **必须**支持动态路由
### **前端插件开发规范**
#### **插件目录结构**
```
src/plugin/[插件名]/
├── api/ # 插件API接口
│ └── [模块].js
├── components/ # 插件组件(可选)
│ └── [组件名].vue
├── view/ # 插件页面
│ └── [页面名].vue
├── form/ # 插件表单(可选)
│ └── [表单名].vue
└── index.js # 插件入口文件(可选)
```
#### **插件开发原则**
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
2. **可配置性**: 插件应该支持配置化,便于定制
3. **可扩展性**: 插件应该预留扩展接口
4. **一致性**: 插件UI风格应与主系统保持一致
### **代码质量要求**
1. **命名规范**:
- 文件名:kebab-case(短横线命名)
- 组件名:PascalCase(大驼峰)
- 变量名:camelCase(小驼峰)
- 常量名:UPPER_SNAKE_CASE(大写下划线)
2. **注释规范**:
- **必须**为所有API函数添加JSDoc注释
- **必须**为复杂组件添加功能说明
- **必须**为关键业务逻辑添加行内注释
3. **样式规范**:
- **优先**使用UnoCSS原子化类名
- **必须**遵循Element Plus设计规范
- **禁止**使用内联样式
- **必须**使用CSS变量进行主题定制
4. **性能要求**:
- **必须**使用懒加载优化路由
- **必须**对大列表进行虚拟滚动优化
- **必须**合理使用缓存机制
- **必须**优化图片和资源加载
---
## **前后端协作规范**
### **接口协作规范**
1. **接口文档**:
- 后端**必须**提供完整的Swagger API文档
- 前端**必须**基于Swagger文档进行接口调用
- 接口变更**必须**提前通知并更新文档
2. **数据格式**:
- **统一**使用JSON格式进行数据交换
- **统一**响应格式:`{code, data, msg}`
- **统一**分页格式:`{page, pageSize, total, list}`
- **统一**时间格式:ISO 8601标准
- **⚠️ 数据类型一致性**:
- 前后端对于同一字段**必须**使用相同的数据类型
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
3. **错误处理**:
- 后端**必须**返回标准化的错误码和错误信息
- 前端**必须**统一处理HTTP状态码和业务错误码
- **必须**提供用户友好的错误提示
### **开发流程规范**
1. **需求分析阶段**:
- 确定功能需求和接口设计
- 定义数据模型和业务流程
- 制定前后端开发计划
2. **开发阶段**:
- 后端优先开发API接口
- 前端基于Mock数据进行并行开发
- 定期进行接口联调测试
3. **测试阶段**:
- 单元测试:前后端各自负责
- 集成测试:前后端协作完成
- 用户验收测试:产品团队主导
### **版本管理规范**
1. **分支策略**:
- `main`:生产环境分支
- `develop`:开发环境分支
- `feature/*`:功能开发分支
- `hotfix/*`:紧急修复分支
2. **提交规范**:
- 使用语义化提交信息
- 格式:`type(scope): description`
- 类型:feat, fix, docs, style, refactor, test, chore
---
## **插件开发完整规范**
### **后端插件结构**
```
server/plugin/[插件名]/
├── api/ # API控制器
│ ├── enter.go # API组入口
│ └── [模块].go # 具体API实现
├── config/ # 插件配置
│ └── config.go
├── initialize/ # 初始化模块
│ ├── api.go # API注册
│ ├── gorm.go # 数据库初始化
│ ├── menu.go # 菜单初始化
│ ├── router.go # 路由初始化
│ └── viper.go # 配置初始化
├── model/ # 数据模型
│ ├── [模型].go # 数据库模型
│ └── request/ # 请求模型
├── router/ # 路由定义
│ ├── enter.go # 路由组入口
│ └── [模块].go # 具体路由
├── service/ # 业务服务
│ ├── enter.go # 服务组入口
│ └── [模块].go # 具体服务
└── plugin.go # 插件入口
```
### **前端插件结构**
```
web/src/plugin/[插件名]/
├── api/ # API接口
│ └── [模块].js
├── components/ # 插件组件
│ └── [组件].vue
├── view/ # 插件页面
│ └── [页面].vue
├── form/ # 表单组件
│ └── [表单].vue
└── config.js # 插件配置
```
### **插件开发工作流**
1. **【第一步】需求分析**:
- 明确插件功能和业务需求
- 设计数据模型和接口规范
- 规划前端页面和交互流程
2. **【第二步】后端开发**:
- 创建数据模型和请求模型
- 实现服务层业务逻辑
- 开发API控制器和路由
- 编写初始化和配置代码
3. **【第三步】前端开发**:
- 创建API接口封装
- 开发页面组件和表单
- 实现业务逻辑和状态管理
- 集成到主系统菜单
4. **【第四步】测试集成**:
- 单元测试和集成测试
- 前后端联调测试
- 用户体验测试
- 性能和安全测试
### **插件质量标准**
1. **功能完整性**: 插件功能完整,满足业务需求
2. **代码质量**: 代码规范,注释完整,易于维护
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
4. **性能表现**: 响应速度快,资源占用合理
5. **用户体验**: 界面友好,操作流畅,错误处理完善
6. **兼容性**: 与主系统兼容,不影响其他功能
7. **安全性**: 数据安全,权限控制,防止安全漏洞
---
### **建议和方案**
基于以上规范,建议AI在开发gin-vue-admin项目时:
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
6. **重视安全性**:实现完善的权限控制和数据验证机制
- 不要在这里扩写项目级规则
- 项目级规则变更时,先更新 `/AGENT.MD` 与 `/aiDoc/`
- 工具目录只保留薄适配层职责,不再保存独立 project rule 副本
- 代码读取约束以 `/AGENT.MD` 为准,其中包括:无论什么情况,都不要直接读取 `node_modules/` 中的代码
+24
View File
@@ -0,0 +1,24 @@
---
tool: codex
role: compatibility-adapter
canonical_source: /AGENT.MD
structured_context: /aiDoc
---
# Codex 规则适配层
本文件只用于兼容 Codex 现有的自动加载路径。
## 真实规则入口
请按下面顺序读取:
1. `/AGENT.MD`
2. `/aiDoc/README.md`
3. `/aiDoc/relations/`、`/aiDoc/modules/`、`/aiDoc/frontend-backend/`、`/aiDoc/examples/`、`/aiDoc/memory/` 中与当前任务相关的文件
## 适配层约束
- 不要在这里扩写项目级规则
- 项目级规则变更时,先更新 `/AGENT.MD` 与 `/aiDoc/`
- 工具目录只保留薄适配层职责,不再保存独立 project rule 副本
+15 -752
View File
@@ -1,761 +1,24 @@
### 功能描述以及必要性描述
---
name: gin-vue-admin
description: |
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
前端技术栈:
- Vue 3.5.7 + Composition API
- Vite 6.2.3 构建工具
- Pinia 2.2.2 状态管理
- Element Plus 2.10.2 UI组件库
- UnoCSS 66.4.2 原子化CSS框架
- Vue Router 4.4.3 路由管理
- Axios 1.8.2 HTTP客户端
- ECharts 5.5.1 数据可视化
- @vueuse/core Vue组合式API工具集
后端技术栈:
- Go 1.23 + Gin 1.10.0 Web框架
- GORM 1.25.12 ORM框架
- Casbin 2.103.0 权限管理
- Viper 1.19.0 配置管理
- Zap 1.27.0 日志系统
- Redis 9.7.0 缓存
- JWT 5.2.2 认证授权
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
核心特性:
- 完整的RBAC权限控制系统
- 代码自动生成功能
- 丰富的中间件支持
- 插件化架构设计
- Swagger API文档
tool: cursor
role: compatibility-adapter
canonical_source: /AGENT.MD
structured_context: /aiDoc
---
#### **角色与目标**
# Cursor 规则适配层
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
本文件只用于兼容 Cursor 现有的自动加载路径。
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
## 真实规则入口
---
请按下面顺序读取:
### **🚀 重要提示:GVA Helper MCP 支持**
1. `/AGENT.MD`
2. `/aiDoc/README.md`
3. `/aiDoc/relations/`、`/aiDoc/modules/`、`/aiDoc/frontend-backend/`、`/aiDoc/examples/`、`/aiDoc/memory/` 中与当前任务相关的文件
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
## 适配层约束
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
3. **开发流程**:
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
4. **优势**: 通过GVA Helper可以获得:
- 最新的GVA框架特性和最佳实践
- 符合项目规范的代码模板
- 避免常见的开发陷阱和错误
- 确保代码质量和一致性
**请始终记住:GVA Helper → 获得支持 → 开始开发**
---
### **核心开发指令:绝不可违背的原则**
## **项目结构说明**
### **整体架构**
gin-vue-admin 采用前后端分离架构:
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
### **后端目录结构 (server/)**
```
server/
├── api/ # API控制器层
│ └── v1/ # API版本控制
│ ├── enter.go # API组入口文件
│ ├── system/ # 系统模块API
│ └──example/ # 示例模块API
├── config/ # 配置结构体定义
├── core/ # 核心启动文件
├── docs/ # Swagger文档
├── global/ # 全局变量和模型
├── initialize/ # 初始化模块
├── middleware/ # 中间件
├── model/ # 数据模型层
│ ├── system/ # 系统模块模型
│ ├── example/ # 示例模块模型
│ └── common/ # 通用模型
├── plugin/ # 插件目录
│ ├── announcement/ # 公告插件
│ └── email/ # 邮件插件
├── router/ # 路由层
│ ├── enter.go # 路由组入口
│ ├── system/ # 系统路由
│ └──example/ # 示例路由
├── service/ # 服务层
│ ├── enter.go # 服务组入口
│ ├── system/ # 系统服务
│ └── example/ # 示例服务
├── source/ # 数据初始化
├── utils/ # 工具包
├── config.yaml # 配置文件
└── main.go # 程序入口
```
### **前端目录结构 (web/)**
```
web/
├── public/ # 静态资源
├── src/
│ ├── api/ # API接口定义
│ │ ├── user.js # 用户相关API
│ │ ├── menu.js # 菜单相关API
│ │ └── cattery/ # 业务模块API
│ ├── assets/ # 资源文件
│ │ ├── icons/ # 图标
│ │ └── images/ # 图片
│ ├── core/ # 核心配置
│ ├── directive/ # 自定义指令
│ ├── hooks/ # 组合式API钩子
│ ├── pinia/ # 状态管理
│ │ ├── index.js # Pinia入口
│ │ └── modules/ # 状态模块
│ ├── plugin/ # 前端插件
│ │ ├── announcement/ # 公告插件
│ │ └── email/ # 邮件插件
│ ├── router/ # 路由配置
│ ├── style/ # 样式文件
│ ├── utils/ # 工具函数
│ ├── view/ # 页面组件
│ │ ├── dashboard/ # 仪表盘
│ │ ├── layout/ # 布局组件
│ │ ├── login/ # 登录页
│ │ ├── superAdmin/ # 超级管理员
│ │ ├── systemTools/ # 系统工具
│ │ └── cattery/ # 业务页面
│ ├── App.vue # 根组件
│ └── main.js # 程序入口
├── package.json # 依赖配置
├── vite.config.js # Vite配置
└── uno.config.js # UnoCSS配置
```
---
#### 后端规则
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的分层架构**:
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
2. **`enter.go` 组管理模式**:
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
3. **详尽的 Swagger 注释 (API层强制要求)**:
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
4. **统一的响应与错误处理**:
- Service 层函数遇到业务错误时,应返回 `error` 对象。
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
---
### **各层级代码实现规范**
#### **1. 模型层 (`model/`)**
- **数据模型 (`model/xxx.go`)**:
- 用于定义与数据库表映射的 GORM 结构体。
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
- **⚠️ 重要提醒:数据类型一致性**
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
- **⚠️ 指针类型处理**:
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
- **请求模型 (`model/request/xxx.go`)**:
- 用于定义接收前端请求参数的结构体(DTOs)。
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
#### **2. 服务层 (`service/`)**
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
- **⚠️ 数据类型处理注意事项**:
- 在进行数据模型转换时,**必须确保**字段类型的一致性
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
#### **3. API层 (`api/`)**
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
- **Swagger 示例 (必须遵循)**:
Go
```
// CreateXxx 创建XXX
// @Tags XxxModule
// @Summary 创建一个新的XXX
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /xxx/createXxx [post]
func (a *XxxApi) CreateXxx(c *gin.Context) {
// ...
}
```
#### **4. 路由层 (`router/`)**
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
#### **5. 初始化层 (`initialize/`)**
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
- viper.go: 加载插件配置文件
- api.go: 注册API到系统
#### **6. 插件入口 (`plugin.go`)
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
### 模块间引用关系:
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
### 代码组织示例:
1. Service入口 (service/enter.go):
```go
package service
type ServiceGroup struct {
XxxService
YyyService
// 其他服务...
}
var ServiceGroupApp = new(ServiceGroup)
```
2. API入口 (api/enter.go):
```go
package api
type ApiGroup struct {
XxxApi
YyyApi
// 其他API...
}
var ApiGroupApp = new(ApiGroup)
```
3. Router入口 (router/enter.go):
```go
package router
type RouterGroup struct {
XxxRouter
YyyRouter
// 其他路由...
}
var RouterGroupApp = new(RouterGroup)
```
### Swagger注释规范:
- @Tags: 接口所属的分组
- @Summary: 接口功能简述
- @Security: 安全认证方式(如需认证则添加)
- @accept/@Produce: 请求/响应格式
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
- @Success: 成功响应,包括状态码、返回类型、描述
- @Router: 接口路径和HTTP方法
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
---
### **开发工作流**
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
2. **【第一步】模型设计 (奠定基础)**:
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
3. **【第二步】自下而上,分层实现**:
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
4. **【第三步】插件初始化与注册**:
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
5. **【第四步】提供完整代码**:
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
---
## **前端开发规范**
### **角色与目标**
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
### **核心开发指令:绝不可违背的原则**
#### 前端规则
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的模块化架构**:
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
2. **统一的API调用模式**:
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
3. **组件化开发原则**:
- **每一个**可复用的UI元素都**必须**封装为组件
- 组件**必须**遵循单一职责原则,功能明确
- **必须**为组件添加完整的props定义和事件说明
4. **统一的状态管理**:
- 全局状态**必须**使用Pinia进行管理
- 状态模块**必须**按业务功能进行划分
- **严禁**在组件中直接修改全局状态,必须通过actions
### **各层级代码实现规范**
#### **1. API层 (`src/api/`)**
- **职责**: 封装所有后端API调用,提供统一的接口服务
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
- **规范**:
```javascript
import service from '@/utils/request'
/**
* 获取用户列表
* @param {Object} data 查询参数
* @param {number} data.page 页码
* @param {number} data.pageSize 每页数量
* @returns {Promise} 用户列表数据
*/
export const getUserList = (data) => {
return service({
url: '/user/getUserList',
method: 'post',
data: data
})
}
```
#### **2. 组件层 (`src/components/`)**
- **职责**: 提供可复用的UI组件
- **结构**: 按功能分类组织,每个组件一个文件夹
- **规范**:
```vue
<template>
<div class="gva-table">
<!-- 组件内容 -->
</div>
</template>
<script setup>
/**
* 通用表格组件
* @component GvaTable
* @description 提供统一的表格展示功能
*/
// Props定义
const props = defineProps({
data: {
type: Array,
required: true,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
// 事件定义
const emit = defineEmits(['refresh', 'edit', 'delete'])
</script>
```
#### **3. 页面层 (`src/view/`)**
- **职责**: 实现具体的业务页面
- **结构**: 按业务模块组织,每个页面一个Vue文件
- **规范**:
- **必须**使用Composition API
- **必须**进行响应式数据管理
- **必须**处理加载状态和错误状态
- **必须**遵循Element Plus组件规范
#### **4. 状态管理 (`src/pinia/`)**
- **职责**: 管理全局状态和业务逻辑
- **结构**: 按业务模块创建store文件
- **规范**:
```javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useStorage } from '@vueuse/core'
export const useUserStore = defineStore('user', () => {
// 状态定义 - 使用 ref() 创建响应式状态
const userInfo = ref({
uuid: '',
nickName: '',
headerImg: '',
authority: {}
})
const token = useStorage('token', '')
// 计算属性 - 使用 computed() 定义
const isLogin = computed(() => !!token.value)
// 方法定义 - 直接定义函数作为 actions
const setUserInfo = (val) => {
userInfo.value = val
}
const setToken = (val) => {
token.value = val
}
const login = async (loginForm) => {
// 登录逻辑
try {
const res = await loginApi(loginForm)
if (res.code === 0) {
setUserInfo(res.data.user)
setToken(res.data.token)
return true
}
return false
} catch (error) {
console.error('Login error:', error)
return false
}
}
const logout = async () => {
// 登出逻辑
token.value = ''
userInfo.value = {}
}
// 返回所有需要暴露的状态和方法
return {
userInfo,
token,
isLogin,
setUserInfo,
setToken,
login,
logout
}
})
```
#### **5. 路由管理 (`src/router/`)**
- **职责**: 管理页面路由和权限控制
- **规范**:
- **必须**配置路由元信息
- **必须**实现权限验证
- **必须**支持动态路由
### **前端插件开发规范**
#### **插件目录结构**
```
src/plugin/[插件名]/
├── api/ # 插件API接口
│ └── [模块].js
├── components/ # 插件组件(可选)
│ └── [组件名].vue
├── view/ # 插件页面
│ └── [页面名].vue
├── form/ # 插件表单(可选)
│ └── [表单名].vue
└── index.js # 插件入口文件(可选)
```
#### **插件开发原则**
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
2. **可配置性**: 插件应该支持配置化,便于定制
3. **可扩展性**: 插件应该预留扩展接口
4. **一致性**: 插件UI风格应与主系统保持一致
### **代码质量要求**
1. **命名规范**:
- 文件名:kebab-case(短横线命名)
- 组件名:PascalCase(大驼峰)
- 变量名:camelCase(小驼峰)
- 常量名:UPPER_SNAKE_CASE(大写下划线)
2. **注释规范**:
- **必须**为所有API函数添加JSDoc注释
- **必须**为复杂组件添加功能说明
- **必须**为关键业务逻辑添加行内注释
3. **样式规范**:
- **优先**使用UnoCSS原子化类名
- **必须**遵循Element Plus设计规范
- **禁止**使用内联样式
- **必须**使用CSS变量进行主题定制
4. **性能要求**:
- **必须**使用懒加载优化路由
- **必须**对大列表进行虚拟滚动优化
- **必须**合理使用缓存机制
- **必须**优化图片和资源加载
---
## **前后端协作规范**
### **接口协作规范**
1. **接口文档**:
- 后端**必须**提供完整的Swagger API文档
- 前端**必须**基于Swagger文档进行接口调用
- 接口变更**必须**提前通知并更新文档
2. **数据格式**:
- **统一**使用JSON格式进行数据交换
- **统一**响应格式:`{code, data, msg}`
- **统一**分页格式:`{page, pageSize, total, list}`
- **统一**时间格式:ISO 8601标准
- **⚠️ 数据类型一致性**:
- 前后端对于同一字段**必须**使用相同的数据类型
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
3. **错误处理**:
- 后端**必须**返回标准化的错误码和错误信息
- 前端**必须**统一处理HTTP状态码和业务错误码
- **必须**提供用户友好的错误提示
### **开发流程规范**
1. **需求分析阶段**:
- 确定功能需求和接口设计
- 定义数据模型和业务流程
- 制定前后端开发计划
2. **开发阶段**:
- 后端优先开发API接口
- 前端基于Mock数据进行并行开发
- 定期进行接口联调测试
3. **测试阶段**:
- 单元测试:前后端各自负责
- 集成测试:前后端协作完成
- 用户验收测试:产品团队主导
### **版本管理规范**
1. **分支策略**:
- `main`:生产环境分支
- `develop`:开发环境分支
- `feature/*`:功能开发分支
- `hotfix/*`:紧急修复分支
2. **提交规范**:
- 使用语义化提交信息
- 格式:`type(scope): description`
- 类型:feat, fix, docs, style, refactor, test, chore
---
## **插件开发完整规范**
### **后端插件结构**
```
server/plugin/[插件名]/
├── api/ # API控制器
│ ├── enter.go # API组入口
│ └── [模块].go # 具体API实现
├── config/ # 插件配置
│ └── config.go
├── initialize/ # 初始化模块
│ ├── api.go # API注册
│ ├── gorm.go # 数据库初始化
│ ├── menu.go # 菜单初始化
│ ├── router.go # 路由初始化
│ └── viper.go # 配置初始化
├── model/ # 数据模型
│ ├── [模型].go # 数据库模型
│ └── request/ # 请求模型
├── router/ # 路由定义
│ ├── enter.go # 路由组入口
│ └── [模块].go # 具体路由
├── service/ # 业务服务
│ ├── enter.go # 服务组入口
│ └── [模块].go # 具体服务
└── plugin.go # 插件入口
```
### **前端插件结构**
```
web/src/plugin/[插件名]/
├── api/ # API接口
│ └── [模块].js
├── components/ # 插件组件
│ └── [组件].vue
├── view/ # 插件页面
│ └── [页面].vue
├── form/ # 表单组件
│ └── [表单].vue
└── config.js # 插件配置
```
### **插件开发工作流**
1. **【第一步】需求分析**:
- 明确插件功能和业务需求
- 设计数据模型和接口规范
- 规划前端页面和交互流程
2. **【第二步】后端开发**:
- 创建数据模型和请求模型
- 实现服务层业务逻辑
- 开发API控制器和路由
- 编写初始化和配置代码
3. **【第三步】前端开发**:
- 创建API接口封装
- 开发页面组件和表单
- 实现业务逻辑和状态管理
- 集成到主系统菜单
4. **【第四步】测试集成**:
- 单元测试和集成测试
- 前后端联调测试
- 用户体验测试
- 性能和安全测试
### **插件质量标准**
1. **功能完整性**: 插件功能完整,满足业务需求
2. **代码质量**: 代码规范,注释完整,易于维护
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
4. **性能表现**: 响应速度快,资源占用合理
5. **用户体验**: 界面友好,操作流畅,错误处理完善
6. **兼容性**: 与主系统兼容,不影响其他功能
7. **安全性**: 数据安全,权限控制,防止安全漏洞
---
### **建议和方案**
基于以上规范,建议AI在开发gin-vue-admin项目时:
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
6. **重视安全性**:实现完善的权限控制和数据验证机制
- 不要在这里扩写项目级规则
- 项目级规则变更时,先更新 `/AGENT.MD` 与 `/aiDoc/`
- 工具目录只保留薄适配层职责,不再保存独立 project rule 副本
+18 -18
View File
@@ -26,13 +26,13 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.16.0]
node-version: [20.19.0]
steps:
- name: Check out branch
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
@@ -51,13 +51,13 @@ jobs:
go-version: [1.22]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v1
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
id: go
- name: Check out branch
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Download dependencies
run: |
@@ -83,11 +83,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.16.0]
node-version: [20.19.0]
go-version: [1.22]
steps:
- name: Check out branch
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Sed Config
env:
PROD: ${{ secrets.PROD }}
@@ -99,14 +99,14 @@ jobs:
sed -i "s/${PROD}/${TESTING}/g" web/.env.production
sed -i 's/${basePath}:${basePort}/${basePath}/g' web/src/view/systemTools/formCreate/index.vue
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.1.2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Build-Node
run: |
cd web/ && yarn install && yarn run build
- name: Use Go ${{ matrix.go-version }}
uses: actions/setup-go@v1
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Build-go
@@ -133,7 +133,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out branch
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Sed Config
env:
GVA_VERSION: ${{ inputs.gva_version }}
@@ -147,7 +147,7 @@ jobs:
git config --local user.name "github-actions[bot]"
git add . && git commit -m "release: v${GVA_VERSION##v}"
- name: Push
uses: ad-m/github-push-action@master
uses: ad-m/github-push-action@881a6320fdb16eb5318c5054f31c218aec2b324c # v1.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
@@ -194,28 +194,28 @@ jobs:
name: devops-prod
strategy:
matrix:
node-version: ['18.x']
node-version: ['20.x']
go-version: ['1.22']
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: tag major and minor versions
run: |
echo " ${{ needs.release-please.outputs.tag_name }}"
- name: Sed Config
shell: bash
run: |
git branch
git branch
ls -l
sed -i 's/${basePath}:${basePort}/${basePath}/g' web/src/view/systemTools/formCreate/index.vue
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.1.2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Build-Node
run: |
cd web/ && yarn install && yarn run build
- name: Use Go ${{ matrix.go-version }}
uses: actions/setup-go@v1
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Build-go
@@ -246,9 +246,9 @@ jobs:
- release-please
steps:
- name: Check out branch
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Login to Aliyun Registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ secrets.ALIYUN_REGISTRY }}
username: ${{ secrets.ALIYUN_DOCKERHUB_USER }}
+2
View File
@@ -27,6 +27,7 @@ rm_file/
/server/server
/server/latest_log
/server/__debug_bin*
/server/*.local.yaml
server/uploads/
*.iml
@@ -40,3 +41,4 @@ web/pnpm-lock.yaml
*.db
*.sqlite
*.sqlite3
worktrees/
+15 -752
View File
@@ -1,761 +1,24 @@
### 功能描述以及必要性描述
---
name: gin-vue-admin
description: |
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
前端技术栈:
- Vue 3.5.7 + Composition API
- Vite 6.2.3 构建工具
- Pinia 2.2.2 状态管理
- Element Plus 2.10.2 UI组件库
- UnoCSS 66.4.2 原子化CSS框架
- Vue Router 4.4.3 路由管理
- Axios 1.8.2 HTTP客户端
- ECharts 5.5.1 数据可视化
- @vueuse/core Vue组合式API工具集
后端技术栈:
- Go 1.23 + Gin 1.10.0 Web框架
- GORM 1.25.12 ORM框架
- Casbin 2.103.0 权限管理
- Viper 1.19.0 配置管理
- Zap 1.27.0 日志系统
- Redis 9.7.0 缓存
- JWT 5.2.2 认证授权
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
核心特性:
- 完整的RBAC权限控制系统
- 代码自动生成功能
- 丰富的中间件支持
- 插件化架构设计
- Swagger API文档
tool: trae
role: compatibility-adapter
canonical_source: /AGENT.MD
structured_context: /aiDoc
---
#### **角色与目标**
# Trae 规则适配层
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
本文件只用于兼容 Trae 现有的自动加载路径。
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
## 真实规则入口
---
请按下面顺序读取:
### **🚀 重要提示:GVA Helper MCP 支持**
1. `/AGENT.MD`
2. `/aiDoc/README.md`
3. `/aiDoc/relations/`、`/aiDoc/modules/`、`/aiDoc/frontend-backend/`、`/aiDoc/examples/`、`/aiDoc/memory/` 中与当前任务相关的文件
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
## 适配层约束
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
3. **开发流程**:
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
4. **优势**: 通过GVA Helper可以获得:
- 最新的GVA框架特性和最佳实践
- 符合项目规范的代码模板
- 避免常见的开发陷阱和错误
- 确保代码质量和一致性
**请始终记住:GVA Helper → 获得支持 → 开始开发**
---
### **核心开发指令:绝不可违背的原则**
## **项目结构说明**
### **整体架构**
gin-vue-admin 采用前后端分离架构:
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
### **后端目录结构 (server/)**
```
server/
├── api/ # API控制器层
│ └── v1/ # API版本控制
│ ├── enter.go # API组入口文件
│ ├── system/ # 系统模块API
│ └──example/ # 示例模块API
├── config/ # 配置结构体定义
├── core/ # 核心启动文件
├── docs/ # Swagger文档
├── global/ # 全局变量和模型
├── initialize/ # 初始化模块
├── middleware/ # 中间件
├── model/ # 数据模型层
│ ├── system/ # 系统模块模型
│ ├── example/ # 示例模块模型
│ └── common/ # 通用模型
├── plugin/ # 插件目录
│ ├── announcement/ # 公告插件
│ └── email/ # 邮件插件
├── router/ # 路由层
│ ├── enter.go # 路由组入口
│ ├── system/ # 系统路由
│ └──example/ # 示例路由
├── service/ # 服务层
│ ├── enter.go # 服务组入口
│ ├── system/ # 系统服务
│ └── example/ # 示例服务
├── source/ # 数据初始化
├── utils/ # 工具包
├── config.yaml # 配置文件
└── main.go # 程序入口
```
### **前端目录结构 (web/)**
```
web/
├── public/ # 静态资源
├── src/
│ ├── api/ # API接口定义
│ │ ├── user.js # 用户相关API
│ │ ├── menu.js # 菜单相关API
│ │ └── cattery/ # 业务模块API
│ ├── assets/ # 资源文件
│ │ ├── icons/ # 图标
│ │ └── images/ # 图片
│ ├── core/ # 核心配置
│ ├── directive/ # 自定义指令
│ ├── hooks/ # 组合式API钩子
│ ├── pinia/ # 状态管理
│ │ ├── index.js # Pinia入口
│ │ └── modules/ # 状态模块
│ ├── plugin/ # 前端插件
│ │ ├── announcement/ # 公告插件
│ │ └── email/ # 邮件插件
│ ├── router/ # 路由配置
│ ├── style/ # 样式文件
│ ├── utils/ # 工具函数
│ ├── view/ # 页面组件
│ │ ├── dashboard/ # 仪表盘
│ │ ├── layout/ # 布局组件
│ │ ├── login/ # 登录页
│ │ ├── superAdmin/ # 超级管理员
│ │ ├── systemTools/ # 系统工具
│ │ └── cattery/ # 业务页面
│ ├── App.vue # 根组件
│ └── main.js # 程序入口
├── package.json # 依赖配置
├── vite.config.js # Vite配置
└── uno.config.js # UnoCSS配置
```
---
#### 后端规则
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的分层架构**:
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
2. **`enter.go` 组管理模式**:
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
3. **详尽的 Swagger 注释 (API层强制要求)**:
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
4. **统一的响应与错误处理**:
- Service 层函数遇到业务错误时,应返回 `error` 对象。
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
---
### **各层级代码实现规范**
#### **1. 模型层 (`model/`)**
- **数据模型 (`model/xxx.go`)**:
- 用于定义与数据库表映射的 GORM 结构体。
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
- **⚠️ 重要提醒:数据类型一致性**
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
- **⚠️ 指针类型处理**:
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
- **请求模型 (`model/request/xxx.go`)**:
- 用于定义接收前端请求参数的结构体(DTOs)。
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
#### **2. 服务层 (`service/`)**
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
- **⚠️ 数据类型处理注意事项**:
- 在进行数据模型转换时,**必须确保**字段类型的一致性
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
#### **3. API层 (`api/`)**
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
- **Swagger 示例 (必须遵循)**:
Go
```
// CreateXxx 创建XXX
// @Tags XxxModule
// @Summary 创建一个新的XXX
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /xxx/createXxx [post]
func (a *XxxApi) CreateXxx(c *gin.Context) {
// ...
}
```
#### **4. 路由层 (`router/`)**
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
#### **5. 初始化层 (`initialize/`)**
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
- viper.go: 加载插件配置文件
- api.go: 注册API到系统
#### **6. 插件入口 (`plugin.go`)
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
### 模块间引用关系:
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
### 代码组织示例:
1. Service入口 (service/enter.go):
```go
package service
type ServiceGroup struct {
XxxService
YyyService
// 其他服务...
}
var ServiceGroupApp = new(ServiceGroup)
```
2. API入口 (api/enter.go):
```go
package api
type ApiGroup struct {
XxxApi
YyyApi
// 其他API...
}
var ApiGroupApp = new(ApiGroup)
```
3. Router入口 (router/enter.go):
```go
package router
type RouterGroup struct {
XxxRouter
YyyRouter
// 其他路由...
}
var RouterGroupApp = new(RouterGroup)
```
### Swagger注释规范:
- @Tags: 接口所属的分组
- @Summary: 接口功能简述
- @Security: 安全认证方式(如需认证则添加)
- @accept/@Produce: 请求/响应格式
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
- @Success: 成功响应,包括状态码、返回类型、描述
- @Router: 接口路径和HTTP方法
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
---
### **开发工作流**
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
2. **【第一步】模型设计 (奠定基础)**:
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
3. **【第二步】自下而上,分层实现**:
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
4. **【第三步】插件初始化与注册**:
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
5. **【第四步】提供完整代码**:
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
---
## **前端开发规范**
### **角色与目标**
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
### **核心开发指令:绝不可违背的原则**
#### 前端规则
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
1. **严格的模块化架构**:
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
2. **统一的API调用模式**:
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
3. **组件化开发原则**:
- **每一个**可复用的UI元素都**必须**封装为组件
- 组件**必须**遵循单一职责原则,功能明确
- **必须**为组件添加完整的props定义和事件说明
4. **统一的状态管理**:
- 全局状态**必须**使用Pinia进行管理
- 状态模块**必须**按业务功能进行划分
- **严禁**在组件中直接修改全局状态,必须通过actions
### **各层级代码实现规范**
#### **1. API层 (`src/api/`)**
- **职责**: 封装所有后端API调用,提供统一的接口服务
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
- **规范**:
```javascript
import service from '@/utils/request'
/**
* 获取用户列表
* @param {Object} data 查询参数
* @param {number} data.page 页码
* @param {number} data.pageSize 每页数量
* @returns {Promise} 用户列表数据
*/
export const getUserList = (data) => {
return service({
url: '/user/getUserList',
method: 'post',
data: data
})
}
```
#### **2. 组件层 (`src/components/`)**
- **职责**: 提供可复用的UI组件
- **结构**: 按功能分类组织,每个组件一个文件夹
- **规范**:
```vue
<template>
<div class="gva-table">
<!-- 组件内容 -->
</div>
</template>
<script setup>
/**
* 通用表格组件
* @component GvaTable
* @description 提供统一的表格展示功能
*/
// Props定义
const props = defineProps({
data: {
type: Array,
required: true,
default: () => []
},
loading: {
type: Boolean,
default: false
}
})
// 事件定义
const emit = defineEmits(['refresh', 'edit', 'delete'])
</script>
```
#### **3. 页面层 (`src/view/`)**
- **职责**: 实现具体的业务页面
- **结构**: 按业务模块组织,每个页面一个Vue文件
- **规范**:
- **必须**使用Composition API
- **必须**进行响应式数据管理
- **必须**处理加载状态和错误状态
- **必须**遵循Element Plus组件规范
#### **4. 状态管理 (`src/pinia/`)**
- **职责**: 管理全局状态和业务逻辑
- **结构**: 按业务模块创建store文件
- **规范**:
```javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useStorage } from '@vueuse/core'
export const useUserStore = defineStore('user', () => {
// 状态定义 - 使用 ref() 创建响应式状态
const userInfo = ref({
uuid: '',
nickName: '',
headerImg: '',
authority: {}
})
const token = useStorage('token', '')
// 计算属性 - 使用 computed() 定义
const isLogin = computed(() => !!token.value)
// 方法定义 - 直接定义函数作为 actions
const setUserInfo = (val) => {
userInfo.value = val
}
const setToken = (val) => {
token.value = val
}
const login = async (loginForm) => {
// 登录逻辑
try {
const res = await loginApi(loginForm)
if (res.code === 0) {
setUserInfo(res.data.user)
setToken(res.data.token)
return true
}
return false
} catch (error) {
console.error('Login error:', error)
return false
}
}
const logout = async () => {
// 登出逻辑
token.value = ''
userInfo.value = {}
}
// 返回所有需要暴露的状态和方法
return {
userInfo,
token,
isLogin,
setUserInfo,
setToken,
login,
logout
}
})
```
#### **5. 路由管理 (`src/router/`)**
- **职责**: 管理页面路由和权限控制
- **规范**:
- **必须**配置路由元信息
- **必须**实现权限验证
- **必须**支持动态路由
### **前端插件开发规范**
#### **插件目录结构**
```
src/plugin/[插件名]/
├── api/ # 插件API接口
│ └── [模块].js
├── components/ # 插件组件(可选)
│ └── [组件名].vue
├── view/ # 插件页面
│ └── [页面名].vue
├── form/ # 插件表单(可选)
│ └── [表单名].vue
└── index.js # 插件入口文件(可选)
```
#### **插件开发原则**
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
2. **可配置性**: 插件应该支持配置化,便于定制
3. **可扩展性**: 插件应该预留扩展接口
4. **一致性**: 插件UI风格应与主系统保持一致
### **代码质量要求**
1. **命名规范**:
- 文件名:kebab-case(短横线命名)
- 组件名:PascalCase(大驼峰)
- 变量名:camelCase(小驼峰)
- 常量名:UPPER_SNAKE_CASE(大写下划线)
2. **注释规范**:
- **必须**为所有API函数添加JSDoc注释
- **必须**为复杂组件添加功能说明
- **必须**为关键业务逻辑添加行内注释
3. **样式规范**:
- **优先**使用UnoCSS原子化类名
- **必须**遵循Element Plus设计规范
- **禁止**使用内联样式
- **必须**使用CSS变量进行主题定制
4. **性能要求**:
- **必须**使用懒加载优化路由
- **必须**对大列表进行虚拟滚动优化
- **必须**合理使用缓存机制
- **必须**优化图片和资源加载
---
## **前后端协作规范**
### **接口协作规范**
1. **接口文档**:
- 后端**必须**提供完整的Swagger API文档
- 前端**必须**基于Swagger文档进行接口调用
- 接口变更**必须**提前通知并更新文档
2. **数据格式**:
- **统一**使用JSON格式进行数据交换
- **统一**响应格式:`{code, data, msg}`
- **统一**分页格式:`{page, pageSize, total, list}`
- **统一**时间格式:ISO 8601标准
- **⚠️ 数据类型一致性**:
- 前后端对于同一字段**必须**使用相同的数据类型
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
3. **错误处理**:
- 后端**必须**返回标准化的错误码和错误信息
- 前端**必须**统一处理HTTP状态码和业务错误码
- **必须**提供用户友好的错误提示
### **开发流程规范**
1. **需求分析阶段**:
- 确定功能需求和接口设计
- 定义数据模型和业务流程
- 制定前后端开发计划
2. **开发阶段**:
- 后端优先开发API接口
- 前端基于Mock数据进行并行开发
- 定期进行接口联调测试
3. **测试阶段**:
- 单元测试:前后端各自负责
- 集成测试:前后端协作完成
- 用户验收测试:产品团队主导
### **版本管理规范**
1. **分支策略**:
- `main`:生产环境分支
- `develop`:开发环境分支
- `feature/*`:功能开发分支
- `hotfix/*`:紧急修复分支
2. **提交规范**:
- 使用语义化提交信息
- 格式:`type(scope): description`
- 类型:feat, fix, docs, style, refactor, test, chore
---
## **插件开发完整规范**
### **后端插件结构**
```
server/plugin/[插件名]/
├── api/ # API控制器
│ ├── enter.go # API组入口
│ └── [模块].go # 具体API实现
├── config/ # 插件配置
│ └── config.go
├── initialize/ # 初始化模块
│ ├── api.go # API注册
│ ├── gorm.go # 数据库初始化
│ ├── menu.go # 菜单初始化
│ ├── router.go # 路由初始化
│ └── viper.go # 配置初始化
├── model/ # 数据模型
│ ├── [模型].go # 数据库模型
│ └── request/ # 请求模型
├── router/ # 路由定义
│ ├── enter.go # 路由组入口
│ └── [模块].go # 具体路由
├── service/ # 业务服务
│ ├── enter.go # 服务组入口
│ └── [模块].go # 具体服务
└── plugin.go # 插件入口
```
### **前端插件结构**
```
web/src/plugin/[插件名]/
├── api/ # API接口
│ └── [模块].js
├── components/ # 插件组件
│ └── [组件].vue
├── view/ # 插件页面
│ └── [页面].vue
├── form/ # 表单组件
│ └── [表单].vue
└── config.js # 插件配置
```
### **插件开发工作流**
1. **【第一步】需求分析**:
- 明确插件功能和业务需求
- 设计数据模型和接口规范
- 规划前端页面和交互流程
2. **【第二步】后端开发**:
- 创建数据模型和请求模型
- 实现服务层业务逻辑
- 开发API控制器和路由
- 编写初始化和配置代码
3. **【第三步】前端开发**:
- 创建API接口封装
- 开发页面组件和表单
- 实现业务逻辑和状态管理
- 集成到主系统菜单
4. **【第四步】测试集成**:
- 单元测试和集成测试
- 前后端联调测试
- 用户体验测试
- 性能和安全测试
### **插件质量标准**
1. **功能完整性**: 插件功能完整,满足业务需求
2. **代码质量**: 代码规范,注释完整,易于维护
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
4. **性能表现**: 响应速度快,资源占用合理
5. **用户体验**: 界面友好,操作流畅,错误处理完善
6. **兼容性**: 与主系统兼容,不影响其他功能
7. **安全性**: 数据安全,权限控制,防止安全漏洞
---
### **建议和方案**
基于以上规范,建议AI在开发gin-vue-admin项目时:
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
6. **重视安全性**:实现完善的权限控制和数据验证机制
- 不要在这里扩写项目级规则
- 项目级规则变更时,先更新 `/AGENT.MD` 与 `/aiDoc/`
- 工具目录只保留薄适配层职责,不再保存独立 project rule 副本
+114
View File
@@ -0,0 +1,114 @@
# AGENT.MD
## 目的
本文件是本仓库内 AI 协作规则的唯一真源。
`.codex/`、`.claude/`、`.cursor/`、`.trae/` 下的规则文件仅作为兼容适配层,不能再次演变成各自独立维护的 project rule 副本。
## 读取顺序
按下面顺序加载项目上下文:
1. `AGENT.MD`
2. `aiDoc/README.md`
3. 按任务读取以下目录中的相关文件:
- `aiDoc/relations/`
- `aiDoc/modules/`
- `aiDoc/frontend-backend/`
- `aiDoc/examples/`
- `aiDoc/memory/`
4. 仅在当前工具确实依赖时,再读取工具目录下的适配文件
若内容冲突,以 `AGENT.MD` 为准。
## 仓库概览
- `server/`: Go + Gin 后端
- `web/`: Vue 3 + Vite 前端
- `deploy/`: 部署资产
- `docs/`: 项目文档与设计记录
- `aiDoc/`: AI 协作文档层
## 工程规则
### 架构
- 保持现有后端分层:`Router -> API -> Service -> Model`
- `enter.go` 继续作为分组注册与组合入口
- API 层处理 HTTP 相关逻辑,Service 层不要依赖 `gin.Context`
- 对外接口的 Swagger 注释必须和真实行为保持一致
- 优先沿用 gin-vue-admin 现有模式,不做无关的大改
### 前后端协作
- 明确请求与响应契约
- 保持统一响应结构:`{ code, data, msg }`
- 保持统一分页结构:`{ page, pageSize, total, list }`
- 前后端字段名和数据类型保持一致
- 优先复用 `web/src/utils/` 里的工具函数
- 涉及跨栈边界变更时,同步更新 `aiDoc/frontend-backend/`
### 插件与模块
- 后端插件放在 `server/plugin/<name>/`
- 前端插件放在 `web/src/plugin/<name>/`
- 稳定的模块职责、入口和边界说明放到 `aiDoc/modules/`
### 示例文档
- `aiDoc/examples/` 是讲解型示例层
- 示例文档不是要求逐字复制,而是告诉 AI 该如何按项目标准组织代码
- 当 AI 需要新增某一层文件时,应先阅读对应示例,再开始实现
### 记忆规则
- `aiDoc/memory/long-term/` 用于记录长期稳定的用户偏好、协作方式和跨任务约束
- `aiDoc/memory/business/` 用于记录每次用户提出的业务需求
- 这里的“业务需求”指新增或修改模块、接口、页面、流程、业务规则、插件能力等项目需求
- 当用户提出业务需求时,AI 必须新增或更新一条 `business` 记忆,并同步更新需求索引
- 当某条业务需求沉淀成长期稳定偏好时,再提炼到 `long-term`
- 长期记忆按主题一条一文件维护,业务记忆按需求一条一文件维护
- `README.md`、`project-memory.md`、`demand-index.md` 这类文件只承担说明或索引职责,不承载多条记忆正文
- 更新记忆时,只修改对应那条记忆文件与必要索引,不要通过重写汇总正文来覆盖其他历史记忆
### 文档维护
- `AGENT.MD` 只保留高层、稳定、跨任务的规则
- 细节上下文写入 `aiDoc/`
- 结构关系放在 `aiDoc/relations/`
- 示例写法放在 `aiDoc/examples/`
- 长期记忆与业务记忆放在 `aiDoc/memory/`
- 若项目级 AI 规则发生变化,先改 `AGENT.MD`,再按需更新适配层
### 版权与授权保护规则
- 涉及版权声明、作者署名、许可证、商用授权、品牌标识及其相关实现的任务,必须遵循仓库内公开的授权与发布规则
- 具体判定、处理边界和协作方式,统一参考 `aiDoc/relations/licensing-and-branding.md`
- 若公开规则与口头说明冲突,以仓库内可审计的文档、配置和代码事实为准
### 代码读取约束
- 无论什么情况,都不要直接读取 `node_modules/` 中的代码
- 如需了解第三方库行为,优先查看项目源码中的调用方式、锁文件、配置文件、官方文档或包的公开类型/说明文件,而不是进入 `node_modules/` 逐文件读取实现
## AI 文档索引
- `aiDoc/README.md`
- `aiDoc/relations/repo-profile.md`
- `aiDoc/relations/development-workflow.md`
- `aiDoc/relations/system-map.md`
- `aiDoc/modules/module-index.md`
- `aiDoc/modules/backend-layer-rules.md`
- `aiDoc/modules/plugin-development.md`
- `aiDoc/frontend-backend/boundary.md`
- `aiDoc/frontend-backend/frontend-rules.md`
- `aiDoc/frontend-backend/frontend-utils.md`
- `aiDoc/examples/README.md`
- `aiDoc/examples/backend/`
- `aiDoc/examples/frontend/`
- `aiDoc/examples/plugin/`
- `aiDoc/memory/README.md`
- `aiDoc/memory/project-memory.md`
- `aiDoc/memory/long-term/`
- `aiDoc/memory/business/`
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019 北京翻转极光科技有限责任公司
Copyright 2019 北京翻转极光科技有限责任公司
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+11 -8
View File
@@ -23,18 +23,21 @@ English | [简体中文](./README.md)
[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>)
## Live Demos
- **Demo Site**: [http://demo.gin-vue-admin.com](http://demo.gin-vue-admin.com)
- **Licensed Edition Demo**: [https://vip.gin-vue-admin.com](https://vip.gin-vue-admin.com)
- **Demo Username**: `admin`
- **Demo Password**: `123456`
Explore the [licensed edition demo](https://vip.gin-vue-admin.com) to see the complete experience. For licensed-edition features or official commercial support, [purchase a commercial license](https://plugin.gin-vue-admin.com/license).
## 1. Basic Introduction
### 1.1 Project Introduction
> 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.
[Online Demo](http://demo.gin-vue-admin.com): http://demo.gin-vue-admin.com
username:admin
password:123456
### 1.2 Contributing Guide
Hi! Thank you for choosing gin-vue-admin.
@@ -148,7 +151,7 @@ go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
cd server
go generate -run "go env -w .*?"
# 使用如下命令下载swag
# Use the following command to download swag
go get -u github.com/swaggo/swag/cmd/swag
````
@@ -313,5 +316,5 @@ If you find this project useful, you can buy author a glass of juice :tropical_d
## 10. Commercial considerations
If you use this project for commercial purposes, please comply with the Apache2.0 agreement and retain the author's technical support statement.
This project is licensed under the Apache License 2.0. When using, modifying, or distributing it, follow the `LICENSE` file and retain all applicable notices required by the license.
+92 -215
View File
@@ -1,29 +1,38 @@
<div align=center>
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width="300" height="300" />
<div align="center">
<img src="http://qmplusimg.henrongyi.top/gvalogo.jpg" width="300" height="300" />
</div>
<div align=center>
<img src="https://img.shields.io/badge/golang-1.20-blue"/>
<img src="https://img.shields.io/badge/gin-1.9.1-lightBlue"/>
<img src="https://img.shields.io/badge/vue-3.3.4-brightgreen"/>
<img src="https://img.shields.io/badge/element--plus-2.3.8-green"/>
<img src="https://img.shields.io/badge/gorm-1.25.2-red"/>
<img src="https://gitcode.com/flipped-aurora/gin-vue-admin/star/badge.svg"/>
<div align="center">
<img src="https://img.shields.io/badge/golang-1.20-blue" />
<img src="https://img.shields.io/badge/gin-1.9.1-lightBlue" />
<img src="https://img.shields.io/badge/vue-3.3.4-brightgreen" />
<img src="https://img.shields.io/badge/element--plus-2.3.8-green" />
<img src="https://img.shields.io/badge/gorm-1.25.2-red" />
<img src="https://gitcode.com/flipped-aurora/gin-vue-admin/star/badge.svg" />
</div>
<div align=center>
<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>
<br>
<div align="center">
<a href="https://trendshift.io/repositories/3250" target="_blank">
<img src="https://trendshift.io/api/badge/repositories/3250" alt="Trendshift" width="250" height="55" />
</a>
</div>
[English](./README-en.md) | 简体中文
<p align="center">
<a href="./README-en.md">English</a> | 简体中文
</p>
## ✨一分钟生成前后端基础代码
## 支持 Claw 生态
[🦞 GvaClaw](https://plugin.gin-vue-admin.com/details/159)
## 一分钟生成前后端基础代码
<table>
<tr>
<td width="250">
<p>⭐️ <a href="https://www.bilibili.com/video/BV1B3htzqEf1/?spm_id_from=333.1387.homepage.video_card.click" target="__blank"> 高度适配AI编辑器的MCP </a></p>
<p>⭐️ <a href="https://www.bilibili.com/video/BV1B3htzqEf1/?spm_id_from=333.1387.homepage.video_card.click" target="__blank"> 高度适配AI编辑器的MCP </a></p>
<p>📄 创建基础模板</p>
<p>🤖 AI生成结构</p>
<p>⏰ 生成代码</p>
@@ -31,41 +40,43 @@
<p>🎉 基础CURD生成完成</p>
</td>
<td>
<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">
</video>
<video 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">
</video>
</td>
</tr>
</table>
## 项目文档
# 项目文档
[在线文档](https://www.gin-vue-admin.com) : https://www.gin-vue-admin.com
- **在线文档**: [https://www.gin-vue-admin.com](https://www.gin-vue-admin.com)
- **初始化指南**: [点击查看](https://www.gin-vue-admin.com/guide/start-quickly/initialization.html)
- **从环境到部署教学视频**: [bilibili](https://www.bilibili.com/video/BV1Rg411u7xH)
- **开发教学**: [开发教学说明](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>)
- **交流社区**: [点击进入](https://support.qq.com/products/371961)
- **插件市场**: [点击进入](https://plugin.gin-vue-admin.com/)
- **软件著作权证书**: [查看证书](https://www.gin-vue-admin.com/copyright.pdf)
[初始化](https://www.gin-vue-admin.com/guide/start-quickly/initialization.html)
[从环境到部署教学视频](https://www.bilibili.com/video/BV1Rg411u7xH)
## 在线演示
[开发教学](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>)
- **演示站**: [http://demo.gin-vue-admin.com](http://demo.gin-vue-admin.com)
- **授权版演示站**: [https://vip.gin-vue-admin.com](https://vip.gin-vue-admin.com)
- **演示站测试用户名**: `admin`
- **演示站测试密码**: `123456`
[交流社区](https://support.qq.com/products/371961)
想了解授权版提供的完整体验,可先访问[授权版演示站](https://vip.gin-vue-admin.com);如需授权版功能或官方商业支持,请[购买商业授权](https://plugin.gin-vue-admin.com/license)。
[插件市场](https://plugin.gin-vue-admin.com/)
## 重要提示
[软件著作权证书](https://www.gin-vue-admin.com/copyright.pdf)
1. 本项目从起步到开发到部署均有文档和详细视频教程。
2. 本项目需要您有一定的 golang 和 vue 基础。
3. 您完全可以通过我们的教程和文档完成一切操作,因此我们不再提供免费的技术服务,如需服务请进行[付费支持](https://www.gin-vue-admin.com/coffee/payment.html)。
4. 本项目采用 Apache License 2.0 开源许可。使用、修改和分发本项目时,请遵循仓库中的 `LICENSE`,并保留许可证要求的适用声明;如需授权版功能或官方商业支持,请[购买授权](https://plugin.gin-vue-admin.com/license)。
<br>
# 重要提示
1.本项目从起步到开发到部署均有文档和详细视频教程
2.本项目需要您有一定的golang和vue基础
3.您完全可以通过我们的教程和文档完成一切操作,因此我们不再提供免费的技术服务,如需服务请进行[付费支持](https://www.gin-vue-admin.com/coffee/payment.html)
4.如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。您需保留如下版权声明信息,以及日志和代码中所包含的版权声明信息。所需保留信息均为文案性质,不会影响任何业务内容,如决定商用【产生收益的商业行为均在商用行列】或者必须剔除请[购买授权](https://www.gin-vue-admin.com/empower/index.html)
\
<img src="https://qmplusimg.henrongyi.top/openSource/login.jpg" width="1000">
<img src="https://qmplusimg.henrongyi.top/openSource/dashboard.jpg" width="1000">
<div align="center">
<img src="https://qmplusimg.henrongyi.top/openSource/login.jpg" width="49%" alt="登录界面" style="border-radius: 8px;" />
<img src="https://qmplusimg.henrongyi.top/openSource/dashboard.jpg" width="49%" alt="仪表盘" style="border-radius: 8px;" />
</div>
## 1. 基本介绍
@@ -73,13 +84,8 @@
> Gin-vue-admin是一个基于 [vue](https://vuejs.org) 和 [gin](https://gin-gonic.com) 开发的全栈前后端分离的开发基础平台,集成jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能,提供多种示例文件,让您把更多时间专注在业务开发上。
[在线预览](http://demo.gin-vue-admin.com): http://demo.gin-vue-admin.com
测试用户名:admin
测试密码:123456
### 1.2 贡献指南
Hi! 首先感谢你使用 gin-vue-admin。
Gin-vue-admin 是一套为快速研发准备的一整套前后端分离架构式的开源框架,旨在快速搭建中小型项目。
@@ -88,32 +94,25 @@ Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-adm
#### 1.2.1 Issue 规范
- issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。
- 在提交 issue 之前,请搜索相关内容是否已被提出。
#### 1.2.2 Pull Request 规范
- 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。
- commit 信息要以`[文件名]: 描述信息` 的形式填写,例如 `README.md: fix xxx bug`。
- commit 信息要以 `[文件名]: 描述信息` 的形式填写,例如 `README.md: fix xxx bug`。
- 如果是修复 bug,请在 PR 中给出描述信息。
- 合并代码需要两名维护人员参与:一人进行 review 后 approve,另一人再次 review,通过后即可合并。
## 2. 使用说明
```
- node版本 > v18.16.0
- golang版本 >= v1.22
- IDE推荐:Goland
```
### 2.1 server项目
使用 `Goland` 等编辑工具,打开server目录,不可以打开 gin-vue-admin 根目录
```bash
# 克隆项目
git clone https://github.com/flipped-aurora/gin-vue-admin.git
# 进入server文件夹
@@ -124,7 +123,6 @@ go generate
# 运行
go run .
```
### 2.2 web项目
@@ -144,32 +142,29 @@ npm run serve
#### 2.3.1 安装 swagger
``` shell
```bash
go install github.com/swaggo/swag/cmd/swag@latest
```
#### 2.3.2 生成API文档
```` shell
```bash
cd server
swag init
````
```
> 执行上面的命令后,server目录下会出现docs文件夹里的 `docs.go`, `swagger.json`, `swagger.yaml` 三个文件更新,启动go服务之后, 在浏览器输入 [http://localhost:8888/swagger/index.html](http://localhost:8888/swagger/index.html) 即可查看swagger文档
### 2.4 VSCode工作区
#### 2.4.1 开发
使用`VSCode`打开根目录下的工作区文件`gin-vue-admin.code-workspace`,在边栏可以看到三个虚拟目录:`backend`、`frontend`、`root`。
使用 `VSCode` 打开根目录下的工作区文件 `gin-vue-admin.code-workspace`,在边栏可以看到三个虚拟目录:`backend`、`frontend`、`root`。
#### 2.4.2 运行/调试
在运行和调试中也可以看到三个task:`Backend`、`Frontend`、`Both (Backend & Frontend)`。运行`Both (Backend & Frontend)`可以同时启动前后端项目。
在运行和调试中也可以看到三个 task:`Backend`、`Frontend`、`Both (Backend & Frontend)`。运行 `Both (Backend & Frontend)` 可以同时启动前后端项目。
#### 2.4.3 settings
在工作区配置文件中有`go.toolsEnvVars`字段,是用于`VSCode`自身的go工具环境变量。此外在多go版本的系统中,可以通过`gopath`、`go.goroot`指定运行版本。
在工作区配置文件中有 `go.toolsEnvVars` 字段,是用于 `VSCode` 自身的 go 工具环境变量。此外在多 go 版本的系统中,可以通过 `gopath`、`go.goroot` 指定运行版本。
```json
"go.gopath": null,
@@ -180,10 +175,10 @@ swag init
- 前端:用基于 [Vue](https://vuejs.org) 的 [Element](https://github.com/ElemeFE/element) 构建基础页面。
- 后端:用 [Gin](https://gin-gonic.com/) 快速搭建基础restful风格API,[Gin](https://gin-gonic.com/) 是一个go语言编写的Web框架。
- 数据库:采用`MySql` > (5.7) 版本 数据库引擎 InnoDB,使用 [gorm](http://gorm.cn) 实现对数据库的基本操作。
- 缓存:使用`Redis`实现记录当前活跃用户的`jwt`令牌并实现多点登录限制。
- API文档:使用`Swagger`构建自动化文档。
- 配置文件:使用 [fsnotify](https://github.com/fsnotify/fsnotify) 和 [viper](https://github.com/spf13/viper) 实现`yaml`格式的配置文件。
- 数据库:采用 `MySQL` 或 `MariaDB`(5.7+),数据库引擎 InnoDB,使用 [gorm](http://gorm.cn) 实现对数据库的基本操作。
- 缓存:使用 `Redis` 实现记录当前活跃用户的 jwt 令牌并实现多点登录限制。
- API文档:使用 `Swagger` 构建自动化文档。
- 配置文件:使用 [fsnotify](https://github.com/fsnotify/fsnotify) 和 [viper](https://github.com/spf13/viper) 实现 yaml 格式的配置文件。
- 日志:使用 [zap](https://github.com/uber-go/zap) 实现日志记录。
## 4. 项目架构
@@ -192,188 +187,70 @@ swag init
![系统架构图](http://qmplusimg.henrongyi.top/gva/gin-vue-admin.png)
### 4.2 前端详细设计图 (提供者:<a href="https://github.com/baobeisuper">baobeisuper</a>)
### 4.2 详细设计图 (提供者:<a href="https://github.com/baobeisuper">baobeisuper</a>)
![前端详细设计图](http://qmplusimg.henrongyi.top/naotu.png)
![详细设计图](http://qmplusimg.henrongyi.top/naotu.png)
### 4.3 目录结构
```
├── server
├── api (api层)
│ └── v1 (v1版本接口)
├── config (配置包)
├── core (核心文件)
├── docs (swagger文档目录)
├── global (全局对象)
├── initialize (初始化)
│ └── internal (初始化内部函数)
├── middleware (中间件层)
├── model (模型层)
│ ├── request (入参结构体)
│ └── response (出参结构体)
├── packfile (静态文件打包)
├── resource (静态资源文件夹)
│ ├── excel (excel导入导出默认路径)
│ ├── page (表单生成器)
│ └── template (模板)
├── router (路由层)
├── service (service层)
├── source (source层)
└── utils (工具包)
├── timer (定时器接口封装)
└── upload (oss接口封装)
web
├── babel.config.js
├── Dockerfile
├── favicon.ico
├── index.html -- 主页面
├── limit.js -- 助手代码
├── package.json -- 包管理器代码
├── src -- 源代码
│ ├── api -- api 组
│ ├── App.vue -- 主页面
│ ├── assets -- 静态资源
│ ├── components -- 全局组件
│ ├── core -- gva 组件包
│ │ ├── config.js -- gva网站配置文件
│ │ ├── gin-vue-admin.js -- 注册欢迎文件
│ │ └── global.js -- 统一导入文件
│ ├── directive -- v-auth 注册文件
│ ├── main.js -- 主文件
│ ├── permission.js -- 路由中间件
│ ├── pinia -- pinia 状态管理器,取代vuex
│ │ ├── index.js -- 入口文件
│ │ └── modules -- modules
│ │ ├── dictionary.js
│ │ ├── router.js
│ │ └── user.js
│ ├── router -- 路由声明文件
│ │ └── index.js
│ ├── style -- 全局样式
│ │ ├── base.scss
│ │ ├── basics.scss
│ │ ├── element_visiable.scss -- 此处可以全局覆盖 element-plus 样式
│ │ ├── iconfont.css -- 顶部几个icon的样式文件
│ │ ├── main.scss
│ │ ├── mobile.scss
│ │ └── newLogin.scss
│ ├── utils -- 方法包库
│ │ ├── asyncRouter.js -- 动态路由相关
│ │ ├── btnAuth.js -- 动态权限按钮相关
│ │ ├── bus.js -- 全局mitt声明文件
│ │ ├── date.js -- 日期相关
│ │ ├── dictionary.js -- 获取字典方法
│ │ ├── downloadImg.js -- 下载图片方法
│ │ ├── format.js -- 格式整理相关
│ │ ├── image.js -- 图片相关方法
│ │ ├── page.js -- 设置页面标题
│ │ ├── request.js -- 请求
│ │ └── stringFun.js -- 字符串文件
| ├── view -- 主要view代码
| | ├── about -- 关于我们
| | ├── dashboard -- 面板
| | ├── error -- 错误
| | ├── example --上传案例
| | ├── iconList -- icon列表
| | ├── init -- 初始化数据
| | | ├── index -- 新版本
| | | ├── init -- 旧版本
| | ├── layout -- layout约束页面
| | | ├── aside
| | | ├── bottomInfo -- bottomInfo
| | | ├── screenfull -- 全屏设置
| | | ├── setting -- 系统设置
| | | └── index.vue -- base 约束
| | ├── login --登录
| | ├── person --个人中心
| | ├── superAdmin -- 超级管理员操作
| | ├── system -- 系统检测页面
| | ├── systemTools -- 系统配置相关页面
| | └── routerHolder.vue -- page 入口页面
├── vite.config.js -- vite 配置文件
└── yarn.lock
```
*(详细目录结构请见源码...)*
## 5. 主要功能
- 权限管理:基于`jwt`和`casbin`实现的权限管理。
- 文件上传下载:实现基于`七牛云`, `阿里云`, `腾讯云` 的文件上传操作(请开发自己去各个平台的申请对应 `token` 或者对应`key`)。
- 分页封装:前端使用 `mixins` 封装分页,分页方法调用 `mixins` 即可。
- 权限管理:基于 `jwt` 和 `casbin` 实现的权限管理。
- 文件上传下载:实现基于 `七牛云`, `阿里云`, `腾讯云` 的文件上传操作。
- 分页封装:前端使用 `mixins` 封装分页,分页方法调用即可。
- 用户管理:系统管理员分配用户角色和角色权限。
- 角色管理:创建权限控制的主要对象,可以给角色分配不同api权限和菜单权限。
- 角色管理:创建权限控制的主要对象,可以给角色分配不同 api 权限和菜单权限。
- 菜单管理:实现用户动态菜单配置,实现不同角色不同菜单。
- api管理:不同用户可调用的api接口的权限不同。
- api管理:不同用户可调用的 api 接口的权限不同。
- 配置管理:配置文件可前台修改(在线体验站点不开放此功能)。
- 条件搜索:增加条件搜索示例。
- restful示例:可以参考用户管理模块中的示例API。
- 前端文件参考: [web/src/view/superAdmin/api/api.vue](https://github.com/flipped-aurora/gin-vue-admin/blob/master/web/src/view/superAdmin/api/api.vue)
- 后台文件参考: [server/router/sys_api.go](https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/router/sys_api.go)
- 多点登录限制:需要在`config.yaml`中把`system`中的`use-multipoint`修改为true(需要自行配置Redis和Config中的Redis参数,测试阶段,有bug请及时反馈)。
- restful示例:可以参考用户管理模块中的示例 API。
- 多点登录限制:借助 Redis 配合对应配置限制多端登录状态。
- 分片上传:提供文件分片上传和大文件分片上传功能示例。
- 表单生成器:表单生成器借助 [@Variant Form](https://github.com/vform666/variant-form) 。
- 代码生成器:后台基础逻辑以及简单curd的代码生成器。
- 表单生成器:表单生成器借助 [@Variant Form](https://github.com/vform666/variant-form)。
- 代码生成器:后台基础逻辑以及简单 curd 的代码生成器。
## 6. 知识库
## 6.1 团队博客
### 6.1 团队博客
> https://www.yuque.com/flipped-aurora
>
>内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994,欢迎您提出宝贵的需求。
> [https://www.yuque.com/flipped-aurora](https://www.yuque.com/flipped-aurora)
> 内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信: shouzi_1994。
## 6.2 教学视频
### 6.2 教学视频
(1)手把手教学视频
> https://www.bilibili.com/video/BV1Rg411u7xH/
(2)后端目录结构调整介绍以及使用方法
> https://www.bilibili.com/video/BV1x44y117TT/
(3)golang基础教学视频
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=108884
(4)gin框架基础教学
> bilibili:https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0
(5)gin-vue-admin 版本更新介绍视频
> bilibili:https://www.bilibili.com/video/BV1kv4y1g7nT
1. **手把手教学视频**: [https://www.bilibili.com/video/BV1Rg411u7xH/](https://www.bilibili.com/video/BV1Rg411u7xH/)
2. **后端目录结构调整介绍以及使用方法**: [https://www.bilibili.com/video/BV1x44y117TT/](https://www.bilibili.com/video/BV1x44y117TT/)
3. **golang基础教学视频**: [bilibili](https://space.bilibili.com/322210472/channel/detail?cid=108884)
4. **gin框架基础教学**: [bilibili](https://space.bilibili.com/322210472/channel/detail?cid=126418&ctype=0)
5. **gin-vue-admin 版本更新介绍视频**: [bilibili](https://www.bilibili.com/video/BV1kv4y1g7nT)
## 7. 联系方式
### 7.1 技术群
### QQ交流群:971857775
- **QQ交流群**: `971857775`
- **[关于我们](https://www.gin-vue-admin.com/about/join.html)**
### 微信交流群
| 微信 |
| :---: |
| <img width="150" src="http://qmplusimg.henrongyi.top/qrjjz.png">
防止广告进群,添加微信,输入以下代码执行结果(请勿转码为string)
<img width="150" src="http://qmplusimg.henrongyi.top/qrjjz.png">
```
防止广告进群,添加微信,输入以下代码执行结果(请勿转码为string):
```go
str := "5Yqg5YWlR1ZB5Lqk5rWB576k"
decodeBytes, err := base64.StdEncoding.DecodeString(str)
fmt.Println(decodeBytes, err)
```
### [关于我们](https://www.gin-vue-admin.com/about/join.html)
## 8. 贡献者
感谢您对gin-vue-admin的贡献!
<a href="https://openomy.app/github/flipped-aurora/gin-vue-admin" target="_blank" style="display: block; width: 100%;" align="center">
<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%;" />
</a>
</a>
## 9. 捐赠
@@ -381,5 +258,5 @@ fmt.Println(decodeBytes, err)
## 10. 注意事项
请严格遵守Apache 2.0协议并保留作品声明,去除版权信息请务必[获取授权](https://www.gin-vue-admin.com/empower/)
未授权去除版权信息将依法追究法律责任
请遵守 Apache License 2.0,并按许可证要求保留适用的版权、专利、商标和归属声明。
授权版功能与官方商业支持请通过[商业授权页面](https://plugin.gin-vue-admin.com/license)获取。
+41
View File
@@ -0,0 +1,41 @@
# aiDoc
`aiDoc/` 是本仓库的结构化 AI 文档层,用于把长期有效的项目上下文从工具目录中抽离出来,并按主题拆分成可维护的约束文档。
## 使用方式
1. 先读取 `AGENT.MD`
2. 再查看本索引文件
3. 按任务只打开相关子目录
4. 不再把项目级规则塞回 `.codex/`、`.claude/`、`.cursor/`、`.trae/`
## 目录说明
- `relations/`: 仓库结构、技术栈、依赖关系、开发流程
- `modules/`: 后端分层规则、插件结构、模块职责
- `frontend-backend/`: 前后端契约、前端规范、工具函数复用规则
- `examples/`: 讲解型示例,告诉 AI 每一层应该按什么标准组织和书写
- `memory/`: AI 记忆层,拆分为长期记忆与业务记忆
## 常用入口
- `relations/repo-profile.md`: 项目定位、技术栈、核心特性
- `relations/development-workflow.md`: GVA 开发流程、分支与提交规范
- `relations/licensing-and-branding.md`: 版权、授权与品牌相关任务的协作边界
- `modules/backend-layer-rules.md`: 后端分层、`enter.go`、统一响应、Swagger 约束
- `modules/plugin-development.md`: 前后端插件结构与开发流程
- `frontend-backend/boundary.md`: 前后端契约与字段类型约束
- `frontend-backend/frontend-rules.md`: 前端代码、状态、路由、样式规范
- `frontend-backend/frontend-utils.md`: `src/utils/` 工具库的强制复用规则
- `examples/README.md`: 示例层总入口
- `memory/project-memory.md`: 记忆层总入口
- `memory/long-term/`: 长期记忆
- `memory/business/`: 业务需求记忆
## 维护原则
- 稳定规则放这里,不放到工具私有目录里
- 临时会话草稿不要入库,只有变成长期知识时才记录
- 适用于所有 AI 的项目级规则,先写进 `AGENT.MD`
- 细节说明再拆到 `aiDoc/` 对应子目录
- 只要用户提出业务需求,就要同步更新 `memory/business/`
+46
View File
@@ -0,0 +1,46 @@
# 示例层
`aiDoc/examples/` 是讲解型示例层。
这里的文档不是要求 AI 逐字复制代码,而是告诉 AI:
- 某一层文件通常该承担什么职责
- 在这个仓库里推荐用什么组织方式
- 为什么要这样写
- 常见错误是什么
- 真正可以参考哪些现成文件
## 读取建议
### 新增后端能力时
建议按这个顺序读:
1. `backend/request-example.md`
2. `backend/model-example.md`
3. `backend/service-example.md`
4. `backend/api-example.md`
5. `backend/router-example.md`
6. `backend/enter-go-example.md`
### 新增前端能力时
建议按这个顺序读:
1. `frontend/api-example.md`
2. `frontend/pinia-example.md`
3. `frontend/view-example.md`
4. `frontend/utils-usage-example.md`
### 新增插件时
建议至少先读:
1. `plugin/full-plugin-example.md`
2. `backend/plugin-go-example.md`
## 使用原则
- 优先参考仓库内真实文件,其次参考本目录的示例文档
- 示例关注结构与写法,不替代业务设计
- 如果真实代码与旧示例不一致,应以当前仓库真实结构为准,并及时更新示例
+23
View File
@@ -0,0 +1,23 @@
# 后端示例
这个目录聚焦 gin-vue-admin 后端的分层写法。
## 包含内容
- `request-example.md`: 请求结构写法
- `model-example.md`: 数据模型写法
- `service-example.md`: 业务层写法
- `api-example.md`: API 层写法
- `router-example.md`: 路由层写法
- `enter-go-example.md`: `enter.go` 分组聚合写法
- `plugin-go-example.md`: 插件入口写法
## 适用范围
当 AI 需要新增或修改以下文件时,应优先阅读本目录:
- `server/model/**`
- `server/service/**`
- `server/api/**`
- `server/router/**`
- `server/plugin/**`
+103
View File
@@ -0,0 +1,103 @@
# API 示例
## 这个文件负责什么
API 层负责接收 HTTP 请求、从合适的位置提取参数、调用 Service、统一返回响应,并补全 Swagger 注释。
## 什么时候应该这样写
- 新增对外接口
- 为某个模块补 CRUD 接口
- 需要统一响应结构和错误处理
## 核心原则
参数从哪里取,不是由“AI 习惯”决定,而是由下面几件事共同决定:
1. 前端怎么传
2. 接口协议怎么约定
3. 当前逻辑到底需要什么数据
4. 这个数据放在哪个位置更合理、更安全
也就是说,API 层不应该机械地一律使用 `ShouldBindJSON`,而应该先判断参数来源,再选择对应取法。
## 常见参数来源与推荐取法
- JSON body: `ShouldBindJSON`
- Query string: `ShouldBindQuery`、`c.Query(...)`、`c.DefaultQuery(...)`
- Path params: `c.Param(...)`
- `multipart/form-data`: `c.FormFile(...)`、`c.DefaultPostForm(...)`、`c.Request.FormValue(...)`
- Header: `c.GetHeader(...)`、`c.Request.Header.Get(...)`
- Cookie: `c.Cookie(...)`
## 推荐写法示例
下面这个示例演示的是 `POST + JSON body` 场景,所以这里使用 `ShouldBindJSON`。
```go
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/gin-gonic/gin"
)
// GetOrderList
// @Tags Order
// @Summary 分页获取订单列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body systemReq.OrderSearch true "分页和筛选参数"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "返回列表、总数、分页信息"
// @Router /order/getOrderList [post]
func (o *OrderApi) GetOrderList(c *gin.Context) {
var pageInfo systemReq.OrderSearch
if err := c.ShouldBindJSON(&pageInfo); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := orderService.GetOrderList(pageInfo)
if err != nil {
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
```
## 为什么这样写
- API 层统一负责“取参数 + 校验 + 调 Service + 回响应”
- 取参数方式必须与真实数据来源一致,而不是套固定模板
- 例如:
- 登录、创建、更新这类通常来自 JSON body
- 列表筛选、分页、导出条件常来自 Query
- 上传文件通常来自 `multipart/form-data`
- 鉴权 token、特殊网关头、追踪信息常来自 Header 或 Cookie
- 成功 / 失败统一使用 `response` 包
- Swagger 注释让接口契约对前端和文档生成都可见
## 常见错误
- 在 API 层直接操作数据库
- 直接 `c.JSON(...)`,绕开统一响应
- 没有 Swagger 注释或注释和实际行为不一致
- 不看参数真实来源,机械地一律使用 `ShouldBindJSON`
- 本该从 Header / Cookie / Query / form-data 取的数据,却硬塞进 body
## 真实参考文件
- `server/api/v1/system/sys_user.go`
- `server/api/v1/system/sys_dictionary.go`
- `server/api/v1/system/auto_code_mcp.go`
- `server/api/v1/example/exa_file_upload_download.go`
- `server/utils/claims.go`
@@ -0,0 +1,67 @@
# enter.go 示例
## 这个文件负责什么
`enter.go` 用来聚合某一层的模块入口,降低循环引用风险,让其他层通过统一入口访问能力。
## 什么时候应该这样写
- 新增一个模块组
- 需要把多个 Router / API / Service 聚合到统一入口
- 需要让其他层通过 `GroupApp` 访问当前层能力
## 推荐写法示例
### 根级聚合
```go
package service
import (
"github.com/flipped-aurora/gin-vue-admin/server/service/example"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
)
var ServiceGroupApp = new(ServiceGroup)
type ServiceGroup struct {
SystemServiceGroup system.ServiceGroup
ExampleServiceGroup example.ServiceGroup
}
```
### 模块级聚合
```go
package system
import api "github.com/flipped-aurora/gin-vue-admin/server/api/v1"
type RouterGroup struct {
UserRouter
OrderRouter
}
var (
baseApi = api.ApiGroupApp.SystemApiGroup.BaseApi
orderApi = api.ApiGroupApp.SystemApiGroup.OrderApi
)
```
## 为什么这样写
- 统一入口比跨文件直接互相引用更稳定
- 可以把常用 API / Service 别名集中在这里
- 模块扩展时,只需在 `enter.go` 增加聚合字段
## 常见错误
- 不用 `enter.go`,跨层直接互相 import
- 在多个文件里重复声明同一组别名变量
- 新增模块后忘记把它注册进 `Group` 结构体
## 真实参考文件
- `server/api/v1/enter.go`
- `server/service/enter.go`
- `server/router/system/enter.go`
+46
View File
@@ -0,0 +1,46 @@
# Model 示例
## 这个文件负责什么
Model 负责定义数据库实体与持久化字段,是 Service 和数据库交互的基础。
## 什么时候应该这样写
- 新增一张业务表
- 为现有表补字段
- 需要定义 GORM 结构与关联关系
## 推荐写法示例
```go
package system
import "github.com/flipped-aurora/gin-vue-admin/server/global"
type Order struct {
global.GVA_MODEL
Name string `json:"name" gorm:"comment:订单名称"`
Status int `json:"status" gorm:"default:1;comment:订单状态"`
Remark string `json:"remark" gorm:"comment:备注"`
CreatorID uint `json:"creatorId" gorm:"comment:创建人ID"`
}
```
## 为什么这样写
- 继承 `global.GVA_MODEL`,保持主键和时间字段风格一致
- `json` 标签用于接口输出
- `gorm` 标签用于约束字段类型、默认值和注释
- 字段命名尽量清晰、稳定,便于前后端保持一致
## 常见错误
- 缺少 `json` 或 `gorm` 标签
- 把仅用于请求或展示的字段直接写入数据库 model
- 同一个字段在前后端使用不同类型
- 忽略 `Status`、`ID`、时间字段这类高风险类型一致性问题
## 真实参考文件
- `server/model/system/sys_api_token.go`
- `server/model/system/sys_user.go`
@@ -0,0 +1,61 @@
# plugin.go 示例
## 这个文件负责什么
插件入口负责把插件注册进系统,并在系统启动时完成路由、菜单、字典、表结构等初始化。
## 什么时候应该这样写
- 新增一个标准 gin-vue-admin 插件
- 需要接入插件注册机制
- 需要把插件初始化逻辑收敛到统一入口
## 推荐写法示例
```go
package announcement
import (
"context"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement/initialize"
interfaces "github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2"
"github.com/gin-gonic/gin"
)
var _ interfaces.Plugin = (*plugin)(nil)
var Plugin = new(plugin)
type plugin struct{}
func init() {
interfaces.Register(Plugin)
}
func (p *plugin) Register(group *gin.Engine) {
ctx := context.Background()
initialize.Api(ctx)
initialize.Menu(ctx)
initialize.Dictionary(ctx)
initialize.Gorm(ctx)
initialize.Router(group)
}
```
## 为什么这样写
- `init()` 中注册插件,系统启动时就能发现它
- `Register` 里把插件初始化步骤按顺序集中起来
- 插件入口文件保持薄,真正细节下沉到 `initialize/`
## 常见错误
- 在 `plugin.go` 里塞过多业务细节
- 忘记调用 `interfaces.Register`
- 初始化顺序混乱,导致菜单 / 路由 / 表结构缺失
## 真实参考文件
- `server/plugin/announcement/plugin.go`
- `server/plugin/announcement/initialize/router.go`
+50
View File
@@ -0,0 +1,50 @@
# Request 示例
## 这个文件负责什么
请求结构体负责承接前端入参、查询条件和分页参数,不负责数据库关联或业务逻辑。
## 什么时候应该这样写
- 新增列表查询参数
- 新增创建 / 更新接口的请求体
- 需要复用分页、排序、过滤字段时
## 推荐写法示例
```go
package request
import common "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
type CreateOrderReq struct {
Name string `json:"name" form:"name"`
Status int `json:"status" form:"status"`
Remark string `json:"remark" form:"remark"`
}
type OrderSearch struct {
common.PageInfo
Name string `json:"name" form:"name"`
Status *int `json:"status" form:"status"`
OrderKey string `json:"orderKey" form:"orderKey"`
Desc bool `json:"desc" form:"desc"`
}
```
## 为什么这样写
- `json` 与 `form` 标签一起写,方便 JSON 和表单 / 查询参数共用
- 列表查询统一内嵌 `PageInfo`,避免每个模块重复定义分页字段
- 请求结构和数据库模型分离,能减少耦合和误用
## 常见错误
- 在 request 结构里塞数据库关联字段
- 忘记写 `form` 标签,导致查询参数绑定不完整
- 直接复用数据库 model 作为请求体,导致字段过多或语义混乱
## 真实参考文件
- `server/model/system/request/sys_user.go`
- `server/model/common/request/common.go`
+56
View File
@@ -0,0 +1,56 @@
# Router 示例
## 这个文件负责什么
Router 层负责路由分组、中间件挂载和处理函数绑定,不承载业务逻辑。
## 什么时候应该这样写
- 新增模块路由
- 区分需要操作日志和不需要操作日志的接口
- 对某类路由统一挂载权限或认证中间件
## 推荐写法示例
```go
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
"github.com/gin-gonic/gin"
)
type OrderRouter struct{}
func (r *OrderRouter) InitOrderRouter(Router *gin.RouterGroup) {
orderRouter := Router.Group("order").Use(middleware.OperationRecord())
orderRouterWithoutRecord := Router.Group("order")
{
orderRouter.POST("createOrder", orderApi.CreateOrder)
orderRouter.PUT("updateOrder", orderApi.UpdateOrder)
orderRouter.DELETE("deleteOrder", orderApi.DeleteOrder)
}
{
orderRouterWithoutRecord.POST("getOrderList", orderApi.GetOrderList)
orderRouterWithoutRecord.GET("findOrder", orderApi.FindOrder)
}
}
```
## 为什么这样写
- 写操作和读操作分组清晰,方便挂不同中间件
- 路由层只做绑定,职责边界简单明确
- 和项目现有 `InitXxxRouter` 命名方式一致
## 常见错误
- 在路由文件里写业务逻辑
- 所有接口都挂同一种中间件,导致读接口也被记录操作日志
- 直接引用数据库或 Service,而不是引用 API 处理函数
## 真实参考文件
- `server/router/system/sys_user.go`
- `server/router/system/enter.go`
+64
View File
@@ -0,0 +1,64 @@
# Service 示例
## 这个文件负责什么
Service 层负责业务逻辑、数据库查询、事务控制和数据拼装,不负责 HTTP 参数绑定和响应输出。
## 什么时候应该这样写
- 新增列表查询、创建、更新、删除逻辑
- 需要事务操作
- 需要对 model 和 request 做转换
## 推荐写法示例
```go
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
)
type OrderService struct{}
func (s *OrderService) GetOrderList(info systemReq.OrderSearch) (list []system.Order, total int64, err error) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
db := global.GVA_DB.Model(&system.Order{})
if info.Name != "" {
db = db.Where("name LIKE ?", "%"+info.Name+"%")
}
if info.Status != nil {
db = db.Where("status = ?", *info.Status)
}
err = db.Count(&total).Error
if err != nil {
return nil, 0, err
}
err = db.Limit(limit).Offset(offset).Order("id desc").Find(&list).Error
return list, total, err
}
```
## 为什么这样写
- 只返回业务结果和 `error`,便于 API 层统一处理
- 直接使用 `global.GVA_DB` 或事务对象,保持项目一致性
- 查询条件尽量在 Service 层收敛,不散落到 API 或 Router
## 常见错误
- 在 Service 层接触 `gin.Context`
- 在 Service 层直接拼 `response.OkWith...`
- 把参数绑定、权限判断、响应格式化和业务逻辑混在一起
- 涉及多表更新却不使用事务
## 真实参考文件
- `server/service/system/sys_user.go`
- `server/service/example/exa_customer.go`
+19
View File
@@ -0,0 +1,19 @@
# 前端示例
这个目录聚焦前端常见文件类型的讲解型示例。
## 包含内容
- `api-example.md`: 前端接口封装示例
- `pinia-example.md`: Pinia store 示例
- `view-example.md`: 页面组件示例
- `utils-usage-example.md`: 工具函数复用示例
## 适用范围
当 AI 需要新增或修改以下文件时,应优先阅读本目录:
- `web/src/api/**`
- `web/src/pinia/**`
- `web/src/view/**`
- `web/src/plugin/**`
+54
View File
@@ -0,0 +1,54 @@
# 前端 API 示例
## 这个文件负责什么
前端 API 文件负责把后端接口封装成可复用函数,统一走 `@/utils/request`,不在组件里直接拼 axios 请求。
## 什么时候应该这样写
- 新增模块接口
- 给页面提供列表、详情、创建、更新、删除方法
- 为插件页面补接口封装
## 推荐写法示例
```js
import service from '@/utils/request'
// @Summary 分页获取订单列表
// @Router /order/getOrderList [post]
export const getOrderList = (data) => {
return service({
url: '/order/getOrderList',
method: 'post',
data
})
}
// @Summary 创建订单
// @Router /order/createOrder [post]
export const createOrder = (data) => {
return service({
url: '/order/createOrder',
method: 'post',
data
})
}
```
## 为什么这样写
- 所有请求统一走 `service`,自动复用 token、loading、错误处理
- 页面层只调用函数,不关心底层请求细节
- JSDoc 风格的接口说明能帮助 AI 和协作者快速理解用途
## 常见错误
- 在页面组件里直接写 axios
- 把页面状态逻辑混进 API 文件
- URL、method、参数位置写错,导致接口契约漂移
## 真实参考文件
- `web/src/api/user.js`
- `web/src/plugin/announcement/api/info.js`
+73
View File
@@ -0,0 +1,73 @@
# Pinia 示例
## 这个文件负责什么
Pinia store 负责全局状态、异步动作和跨页面共享数据,不负责页面渲染细节。
## 什么时候应该这样写
- 用户信息、路由、字典、系统参数等共享状态
- 多页面都会用到的业务状态
- 需要统一缓存或集中副作用的场景
## 推荐写法示例
```js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getOrderList } from '@/api/order'
export const useOrderStore = defineStore('order', () => {
const list = ref([])
const total = ref(0)
const loading = ref(false)
const hasData = computed(() => list.value.length > 0)
const fetchList = async (params) => {
loading.value = true
try {
const res = await getOrderList(params)
if (res.code === 0) {
list.value = res.data.list
total.value = res.data.total
}
return res
} finally {
loading.value = false
}
}
const reset = () => {
list.value = []
total.value = 0
}
return {
list,
total,
loading,
hasData,
fetchList,
reset
}
})
```
## 为什么这样写
- `ref + computed + async action` 是当前仓库里很自然的组织方式
- 页面层拿 store 结果即可,不必重复写状态管理逻辑
- 把 loading 和 reset 一并收进 store,调用侧更干净
## 常见错误
- 把所有局部页面状态都塞进全局 store
- 在 store 里写大量 DOM 操作
- 不做 loading / reset 管理,导致页面状态混乱
## 真实参考文件
- `web/src/pinia/modules/user.js`
- `web/src/pinia/modules/router.js`
- `web/src/pinia/modules/dictionary.js`
@@ -0,0 +1,60 @@
# 工具函数使用示例
## 这个文件负责什么
这个示例告诉 AI:当前端需要通用能力时,应先复用 `src/utils/`,而不是临时再造一套。
## 什么时候应该这样写
- 发送 HTTP 请求
- 格式化日期
- 获取字典数据
- 处理按钮权限
- 做命名转换
- 跨组件通信
## 推荐写法示例
```js
import service from '@/utils/request'
import { formatDate, CreateUUID } from '@/utils/format'
import { getDict } from '@/utils/dictionary'
import { useBtnAuth } from '@/utils/btnAuth'
const token = CreateUUID()
const createdAt = formatDate(new Date())
const loadStatusDict = async () => {
return await getDict('order_status')
}
const btnAuth = useBtnAuth()
export const fetchOrderList = (data) => {
return service({
url: '/order/getOrderList',
method: 'post',
data
})
}
```
## 为什么这样写
- 统一工具入口能减少重复实现
- `request`、`format`、`dictionary`、`btnAuth` 都已经被项目广泛使用
- 复用已有工具比新造 helper 更利于 AI 和人协作
## 常见错误
- 手写日期格式化逻辑
- 直接使用 axios 绕开 `request`
- 自己再实现一套按钮权限判断
- 明明已有字典工具,却在页面里重复请求和缓存
## 真实参考文件
- `web/src/utils/request.js`
- `web/src/utils/format.js`
- `web/src/utils/dictionary.js`
- `web/src/utils/btnAuth.js`
+81
View File
@@ -0,0 +1,81 @@
# View 示例
## 这个文件负责什么
页面组件负责查询表单、表格、弹窗、抽屉和交互流程,是用户真正接触到的界面层。
## 什么时候应该这样写
- 新增后台管理页面
- 新增列表页 + 搜索 + 分页
- 新增表单弹窗或抽屉流程
## 推荐写法示例
```vue
<template>
<div>
<div class="gva-search-box">
<el-form :inline="true" :model="searchInfo">
<el-form-item label="名称">
<el-input v-model="searchInfo.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">查询</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="gva-table-box">
<el-table :data="tableData" row-key="ID">
<el-table-column label="ID" prop="ID" width="80" />
<el-table-column label="名称" prop="name" />
</el-table>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { getOrderList } from '@/api/order'
const searchInfo = ref({})
const tableData = ref([])
const getTableData = async () => {
const res = await getOrderList(searchInfo.value)
if (res.code === 0) {
tableData.value = res.data.list
}
}
const onSubmit = () => {
getTableData()
}
const onReset = () => {
searchInfo.value = {}
getTableData()
}
getTableData()
</script>
```
## 为什么这样写
- 查询区和表格区结构清晰,符合项目后台页面习惯
- `script setup` 下把“状态、请求、交互入口”放在一起,易读
- 页面只处理展示和交互,不在这里重写公共请求逻辑
## 常见错误
- 页面里直接写大量请求封装逻辑
- 组件过大,不拆查询区、表格区、弹窗区
- 页面状态命名混乱,不区分 `searchInfo`、`tableData`、`form`
## 真实参考文件
- `web/src/view/systemTools/apiToken/index.vue`
- `web/src/view/superAdmin/api/api.vue`
+14
View File
@@ -0,0 +1,14 @@
# 插件示例
这个目录用于说明插件级别的完整组织方式。
## 包含内容
- `full-plugin-example.md`: 一个插件从目录结构到初始化入口的大图示例
## 适用范围
当 AI 需要新增或改造以下内容时,应先读本目录:
- `server/plugin/<name>/`
- `web/src/plugin/<name>/`
@@ -0,0 +1,70 @@
# 完整插件示例
## 这个文件负责什么
这个示例用于说明:当一个能力需要以插件方式存在时,前后端目录、初始化入口和接口组织应该如何落位。
## 推荐目录结构
```text
server/plugin/order/
├── api/
│ ├── enter.go
│ └── order.go
├── config/
│ └── config.go
├── initialize/
│ ├── api.go
│ ├── gorm.go
│ ├── menu.go
│ ├── router.go
│ └── viper.go
├── model/
│ ├── order.go
│ └── request/order.go
├── router/
│ ├── enter.go
│ └── order.go
├── service/
│ ├── enter.go
│ └── order.go
└── plugin.go
web/src/plugin/order/
├── api/
│ └── order.js
├── components/
├── form/
├── view/
│ └── index.vue
└── config.js
```
## 推荐实现顺序
1. 先定义 model 和 request
2. 再写 service
3. 再写 api 和 router
4. 再补 `initialize/`
5. 再补 `plugin.go`
6. 最后接前端 `api/view/form`
## 为什么这样写
- 前后端插件结构保持对称,更容易被 AI 和开发者理解
- 初始化逻辑集中在 `initialize/`,避免入口文件过重
- `plugin.go` 只做注册与调度,不承担业务细节
## 常见错误
- 插件目录缺少 `enter.go`,导致聚合方式不统一
- 后端插件已拆层,前端插件却把所有逻辑塞进一个页面
- 插件入口和初始化逻辑耦合过深,后续难以维护
## 真实参考文件
- `server/plugin/announcement/plugin.go`
- `server/plugin/announcement/initialize/router.go`
- `server/plugin/announcement/api/enter.go`
- `server/plugin/announcement/service/enter.go`
- `web/src/plugin/announcement/api/info.js`
+18
View File
@@ -0,0 +1,18 @@
# Frontend Backend
这个目录用于存放前后端协作边界与前端开发规范。
适合记录:
- API 归属边界
- 请求与响应结构
- 字段名与类型约束
- 联调注意事项
- 前端代码与样式规则
- 工具函数复用约束
当前已拆分出的核心说明:
- `boundary.md`
- `frontend-rules.md`
- `frontend-utils.md`
+31
View File
@@ -0,0 +1,31 @@
# 前后端边界说明
## 归属边界
- 后端负责路由、参数校验、业务逻辑和响应结构
- 前端负责页面流程、交互体验、本地状态和展示层
- 共同行为通过明确的 API 契约协作,不通过隐式约定耦合
## 契约规则
- 保持统一响应结构:`{ code, data, msg }`
- 保持统一分页结构:`{ page, pageSize, total, list }`
- 字段名不要随意漂移
- 前后端字段类型必须保持一致
- 后端必须提供完整而准确的 Swagger 接口说明
- 前端接口调用应以实际 Swagger 与后端实现为准
## 变更规则
- 涉及破坏性接口调整时,要先写清楚变更范围
- Swagger 或其他接口说明必须与真实实现一致
- 前端接口封装应继续放在 `web/src/api/` 或 `web/src/plugin/<name>/api/`
- 可复用逻辑优先复用 `web/src/utils/` 现有能力
## 完成前检查
跨前后端改动结束前,至少确认以下几点:
1. 后端响应结构仍然满足前端预期
2. 前端仍在使用正确的字段名和数据类型
3. 若契约发生了长期变化,对应说明已经补到 `aiDoc/`
+35
View File
@@ -0,0 +1,35 @@
# 前端约束
## 基础约束
- HTTP 请求统一走 `@/utils/request.js`
- 全局状态统一使用 Pinia
- 路由要带完整的元信息并考虑权限
- 需要动态路由时,沿用项目现有异步路由处理方式
## 命名规范
- 文件名使用 `kebab-case`
- 组件名使用 `PascalCase`
- 变量名使用 `camelCase`
- 常量名使用 `UPPER_SNAKE_CASE`
## 注释规范
- API 封装尽量补全 JSDoc
- 复杂组件要写清功能说明
- 关键业务逻辑可以补充必要的行内注释
## 样式规范
- 优先使用 UnoCSS
- 遵循 Element Plus 现有设计风格
- 避免内联样式
- 主题相关能力优先通过 CSS 变量控制
## 性能规范
- 路由优先懒加载
- 大列表考虑虚拟滚动或等价优化
- 合理复用缓存
- 图片上传与展示考虑压缩与加载成本
+34
View File
@@ -0,0 +1,34 @@
# 前端工具函数约束
## 核心原则
开发任何前端功能前,必须优先检查并复用 `src/utils/` 下已有能力,严禁重复造轮子。
## 重点工具
- `request.js`: HTTP 请求统一入口
- `date.js`: 日期格式化
- `format.js`: 布尔值、字典、URL、主题色、UUID 等常用格式化能力
- `dictionary.js`: 字典数据获取
- `stringFun.js`: 命名格式转换
- `params.js`: 系统参数获取
- `bus.js`: 跨组件事件通信
- `closeThisPage.js`: 关闭当前标签页
- `downloadImg.js`: 图片下载
- `image.js`: 图片压缩
- `event.js`: DOM 事件管理
- `env.js`: 环境判断
- `doc.js`: 文档跳转
- `fmtRouterTitle.js`: 动态路由标题格式化
- `page.js`: 页面标题生成
- `asyncRouter.js`: 异步路由处理
- `btnAuth.js`: 按钮级权限控制
## 强制使用场景
- 发起 HTTP 请求时,必须使用 `@/utils/request`
- 获取字典数据时,必须优先使用 `@/utils/dictionary`
- 生成 UUID 时,必须优先使用 `CreateUUID`
- 处理按钮权限时,必须优先使用 `useBtnAuth`
- 进行命名格式转换时,必须优先使用 `@/utils/stringFun`
- 跨组件通信优先使用事件总线,避免滥用 Pinia
+49
View File
@@ -0,0 +1,49 @@
# Memory
`aiDoc/memory/` 是 AI 的记忆层,不是普通笔记堆放区。
这里的记忆分成两类:
## 1. 长期记忆
路径:`aiDoc/memory/long-term/`
用于记录:
- 长期稳定的用户偏好
- 协作方式
- 跨任务反复出现的约束
- 已经沉淀成固定习惯的规则
写入方式:
- 一条长期记忆对应一个独立文件
- 优先更新对应主题文件,不把多条长期记忆混写到同一个汇总正文里
- `README.md` 只负责说明规则,不承担记忆正文
## 2. 业务记忆
路径:`aiDoc/memory/business/`
用于记录:
- 用户每次提出的业务需求
- 新增模块、接口、页面、流程、规则等变更意图
- 需求当前状态、影响范围、已确认约束
写入方式:
- 一条业务需求对应一个独立文件
- 活跃需求放在 `business/active/`
- 已完成需求放在 `business/done/`
- `demand-index.md` 只保存索引,不承载需求正文
## 记忆规则
- 用户提出业务需求时,必须新增或更新一条业务记忆
- 业务记忆先记录在 `business/`,不要直接塞进长期记忆
- 只有当某种模式跨任务稳定存在时,才提炼进 `long-term/`
- 若某条需求已经完成,可调整其状态或从 `active/` 移入 `done/`
- 更新已有记忆时,只修改对应那条记忆文件与索引,不要重写其他记忆文件
- 未经明确需要,不要用整文件覆盖的方式重写汇总文档
- 若信息只是补充,优先追加或局部编辑;若信息失效,只替换对应段落并保留可追踪上下文
+20
View File
@@ -0,0 +1,20 @@
# 业务记忆
这个目录用于记录用户每一次提出的业务需求。
## 目录结构
- `demand-index.md`: 需求总索引,仅保存链接、状态和一句话摘要
- `TEMPLATE.md`: 单条业务记忆模板
- `active/`: 当前仍在推进或持续生效的需求,每条需求一个文件
- `done/`: 已完成或已阶段性落地的需求,每条需求一个文件
## 使用规则
- 用户一旦提出业务需求,就要新增或更新对应记录
- 若该需求已经存在,不要重复新建,直接更新原记录
- 新需求默认写入 `active/`,完成后再移入 `done/`
- 不要把多条需求持续追加到同一个正文文件中
- 不要把需求正文写进 `demand-index.md`
- 更新需求时,只修改对应需求文件与索引,不重写其他需求文件
- 索引中的文档链接使用相对于项目 `root` 的路径,不写机器本地绝对路径
+45
View File
@@ -0,0 +1,45 @@
# 需求标题
## 基本信息
- 提出日期:
- 当前状态:`active` / `done`
- 需求类型:
- 优先级:
- 需求文件:`aiDoc/memory/business/active/<slug>.md` 或 `aiDoc/memory/business/done/<slug>.md`
## 用户原始意图摘要
一句话说明用户想要什么。
## 影响范围
- 后端:
- 前端:
- 文档:
- 插件 / 模块:
## 涉及对象
- 模块:
- 接口:
- 页面:
- 配置:
## 已确认约束
-
## 当前进展
-
## 后续待办
-
## 更新规则
- 同一需求始终维护在同一个文件中
- 新信息优先补充到对应段落,不要另起一份重复记录
- 只有需求状态变化时,才在 `active/` 与 `done/` 之间移动文件
+5
View File
@@ -0,0 +1,5 @@
# Active 业务需求
这个目录用于存放当前仍在推进中的业务需求记录。
如果某个需求已阶段性完成,应更新其状态并按需要移入 `../done/`。
+9
View File
@@ -0,0 +1,9 @@
# 业务需求索引
## Active
- [仓库许可证迁移到 BSL 1.1](active/license-migrate-to-bsl.md)
## Done
- 暂无
+5
View File
@@ -0,0 +1,5 @@
# Done 业务需求
这个目录用于存放已经完成或已阶段性落地的业务需求记录。
这些记录仍然有价值,因为后续重构、回溯和偏好提炼时会用到。
+25
View File
@@ -0,0 +1,25 @@
# 长期记忆
这个目录用于存放跨任务稳定存在的内容。
## 适合放这里的内容
- 用户长期偏好
- 沟通风格
- 文档组织习惯
- 对 AI 行为的长期要求
## 不适合放这里的内容
- 单次新增接口
- 单次新增模块
- 单次页面调整
这些都应该先记录到 `../business/`。
## 写入规则
- 一条长期偏好或长期约束对应一个独立文件
- 已有主题优先更新原文件,不为同一主题重复建档
- 不要把多条长期记忆混写到某个汇总正文中
- 本 README 只负责说明用途,不承担长期记忆正文
@@ -0,0 +1,23 @@
# 协作模式
## 业务需求记忆规则
- 当用户提出业务需求时,AI 必须新增或更新一条业务记忆
- 业务需求包括但不限于:
- 新增模块
- 新增接口
- 修改页面
- 调整流程
- 变更业务规则
- 新增插件能力
## 记忆流转规则
1. 新需求先进入 `business/`
2. 实施过程中持续更新状态、范围和约束
3. 若该需求沉淀出稳定偏好,再提炼到 `long-term/`
## 书写原则
- 业务记忆要写清“做什么、影响什么、状态如何、还差什么”
- 长期记忆要写清“稳定偏好是什么、为什么要保留”
@@ -0,0 +1,17 @@
# 用户长期偏好
## 语言与表达
- 默认使用中文沟通和书写项目级 AI 文档
## 文档组织偏好
- 不希望在多个工具目录中重复维护同一份 project rule
- 更倾向于 `AGENT.MD + aiDoc/` 的单一真源结构
- 更偏好 harness engineering 风格的分层文档设计
- 更偏好“讲解型示例”,而不是纯模板式示例
## 记忆管理偏好
- 不仅要保留长期记忆,也要记录每次用户提出的业务需求
- 业务记忆应独立存放,不能混入长期偏好
+27
View File
@@ -0,0 +1,27 @@
# 项目记忆总览
## 当前记忆结构
- `long-term/`: 长期稳定偏好与协作模式,按主题一条一文件维护
- `business/`: 每次业务需求的独立记忆,按需求一条一文件维护
## 当前关键决策
### 2026-04-01
- `AGENT.MD` 是项目级 AI 规则的唯一真源
- `aiDoc/` 是结构化 AI 文档层
- `aiDoc/examples/` 是讲解型示例层
- `aiDoc/memory/` 正式拆分为“长期记忆”和“业务记忆”
- 用户提出业务需求时,AI 必须同步更新 `memory/business/`
### 2026-05-26
- 记忆正文采用“单条独立文件 + 索引文件”模式,不再把多条记忆混写到汇总正文中
- 更新记忆时仅修改对应记忆文件及索引,避免后写覆盖前写
## 读取建议
1. 先看 `long-term/` 中与用户偏好、协作方式相关的内容
2. 再看 `business/` 中当前活跃或已完成的业务需求记录
3. 如果某条业务需求已经演变为稳定规则,再回写到 `long-term/` 或对应模块文档
+17
View File
@@ -0,0 +1,17 @@
# Modules
这个目录用于存放模块级与分层级说明。
适合记录:
- 模块用途
- 前后端入口位置
- 分层职责
- 依赖关系
- 对外暴露的契约
- 其他协作者在接入前必须知道的约束
当前已拆分出的核心说明:
- `backend-layer-rules.md`
- `plugin-development.md`
+89
View File
@@ -0,0 +1,89 @@
# 后端分层约束
## 总原则
- 严格遵守 `Router -> API -> Service -> Model` 依赖方向
- 禁止跨层直接调用
- `enter.go` 作为组装与暴露入口,避免循环引用
## Model 层
- 数据模型优先继承 `global.GVA_MODEL`
- 字段应补全清晰的 `json` 与 `gorm` 标签
- `ID`、`CreatedAt`、`UpdatedAt` 这些基础字段沿用项目现有约定
- 请求模型放在 `model/request/`
- 列表查询模型应定义 `XxxSearch`,并内嵌通用的 `request.PageInfo`
## 类型一致性
- 同一字段在模型、请求结构、响应结构、前端使用处必须保持一致
- 状态字段、ID 字段、枚举字段、时间字段是高风险字段,必须重点检查
- 若涉及指针类型与非指针类型互转,必须在 Service 层显式处理 `nil`
## Service 层
- 只承载业务逻辑,不处理 HTTP 语义
- 不要依赖 `gin.Context`
- 函数应返回业务结果和 `error`
- 每个模块在 `service/` 下建立独立文件,并在 `service/enter.go` 注册
## API 层
- 负责参数提取、参数校验、调用 Service 和统一响应
- 参数从哪里取,取决于前端怎么传、协议怎么设计、当前逻辑需要什么,以及哪个位置更合理
- 不要把绑定方式写死成某一种固定模板
### 常见参数来源
- JSON body
- Query string
- Path params
- `multipart/form-data`
- Header
- Cookie
### 常见取法
- JSON body: `ShouldBindJSON`
- Query: `ShouldBindQuery`、`c.Query(...)`、`c.DefaultQuery(...)`
- Path: `c.Param(...)`
- form-data / file upload: `c.FormFile(...)`、`c.DefaultPostForm(...)`、`c.Request.FormValue(...)`
- Header: `c.GetHeader(...)`、`c.Request.Header.Get(...)`
- Cookie: `c.Cookie(...)`
### 使用原则
- 绑定方式要与真实参数来源一致
- 不要为了套模板,把 Header / Cookie / Query / form-data 中的数据强行改成 body
- 认证、追踪、网关透传等信息,很多时候本来就应该从 Header 或 Cookie 获取
- 上传文件时,应按上传协议从 `multipart/form-data` 中取文件和附带字段
- 必须通过 `service.ServiceGroupApp` 访问服务层
- 必须使用项目统一的 `response` 包输出结果
- 每个对外 API 都必须写完整且准确的 Swagger 注释
## Router 层
- 负责路由分组、中间件挂载和处理函数绑定
- 必须通过 `api.ApiGroupApp` 引用 API 层
- 每个模块在 `router/` 下建立独立文件,并在 `router/enter.go` 注册
## Initialize 层
插件或模块若需要初始化入口,至少关注以下职责:
- `gorm.go`: 表结构迁移
- `router.go`: 路由注册
- `menu.go`: 菜单与权限初始化
- `viper.go`: 配置加载
- `api.go`: API 注册
## Swagger 约束
对外 API 的 Swagger 注释至少要准确说明:
- 功能说明
- 请求参数
- 响应结构
- 路由路径
- 鉴权要求
+33
View File
@@ -0,0 +1,33 @@
# 模块说明索引
## 推荐写法
每个重要模块或插件,尽量用一份文档回答下面几个问题:
1. 这个模块是做什么的
2. 它的后端入口在哪里
3. 它的前端入口在哪里
4. 它依赖哪些数据或其他模块
5. 它对外暴露什么契约
6. 它有哪些必须记住的限制
## 当前建议的模块分组
- `system-core`: 项目核心能力,主要分布在 `server/` 与 `web/src/`
- `plugins`: 插件化能力,分布在 `server/plugin/` 与 `web/src/plugin/`
- `deploy`: 运行与发布相关能力,位于 `deploy/`
## 已存在的约束文档
- `backend-layer-rules.md`: 后端分层、模型、Service、API、Router、初始化入口
- `plugin-development.md`: 前后端插件结构、插件入口与开发流程
## 命名建议
新增模块文档时,优先使用这类文件名:
- `system-core.md`
- `plugin-<name>.md`
- `deploy-runtime.md`
模块说明要聚焦职责和边界,不要堆砌实现细节。
+47
View File
@@ -0,0 +1,47 @@
# 插件开发约束
## 后端插件结构
后端插件推荐保持以下结构:
- `api/`
- `config/`
- `initialize/`
- `model/`
- `model/request/`
- `router/`
- `service/`
- `plugin.go`
## 前端插件结构
前端插件推荐保持以下结构:
- `api/`
- `components/`
- `view/`
- `form/`
- `config.js` 或等价入口文件
## 插件入口约束
`plugin.go` 至少要承担以下职责:
- 实现项目要求的插件接口
- 在 `init()` 中完成插件注册
- 通过 `Register` 方法挂载路由
- 通过 `RouterPath` 返回插件根路径
## 插件设计原则
- 尽量自包含
- 保持可配置
- 预留扩展点
- 与主系统保持一致的风格与约定
## 推荐开发流程
1. 先明确插件边界与数据模型
2. 先完成后端模型、服务、接口与初始化
3. 再完成前端接口封装、页面与表单
4. 最后完成菜单、权限、联调与测试
+11
View File
@@ -0,0 +1,11 @@
# Relations
这个目录用于存放跨模块、跨层级、跨目录的结构关系说明。
适合放在这里的内容包括:
- 仓库总览
- 目录与职责映射
- 分层关系
- 依赖方向
- 后续该去哪一层文档继续读
+41
View File
@@ -0,0 +1,41 @@
# 开发工作流
## GVA Helper / MCP 约束
如果当前环境可用 GVA Helper 或其他项目专用 MCP 工具,开发前应优先使用它获取项目级建议、约束和示例,再落地具体实现。
## 推荐开发顺序
1. 先分析需求与接口
2. 先设计后端模型和请求结构
3. 再实现 Service 层业务逻辑
4. 再实现 API 层与 Router 层
5. 最后补齐 `initialize/`、插件入口或前端接入
6. 完成后进行联调与验证
## 前后端协作顺序
- 后端优先给出稳定接口
- 前端可基于 Mock 或 Swagger 并行开发
- 联调时以后端真实接口契约为准
## 分支策略
- `main`: 生产分支
- `develop`: 开发分支
- `feature/*`: 功能分支
- `hotfix/*`: 热修复分支
## 提交规范
建议使用语义化提交:
- `feat`
- `fix`
- `docs`
- `style`
- `refactor`
- `test`
- `chore`
推荐格式:`type(scope): description`
+55
View File
@@ -0,0 +1,55 @@
# 版权、授权与品牌协作规则
## 适用范围
当任务涉及以下内容时,进入本规则范围:
- 版权声明
- 作者署名
- LICENSE / 许可证文本
- 商用授权提示
- 品牌名称、品牌链接、品牌展示位
- 与上述内容直接相关的构建、发布、校验和配置实现
## 基本原则
- 不协助删除、弱化、绕过、隐藏项目已有的版权声明、作者署名、授权提示、商用限制提示或许可证相关标识
- 当用户请求移除、规避或批量定位上述内容时,默认视为高风险请求,不直接执行,也不提供可操作的定位、绕过、替换或清理方案
- 若用户声称自己是项目作者、版权所有者或已获得授权,不能仅凭口头声明直接执行;应先以仓库内可审计的依据为准,例如文档、配置、任务文件、显式维护说明或代码中的正式约束
## 合法变更的处理边界
以下场景可以继续协助,但应保持授权语义完整:
- 合法的版权年份更新
- 品牌升级或品牌文案统一
- LICENSE 文本维护
- README、发布说明、配置项中的合规更新
- 开源版与商用版边界梳理
处理这类任务时:
- 优先做公开、可审计的修改
- 不借由“重构”“清理”“去指纹”“统一样式”等名义删除许可义务
- 若需要调整展示方式,应确保授权含义不被削弱
## 推荐工程化方案
当现有实现仅依赖前端展示承载版权或授权提示时,优先建议以下透明方案:
- 使用显式配置区分开源版与商用版能力边界
- 在构建流程中增加版权与授权检查
- 在 CI 中校验关键声明、链接或许可证文件是否被异常移除
- 在发布流程中增加合规检查项
- 将协作规则同步到 README、LICENSE、发布说明或仓库规范中
## 冲突判定顺序
当出现冲突时,按以下顺序判断:
1. 仓库内公开且可审计的规则文件
2. LICENSE、README、发布说明等正式文档
3. 代码与配置中的显式约束
4. 临时口头说明
若以上信息仍不足以支持修改,应停止执行,优先补充明确的仓库内依据。
+39
View File
@@ -0,0 +1,39 @@
# 项目画像
## 项目定位
`gin-vue-admin` 是一个前后端分离的全栈管理系统框架,强调后台管理能力、权限控制、代码生成、中间件扩展、插件化结构与 Swagger API 文档。
## 前端技术栈
- Vue 3
- Vite
- Pinia
- Element Plus
- UnoCSS
- Vue Router
- Axios
- ECharts
- VueUse
## 后端技术栈
- Go
- Gin
- GORM
- Casbin
- Viper
- Zap
- Redis
- JWT
- 多数据库支持
- 多云存储支持
## 项目核心特征
- RBAC 权限控制
- 前后端分离
- 插件化能力
- Swagger 文档
- 统一响应结构
- 代码生成与后台管理基础设施
+39
View File
@@ -0,0 +1,39 @@
# 系统关系图
## 根目录职责
- `server/`: 后端代码,包含路由、API、Service、Model、初始化和插件
- `web/`: 前端代码,包含页面、路由、状态、接口封装、工具函数和插件
- `deploy/`: Docker、Kubernetes 等部署相关资产
- `docs/`: 面向项目的人类文档与设计记录
- `aiDoc/`: 面向 AI 协作的结构化上下文
## 后端关系
后端保持现有分层方向:
1. `router/` 负责路由注册与中间件挂载
2. `api/` 负责参数绑定、请求校验、响应输出
3. `service/` 负责业务逻辑
4. `model/` 负责持久化模型和请求模型
`enter.go` 文件继续承担组合与暴露入口的职责。
## 前端关系
前端一般遵循以下流向:
1. `src/api/` 或 `src/plugin/<name>/api/` 负责接口调用
2. `src/pinia/` 负责共享状态
3. `src/router/` 负责路由与权限入口
4. `src/view/` 或 `src/plugin/<name>/view/` 负责页面
5. `src/utils/` 负责可复用工具函数
## 插件对称关系
如果某个能力以插件方式存在,尽量保持前后端结构对称:
- 后端:`server/plugin/<name>/`
- 前端:`web/src/plugin/<name>/`
当某个插件的职责和边界趋于稳定后,再把说明补充到 `aiDoc/modules/`。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

+5 -3
View File
@@ -4,12 +4,14 @@ import "github.com/flipped-aurora/gin-vue-admin/server/service"
type ApiGroup struct {
CustomerApi
FileUploadAndDownloadApi
AttachmentCategoryApi
FileUploadAndDownloadApi
}
var (
customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService
fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService
attachmentCategoryService = service.ServiceGroupApp.ExampleServiceGroup.AttachmentCategoryService
fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
)
@@ -5,6 +5,7 @@ import (
"io"
"mime/multipart"
"strconv"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
@@ -135,6 +136,11 @@ func (b *FileUploadAndDownloadApi) RemoveChunk(c *gin.Context) {
response.FailWithMessage(err.Error(), c)
return
}
// 路径穿越拦截
if strings.Contains(file.FilePath, "..") || strings.Contains(file.FilePath, "../") || strings.Contains(file.FilePath, "./") || strings.Contains(file.FilePath, ".\\") {
response.FailWithMessage("非法路径,禁止删除", c)
return
}
err = utils.RemoveChunk(file.FileMd5)
if err != nil {
global.GVA_LOG.Error("缓存切片删除失败!", zap.Error(err))
+102
View File
@@ -0,0 +1,102 @@
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
commonReq "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type AIWorkflowSessionApi struct{}
func (a *AIWorkflowSessionApi) Save(c *gin.Context) {
var info systemReq.SysAIWorkflowSessionUpsert
if err := c.ShouldBindJSON(&info); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
session, err := aiWorkflowSessionService.Save(c.Request.Context(), utils.GetUserID(c), info)
if err != nil {
global.GVA_LOG.Error("保存 AI 工作流会话失败", zap.Error(err))
response.FailWithMessage("保存会话失败", c)
return
}
response.OkWithDetailed(gin.H{"session": session}, "保存成功", c)
}
func (a *AIWorkflowSessionApi) GetList(c *gin.Context) {
var info systemReq.SysAIWorkflowSessionSearch
if err := c.ShouldBindJSON(&info); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := aiWorkflowSessionService.GetList(c.Request.Context(), utils.GetUserID(c), info)
if err != nil {
global.GVA_LOG.Error("获取 AI 工作流会话列表失败", zap.Error(err))
response.FailWithMessage("获取会话列表失败", c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: info.Page,
PageSize: info.PageSize,
}, "获取成功", c)
}
func (a *AIWorkflowSessionApi) GetDetail(c *gin.Context) {
var info commonReq.GetById
if err := c.ShouldBindJSON(&info); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
session, err := aiWorkflowSessionService.GetDetail(c.Request.Context(), utils.GetUserID(c), info.Uint())
if err != nil {
global.GVA_LOG.Error("获取 AI 工作流会话详情失败", zap.Error(err))
response.FailWithMessage("获取会话详情失败", c)
return
}
response.OkWithDetailed(gin.H{"session": session}, "获取成功", c)
}
func (a *AIWorkflowSessionApi) Delete(c *gin.Context) {
var info commonReq.GetById
if err := c.ShouldBindJSON(&info); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if err := aiWorkflowSessionService.Delete(c.Request.Context(), utils.GetUserID(c), info.Uint()); err != nil {
global.GVA_LOG.Error("删除 AI 工作流会话失败", zap.Error(err))
response.FailWithMessage("删除会话失败", c)
return
}
response.OkWithMessage("删除成功", c)
}
func (a *AIWorkflowSessionApi) DumpMarkdown(c *gin.Context) {
var info systemReq.SysAIWorkflowMarkdownDump
if err := c.ShouldBindJSON(&info); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
result, err := aiWorkflowSessionService.DumpMarkdown(c.Request.Context(), utils.GetUserID(c), info)
if err != nil {
global.GVA_LOG.Error("AI 工作流 Markdown 落盘失败", zap.Error(err))
response.FailWithMessage(err.Error(), c)
return
}
response.OkWithDetailed(gin.H{"result": result}, "落盘成功", c)
}
+113 -84
View File
@@ -1,8 +1,10 @@
package system
import (
"fmt"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global"
mcpTool "github.com/flipped-aurora/gin-vue-admin/server/mcp"
"github.com/flipped-aurora/gin-vue-admin/server/mcp/client"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
@@ -10,19 +12,9 @@ import (
"github.com/mark3labs/mcp-go/mcp"
)
// Create
// @Tags mcp
// @Summary 自动McpTool
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.AutoMcpTool true "创建自动代码"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
// @Router /autoCode/mcp [post]
func (a *AutoCodeTemplateApi) MCP(c *gin.Context) {
var info request.AutoMcpTool
err := c.ShouldBindJSON(&info)
if err != nil {
if err := c.ShouldBindJSON(&info); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
@@ -36,109 +28,146 @@ func (a *AutoCodeTemplateApi) MCP(c *gin.Context) {
response.OkWithMessage("创建成功,MCP Tool路径:"+toolFilePath, c)
}
// Create
// @Tags mcp
// @Summary 自动McpTool
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.AutoMcpTool true "创建自动代码"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
// @Router /autoCode/mcpList [post]
func (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
defer testClient.Close()
toolsRequest := mcp.ListToolsRequest{}
list, err := testClient.ListTools(c.Request.Context(), toolsRequest)
func (a *AutoCodeTemplateApi) MCPStatus(c *gin.Context) {
response.OkWithData(gin.H{
"status": mcpTool.GetManagedStandaloneStatus(c.Request.Context()),
"mcpServerConfig": buildMCPServerConfig(),
}, c)
}
func (a *AutoCodeTemplateApi) MCPStart(c *gin.Context) {
status, err := mcpTool.StartManagedStandalone(c.Request.Context())
if err != nil {
response.FailWithMessage("创建失败", c)
global.GVA_LOG.Error(err.Error())
response.FailWithDetailed(gin.H{
"status": status,
"mcpServerConfig": buildMCPServerConfig(),
}, err.Error(), c)
return
}
mcpServerConfig := map[string]interface{}{
"mcpServers": map[string]interface{}{
global.GVA_CONFIG.MCP.Name: map[string]string{
"url": baseUrl,
},
},
response.OkWithDetailed(gin.H{
"status": status,
"mcpServerConfig": buildMCPServerConfig(),
}, "MCP独立服务已启动", c)
}
func (a *AutoCodeTemplateApi) MCPStop(c *gin.Context) {
status, err := mcpTool.StopManagedStandalone(c.Request.Context())
if err != nil {
response.FailWithDetailed(gin.H{
"status": status,
"mcpServerConfig": buildMCPServerConfig(),
}, err.Error(), c)
return
}
response.OkWithDetailed(gin.H{
"status": status,
"mcpServerConfig": buildMCPServerConfig(),
}, "MCP独立服务已停用", c)
}
func (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {
baseURL := mcpTool.ResolveMCPServiceURL()
testClient, err := client.NewClient(baseURL, "testClient", "v1.0.0", mcpServerName(), incomingMCPHeaders(c))
if err != nil {
response.FailWithDetailed(gin.H{
"status": mcpTool.GetManagedStandaloneStatus(c.Request.Context()),
"mcpServerConfig": buildMCPServerConfig(),
}, "连接MCP服务失败:"+err.Error(), c)
return
}
defer testClient.Close()
list, err := testClient.ListTools(c.Request.Context(), mcp.ListToolsRequest{})
if err != nil {
response.FailWithDetailed(gin.H{
"status": mcpTool.GetManagedStandaloneStatus(c.Request.Context()),
"mcpServerConfig": buildMCPServerConfig(),
}, "获取工具列表失败:"+err.Error(), c)
return
}
response.OkWithData(gin.H{
"mcpServerConfig": mcpServerConfig,
"status": mcpTool.GetManagedStandaloneStatus(c.Request.Context()),
"mcpServerConfig": buildMCPServerConfig(),
"list": list,
}, c)
}
// Create
// @Tags mcp
// @Summary 测试McpTool
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body object true "调用MCP Tool的参数"
// @Success 200 {object} response.Response "{"success":true,"data":{},"msg":"测试成功"}"
// @Router /autoCode/mcpTest [post]
func (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {
// 定义接口请求结构
var testRequest struct {
Name string `json:"name" binding:"required"` // 工具名称
Arguments map[string]interface{} `json:"arguments" binding:"required"` // 工具参数
}
func (a *AutoCodeTemplateApi) MCPRoutes(c *gin.Context) {
response.OkWithData(gin.H{
"routes": global.GVA_ROUTERS,
}, c)
}
// 绑定JSON请求体
func (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {
var testRequest struct {
Name string `json:"name" binding:"required"`
Arguments map[string]interface{} `json:"arguments" binding:"required"`
}
if err := c.ShouldBindJSON(&testRequest); err != nil {
response.FailWithMessage("参数解析失败:"+err.Error(), c)
return
}
// 创建MCP客户端
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
baseURL := mcpTool.ResolveMCPServiceURL()
testClient, err := client.NewClient(baseURL, "testClient", "v1.0.0", mcpServerName(), incomingMCPHeaders(c))
if err != nil {
response.FailWithMessage("创建MCP客户端失败:"+err.Error(), c)
response.FailWithMessage("连接MCP服务失败:"+err.Error(), c)
return
}
defer testClient.Close()
ctx := c.Request.Context()
callRequest := mcp.CallToolRequest{}
callRequest.Params.Name = testRequest.Name
callRequest.Params.Arguments = testRequest.Arguments
// 初始化MCP连接
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "testClient",
Version: "v1.0.0",
}
_, err = testClient.Initialize(ctx, initRequest)
if err != nil {
response.FailWithMessage("初始化MCP连接失败:"+err.Error(), c)
return
}
// 构建工具调用请求
request := mcp.CallToolRequest{}
request.Params.Name = testRequest.Name
request.Params.Arguments = testRequest.Arguments
// 调用工具
result, err := testClient.CallTool(ctx, request)
result, err := testClient.CallTool(c.Request.Context(), callRequest)
if err != nil {
response.FailWithMessage("工具调用失败:"+err.Error(), c)
return
}
// 处理响应结果
if len(result.Content) == 0 {
response.FailWithMessage("工具未返回任何内容", c)
return
}
// 返回结果
response.OkWithData(result.Content, c)
}
func incomingMCPHeaders(c *gin.Context) map[string]string {
headerName := mcpTool.ConfiguredAuthHeader()
headerValue := c.GetHeader(headerName)
if headerValue == "" {
return nil
}
return map[string]string{
headerName: headerValue,
}
}
func buildMCPServerConfig() map[string]interface{} {
baseURL := mcpTool.ResolveMCPServiceURL()
authHeader := mcpTool.ConfiguredAuthHeader()
serverName := mcpServerName()
return map[string]interface{}{
"mcpServers": map[string]interface{}{
serverName: map[string]interface{}{
"url": baseURL,
"headers": map[string]string{
authHeader: "${YOUR_GVA_TOKEN}",
},
},
},
}
}
func mcpServerName() string {
if name := strings.TrimSpace(global.GVA_CONFIG.MCP.Name); name != "" {
return name
}
return "gva"
}
+99
View File
@@ -2,9 +2,14 @@ package system
import (
"fmt"
"os"
"path/filepath"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
@@ -117,3 +122,97 @@ func (a *AutoCodePluginApi) InitAPI(c *gin.Context) {
}
response.OkWithMessage("文件变更成功", c)
}
// InitDictionary
// @Tags AutoCodePlugin
// @Summary 打包插件
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
// @Router /autoCode/initDictionary [post]
func (a *AutoCodePluginApi) InitDictionary(c *gin.Context) {
var dictInfo request.InitDictionary
err := c.ShouldBindJSON(&dictInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = autoCodePluginService.InitDictionary(dictInfo)
if err != nil {
global.GVA_LOG.Error("创建初始化Dictionary失败!", zap.Error(err))
response.FailWithMessage("创建初始化Dictionary失败"+err.Error(), c)
return
}
response.OkWithMessage("文件变更成功", c)
}
// GetPluginList
// @Tags AutoCodePlugin
// @Summary 获取插件列表
// @Security ApiKeyAuth
// @Produce application/json
// @Success 200 {object} response.Response{data=[]systemRes.PluginInfo} "获取插件列表成功"
// @Router /autoCode/getPluginList [get]
func (a *AutoCodePluginApi) GetPluginList(c *gin.Context) {
serverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin")
webDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin")
serverEntries, _ := os.ReadDir(serverDir)
webEntries, _ := os.ReadDir(webDir)
configMap := make(map[string]string)
for _, entry := range serverEntries {
if entry.IsDir() {
configMap[entry.Name()] = "server"
}
}
for _, entry := range webEntries {
if entry.IsDir() {
if val, ok := configMap[entry.Name()]; ok {
if val == "server" {
configMap[entry.Name()] = "full"
}
} else {
configMap[entry.Name()] = "web"
}
}
}
var list []systemRes.PluginInfo
for k, v := range configMap {
apis, menus, dicts := utils.GetPluginData(k)
list = append(list, systemRes.PluginInfo{
PluginName: k,
PluginType: v,
Apis: apis,
Menus: menus,
Dictionaries: dicts,
})
}
response.OkWithDetailed(list, "获取成功", c)
}
// Remove
// @Tags AutoCodePlugin
// @Summary 删除插件
// @Security ApiKeyAuth
// @Produce application/json
// @Param pluginName query string true "插件名称"
// @Param pluginType query string true "插件类型"
// @Success 200 {object} response.Response{msg=string} "删除插件成功"
// @Router /autoCode/removePlugin [post]
func (a *AutoCodePluginApi) Remove(c *gin.Context) {
pluginName := c.Query("pluginName")
pluginType := c.Query("pluginType")
err := autoCodePluginService.Remove(pluginName, pluginType)
if err != nil {
global.GVA_LOG.Error("删除失败!", zap.Error(err))
response.FailWithMessage("删除失败"+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
+30 -20
View File
@@ -23,27 +23,37 @@ type ApiGroup struct {
AutoCodeTemplateApi
SysParamsApi
SysVersionApi
SysErrorApi
LoginLogApi
ApiTokenApi
SkillsApi
AIWorkflowSessionApi
}
var (
apiService = service.ServiceGroupApp.SystemServiceGroup.ApiService
jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService
menuService = service.ServiceGroupApp.SystemServiceGroup.MenuService
userService = service.ServiceGroupApp.SystemServiceGroup.UserService
initDBService = service.ServiceGroupApp.SystemServiceGroup.InitDBService
casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService
baseMenuService = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService
authorityService = service.ServiceGroupApp.SystemServiceGroup.AuthorityService
dictionaryService = service.ServiceGroupApp.SystemServiceGroup.DictionaryService
authorityBtnService = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService
systemConfigService = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService
sysParamsService = service.ServiceGroupApp.SystemServiceGroup.SysParamsService
operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
autoCodeService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService
autoCodePluginService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePlugin
autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
apiService = service.ServiceGroupApp.SystemServiceGroup.ApiService
jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService
menuService = service.ServiceGroupApp.SystemServiceGroup.MenuService
userService = service.ServiceGroupApp.SystemServiceGroup.UserService
initDBService = service.ServiceGroupApp.SystemServiceGroup.InitDBService
casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService
baseMenuService = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService
authorityService = service.ServiceGroupApp.SystemServiceGroup.AuthorityService
dictionaryService = service.ServiceGroupApp.SystemServiceGroup.DictionaryService
authorityBtnService = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService
systemConfigService = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService
sysParamsService = service.ServiceGroupApp.SystemServiceGroup.SysParamsService
operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
autoCodeService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService
aiWorkflowSessionService = service.ServiceGroupApp.SystemServiceGroup.AIWorkflowSession
autoCodePluginService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePlugin
autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
sysErrorService = service.ServiceGroupApp.SystemServiceGroup.SysErrorService
loginLogService = service.ServiceGroupApp.SystemServiceGroup.LoginLogService
apiTokenService = service.ServiceGroupApp.SystemServiceGroup.ApiTokenService
skillsService = service.ServiceGroupApp.SystemServiceGroup.SkillsService
)
+58
View File
@@ -321,3 +321,61 @@ func (s *SystemApiApi) FreshCasbin(c *gin.Context) {
}
response.OkWithMessage("刷新成功", c)
}
// GetApiRoles
// @Tags SysApi
// @Summary 获取拥有指定API权限的角色ID列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param path query string true "API路径"
// @Param method query string true "请求方法"
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取成功"
// @Router /api/getApiRoles [get]
func (s *SystemApiApi) GetApiRoles(c *gin.Context) {
path := c.Query("path")
method := c.Query("method")
if path == "" || method == "" {
response.FailWithMessage("API路径和请求方法不能为空", c)
return
}
authorityIds, err := casbinService.GetAuthoritiesByApi(path, method)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败"+err.Error(), c)
return
}
if authorityIds == nil {
authorityIds = []uint{}
}
response.OkWithDetailed(authorityIds, "获取成功", c)
}
// SetApiRoles
// @Tags SysApi
// @Summary 全量覆盖某API关联的角色列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body systemReq.SetApiAuthorities true "API路径、请求方法和角色ID列表"
// @Success 200 {object} response.Response{msg=string} "设置成功"
// @Router /api/setApiRoles [post]
func (s *SystemApiApi) SetApiRoles(c *gin.Context) {
var req systemReq.SetApiAuthorities
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.Path == "" || req.Method == "" {
response.FailWithMessage("API路径和请求方法不能为空", c)
return
}
if err := casbinService.SetApiAuthorities(req.Path, req.Method, req.AuthorityIds); err != nil {
global.GVA_LOG.Error("设置失败!", zap.Error(err))
response.FailWithMessage("设置失败"+err.Error(), c)
return
}
// 刷新casbin缓存使策略立即生效
_ = casbinService.FreshCasbin()
response.OkWithMessage("设置成功", c)
}
+81
View File
@@ -0,0 +1,81 @@
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
sysReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type ApiTokenApi struct{}
// CreateApiToken 签发Token
func (s *ApiTokenApi) CreateApiToken(c *gin.Context) {
var req struct {
UserID uint `json:"userId"`
AuthorityID uint `json:"authorityId"`
Days int `json:"days"` // -1为永久, 其他为天数
Remark string `json:"remark"`
}
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
token := system.SysApiToken{
UserID: req.UserID,
AuthorityID: req.AuthorityID,
Remark: req.Remark,
}
jwtStr, err := apiTokenService.CreateApiToken(token, req.Days)
if err != nil {
global.GVA_LOG.Error("签发失败!", zap.Error(err))
response.FailWithMessage("签发失败: "+err.Error(), c)
return
}
response.OkWithDetailed(gin.H{"token": jwtStr}, "签发成功", c)
}
// GetApiTokenList 获取列表
func (s *ApiTokenApi) GetApiTokenList(c *gin.Context) {
var pageInfo sysReq.SysApiTokenSearch
err := c.ShouldBindJSON(&pageInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := apiTokenService.GetApiTokenList(pageInfo)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
// DeleteApiToken 作废Token
func (s *ApiTokenApi) DeleteApiToken(c *gin.Context) {
var req system.SysApiToken
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = apiTokenService.DeleteApiToken(req.ID)
if err != nil {
global.GVA_LOG.Error("作废失败!", zap.Error(err))
response.FailWithMessage("作废失败", c)
return
}
response.OkWithMessage("作废成功", c)
}
+55
View File
@@ -4,6 +4,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
@@ -200,3 +201,57 @@ func (a *AuthorityApi) SetDataAuthority(c *gin.Context) {
}
response.OkWithMessage("设置成功", c)
}
// GetUsersByAuthority
// @Tags Authority
// @Summary 获取拥有指定角色的用户ID列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param authorityId query uint true "角色ID"
// @Success 200 {object} response.Response{data=[]uint,msg=string} "获取成功"
// @Router /authority/getUsersByAuthority [get]
func (a *AuthorityApi) GetUsersByAuthority(c *gin.Context) {
var req systemReq.SetRoleUsers
if err := c.ShouldBindQuery(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
userIds, err := authorityService.GetUserIdsByAuthorityId(req.AuthorityId)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败"+err.Error(), c)
return
}
if userIds == nil {
userIds = []uint{}
}
response.OkWithDetailed(userIds, "获取成功", c)
}
// SetRoleUsers
// @Tags Authority
// @Summary 全量覆盖某角色关联的用户列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body systemReq.SetRoleUsers true "角色ID和用户ID列表"
// @Success 200 {object} response.Response{msg=string} "设置成功"
// @Router /authority/setRoleUsers [post]
func (a *AuthorityApi) SetRoleUsers(c *gin.Context) {
var req systemReq.SetRoleUsers
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.AuthorityId == 0 {
response.FailWithMessage("角色ID不能为空", c)
return
}
if err := authorityService.SetRoleUsers(req.AuthorityId, req.UserIds); err != nil {
global.GVA_LOG.Error("设置失败!", zap.Error(err))
response.FailWithMessage("设置失败"+err.Error(), c)
return
}
response.OkWithMessage("设置成功", c)
}
+131 -67
View File
@@ -1,57 +1,43 @@
package system
import (
"errors"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/model/common"
"github.com/goccy/go-json"
"io"
"net/http"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/utils/request"
"github.com/gin-gonic/gin"
"github.com/goccy/go-json"
"go.uber.org/zap"
)
type AutoCodeApi struct{}
// GetDB
// @Tags AutoCode
// @Summary 获取当前所有数据库
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前所有数据库"
// @Router /autoCode/getDB [get]
func (autoApi *AutoCodeApi) GetDB(c *gin.Context) {
businessDB := c.Query("businessDB")
dbs, err := autoCodeService.Database(businessDB).GetDB(businessDB)
var dbList []map[string]interface{}
for _, db := range global.GVA_CONFIG.DBList {
var item = make(map[string]interface{})
item["aliasName"] = db.AliasName
item["dbName"] = db.Dbname
item["disable"] = db.Disable
item["dbtype"] = db.Type
item := map[string]interface{}{
"aliasName": db.AliasName,
"dbName": db.Dbname,
"disable": db.Disable,
"dbtype": db.Type,
}
dbList = append(dbList, item)
}
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
} else {
response.OkWithDetailed(gin.H{"dbs": dbs, "dbList": dbList}, "获取成功", c)
return
}
response.OkWithDetailed(gin.H{"dbs": dbs, "dbList": dbList}, "获取成功", c)
}
// GetTables
// @Tags AutoCode
// @Summary 获取当前数据库所有表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前数据库所有表"
// @Router /autoCode/getTables [get]
func (autoApi *AutoCodeApi) GetTables(c *gin.Context) {
dbName := c.Query("dbName")
businessDB := c.Query("businessDB")
@@ -70,19 +56,11 @@ func (autoApi *AutoCodeApi) GetTables(c *gin.Context) {
if err != nil {
global.GVA_LOG.Error("查询table失败!", zap.Error(err))
response.FailWithMessage("查询table失败", c)
} else {
response.OkWithDetailed(gin.H{"tables": tables}, "获取成功", c)
return
}
response.OkWithDetailed(gin.H{"tables": tables}, "获取成功", c)
}
// GetColumn
// @Tags AutoCode
// @Summary 获取当前表所有字段
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前表所有字段"
// @Router /autoCode/getColumn [get]
func (autoApi *AutoCodeApi) GetColumn(c *gin.Context) {
businessDB := c.Query("businessDB")
dbName := c.Query("dbName")
@@ -101,55 +79,141 @@ func (autoApi *AutoCodeApi) GetColumn(c *gin.Context) {
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
} else {
response.OkWithDetailed(gin.H{"columns": columns}, "获取成功", c)
return
}
response.OkWithDetailed(gin.H{"columns": columns}, "获取成功", c)
}
func (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) {
var llm common.JSONMap
err := c.ShouldBindJSON(&llm)
if err != nil {
if err := c.ShouldBindJSON(&llm); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if global.GVA_CONFIG.AutoCode.AiPath == "" {
response.FailWithMessage("请先前往插件市场个人中心获取AiPath并填入config.yaml中", c)
if shouldStreamLLM(c, llm) {
if err := autoApi.proxyLLMStream(c, llm); err != nil {
global.GVA_LOG.Error("大模型流式代理失败!", zap.Error(err))
if c.Writer.Written() {
writeLLMStreamError(c, err)
return
}
response.FailWithMessage(err.Error(), c)
}
return
}
path := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", fmt.Sprintf("api/chat/%s", llm["mode"]))
res, err := request.HttpRequest(
path,
"POST",
nil,
nil,
llm,
)
data, err := autoCodeService.LLMAuto(c.Request.Context(), llm)
if err != nil {
global.GVA_LOG.Error("大模型生成失败!", zap.Error(err))
response.FailWithMessage("大模型生成失败"+err.Error(), c)
response.FailWithMessage(err.Error(), c)
return
}
var resStruct response.Response
b, err := io.ReadAll(res.Body)
response.OkWithData(data, c)
}
func shouldStreamLLM(c *gin.Context, llm common.JSONMap) bool {
responseMode := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", llm["response_mode"])))
if responseMode == "streaming" || responseMode == "sse" {
return true
}
if stream, ok := llm["stream"].(bool); ok && stream {
return true
}
return strings.Contains(strings.ToLower(c.GetHeader("Accept")), "text/event-stream")
}
func (autoApi *AutoCodeApi) proxyLLMStream(c *gin.Context, llm common.JSONMap) error {
res, err := autoCodeService.LLMAutoStream(c.Request.Context(), llm)
if err != nil {
return err
}
defer res.Body.Close()
if err != nil {
global.GVA_LOG.Error("大模型生成失败!", zap.Error(err))
response.FailWithMessage("大模型生成失败"+err.Error(), c)
return
if res.StatusCode < 200 || res.StatusCode >= 300 {
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w", res.StatusCode, res.Header.Get("Content-Type"), readErr)
}
return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s", res.StatusCode, res.Header.Get("Content-Type"), previewResponseBody(body))
}
flusher, ok := c.Writer.(http.Flusher)
if !ok {
return errors.New("当前响应不支持流式输出")
}
copyLLMStreamHeaders(c.Writer.Header(), res.Header)
if c.Writer.Header().Get("Content-Type") == "" {
c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
}
if c.Writer.Header().Get("Cache-Control") == "" {
c.Writer.Header().Set("Cache-Control", "no-cache")
}
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("X-Accel-Buffering", "no")
c.Status(res.StatusCode)
flusher.Flush()
buf := make([]byte, 32*1024)
for {
n, readErr := res.Body.Read(buf)
if n > 0 {
if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {
return fmt.Errorf("向客户端写入流式响应失败: %w", writeErr)
}
flusher.Flush()
}
if readErr != nil {
if errors.Is(readErr, io.EOF) {
return nil
}
return fmt.Errorf("读取上游流式响应失败: %w", readErr)
}
}
}
func copyLLMStreamHeaders(dst, src http.Header) {
for _, key := range []string{
"Content-Type",
"Cache-Control",
"Content-Encoding",
"Content-Language",
"X-Accel-Buffering",
} {
if value := src.Get(key); value != "" {
dst.Set(key, value)
}
}
}
func writeLLMStreamError(c *gin.Context, err error) {
payload, marshalErr := json.Marshal(gin.H{
"message": err.Error(),
})
if marshalErr != nil {
payload = []byte(`{"message":"流式代理失败"}`)
}
err = json.Unmarshal(b, &resStruct)
if err != nil {
global.GVA_LOG.Error("大模型生成失败!", zap.Error(err))
response.FailWithMessage("大模型生成失败"+err.Error(), c)
return
_, _ = c.Writer.WriteString("event: error\n")
_, _ = c.Writer.WriteString("data: ")
_, _ = c.Writer.Write(payload)
_, _ = c.Writer.WriteString("\n\n")
if flusher, ok := c.Writer.(http.Flusher); ok {
flusher.Flush()
}
}
if resStruct.Code == 7 {
global.GVA_LOG.Error("大模型生成失败!"+resStruct.Msg, zap.Error(err))
response.FailWithMessage("大模型生成失败"+resStruct.Msg, c)
return
func previewResponseBody(body []byte) string {
text := strings.TrimSpace(string(body))
text = strings.ReplaceAll(text, "\r", " ")
text = strings.ReplaceAll(text, "\n", " ")
text = strings.Join(strings.Fields(text), " ")
if text == "" {
return "<empty>"
}
runes := []rune(text)
if len(runes) > 300 {
return string(runes[:300]) + "..."
}
response.OkWithData(resStruct.Data, c)
return text
}
+210
View File
@@ -0,0 +1,210 @@
package system
import (
"bufio"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/gin-contrib/sse"
"github.com/gin-gonic/gin"
"github.com/goccy/go-json"
"go.uber.org/zap"
)
func (autoApi *AutoCodeApi) LLMAutoSSE(c *gin.Context) {
var llm common.JSONMap
if err := c.ShouldBindJSON(&llm); err != nil {
global.GVA_LOG.Error("LLMAutoSSE 参数绑定失败!", zap.Error(err))
response.FailWithMessage(err.Error(), c)
return
}
if llm == nil {
llm = common.JSONMap{}
}
llm["response_mode"] = "streaming"
global.GVA_LOG.Info("LLMAutoSSE 收到请求", zap.Any("mode", llm["mode"]))
if err := autoApi.streamLLMAsSSE(c, llm); err != nil {
global.GVA_LOG.Error("大模型 SSE 代理失败!", zap.Error(err))
if c.Writer.Written() {
writeLLMStreamError(c, err)
return
}
response.FailWithMessage(err.Error(), c)
}
}
func (autoApi *AutoCodeApi) streamLLMAsSSE(c *gin.Context, llm common.JSONMap) error {
res, err := autoCodeService.LLMAutoStream(c.Request.Context(), llm)
if err != nil {
return fmt.Errorf("调用上游大模型失败: %w", err)
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 {
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w", res.StatusCode, res.Header.Get("Content-Type"), readErr)
}
return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s", res.StatusCode, res.Header.Get("Content-Type"), previewResponseBody(body))
}
ct := res.Header.Get("Content-Type")
global.GVA_LOG.Info("LLMAutoSSE 上游返回成功,开始 SSE 流式转发",
zap.Int("status", res.StatusCode),
zap.String("content-type", ct))
// 如果上游返回的不是 SSE 流(可能是 blocking 模式返回的 JSON),直接读取并转发
if !strings.Contains(ct, "text/event-stream") && !strings.Contains(ct, "text/plain") {
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return fmt.Errorf("读取上游非流式响应失败: %w", readErr)
}
global.GVA_LOG.Warn("LLMAutoSSE 上游返回非 SSE 流,Content-Type: "+ct+", 将以单次事件转发",
zap.String("body_preview", previewResponseBody(body)))
flusher, ok := c.Writer.(http.Flusher)
if !ok {
return errors.New("当前响应不支持流式输出")
}
prepareSSEHeaders(c)
c.Status(http.StatusOK)
var payload any
if err := json.Unmarshal(body, &payload); err != nil {
payload = string(body)
}
if err := renderSSE(c, sse.Event{Event: "message", Data: payload}); err != nil {
return err
}
if err := renderSSE(c, sse.Event{Event: "done", Data: gin.H{"done": true}}); err != nil {
return err
}
flusher.Flush()
return nil
}
flusher, ok := c.Writer.(http.Flusher)
if !ok {
return errors.New("当前响应不支持流式输出")
}
prepareSSEHeaders(c)
c.Status(http.StatusOK)
flusher.Flush()
reader := bufio.NewReader(res.Body)
lines := make([]string, 0, 8)
blockCount := 0
global.GVA_LOG.Info("LLMAutoSSE 开始读取上游流数据...")
for {
global.GVA_LOG.Debug("LLMAutoSSE 等待读取下一行...")
line, readErr := reader.ReadString('\n')
if readErr != nil && !errors.Is(readErr, io.EOF) {
global.GVA_LOG.Error("LLMAutoSSE 读取上游流失败", zap.Int("已转发块数", blockCount), zap.Error(readErr))
return fmt.Errorf("读取上游流式响应失败: %w", readErr)
}
line = strings.TrimRight(line, "\r\n")
if line == "" {
if len(lines) > 0 {
blockCount++
if blockCount <= 3 {
global.GVA_LOG.Debug("LLMAutoSSE 转发 SSE 块", zap.Int("block", blockCount), zap.Strings("lines", lines))
}
}
if err := emitSSEBlock(c, lines); err != nil {
return err
}
lines = lines[:0]
} else {
lines = append(lines, line)
}
if errors.Is(readErr, io.EOF) {
if err := emitSSEBlock(c, lines); err != nil {
return err
}
if err := renderSSE(c, sse.Event{
Event: "done",
Data: gin.H{"done": true},
}); err != nil {
return err
}
flusher.Flush()
global.GVA_LOG.Info("LLMAutoSSE 流式转发完成", zap.Int("总块数", blockCount))
return nil
}
}
}
func prepareSSEHeaders(c *gin.Context) {
header := c.Writer.Header()
header.Set("Content-Type", "text/event-stream; charset=utf-8")
header.Set("Cache-Control", "no-cache, no-transform")
header.Set("Connection", "keep-alive")
header.Set("X-Accel-Buffering", "no")
}
func emitSSEBlock(c *gin.Context, lines []string) error {
if len(lines) == 0 {
return nil
}
eventName := "message"
eventID := ""
dataLines := make([]string, 0, len(lines))
for _, line := range lines {
switch {
case strings.HasPrefix(line, "event:"):
eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
case strings.HasPrefix(line, "id:"):
eventID = strings.TrimSpace(strings.TrimPrefix(line, "id:"))
case strings.HasPrefix(line, "data:"):
dataLines = append(dataLines, strings.TrimSpace(strings.TrimPrefix(line, "data:")))
}
}
rawData := strings.TrimSpace(strings.Join(dataLines, "\n"))
if rawData == "" {
return nil
}
if rawData == "[DONE]" {
return renderSSE(c, sse.Event{
Id: eventID,
Event: "done",
Data: gin.H{"done": true},
})
}
var payload interface{}
if err := json.Unmarshal([]byte(rawData), &payload); err != nil {
payload = rawData
}
return renderSSE(c, sse.Event{
Id: eventID,
Event: eventName,
Data: payload,
})
}
func renderSSE(c *gin.Context, event sse.Event) error {
if err := event.Render(c.Writer); err != nil {
return fmt.Errorf("写入 SSE 事件失败: %w", err)
}
if flusher, ok := c.Writer.(http.Flusher); ok {
flusher.Flush()
}
return nil
}
+63 -1
View File
@@ -4,6 +4,7 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
@@ -116,10 +117,17 @@ func (s *DictionaryApi) FindSysDictionary(c *gin.Context) {
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data query request.SysDictionarySearch true "字典 name 或者 type"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量"
// @Router /sysDictionary/getSysDictionaryList [get]
func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) {
list, err := dictionaryService.GetSysDictionaryInfoList()
var dictionary request.SysDictionarySearch
err := c.ShouldBindQuery(&dictionary)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, err := dictionaryService.GetSysDictionaryInfoList(c, dictionary)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
@@ -127,3 +135,57 @@ func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) {
}
response.OkWithDetailed(list, "获取成功", c)
}
// ExportSysDictionary
// @Tags SysDictionary
// @Summary 导出字典JSON(包含字典详情)
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data query system.SysDictionary true "字典ID"
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "导出字典JSON"
// @Router /sysDictionary/exportSysDictionary [get]
func (s *DictionaryApi) ExportSysDictionary(c *gin.Context) {
var dictionary system.SysDictionary
err := c.ShouldBindQuery(&dictionary)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if dictionary.ID == 0 {
response.FailWithMessage("字典ID不能为空", c)
return
}
exportData, err := dictionaryService.ExportSysDictionary(dictionary.ID)
if err != nil {
global.GVA_LOG.Error("导出失败!", zap.Error(err))
response.FailWithMessage("导出失败", c)
return
}
response.OkWithDetailed(exportData, "导出成功", c)
}
// ImportSysDictionary
// @Tags SysDictionary
// @Summary 导入字典JSON(包含字典详情)
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.ImportSysDictionaryRequest true "字典JSON数据"
// @Success 200 {object} response.Response{msg=string} "导入字典"
// @Router /sysDictionary/importSysDictionary [post]
func (s *DictionaryApi) ImportSysDictionary(c *gin.Context) {
var req request.ImportSysDictionaryRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = dictionaryService.ImportSysDictionary(req.Json)
if err != nil {
global.GVA_LOG.Error("导入失败!", zap.Error(err))
response.FailWithMessage("导入失败: "+err.Error(), c)
return
}
response.OkWithMessage("导入成功", c)
}
@@ -1,6 +1,8 @@
package system
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
@@ -146,3 +148,120 @@ func (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) {
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
// GetDictionaryTreeList
// @Tags SysDictionaryDetail
// @Summary 获取字典详情树形结构
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param sysDictionaryID query int true "字典ID"
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
// @Router /sysDictionaryDetail/getDictionaryTreeList [get]
func (s *DictionaryDetailApi) GetDictionaryTreeList(c *gin.Context) {
sysDictionaryID := c.Query("sysDictionaryID")
if sysDictionaryID == "" {
response.FailWithMessage("字典ID不能为空", c)
return
}
var id uint
if idUint64, err := strconv.ParseUint(sysDictionaryID, 10, 32); err != nil {
response.FailWithMessage("字典ID格式错误", c)
return
} else {
id = uint(idUint64)
}
list, err := dictionaryDetailService.GetDictionaryTreeList(id)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
}
// GetDictionaryTreeListByType
// @Tags SysDictionaryDetail
// @Summary 根据字典类型获取字典详情树形结构
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param type query string true "字典类型"
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
// @Router /sysDictionaryDetail/getDictionaryTreeListByType [get]
func (s *DictionaryDetailApi) GetDictionaryTreeListByType(c *gin.Context) {
dictType := c.Query("type")
if dictType == "" {
response.FailWithMessage("字典类型不能为空", c)
return
}
list, err := dictionaryDetailService.GetDictionaryTreeListByType(dictType)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
}
// GetDictionaryDetailsByParent
// @Tags SysDictionaryDetail
// @Summary 根据父级ID获取字典详情
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data query request.GetDictionaryDetailsByParentRequest true "查询参数"
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情列表"
// @Router /sysDictionaryDetail/getDictionaryDetailsByParent [get]
func (s *DictionaryDetailApi) GetDictionaryDetailsByParent(c *gin.Context) {
var req request.GetDictionaryDetailsByParentRequest
err := c.ShouldBindQuery(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, err := dictionaryDetailService.GetDictionaryDetailsByParent(req)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
}
// GetDictionaryPath
// @Tags SysDictionaryDetail
// @Summary 获取字典详情的完整路径
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param id query uint true "字典详情ID"
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情路径"
// @Router /sysDictionaryDetail/getDictionaryPath [get]
func (s *DictionaryDetailApi) GetDictionaryPath(c *gin.Context) {
idStr := c.Query("id")
if idStr == "" {
response.FailWithMessage("字典详情ID不能为空", c)
return
}
var id uint
if idUint64, err := strconv.ParseUint(idStr, 10, 32); err != nil {
response.FailWithMessage("字典详情ID格式错误", c)
return
} else {
id = uint(idUint64)
}
path, err := dictionaryDetailService.GetDictionaryPath(id)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(gin.H{"path": path}, "获取成功", c)
}
+199
View File
@@ -0,0 +1,199 @@
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type SysErrorApi struct{}
// CreateSysError 创建错误日志
// @Tags SysError
// @Summary 创建错误日志
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body system.SysError true "创建错误日志"
// @Success 200 {object} response.Response{msg=string} "创建成功"
// @Router /sysError/createSysError [post]
func (sysErrorApi *SysErrorApi) CreateSysError(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
var sysError system.SysError
err := c.ShouldBindJSON(&sysError)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = sysErrorService.CreateSysError(ctx, &sysError)
if err != nil {
global.GVA_LOG.Error("创建失败!", zap.Error(err))
response.FailWithMessage("创建失败:"+err.Error(), c)
return
}
response.OkWithMessage("创建成功", c)
}
// DeleteSysError 删除错误日志
// @Tags SysError
// @Summary 删除错误日志
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body system.SysError true "删除错误日志"
// @Success 200 {object} response.Response{msg=string} "删除成功"
// @Router /sysError/deleteSysError [delete]
func (sysErrorApi *SysErrorApi) DeleteSysError(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
ID := c.Query("ID")
err := sysErrorService.DeleteSysError(ctx, ID)
if err != nil {
global.GVA_LOG.Error("删除失败!", zap.Error(err))
response.FailWithMessage("删除失败:"+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// DeleteSysErrorByIds 批量删除错误日志
// @Tags SysError
// @Summary 批量删除错误日志
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
// @Router /sysError/deleteSysErrorByIds [delete]
func (sysErrorApi *SysErrorApi) DeleteSysErrorByIds(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
IDs := c.QueryArray("IDs[]")
err := sysErrorService.DeleteSysErrorByIds(ctx, IDs)
if err != nil {
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
response.FailWithMessage("批量删除失败:"+err.Error(), c)
return
}
response.OkWithMessage("批量删除成功", c)
}
// UpdateSysError 更新错误日志
// @Tags SysError
// @Summary 更新错误日志
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data body system.SysError true "更新错误日志"
// @Success 200 {object} response.Response{msg=string} "更新成功"
// @Router /sysError/updateSysError [put]
func (sysErrorApi *SysErrorApi) UpdateSysError(c *gin.Context) {
// 从ctx获取标准context进行业务行为
ctx := c.Request.Context()
var sysError system.SysError
err := c.ShouldBindJSON(&sysError)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = sysErrorService.UpdateSysError(ctx, sysError)
if err != nil {
global.GVA_LOG.Error("更新失败!", zap.Error(err))
response.FailWithMessage("更新失败:"+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// FindSysError 用id查询错误日志
// @Tags SysError
// @Summary 用id查询错误日志
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param ID query uint true "用id查询错误日志"
// @Success 200 {object} response.Response{data=system.SysError,msg=string} "查询成功"
// @Router /sysError/findSysError [get]
func (sysErrorApi *SysErrorApi) FindSysError(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
ID := c.Query("ID")
resysError, err := sysErrorService.GetSysError(ctx, ID)
if err != nil {
global.GVA_LOG.Error("查询失败!", zap.Error(err))
response.FailWithMessage("查询失败:"+err.Error(), c)
return
}
response.OkWithData(resysError, c)
}
// GetSysErrorList 分页获取错误日志列表
// @Tags SysError
// @Summary 分页获取错误日志列表
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param data query systemReq.SysErrorSearch true "分页获取错误日志列表"
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
// @Router /sysError/getSysErrorList [get]
func (sysErrorApi *SysErrorApi) GetSysErrorList(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
var pageInfo systemReq.SysErrorSearch
err := c.ShouldBindQuery(&pageInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := sysErrorService.GetSysErrorInfoList(ctx, pageInfo)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败:"+err.Error(), c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
// GetSysErrorSolution 触发错误日志的异步处理
// @Tags SysError
// @Summary 根据ID触发处理:标记为处理中,1分钟后自动改为处理完成
// @Security ApiKeyAuth
// @Accept application/json
// @Produce application/json
// @Param id query string true "错误日志ID"
// @Success 200 {object} response.Response{msg=string} "处理已提交"
// @Router /sysError/getSysErrorSolution [get]
func (sysErrorApi *SysErrorApi) GetSysErrorSolution(c *gin.Context) {
// 创建业务用Context
ctx := c.Request.Context()
// 兼容 id 与 ID 两种参数
ID := c.Query("id")
if ID == "" {
response.FailWithMessage("缺少参数: id", c)
return
}
err := sysErrorService.GetSysErrorSolution(ctx, ID)
if err != nil {
global.GVA_LOG.Error("处理触发失败!", zap.Error(err))
response.FailWithMessage("处理触发失败:"+err.Error(), c)
return
}
response.OkWithMessage("已提交至AI处理", c)
}
@@ -50,6 +50,34 @@ type SysExportTemplateApi struct {
var sysExportTemplateService = service.ServiceGroupApp.SystemServiceGroup.SysExportTemplateService
// PreviewSQL 预览最终生成的SQL
// @Tags SysExportTemplate
// @Summary 预览最终生成的SQL(不执行查询,仅返回SQL字符串)
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param templateID query string true "导出模板ID"
// @Param params query string false "查询参数编码字符串,参考 ExportExcel 组件"
// @Success 200 {object} response.Response{data=map[string]string} "获取成功"
// @Router /sysExportTemplate/previewSQL [get]
func (sysExportTemplateApi *SysExportTemplateApi) PreviewSQL(c *gin.Context) {
templateID := c.Query("templateID")
if templateID == "" {
response.FailWithMessage("模板ID不能为空", c)
return
}
// 直接复用导出接口的参数组织方式:使用 URL Query,其中 params 为内部编码的查询字符串
queryParams := c.Request.URL.Query()
if sqlPreview, err := sysExportTemplateService.PreviewSQL(templateID, queryParams); err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
} else {
response.OkWithData(gin.H{"sql": sqlPreview}, c)
}
}
// CreateSysExportTemplate 创建导出模板
// @Tags SysExportTemplate
// @Summary 创建导出模板
+82
View File
@@ -0,0 +1,82 @@
package system
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type LoginLogApi struct{}
func (s *LoginLogApi) DeleteLoginLog(c *gin.Context) {
var loginLog system.SysLoginLog
err := c.ShouldBindJSON(&loginLog)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = loginLogService.DeleteLoginLog(loginLog)
if err != nil {
global.GVA_LOG.Error("删除失败!", zap.Error(err))
response.FailWithMessage("删除失败", c)
return
}
response.OkWithMessage("删除成功", c)
}
func (s *LoginLogApi) DeleteLoginLogByIds(c *gin.Context) {
var SDS request.IdsReq
err := c.ShouldBindJSON(&SDS)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = loginLogService.DeleteLoginLogByIds(SDS)
if err != nil {
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
response.FailWithMessage("批量删除失败", c)
return
}
response.OkWithMessage("批量删除成功", c)
}
func (s *LoginLogApi) FindLoginLog(c *gin.Context) {
var loginLog system.SysLoginLog
err := c.ShouldBindQuery(&loginLog)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
reLoginLog, err := loginLogService.GetLoginLog(loginLog.ID)
if err != nil {
global.GVA_LOG.Error("查询失败!", zap.Error(err))
response.FailWithMessage("查询失败", c)
return
}
response.OkWithDetailed(reLoginLog, "查询成功", c)
}
func (s *LoginLogApi) GetLoginLogList(c *gin.Context) {
var pageInfo systemReq.SysLoginLogSearch
err := c.ShouldBindQuery(&pageInfo)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
list, total, err := loginLogService.GetLoginLogInfoList(pageInfo)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败", c)
return
}
response.OkWithDetailed(response.PageResult{
List: list,
Total: total,
Page: pageInfo.Page,
PageSize: pageInfo.PageSize,
}, "获取成功", c)
}
+70
View File
@@ -244,6 +244,76 @@ func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) {
response.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, "获取成功", c)
}
// GetMenuRoles
// @Tags AuthorityMenu
// @Summary 获取拥有指定菜单的角色ID列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param menuId query uint true "菜单ID"
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取成功"
// @Router /menu/getMenuRoles [get]
func (a *AuthorityMenuApi) GetMenuRoles(c *gin.Context) {
var req systemReq.SetMenuAuthorities
if err := c.ShouldBindQuery(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.MenuId == 0 {
response.FailWithMessage("菜单ID不能为空", c)
return
}
authorityIds, err := menuService.GetAuthoritiesByMenuId(req.MenuId)
if err != nil {
global.GVA_LOG.Error("获取失败!", zap.Error(err))
response.FailWithMessage("获取失败"+err.Error(), c)
return
}
if authorityIds == nil {
authorityIds = []uint{}
}
defaultRouterAuthorityIds, err := menuService.GetDefaultRouterAuthorityIds(req.MenuId)
if err != nil {
global.GVA_LOG.Error("获取首页角色失败!", zap.Error(err))
response.FailWithMessage("获取失败"+err.Error(), c)
return
}
if defaultRouterAuthorityIds == nil {
defaultRouterAuthorityIds = []uint{}
}
response.OkWithDetailed(gin.H{
"authorityIds": authorityIds,
"defaultRouterAuthorityIds": defaultRouterAuthorityIds,
}, "获取成功", c)
}
// SetMenuRoles
// @Tags AuthorityMenu
// @Summary 全量覆盖某菜单关联的角色列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body systemReq.SetMenuAuthorities true "菜单ID和角色ID列表"
// @Success 200 {object} response.Response{msg=string} "设置成功"
// @Router /menu/setMenuRoles [post]
func (a *AuthorityMenuApi) SetMenuRoles(c *gin.Context) {
var req systemReq.SetMenuAuthorities
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.MenuId == 0 {
response.FailWithMessage("菜单ID不能为空", c)
return
}
if err := menuService.SetMenuAuthorities(req.MenuId, req.AuthorityIds); err != nil {
global.GVA_LOG.Error("设置失败!", zap.Error(err))
response.FailWithMessage("设置失败"+err.Error(), c)
return
}
response.OkWithMessage("设置成功", c)
}
// GetMenuList
// @Tags Menu
// @Summary 分页获取基础menu列表
+263
View File
@@ -0,0 +1,263 @@
package system
import (
"net/http"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type SkillsApi struct{}
func (s *SkillsApi) GetTools(c *gin.Context) {
data, err := skillsService.Tools(c.Request.Context())
if err != nil {
global.GVA_LOG.Error("获取工具列表失败", zap.Error(err))
response.FailWithMessage("获取工具列表失败", c)
return
}
response.OkWithDetailed(gin.H{"tools": data}, "获取成功", c)
}
func (s *SkillsApi) GetSkillList(c *gin.Context) {
var req request.SkillToolRequest
_ = c.ShouldBindJSON(&req)
data, err := skillsService.List(c.Request.Context(), req.Tool)
if err != nil {
global.GVA_LOG.Error("获取技能列表失败", zap.Error(err))
response.FailWithMessage("获取技能列表失败", c)
return
}
response.OkWithDetailed(gin.H{"skills": data}, "获取成功", c)
}
func (s *SkillsApi) GetSkillDetail(c *gin.Context) {
var req request.SkillDetailRequest
_ = c.ShouldBindJSON(&req)
data, err := skillsService.Detail(c.Request.Context(), req.Tool, req.Skill)
if err != nil {
global.GVA_LOG.Error("获取技能详情失败", zap.Error(err))
response.FailWithMessage("获取技能详情失败", c)
return
}
response.OkWithDetailed(gin.H{"detail": data}, "获取成功", c)
}
func (s *SkillsApi) SaveSkill(c *gin.Context) {
var req request.SkillSaveRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.Save(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("保存技能失败", zap.Error(err))
response.FailWithMessage("保存技能失败", c)
return
}
response.OkWithMessage("保存成功", c)
}
func (s *SkillsApi) DeleteSkill(c *gin.Context) {
var req request.SkillDeleteRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.Delete(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("删除技能失败", zap.Error(err))
response.FailWithMessage("删除技能失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
func (s *SkillsApi) CreateScript(c *gin.Context) {
var req request.SkillScriptCreateRequest
_ = c.ShouldBindJSON(&req)
fileName, content, err := skillsService.CreateScript(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("创建脚本失败", zap.Error(err))
response.FailWithMessage("创建脚本失败", c)
return
}
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
}
func (s *SkillsApi) GetScript(c *gin.Context) {
var req request.SkillFileRequest
_ = c.ShouldBindJSON(&req)
content, err := skillsService.GetScript(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("读取脚本失败", zap.Error(err))
response.FailWithMessage("读取脚本失败", c)
return
}
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
}
func (s *SkillsApi) SaveScript(c *gin.Context) {
var req request.SkillFileSaveRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.SaveScript(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("保存脚本失败", zap.Error(err))
response.FailWithMessage("保存脚本失败", c)
return
}
response.OkWithMessage("保存成功", c)
}
func (s *SkillsApi) CreateResource(c *gin.Context) {
var req request.SkillResourceCreateRequest
_ = c.ShouldBindJSON(&req)
fileName, content, err := skillsService.CreateResource(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("创建资源失败", zap.Error(err))
response.FailWithMessage("创建资源失败", c)
return
}
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
}
func (s *SkillsApi) GetResource(c *gin.Context) {
var req request.SkillFileRequest
_ = c.ShouldBindJSON(&req)
content, err := skillsService.GetResource(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("读取资源失败", zap.Error(err))
response.FailWithMessage("读取资源失败", c)
return
}
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
}
func (s *SkillsApi) SaveResource(c *gin.Context) {
var req request.SkillFileSaveRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.SaveResource(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("保存资源失败", zap.Error(err))
response.FailWithMessage("保存资源失败", c)
return
}
response.OkWithMessage("保存成功", c)
}
func (s *SkillsApi) CreateReference(c *gin.Context) {
var req request.SkillReferenceCreateRequest
_ = c.ShouldBindJSON(&req)
fileName, content, err := skillsService.CreateReference(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("创建参考失败", zap.Error(err))
response.FailWithMessage("创建参考失败", c)
return
}
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
}
func (s *SkillsApi) GetReference(c *gin.Context) {
var req request.SkillFileRequest
_ = c.ShouldBindJSON(&req)
content, err := skillsService.GetReference(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("读取参考失败", zap.Error(err))
response.FailWithMessage("读取参考失败", c)
return
}
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
}
func (s *SkillsApi) SaveReference(c *gin.Context) {
var req request.SkillFileSaveRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.SaveReference(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("保存参考失败", zap.Error(err))
response.FailWithMessage("保存参考失败", c)
return
}
response.OkWithMessage("保存成功", c)
}
func (s *SkillsApi) CreateTemplate(c *gin.Context) {
var req request.SkillTemplateCreateRequest
_ = c.ShouldBindJSON(&req)
fileName, content, err := skillsService.CreateTemplate(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("创建模板失败", zap.Error(err))
response.FailWithMessage("创建模板失败", c)
return
}
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
}
func (s *SkillsApi) GetTemplate(c *gin.Context) {
var req request.SkillFileRequest
_ = c.ShouldBindJSON(&req)
content, err := skillsService.GetTemplate(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("读取模板失败", zap.Error(err))
response.FailWithMessage("读取模板失败", c)
return
}
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
}
func (s *SkillsApi) SaveTemplate(c *gin.Context) {
var req request.SkillFileSaveRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.SaveTemplate(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("保存模板失败", zap.Error(err))
response.FailWithMessage("保存模板失败", c)
return
}
response.OkWithMessage("保存成功", c)
}
func (s *SkillsApi) GetGlobalConstraint(c *gin.Context) {
var req request.SkillToolRequest
_ = c.ShouldBindJSON(&req)
content, exists, err := skillsService.GetGlobalConstraint(c.Request.Context(), req.Tool)
if err != nil {
global.GVA_LOG.Error("读取全局约束失败", zap.Error(err))
response.FailWithMessage("读取全局约束失败", c)
return
}
response.OkWithDetailed(gin.H{"content": content, "exists": exists}, "获取成功", c)
}
func (s *SkillsApi) SaveGlobalConstraint(c *gin.Context) {
var req request.SkillGlobalConstraintSaveRequest
_ = c.ShouldBindJSON(&req)
if err := skillsService.SaveGlobalConstraint(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("保存全局约束失败", zap.Error(err))
response.FailWithMessage("保存全局约束失败", c)
return
}
response.OkWithMessage("保存成功", c)
}
func (s *SkillsApi) PackageSkill(c *gin.Context) {
var req request.SkillPackageRequest
_ = c.ShouldBindJSON(&req)
fileName, data, err := skillsService.Package(c.Request.Context(), req)
if err != nil {
global.GVA_LOG.Error("打包技能失败", zap.Error(err))
response.FailWithMessage("打包技能失败: "+err.Error(), c)
return
}
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename=\""+fileName+"\"")
c.Data(http.StatusOK, "application/zip", data)
}
func (s *SkillsApi) DownloadOnlineSkill(c *gin.Context) {
var req request.DownloadOnlineSkillReq
if err := c.ShouldBindJSON(&req); err != nil {
response.FailWithMessage("参数错误", c)
return
}
if err := skillsService.DownloadOnlineSkill(c.Request.Context(), req); err != nil {
global.GVA_LOG.Error("下载在线技能失败", zap.Error(err))
response.FailWithMessage("下载在线技能失败: "+err.Error(), c)
return
}
response.OkWithMessage("下载成功", c)
}
+35 -1
View File
@@ -51,6 +51,14 @@ func (b *BaseApi) Login(c *gin.Context) {
// 验证码次数+1
global.BlackCache.Increment(key, 1)
response.FailWithMessage("验证码错误", c)
// 记录登录失败日志
loginLogService.CreateLoginLog(system.SysLoginLog{
Username: l.Username,
Ip: c.ClientIP(),
Agent: c.Request.UserAgent(),
Status: false,
ErrorMessage: "验证码错误",
})
return
}
@@ -61,6 +69,14 @@ func (b *BaseApi) Login(c *gin.Context) {
// 验证码次数+1
global.BlackCache.Increment(key, 1)
response.FailWithMessage("用户名不存在或者密码错误", c)
// 记录登录失败日志
loginLogService.CreateLoginLog(system.SysLoginLog{
Username: l.Username,
Ip: c.ClientIP(),
Agent: c.Request.UserAgent(),
Status: false,
ErrorMessage: "用户名不存在或者密码错误",
})
return
}
if user.Enable != 1 {
@@ -68,6 +84,15 @@ func (b *BaseApi) Login(c *gin.Context) {
// 验证码次数+1
global.BlackCache.Increment(key, 1)
response.FailWithMessage("用户被禁止登录", c)
// 记录登录失败日志
loginLogService.CreateLoginLog(system.SysLoginLog{
Username: l.Username,
Ip: c.ClientIP(),
Agent: c.Request.UserAgent(),
Status: false,
ErrorMessage: "用户被禁止登录",
UserID: user.ID,
})
return
}
b.TokenNext(c, *user)
@@ -81,6 +106,15 @@ func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
response.FailWithMessage("获取token失败", c)
return
}
// 记录登录成功日志
loginLogService.CreateLoginLog(system.SysLoginLog{
Username: user.Username,
Ip: c.ClientIP(),
Agent: c.Request.UserAgent(),
Status: true,
UserID: user.ID,
ErrorMessage: "登录成功",
})
if !global.GVA_CONFIG.System.UseMultipoint {
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
response.OkWithDetailed(systemRes.LoginResponse{
@@ -264,7 +298,7 @@ func (b *BaseApi) SetUserAuthority(c *gin.Context) {
}
c.Header("new-token", token)
c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))
utils.SetToken(c, token, int((claims.ExpiresAt.Unix()-time.Now().Unix())/60))
utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
response.OkWithMessage("修改成功", c)
}
+192
View File
@@ -0,0 +1,192 @@
package main
import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"gopkg.in/yaml.v3"
)
type standaloneConfig struct {
MCP config.MCP `yaml:"mcp"`
AutoCode config.Autocode `yaml:"autocode"`
}
func loadStandaloneConfig() (string, error) {
configPath, err := resolveConfigPath()
if err != nil {
return "", err
}
content, err := os.ReadFile(configPath)
if err != nil {
return "", fmt.Errorf("读取 MCP 配置失败: %w", err)
}
var cfg standaloneConfig
if err := yaml.Unmarshal(content, &cfg); err != nil {
return "", fmt.Errorf("解析 MCP 配置失败: %w", err)
}
applyStandaloneDefaults(configPath, &cfg)
global.GVA_CONFIG.MCP = cfg.MCP
global.GVA_CONFIG.AutoCode = cfg.AutoCode
return configPath, nil
}
func resolveConfigPath() (string, error) {
explicit, err := parseConfigFlag(os.Args[1:])
if err != nil {
return "", err
}
if explicit != "" {
return filepath.Abs(explicit)
}
if envPath := strings.TrimSpace(os.Getenv("GVA_MCP_CONFIG")); envPath != "" {
return filepath.Abs(envPath)
}
wd, _ := os.Getwd()
exe, _ := os.Executable()
exeDir := filepath.Dir(exe)
candidates := []string{
filepath.Join(wd, "config.yaml"),
filepath.Join(wd, "mcp.yaml"),
filepath.Join(wd, "cmd", "mcp", "config.yaml"),
filepath.Join(wd, "server", "cmd", "mcp", "config.yaml"),
filepath.Join(exeDir, "config.yaml"),
filepath.Join(exeDir, "mcp.yaml"),
}
for _, candidate := range candidates {
if candidate == "" {
continue
}
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
return filepath.Abs(candidate)
}
}
return "", errors.New("未找到 MCP 独立配置文件,请在当前目录、cmd/mcp 目录或通过 -config / GVA_MCP_CONFIG 指定 config.yaml")
}
func parseConfigFlag(args []string) (string, error) {
fs := flag.NewFlagSet("gva-mcp", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var configPath string
fs.StringVar(&configPath, "c", "", "MCP config file path")
fs.StringVar(&configPath, "config", "", "MCP config file path")
if err := fs.Parse(args); err != nil {
return "", err
}
return strings.TrimSpace(configPath), nil
}
func applyStandaloneDefaults(configPath string, cfg *standaloneConfig) {
if cfg.MCP.Name == "" {
cfg.MCP.Name = "GVA_MCP"
}
if cfg.MCP.Version == "" {
cfg.MCP.Version = "v1.0.0"
}
if cfg.MCP.Path == "" {
cfg.MCP.Path = "/mcp"
}
if cfg.MCP.Addr == 0 {
cfg.MCP.Addr = 8889
}
if cfg.MCP.AuthHeader == "" {
cfg.MCP.AuthHeader = "x-token"
}
if cfg.MCP.RequestTimeout <= 0 {
cfg.MCP.RequestTimeout = 15
}
if cfg.MCP.UpstreamBaseURL == "" {
cfg.MCP.UpstreamBaseURL = "http://127.0.0.1:8888"
}
if cfg.MCP.BaseURL == "" {
cfg.MCP.BaseURL = fmt.Sprintf("http://127.0.0.1:%d%s", cfg.MCP.Addr, cfg.MCP.Path)
}
configDir := filepath.Dir(configPath)
if cfg.AutoCode.Server == "" {
cfg.AutoCode.Server = "server"
}
if cfg.AutoCode.Web == "" {
cfg.AutoCode.Web = "web/src"
}
if cfg.AutoCode.Root == "" {
if root, err := detectProjectRoot(configDir); err == nil {
cfg.AutoCode.Root = root
}
} else if !filepath.IsAbs(cfg.AutoCode.Root) {
cfg.AutoCode.Root = filepath.Clean(filepath.Join(configDir, cfg.AutoCode.Root))
}
if cfg.AutoCode.Module == "" && cfg.AutoCode.Root != "" {
goModPath := filepath.Join(cfg.AutoCode.Root, cfg.AutoCode.Server, "go.mod")
if module, err := detectGoModule(goModPath); err == nil {
cfg.AutoCode.Module = module
}
}
}
func detectProjectRoot(startDir string) (string, error) {
dir := startDir
for {
serverDir := filepath.Join(dir, "server")
webDir := filepath.Join(dir, "web")
if isDir(serverDir) && isDir(webDir) {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
return "", errors.New("未能自动识别项目根目录,请在 MCP 配置中设置 autocode.root")
}
func detectGoModule(goModPath string) (string, error) {
file, err := os.Open(goModPath)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "module ") {
return strings.TrimSpace(strings.TrimPrefix(line, "module ")), nil
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", errors.New("go.mod 中未找到 module 定义")
}
func isDir(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}
+14
View File
@@ -0,0 +1,14 @@
mcp:
name: GVA_MCP
version: v1.0.0
path: /mcp
addr: 8889
base_url: http://127.0.0.1:8889/mcp
upstream_base_url: http://127.0.0.1:8888
auth_header: x-token
request_timeout: 15
autocode:
root: ../../..
server: server
web: web/src
+17
View File
@@ -0,0 +1,17 @@
package main
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"go.uber.org/zap"
)
func initializeStandaloneLogger() error {
logger, err := zap.NewDevelopment()
if err != nil {
return err
}
global.GVA_LOG = logger
zap.ReplaceGlobals(logger)
return nil
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/global"
mcpTool "github.com/flipped-aurora/gin-vue-admin/server/mcp"
_ "go.uber.org/automaxprocs"
"go.uber.org/zap"
)
func main() {
configPath, err := loadStandaloneConfig()
if err != nil {
panic(err)
}
if err := initializeStandaloneLogger(); err != nil {
panic(err)
}
addr := fmt.Sprintf(":%d", global.GVA_CONFIG.MCP.Addr)
server := mcpTool.NewStreamableHTTPServer()
global.GVA_LOG.Info("mcp独立服务启动",
zap.String("config", configPath),
zap.String("addr", addr),
zap.String("path", global.GVA_CONFIG.MCP.Path),
zap.String("upstream", global.GVA_CONFIG.MCP.UpstreamBaseURL),
)
if err := server.Start(addr); err != nil {
global.GVA_LOG.Fatal("mcp独立服务启动失败", zap.Error(err))
}
}
+2 -3
View File
@@ -278,6 +278,5 @@ cors:
mcp:
name: GVA_MCP
version: v1.0.0
sse_path: /sse
message_path: /message
url_prefix: ''
addr: 8889
separate: false
+12 -4
View File
@@ -24,6 +24,8 @@ redis:
useCluster: false
#使用集群模式addr和db默认无效
addr: 127.0.0.1:6379
#ACL登录账号,云Redis自定义账号必填,默认账号留空
username: ""
password: ""
db: 0
clusterAddrs:
@@ -87,6 +89,8 @@ system:
router-prefix: ""
# 严格角色模式 打开后权限将会存在上下级关系
use-strict-auth: false
# 禁用自动迁移数据库表结构,生产环境建议设为true,手动迁移
disable-auto-migrate: false
# captcha configuration
captcha:
@@ -107,6 +111,7 @@ mysql:
password: ""
max-idle-conns: 10
max-open-conns: 100
conn-max-lifetime: 0
log-mode: ""
log-zap: false
@@ -121,6 +126,7 @@ pgsql:
password: ""
max-idle-conns: 10
max-open-conns: 100
conn-max-lifetime: 0
log-mode: ""
log-zap: false
oracle:
@@ -132,6 +138,7 @@ oracle:
password: ""
max-idle-conns: 10
max-open-conns: 100
conn-max-lifetime: 0
log-mode: ""
log-zap: false
mssql:
@@ -143,6 +150,7 @@ mssql:
password: ""
max-idle-conns: 10
max-open-conns: 100
conn-max-lifetime: 0
log-mode: ""
log-zap: false
sqlite:
@@ -154,6 +162,7 @@ sqlite:
password: ""
max-idle-conns: 10
max-open-conns: 100
conn-max-lifetime: 0
log-mode: ""
log-zap: false
db-list:
@@ -168,6 +177,7 @@ db-list:
password: ""
max-idle-conns: 10
max-open-conns: 100
conn-max-lifetime: 0
log-mode: ""
log-zap: false
@@ -268,7 +278,6 @@ cors:
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
allow-methods: POST, GET
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
allow-credentials: true # 布尔值
- allow-origin: example2.com
allow-headers: content-type
@@ -278,6 +287,5 @@ cors:
mcp:
name: GVA_MCP
version: v1.0.0
sse_path: /sse
message_path: /message
url_prefix: ''
addr: 8889
separate: false
+14 -13
View File
@@ -15,19 +15,20 @@ type DsnProvider interface {
// GeneralDB 也被 Pgsql 和 Mysql 原样使用
type GeneralDB struct {
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 数据库前缀
Port string `mapstructure:"port" json:"port" yaml:"port"` // 数据库端口
Config string `mapstructure:"config" json:"config" yaml:"config"` // 高级配置
Dbname string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` // 数据库名
Username string `mapstructure:"username" json:"username" yaml:"username"` // 数据库账号
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
Path string `mapstructure:"path" json:"path" yaml:"path"` // 数据库地址
Engine string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"` // 数据库引擎,默认InnoDB
LogMode string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"` // 是否开启Gorm全局日志
MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` // 空闲中的最大连接数
MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
Singular bool `mapstructure:"singular" json:"singular" yaml:"singular"` // 是否开启全局禁用复数,true表示开启
LogZap bool `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` // 是否通过zap写入日志文件
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 数据库前缀
Port string `mapstructure:"port" json:"port" yaml:"port"` // 数据库端口
Config string `mapstructure:"config" json:"config" yaml:"config"` // 高级配置
Dbname string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` // 数据库名
Username string `mapstructure:"username" json:"username" yaml:"username"` // 数据库账号
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
Path string `mapstructure:"path" json:"path" yaml:"path"` // 数据库地址
Engine string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"` // 数据库引擎,默认InnoDB
LogMode string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"` // 是否开启Gorm全局日志
MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` // 空闲中的最大连接数
MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
ConnMaxLifetime int `mapstructure:"conn-max-lifetime" json:"conn-max-lifetime" yaml:"conn-max-lifetime"` // 连接最长复用时间,单位秒
Singular bool `mapstructure:"singular" json:"singular" yaml:"singular"` // 是否开启全局禁用复数,true表示开启
LogZap bool `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` // 是否通过zap写入日志文件
}
func (c GeneralDB) LogLevel() logger.LogLevel {
+14 -5
View File
@@ -1,9 +1,18 @@
package config
type MCP struct {
Name string `mapstructure:"name" json:"name" yaml:"name"` // MCP名称
Version string `mapstructure:"version" json:"version" yaml:"version"` // MCP版本
SSEPath string `mapstructure:"sse_path" json:"sse_path" yaml:"sse_path"` // SSE路径
MessagePath string `mapstructure:"message_path" json:"message_path" yaml:"message_path"` // 消息路径
UrlPrefix string `mapstructure:"url_prefix" json:"url_prefix" yaml:"url_prefix"` // URL前缀
Name string `mapstructure:"name" json:"name" yaml:"name"`
Version string `mapstructure:"version" json:"version" yaml:"version"`
Path string `mapstructure:"path" json:"path" yaml:"path"`
Addr int `mapstructure:"addr" json:"addr" yaml:"addr"`
BaseURL string `mapstructure:"base_url" json:"base_url" yaml:"base_url"`
UpstreamBaseURL string `mapstructure:"upstream_base_url" json:"upstream_base_url" yaml:"upstream_base_url"`
AuthHeader string `mapstructure:"auth_header" json:"auth_header" yaml:"auth_header"`
RequestTimeout int `mapstructure:"request_timeout" json:"request_timeout" yaml:"request_timeout"`
// Deprecated fields kept for backward compatibility with older configs.
SSEPath string `mapstructure:"sse_path" json:"sse_path" yaml:"sse_path"`
MessagePath string `mapstructure:"message_path" json:"message_path" yaml:"message_path"`
UrlPrefix string `mapstructure:"url_prefix" json:"url_prefix" yaml:"url_prefix"`
Separate bool `mapstructure:"separate" json:"separate" yaml:"separate"`
}
+1
View File
@@ -3,6 +3,7 @@ package config
type Redis struct {
Name string `mapstructure:"name" json:"name" yaml:"name"` // 代表当前实例的名字
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口
Username string `mapstructure:"username" json:"username" yaml:"username"` // ACL登录账号(云Redis自定义账号必填,默认账号留空)
Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码
DB int `mapstructure:"db" json:"db" yaml:"db"` // 单实例模式下redis的哪个数据库
UseCluster bool `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"` // 是否使用集群模式
+1
View File
@@ -11,4 +11,5 @@ type System struct {
UseRedis bool `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"` // 使用redis
UseMongo bool `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"` // 使用mongo
UseStrictAuth bool `mapstructure:"use-strict-auth" json:"use-strict-auth" yaml:"use-strict-auth"` // 使用树形角色分配模式
DisableAutoMigrate bool `mapstructure:"disable-auto-migrate" json:"disable-auto-migrate" yaml:"disable-auto-migrate"` // 自动迁移数据库表结构,生产环境建议设为false,手动迁移
}
+8 -4
View File
@@ -1,6 +1,7 @@
package internal
import (
"fmt"
"os"
"path/filepath"
"sync"
@@ -79,10 +80,13 @@ func (c *Cutter) Write(bytes []byte) (n int, err error) {
if err != nil {
return 0, err
}
err = removeNDaysFolders(c.director, c.retentionDay)
if err != nil {
return 0, err
}
defer func() {
err := removeNDaysFolders(c.director, c.retentionDay)
if err != nil {
fmt.Println("清理过期日志失败", err)
}
}()
c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return 0, err
+66 -1
View File
@@ -1,10 +1,17 @@
package internal
import (
"context"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/service"
astutil "github.com/flipped-aurora/gin-vue-admin/server/utils/ast"
"github.com/flipped-aurora/gin-vue-admin/server/utils/stacktrace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"strings"
"time"
)
@@ -60,7 +67,65 @@ func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
z.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level)
}
}
return z.Core.Write(entry, fields)
// 先写入原日志目标
err := z.Core.Write(entry, fields)
// 捕捉 Error 及以上级别日志并入库,且可提取 zap.Error(err) 的错误内容
if entry.Level >= zapcore.ErrorLevel {
// 避免与 GORM zap 写入互相递归:跳过由 gorm logger writer 触发的日志
if strings.Contains(entry.Caller.File, "gorm_logger_writer.go") {
return err
}
form := "后端"
level := entry.Level.String()
// 生成基础信息
info := entry.Message
// 提取 zap.Error(err) 内容
var errStr string
for i := 0; i < len(fields); i++ {
f := fields[i]
if f.Type == zapcore.ErrorType || f.Key == "error" || f.Key == "err" {
if f.Interface != nil {
errStr = fmt.Sprintf("%v", f.Interface)
} else if f.String != "" {
errStr = f.String
}
break
}
}
if errStr != "" {
info = fmt.Sprintf("%s | 错误: %s", info, errStr)
}
// 附加来源与堆栈信息
if entry.Caller.File != "" {
info = fmt.Sprintf("%s \n 源文件:%s:%d", info, entry.Caller.File, entry.Caller.Line)
}
stack := entry.Stack
if stack != "" {
info = fmt.Sprintf("%s \n 调用栈:%s", info, stack)
// 解析最终业务调用方,并提取其方法源码
if frame, ok := stacktrace.FindFinalCaller(stack); ok {
fnName, fnSrc, sLine, eLine, exErr := astutil.ExtractFuncSourceByPosition(frame.File, frame.Line)
if exErr == nil {
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s lines %d-%d)\n----- 产生日志的方法代码如下 -----\n%s", info, frame.File, frame.Line, fnName, sLine, eLine, fnSrc)
} else {
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s) | extract_err=%v", info, frame.File, frame.Line, fnName, exErr)
}
}
}
// 使用后台上下文,避免依赖 gin.Context
ctx := context.Background()
_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(ctx, &system.SysError{
Form: &form,
Info: &info,
Level: level,
})
}
return err
}
func (z *ZapCore) Sync() error {
+12 -17
View File
@@ -2,16 +2,17 @@ package core
import (
"fmt"
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize"
mcpTool "github.com/flipped-aurora/gin-vue-admin/server/mcp"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
"go.uber.org/zap"
"time"
)
func RunServer() {
if global.GVA_CONFIG.System.UseRedis {
// 初始化redis服务
initialize.Redis()
if global.GVA_CONFIG.System.UseMultipoint {
initialize.RedisList()
@@ -19,36 +20,30 @@ func RunServer() {
}
if global.GVA_CONFIG.System.UseMongo {
err := initialize.Mongo.Initialization()
if err != nil {
if err := initialize.Mongo.Initialization(); err != nil {
zap.L().Error(fmt.Sprintf("%+v", err))
}
}
// 从db加载jwt数据
if global.GVA_DB != nil {
system.LoadAll()
}
Router := initialize.Routers()
address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr)
mcpBaseURL := mcpTool.ResolveMCPServiceURL()
fmt.Printf(`
欢迎使用 gin-vue-admin
当前版本:%s
加群方式:微信号:shouzi_1994 QQ群:470239250
项目地址:https://github.com/flipped-aurora/gin-vue-admin
项目地址:https://github.com/flipped-aurora/gin-vue-admin
授权版体验:https://vip.gin-vue-admin.com
插件市场:https://plugin.gin-vue-admin.com
GVA讨论社区:https://support.qq.com/products/371961
默认自动化文档地址:http://127.0.0.1%s/swagger/index.html
默认MCP SSE地址:http://127.0.0.1%s%s
默认MCP Message地址:http://127.0.0.1%s%s
MCP 独立服务请手动启动: go run ./cmd/mcp -config ./cmd/mcp/config.yaml
默认MCP StreamHTTP地址:%s
默认前端文件运行地址:http://127.0.0.1:8080
--------------------------------------版权声明--------------------------------------
** 版权所有方:flipped-aurora开源团队 **
** 版权持有公司:北京翻转极光科技有限责任公司 **
** 剔除授权标识需购买商用授权:https://gin-vue-admin.com/empower/index.html **
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
`, global.Version, address, mcpBaseURL)
initServer(address, Router, 10*time.Minute, 10*time.Minute)
}
+13 -9
View File
@@ -1,13 +1,13 @@
package core
import (
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/core/internal"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
// Zap 获取 zap.Logger
@@ -24,9 +24,13 @@ func Zap() (logger *zap.Logger) {
core := internal.NewZapCore(levels[i])
cores = append(cores, core)
}
logger = zap.New(zapcore.NewTee(cores...))
// 构建基础 logger(错误级别的入库逻辑已在自定义 ZapCore 中处理)
logger = zap.New(zapcore.NewTee(cores...))
// 启用 Error 及以上级别的堆栈捕捉,确保 entry.Stack 可用
opts := []zap.Option{zap.AddStacktrace(zapcore.ErrorLevel)}
if global.GVA_CONFIG.Zap.ShowLine {
logger = logger.WithOptions(zap.AddCaller())
opts = append(opts, zap.AddCaller())
}
logger = logger.WithOptions(opts...)
return logger
}
+2 -2
View File
@@ -4,9 +4,9 @@ package global
// 目前只有Version正式使用 其余为预留
const (
// Version 当前版本号
Version = "v2.8.5"
Version = "v2.9.2"
// AppName 应用名称
AppName = "Gin-Vue-Admin"
// Description 应用描述
Description = "使用gin+vue进行极速开发的全栈开发基础平台"
)
)
+59 -36
View File
@@ -1,26 +1,31 @@
module github.com/flipped-aurora/gin-vue-admin/server
go 1.23
go 1.24.0
toolchain go1.23.9
toolchain go1.24.2
require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go v1.55.6
github.com/casbin/casbin/v2 v2.103.0
github.com/casbin/gorm-adapter/v3 v3.32.0
github.com/aws/aws-sdk-go-v2 v1.41.2
github.com/aws/aws-sdk-go-v2/config v1.32.10
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1
github.com/casbin/casbin/v3 v3.10.0
github.com/casbin/gorm-adapter/v3 v3.41.0
github.com/dzwvip/gorm-oracle v0.1.2
github.com/fsnotify/fsnotify v1.8.0
github.com/gin-contrib/sse v1.0.0
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/go-sql-driver/mysql v1.8.1
github.com/go-sql-driver/mysql v1.9.3
github.com/goccy/go-json v0.10.4
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/gookit/color v1.5.4
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/mark3labs/mcp-go v0.31.0
github.com/mark3labs/mcp-go v0.41.1
github.com/mholt/archives v0.1.1
github.com/minio/minio-go/v7 v7.0.84
github.com/mojocn/base64Captcha v1.3.8
@@ -31,9 +36,9 @@ require (
github.com/redis/go-redis/v9 v9.7.0
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.24.5
github.com/songzhibin97/gkit v1.2.13
github.com/songzhibin97/gkit v1.5.0
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.10.0
github.com/stretchr/testify v1.11.1
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4
@@ -43,15 +48,17 @@ require (
go.mongodb.org/mongo-driver v1.17.2
go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.32.0
golang.org/x/sync v0.10.0
golang.org/x/text v0.21.0
golang.org/x/crypto v0.46.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.39.0
golang.org/x/text v0.32.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/datatypes v1.2.5
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.11
gorm.io/driver/sqlserver v1.5.4
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlserver v1.6.3
gorm.io/gen v0.3.26
gorm.io/gorm v1.25.12
gorm.io/gorm v1.31.1
)
require (
@@ -61,13 +68,30 @@ require (
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
github.com/aws/smithy-go v1.24.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.0 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/sonic v1.12.7 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/casbin/govaluate v1.10.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
@@ -78,7 +102,6 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gammazero/toposort v0.1.1 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
@@ -99,16 +122,16 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.2 // indirect
github.com/jackc/pgx/v5 v5.8.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@@ -116,7 +139,7 @@ require (
github.com/magiconair/properties v1.8.9 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microsoft/go-mssqldb v1.8.0 // indirect
github.com/microsoft/go-mssqldb v1.9.5 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -125,7 +148,7 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/mozillazg/go-httpheader v0.4.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
@@ -139,6 +162,7 @@ require (
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sijms/go-ora/v2 v2.7.17 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -153,6 +177,7 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
@@ -165,21 +190,19 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/image v0.23.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/protobuf v1.36.3 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/hints v1.1.2 // indirect
gorm.io/plugin/dbresolver v1.5.3 // indirect
modernc.org/fileutil v1.3.0 // indirect
modernc.org/libc v1.61.9 // indirect
gorm.io/plugin/dbresolver v1.6.2 // indirect
modernc.org/fileutil v1.3.40 // indirect
modernc.org/libc v1.67.4 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.34.5 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.42.2 // indirect
)
+156 -100
View File
@@ -19,25 +19,28 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
@@ -54,11 +57,51 @@ github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3 h1:+mQ8NQBh7B7c2FBtppRnwkrmuwFON1XQQ+5yblomZKk=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.3/go.mod h1:u67RKh3BRmS4FYLH+rN3N4T5fqpd9m2ttAwBJYEdosU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9 h1:IJRzQTvdpjHRPItx9gzNcz7Y1F+xqAR+xiy9rr5ZYl8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.9/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1 h1:giB30dEeoar5bgDnkE0q+z7cFjcHaCjulpmPVmuKR84=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.1/go.mod h1:071TH4M3botFLWDbzQLfBR7tXYi7Fs2RsXSiH7nlUlY=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
@@ -69,17 +112,20 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=
github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus=
github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI=
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
github.com/casbin/casbin/v3 v3.10.0 h1:039ORla55vCeIZWd0LfzWFt1yiEA5X4W41xBW2bQuHs=
github.com/casbin/casbin/v3 v3.10.0/go.mod h1:5rJbQr2e6AuuDDNxnPc5lQlC9nIgg6nS1zYwKXhpHC8=
github.com/casbin/gorm-adapter/v3 v3.41.0 h1:Xhpi0tfRP9aKPDWDf6dgBxHZ9UM6IophxxPIEGWqCNM=
github.com/casbin/gorm-adapter/v3 v3.41.0/go.mod h1:BQZRJhwUnwMpI+pT2m7/cUJwXxrHfzpBpPcNTyMGeGA=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -161,16 +207,14 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
@@ -188,8 +232,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -214,12 +256,11 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@@ -247,12 +288,14 @@ github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
@@ -267,10 +310,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -282,8 +321,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -315,18 +354,19 @@ github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4=
github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA=
github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY=
github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw=
github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo=
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
github.com/microsoft/go-mssqldb v1.9.5 h1:orwya0X/5bsL1o+KasupTkk2eNTNFkTQG0BEe/HxCn0=
github.com/microsoft/go-mssqldb v1.9.5/go.mod h1:VCP2a0KEZZtGLRHd1PsLavLFYy/3xX2yJUPycv3Sr2Q=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
@@ -352,8 +392,8 @@ github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/mozillazg/go-httpheader v0.4.0 h1:aBn6aRXtFzyDLZ4VIRLsZbbJloagQfMnCiYgOq6hK4w=
github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
@@ -398,6 +438,7 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -413,10 +454,12 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sijms/go-ora/v2 v2.7.17 h1:M/pYIqjaMUeBxyzOWp2oj4ntF6fHSBloJWGNH9vbmsU=
github.com/sijms/go-ora/v2 v2.7.17/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
github.com/songzhibin97/gkit v1.2.13 h1:paY0XJkdRuy9/8k9nTnbdrzo8pC22jIIFldUkOQv5nU=
github.com/songzhibin97/gkit v1.2.13/go.mod h1:38CreNR27eTGaG1UMGihrXqI4xc3nGfYxLVKKVx6Ngg=
github.com/songzhibin97/gkit v1.5.0 h1:AKSylBJOA37MasyfUxrU1UucILP+1wFESsdNH3BnHdE=
github.com/songzhibin97/gkit v1.5.0/go.mod h1:dWcGE2Hw3C/xqWTrWlUsAD2of4y8sG7i8amYIVjecBw=
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -442,8 +485,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
@@ -473,6 +518,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -525,16 +572,15 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -543,8 +589,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
@@ -566,11 +612,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -594,13 +641,14 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -618,8 +666,10 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -646,15 +696,15 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -664,12 +714,12 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -687,9 +737,12 @@ golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
@@ -722,8 +775,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -760,8 +813,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -772,7 +825,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -780,53 +832,57 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I=
gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/driver/sqlserver v1.6.3 h1:UR+nWCuphPnq7UxnL57PSrlYjuvs+sf1N59GgFX7uAI=
gorm.io/driver/sqlserver v1.6.3/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U=
gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY=
gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=
gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=
gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=
gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=
gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc=
gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00=
modernc.org/ccgo/v4 v4.23.13/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8=
modernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM=
modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg=
modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74=
modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+5
View File
@@ -43,6 +43,7 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
sysModel.JwtBlacklist{},
sysModel.SysDictionary{},
sysModel.SysAutoCodeHistory{},
sysModel.SysAIWorkflowSession{},
sysModel.SysOperationRecord{},
sysModel.SysDictionaryDetail{},
sysModel.SysBaseMenuParameter{},
@@ -54,6 +55,9 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
sysModel.JoinTemplate{},
sysModel.SysParams{},
sysModel.SysVersion{},
sysModel.SysError{},
sysModel.SysLoginLog{},
sysModel.SysApiToken{},
adapter.CasbinRule{},
example.ExaFile{},
@@ -85,6 +89,7 @@ func (e *ensureTables) TableCreated(ctx context.Context) bool {
sysModel.JwtBlacklist{},
sysModel.SysDictionary{},
sysModel.SysAutoCodeHistory{},
sysModel.SysAIWorkflowSession{},
sysModel.SysOperationRecord{},
sysModel.SysDictionaryDetail{},
sysModel.SysBaseMenuParameter{},
+10 -2
View File
@@ -35,6 +35,11 @@ func Gorm() *gorm.DB {
}
func RegisterTables() {
if global.GVA_CONFIG.System.DisableAutoMigrate {
global.GVA_LOG.Info("auto-migrate is disabled, skipping table registration")
return
}
db := global.GVA_DB
err := db.AutoMigrate(
@@ -57,6 +62,9 @@ func RegisterTables() {
system.JoinTemplate{},
system.SysParams{},
system.SysVersion{},
system.SysError{},
system.SysApiToken{},
system.SysLoginLog{},
example.ExaFile{},
example.ExaCustomer{},
@@ -66,14 +74,14 @@ func RegisterTables() {
)
if err != nil {
global.GVA_LOG.Error("register table failed", zap.Error(err))
os.Exit(0)
os.Exit(1)
}
err = bizModel()
if err != nil {
global.GVA_LOG.Error("register biz_table failed", zap.Error(err))
os.Exit(0)
os.Exit(1)
}
global.GVA_LOG.Info("register table success")
}
+5
View File
@@ -10,9 +10,12 @@ package initialize
*/
import (
"time"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
@@ -37,6 +40,7 @@ func GormMssql() *gorm.DB {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Duration(m.ConnMaxLifetime) * time.Second)
return db
}
}
@@ -59,6 +63,7 @@ func GormMssqlByConfig(m config.Mssql) *gorm.DB {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Duration(m.ConnMaxLifetime) * time.Second)
return db
}
}
+3
View File
@@ -5,6 +5,8 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
_ "github.com/go-sql-driver/mysql"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
@@ -43,6 +45,7 @@ func initMysqlDatabase(m config.Mysql) *gorm.DB {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Duration(m.ConnMaxLifetime) * time.Second)
return db
}
}
+3
View File
@@ -1,6 +1,8 @@
package initialize
import (
"time"
oracle "github.com/dzwvip/gorm-oracle"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
@@ -32,6 +34,7 @@ func initOracleDatabase(m config.Oracle) *gorm.DB {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Duration(m.ConnMaxLifetime) * time.Second)
return db
}
}
+3
View File
@@ -4,6 +4,8 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
@@ -38,6 +40,7 @@ func initPgSqlDatabase(p config.Pgsql) *gorm.DB {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(p.MaxIdleConns)
sqlDB.SetMaxOpenConns(p.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Duration(p.ConnMaxLifetime) * time.Second)
return db
}
}
+3
View File
@@ -4,6 +4,8 @@ import (
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
"time"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
@@ -33,6 +35,7 @@ func initSqliteDatabase(s config.Sqlite) *gorm.DB {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(s.MaxIdleConns)
sqlDB.SetMaxOpenConns(s.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Duration(s.ConnMaxLifetime) * time.Second)
return db
}
}
-25
View File
@@ -1,25 +0,0 @@
package initialize
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
mcpTool "github.com/flipped-aurora/gin-vue-admin/server/mcp"
"github.com/mark3labs/mcp-go/server"
)
func McpRun() *server.SSEServer {
config := global.GVA_CONFIG.MCP
s := server.NewMCPServer(
config.Name,
config.Version,
)
global.GVA_MCP_SERVER = s
mcpTool.RegisterAllTools(s)
return server.NewSSEServer(s,
server.WithSSEEndpoint(config.SSEPath),
server.WithMessageEndpoint(config.MessagePath),
server.WithBaseURL(config.UrlPrefix))
}
+24 -1
View File
@@ -6,10 +6,33 @@ import (
)
func InstallPlugin(PrivateGroup *gin.RouterGroup, PublicRouter *gin.RouterGroup, engine *gin.Engine) {
notifier := GetDBReadyNotifier()
// 保存插件初始化参数
notifier.SetPluginParams(&PluginParams{
PrivateGroup: PrivateGroup,
PublicRouter: PublicRouter,
Engine: engine,
})
if global.GVA_DB == nil {
global.GVA_LOG.Info("项目暂未初始化,无法安装插件,初始化后重启项目即可完成插件安装")
global.GVA_LOG.Info("项目暂未初始化,插件将在数据库初始化完成后自动注册")
// 订阅数据库就绪事件
notifier.Subscribe(func() {
global.GVA_LOG.Info("数据库已就绪,开始注册插件")
params := notifier.GetPluginParams()
if params != nil {
bizPluginV1(params.PrivateGroup, params.PublicRouter)
bizPluginV2(params.Engine)
// 重新同步全局路由表,包含插件注册的路由
global.GVA_ROUTERS = params.Engine.Routes()
global.GVA_LOG.Info("插件注册完成")
}
})
return
}
// 数据库已存在,直接注册
bizPluginV1(PrivateGroup, PublicRouter)
bizPluginV2(engine)
}
+2 -2
View File
@@ -1,7 +1,7 @@
package initialize
import (
"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement"
_ "github.com/flipped-aurora/gin-vue-admin/server/plugin"
"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2"
"github.com/gin-gonic/gin"
)
@@ -12,5 +12,5 @@ func PluginInitV2(group *gin.Engine, plugins ...plugin.Plugin) {
}
}
func bizPluginV2(engine *gin.Engine) {
PluginInitV2(engine, announcement.Plugin)
PluginInitV2(engine, plugin.Registered()...)
}
+112
View File
@@ -0,0 +1,112 @@
package initialize
import (
"sync"
"github.com/flipped-aurora/gin-vue-admin/server/service/system"
"github.com/gin-gonic/gin"
)
func init() {
// 注入数据库就绪回调到 service 层
system.SetDBReadyCallback(func() {
GetDBReadyNotifier().NotifyDBReady()
})
}
var (
dbReadyNotifier *DBReadyNotifier
dbReadyNotifierOnce sync.Once
)
// DBReadyNotifier 数据库就绪通知器
type DBReadyNotifier struct {
mu sync.RWMutex
subscribers []func()
dbReady bool
pluginParams *PluginParams // 保存插件初始化所需的参数
}
// PluginParams 插件初始化参数
type PluginParams struct {
PrivateGroup *gin.RouterGroup
PublicRouter *gin.RouterGroup
Engine *gin.Engine
}
// GetDBReadyNotifier 获取全局通知器实例(单例)
func GetDBReadyNotifier() *DBReadyNotifier {
dbReadyNotifierOnce.Do(func() {
dbReadyNotifier = &DBReadyNotifier{
subscribers: make([]func(), 0),
dbReady: false,
}
})
return dbReadyNotifier
}
// Subscribe 订阅数据库就绪事件
// 如果数据库已经就绪,立即执行回调
// 否则将回调加入订阅列表,等待通知
func (n *DBReadyNotifier) Subscribe(callback func()) {
n.mu.Lock()
defer n.mu.Unlock()
if n.dbReady {
// 数据库已就绪,立即执行
callback()
} else {
// 数据库未就绪,加入订阅列表
n.subscribers = append(n.subscribers, callback)
}
}
// SetPluginParams 设置插件初始化参数
func (n *DBReadyNotifier) SetPluginParams(params *PluginParams) {
n.mu.Lock()
defer n.mu.Unlock()
n.pluginParams = params
}
// GetPluginParams 获取插件初始化参数
func (n *DBReadyNotifier) GetPluginParams() *PluginParams {
n.mu.RLock()
defer n.mu.RUnlock()
return n.pluginParams
}
// NotifyDBReady 通知数据库已就绪
// 会触发所有订阅的回调函数
func (n *DBReadyNotifier) NotifyDBReady() {
n.mu.Lock()
if n.dbReady {
// 已经通知过了,避免重复通知
n.mu.Unlock()
return
}
n.dbReady = true
// 复制订阅列表,避免在执行回调时持有锁
callbacks := make([]func(), len(n.subscribers))
copy(callbacks, n.subscribers)
// 清空订阅列表
n.subscribers = nil
// 释放锁后再执行回调,避免死锁
n.mu.Unlock()
// 执行所有订阅的回调
for _, callback := range callbacks {
callback()
}
}
// IsDBReady 检查数据库是否已就绪
func (n *DBReadyNotifier) IsDBReady() bool {
n.mu.RLock()
defer n.mu.RUnlock()
return n.dbReady
}
+2
View File
@@ -16,12 +16,14 @@ func initRedisClient(redisCfg config.Redis) (redis.UniversalClient, error) {
if redisCfg.UseCluster {
client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: redisCfg.ClusterAddrs,
Username: redisCfg.Username,
Password: redisCfg.Password,
})
} else {
// 使用单例模式
client = redis.NewClient(&redis.Options{
Addr: redisCfg.Addr,
Username: redisCfg.Username,
Password: redisCfg.Password,
DB: redisCfg.DB,
})

Some files were not shown because too many files have changed in this diff Show More