Compare commits

...
Author SHA1 Message Date
zhangwenjian c66cb5c6a8 fix: correct the commented-out queue.redis sample in settings.yml
The sample had producer/consumer nested keys (streamMaxLength,
approximateMaxLength, visibilityTimeout, bufferSize, concurrency,
blockingTimeout, reclaimInterval) that don't exist on config.RedisQueue —
checked against sdk/config/queue.go, which only reads addr, password, and the
embedded RedisOptions fields, plus group, key_prefix and max_attempts. Filling
in the old sample as written would compile and start fine, since it's YAML
under a key the struct doesn't declare, and every one of those settings would
be silently ignored.

Replaced with the fields the struct actually has. Still commented out —
redis stays opt-in, this only fixes what filling it in would produce.
2026-08-20 10:54:53 +08:00
wenjianzhang b16ec0af77 Merge pull request #860 from go-admin-team/chore/upgrade-core
chore🔧: upgrade go-admin-core and route the queue through configuration
2026-08-18 22:48:00 +08:00
zhangwenjian 82ea8539eb chore🔧: upgrade go-admin-core and route the queue through configuration
The pinned core dated from April, before sdk stopped being a separate module,
so the build resolved sdk packages from the old module and core packages from
the new one. Dropping the separate requirement is what makes the two agree
again.

Most of the diff is renames that came with that: the tenant accessors gained a
ByTenant suffix, GetDb now returns one database and GetAllDb the map, and
casbin moved to v3.

The change that matters is four call sites moving from GetMemoryQueue to
GetQueuePrefix. GetMemoryQueue returns a queue fixed at construction, so the
login log, the operate log and the api check ran in process no matter what the
settings file selected — a second instance saw none of it. GetQueuePrefix
returns whatever the configuration built, which is the point of being able to
configure a queue at all.

Verified against core at main: build and vet clean. The two file_store failures
are unchanged from before this branch; they need cloud credentials.
2026-08-18 22:14:08 +08:00
wenjianzhang d17d5c1206 Merge pull request #859 from go-admin-team/docs/clarify-demo-sites
docs📝: 标注 antd 演示站对应 go-admin-pro
2026-08-16 19:31:25 +08:00
zhangwenjian 041d22d0d2 docs📝: 标注 antd 演示站对应 go-admin-pro
README 中两个演示地址并排列出、格式与账号密码完全相同,看不出 antd 站对应
的是另一个产品。用户在该站遇到问题时会认为是本仓库的缺陷(见 #857:登录
返回的错误码在本仓库中并不存在)。

仅在链接文字中补充产品名,不改变呈现方式。
2026-08-16 12:04:26 +08:00
wenjianzhang 5864058a81 Merge pull request #858 from go-admin-team/chore/bump-version-2.4.0
chore🔧: 版本号升至 2.4.0
2026-08-16 11:17:51 +08:00
zhangwenjian 45035a16e4 chore🔧: 版本号升至 2.4.0 2026-08-16 11:14:28 +08:00
wenjianzhang f4d0108d49 Merge pull request #855 from go-admin-team/fix/remove-refresh-token-endpoint
fix🐛: 移除 refresh_token 接口,修复 token 可无限续期问题
2026-08-16 11:08:41 +08:00
zhangwenjian b81611ba72 chore🔧: 清理 refresh_token 的残留权限数据
接口移除后,库中仍留有三类记录:sys_api 的接口登记、sys_menu_api_rule 的
菜单绑定、casbin_rule 的策略。留着会让「接口管理」列出一个不存在的端点,
角色配置里也仍可勾选。

- 新装:从 db.sql 与 db-sqlserver.sql 的种子数据中删除该接口
- 已有部署:新增迁移清理,按 path 匹配而非固定 id,因为执行过
  `server -a` 重新注册接口的库中 id 会与官方种子数据不同
2026-08-14 21:42:59 +08:00
zhangwenjian bb34108831 fix🐛: 移除 refresh_token 接口,修复 token 可无限续期问题
close #820

GET /api/v1/refresh_token 用业务 token 即可换取新 token,而续期上限
MaxRefresh 依据的 orig_iat 在每次续期时被一并重置,上限永远无法到达 ——
token 一旦泄露即等同于永久访问权,且无任何吊销手段。

该路由此前还位于 CasbinExclude 中,不受 Casbin 约束,任何角色的已登录用户
都可调用。

官方前端从未使用它:store 中虽有 refreshToken action,但全仓库无一处
dispatch,属死代码。移除不影响正常登录与鉴权流程。

破坏性变更:自行调用该端点实现续期的使用者需改为重新登录。正确的无感续期
应在 go-admin-core 中区分 access token 与 refresh token 后重新实现,不应
沿用此路由。
2026-08-14 21:42:52 +08:00
wenjianzhang b7fd92f39b Merge pull request #854 from go-admin-team/docs/agents-and-demo-module
feat✨: 新增 app/demo 参照模块与 AGENTS.md 规范文档
2026-08-14 21:37:57 +08:00
zhangwenjian 63b800a3ba docs📝: 补充 sqlite3 构建标签与迁移目录说明
driver 配置为 sqlite3 时不带 -tags sqlite3 会在 nil 函数上 panic,
报错不提及构建标签,容易误判为环境损坏;同时说明 version/ 与
version-local/ 的区别,后者已被 gitignore,提交到本仓库的迁移必须放 version/。
2026-08-14 21:17:37 +08:00
zhangwenjian ed9450a2d5 feat✨: 补充 demo 模块的菜单与权限种子数据
一个业务模块要在界面上可用,需要四类数据协同:

  sys_api           后端路由登记,Casbin 据此判定
  sys_menu          侧边栏菜单,含目录 M、菜单 C、按钮 F 三级
  sys_menu_api_rule 菜单与接口的关联,角色保存时据此生成策略
  casbin_rule       实际生效的权限策略

菜单的 menu_name 与前端组件 name 保持一致(DemoProduct),按钮的
permission 与前端 v-permisaction 标识一致(demo:product:add 等)。

策略写入 casbin_rule 而非 sys_casbin_rule:后者对应的 models.CasbinRule
是历史遗留,其 7 列 size:512 唯一索引在 MySQL 下会超出索引长度限制,实际
生效的是 adapter 创建的 casbin_rule 表。

所有写入均为存在则更新、不存在则插入,迁移可安全地在已有数据的库上执行。
实测:在含 67 条菜单、121 条接口的库上执行后各表数据正确;清除版本记录重
跑一次,各表行数不变,确认幂等。
2026-08-14 16:57:55 +08:00
zhangwenjian 1d551a10ab docs📝: 新增 AGENTS.md 与架构说明
AGENTS.md 是给 AI 编码工具与新贡献者的约定,只记录「不遵守就会出错」的
规则,技术栈版本与命令交由 go.mod 和 Makefile 表达,避免文档与代码脱节。
标准写法指向 app/demo/——那是可编译、有测试的参照物,文档与它冲突时以它
为准。

docs/architecture.md 承载不易从代码直接读出的语义:DataScope 五档的过滤
方式、定时任务的 JobExec 接口、多数据源约束、迁移目录的分工。

内容整理自此前未纳入版本控制的 CLAUDE.md,撰写时逐条对照代码核实,修正
了其中两处失效描述(构建工具已非 Vue CLI;JobExec 的方法是 Exec(interface{})
而非 Run(string))。CLAUDE.md 现改为指向 AGENTS.md 的软链,两者不再各自
漂移。
2026-08-14 16:49:48 +08:00
zhangwenjian 4d7c9e5a12 feat✨: 补充 demo 模块的建表迁移
放在 version/ 而非 version-local/:后者已被 .gitignore 忽略,是留给使用
者存放自身迁移脚本的位置,示例迁移需随框架一起分发。文件注释中说明了这
一区分。
2026-08-14 16:46:53 +08:00
zhangwenjian d1f5fe5681 feat✨: 新增 app/demo 标准 CRUD 参照模块
作为编码约定的可执行参照物:文档会滞后,而这个模块过时会导致构建或测试
失败,因此以它为准。

目录骨架与自动注册文件由项目自带的脚手架生成:

  go run main.go app -n demo

它同时产出 cmd/api/demo.go,其中的 init() 将路由追加进 AppRouters,
无需在任何中心文件手工登记。

模块本身演示了单表 CRUD 的推荐写法——直接使用 common/actions 提供的五个
通用 Action,因此只有 model、dto、router 三个业务文件,没有 apis 与
service。手写 Handler 的场景仅在业务超出单表 CRUD 时才需要。

DTO 中详情/删除入参内嵌 dto.ObjectById 以复用其 Bind 与 GetId,不重复
实现 uri 绑定与批量 ids 合并逻辑。

补充 8 项测试锁定通用 Action 的接口约束,其中最关键的是 Generate() 必须
返回副本——Action 在并发请求间复用实例,就地返回会串数据。反向验证:将
Generate 改为就地返回,测试立即失败。
2026-08-14 16:46:09 +08:00
zhangwenjian e8c2e0a966 chore🔧: 修正 .DS_Store 忽略规则
原规则 `*/.DS_Store` 只匹配子目录一层,仓库根目录下的 .DS_Store 不在其
中。改为 `.DS_Store`,匹配任意层级。
2026-08-14 16:45:55 +08:00
wenjianzhang cef0a19a9c Merge pull request #853 from go-admin-team/fix/community-pr-batch
fix🐛: 处理社区 PR 中仍然成立的四项修复
2026-08-14 15:46:43 +08:00
zhangwenjian c0e81363dc docs📝: 修正 Makefile 注释错别字
「实际决对路径」→「实际绝对路径」。

问题由 PR #847 指出。
2026-08-14 15:35:55 +08:00
zhangwenjian df2e4a2b48 fix🐛: 修正欢迎页 iframe 高度塌陷
页面通过 JS 计算并设置 iframe 高度,但 html 与 body 未声明高度,
百分比高度失去参照,iframe 在部分场景下塌陷为 0。

补充 html,body{height:100%} 与 iframe 的 height:100%,并为原先缺失的
overflow-y 声明补上分号。

问题由 PR #829 指出。
2026-08-14 15:35:55 +08:00
zhangwenjian 9088ebc2e1 refactor🎨: 修正文件名拼写 int_router.go → init_router.go
该文件内容为 init() 函数中的路由注册,原文件名少了一个字母。

问题由 PR #787 指出。
2026-08-14 15:35:55 +08:00
zhangwenjian 9f2dec3036 fix🐛: 修正 GeneralDelDto.GetIds 重复追加 Id
该方法先在开头追加了 Id,随后 else 分支中又追加一次:仅传 Id 时返回
[5 5],删除接口会对同一条记录执行两次 DELETE。

  if g.Id != 0 { ids = append(ids, g.Id) }
  if len(g.Ids) > 0 { ... } else {
      if g.Id > 0 { ids = append(ids, g.Id) }   // 重复
  }

去掉冗余分支,同时将首个判断由 != 0 收紧为 > 0,与 Ids 中逐个元素的
过滤条件保持一致(负数 Id 无意义)。

补充单元测试,覆盖仅 Id、仅 Ids、二者并存、含非正数、全空回退等场景。

问题由 PR #848 指出。
2026-08-14 15:35:55 +08:00
wenjianzhang ea049f9b06 Merge pull request #852 from go-admin-team/fix/ci-deploy-guard
fix🐛: 限制部署步骤仅在 master 收到 push 时执行
2026-08-12 15:41:26 +08:00
zhangwenjian 1f1349a685 docs📝: 更新在线体验地址
Element UI vue2 演示站已升级为 Element Plus + Vue 3,域名同步更换为
vue.go-admin.pro。

Arco Design vue3 演示站(vue3.go-admin.dev)已下线,移除对应条目。
2026-08-12 12:27:41 +08:00
zhangwenjian dcef2df38e fix🐛: 限制部署步骤仅在 master 收到 push 时执行
本工作流同时由 push 与 pull_request 触发,而推送镜像与重启服务两步没有
任何事件限制。其后果是:任何指向 master 的 PR 一经创建,就会把 PR 分支
构建出的镜像推送到镜像仓库,并 docker rm -f 掉线上容器、用该镜像重新启
动 API 服务——发生在代码被审查和合并之前。

同仓库分支发起的 PR 可以取到 secrets,因此该路径实际可达;历史运行记录
中已多次出现由 pull_request 事件触发的成功部署。

为两步加上 event_name 与 ref 双重判断。额外判断 ref 是考虑到日后若有人
向 on.push.branches 追加分支,部署不会随之扩散。

Tidy 与 Build 不受影响,PR 仍会执行编译校验。
2026-08-12 12:27:33 +08:00
zhangwenjian 92834d6e39 publish🚀: 版本号更新至 2.3.0 2026-08-12 00:22:31 +08:00
zhangwenjian f06540883b fix🐛: 修复 Docker 镜像发布的 tag 条件失效问题
if 表达式中不应使用 ${{ }} 包裹:startsWith(${{github.ref}}, 'refs/tags/')
会先将 github.ref 替换为裸字符串再参与表达式求值,导致条件判断失效,
使得每次 push 到 master 都会构建并推送镜像至 ghcr.io,而非仅在打 tag 时发布。

同时 on.push 缺少 tags 配置,打 tag 实际不会触发该工作流。

修正后:push 分支仅执行 Go 构建,打 tag 才发布镜像。
2026-08-11 11:09:39 +08:00
zhangwenjian 3c9ce5b6b0 chore🔧: 升级 x/image 修复 TIFF 解码漏洞 2026-08-10 22:17:28 +08:00
zhangwenjian ff8a59550a fix🐛: 修复镜像同步因浅克隆被拒绝的问题 2026-08-10 21:21:01 +08:00
zhangwenjian 7cddef33a2 git🙈: 将 go.sum 纳入版本控制 2026-08-10 20:51:00 +08:00
zhangwenjian 7013c2fa4a chore🔧: 移除依赖已封禁 action 的 issue 自动化流程 2026-08-10 20:51:00 +08:00
zhangwenjian 65bacacb38 docs📝: 更新 README 环境要求版本说明 2026-08-10 20:45:39 +08:00
zhangwenjian 45587028c3 config🔧: CI 升级 Go 版本并更新 Actions 至最新 2026-08-10 20:45:39 +08:00
zhangwenjian 887c9cca4b chore🔧: 升级 Go 至 1.26.5 并同步升级依赖 2026-08-10 20:45:36 +08:00
wenjianzhang a6ddb113fc Update LICENSE.md 2026-08-08 13:55:08 +08:00
wenjianzhang b83eef8670 Fix image source in README.md
Updated image source in README.md for go-admin.
2026-05-22 11:20:39 +08:00
zhangwenjian 43dcd61c51 config🔧: update go-version to 1.24 in build workflow
go.mod requires go 1.24, go mod tidy fails when runner uses 1.18.
2026-05-15 17:52:12 +08:00
zhangwenjian 1bd64d4562 config🔧: pin all GitHub Actions to full-length commit SHAs
Replace version tags (@v1/@v2/@v3/@master) with pinned commit SHAs
across all workflow files to satisfy go-admin-team organization
security policy requiring immutable action references.
2026-05-15 17:48:41 +08:00
zhangwenjian 44e81bc72f git🙈: 补充忽略本地开发配置文件
- 新增忽略 config/settings.local.dev.yml
2026-05-15 17:37:42 +08:00
zhangwenjian 3312f8b7b9 chore🔧: 升级依赖 mergo 模块路径
- 替换 github.com/imdario/mergo 为上游迁移后的 dario.cat/mergo v1.0.1
2026-05-15 17:37:42 +08:00
zhangwenjian d6a2272f9d git🙈: 完善 .gitignore 忽略规则
- 新增忽略编译产物 go-admin-server
- 新增忽略本地工具配置目录
2026-05-15 17:37:42 +08:00
wenjianzhang a5cc0a9e29 Add read and write timeout to HTTP server 2025-09-10 09:39:54 +08:00
wenjianzhang 3f995735e9 Merge pull request #834 from hosea3000/edit-no-confirm
点击编辑的时候不需要弹框确认,交互不太友好
2025-05-20 11:41:02 +08:00
wenjianzhang b65b74dee5 Merge pull request #832 from hosea3000/fix-number-input
fix🐛: 修复自动生成代码时选择字段类型为int64, 前端提交还是string 导致报错的问题
2025-05-20 11:40:21 +08:00
Hosea 98cf3ad95a fix🐛: 点击编辑的时候不需要弹框确认,交互不友好 2025-05-20 10:57:36 +08:00
Hosea 8649d8d791 fix🐛: 修复自动生成代码时选择字段类型为int64, 前端提交还是string 导致报错的问题 2025-05-13 15:48:45 +08:00
wenjianzhang 817e34c6aa refactor🎨: 重构文件上传逻辑,拆分处理函数以提高可读性和维护性 2025-04-13 22:20:06 +08:00
wenjianzhang 952cd92648 refactor🎨: 清理 sys_server_monitor.go 文件,移除未使用的导入并格式化代码 2025-04-13 22:17:17 +08:00
wenjianzhang 6b1e961a7f refactor🎨: 重构系统监控代码,拆分功能为多个函数以提高可读性和维护性 2025-04-13 22:15:10 +08:00
wenjianzhang 762eba5af7 refactor🎨: 重构 Setup 函数,拆分为多个子函数以提高可读性和维护性 2025-04-13 22:04:12 +08:00
wenjianzhang e82128f679 refactor🎨: 优化获取客户端 IP 的逻辑,增加对 X-Forwarded-For 和 X-Real-IP 的处理 2025-04-13 22:00:04 +08:00
wenjianzhang 8f8a197db1 delete🎉: 移除示例代码 run.go 2025-04-08 20:49:36 +08:00
wenjianzhang 364854eda0 docs📝: 更新 go-admin 版本号至 2.2.0 2025-04-08 20:49:29 +08:00
wenjianzhang b259e91f4d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	go.mod
2025-04-08 20:45:25 +08:00
wenjianzhang 76411f80bc refactor🎨: 优化日志记录方式,统一使用 log.Info 替代 log.Println 2025-04-08 20:30:35 +08:00
wenjianzhang 5494353229 fix🐛: 修复获取本地主机IP的函数调用错误 2025-04-08 20:30:24 +08:00
wenjianzhang afe5efbe36 refactor🎨: remove unused distributed lock setup code in initialize.go 2025-04-08 20:30:05 +08:00
wenjianzhang db422785fc fix🐛: include captcha answer in GenerateCaptchaHandler for improved logging 2025-04-08 20:29:38 +08:00
wenjianzhang 4ac68323da fix: improve error logging in jobbase.go for better clarity 2025-04-08 20:23:23 +08:00
wenjianzhang 44002fcb11 chore: update dependencies in go.mod to latest versions 2025-04-08 20:22:59 +08:00
wenjianzhang 5bbd919745 chore: update dependencies in go.mod to latest versions 2025-03-25 17:16:40 +08:00
wenjianzhang 54dd3de5b6 chore: update dependencies in go.mod to latest versions 2025-03-25 16:48:47 +08:00
wenjianzhang 9540fdfc30 refactor: remove unused GetMenuIDS function and clean up code 2025-03-25 16:44:31 +08:00
wenjianzhang 937775e2a7 chore: update Go version from 1.21 to 1.24 in build configuration 2025-03-25 08:53:39 +08:00
wenjianzhang 84721265dd fix: simplify error handling in GenerateCaptchaHandler 2025-03-24 22:41:02 +08:00
wenjianzhang 3ab67dfa7d chore: update Go version from 1.21 to 1.24 2025-03-24 20:53:09 +08:00
wenjianzhang 6a1941a820 Merge pull request #814 from keemozhang/master
fix🐛: declaration of new local variable causes transactions to be ign…
2025-03-21 15:36:16 +08:00
wenjianzhang 3ae7c44585 Merge pull request #816 from Tiper-In-Github/patch-1
Fix:err is never used
2025-03-21 15:35:23 +08:00
wenjianzhang 9d809f6392 Merge pull request #821 from pigwantacat/master
fix:修复定时任务的日志打印
2024-12-18 00:11:54 +08:00
pigwantacat 4b477b3103 fix:修复定时任务的日志打印 2024-11-01 14:14:30 +08:00
wenjianzhang e7ae2fe019 更新 go_admin.go 2024-10-30 22:12:17 +08:00
wenjianzhang d5ba3d9770 更新 READMEN.md 2024-10-30 22:10:06 +08:00
Akiraka f3d744f6f5 修复获取getinfo时候,userName 事件结果为 nickName 问题 2024-10-24 09:30:15 +08:00
无别 0315631b53 Fix:err is never used
Fix the problem that err is overwritten and becomes invalid
2024-09-29 15:32:36 +08:00
wenjianzhang 48e7ce88ff perf👌: rollback base64Captcha 2024-09-09 15:46:05 +08:00
wenjianzhang 357db6b1c9 Merge remote-tracking branch 'origin/master' 2024-09-08 22:04:20 +08:00
wenjianzhang 83e0531f43 perf👌: format 2024-09-08 22:04:08 +08:00
wenjianzhang 898ba7d8eb Update README.Zh-cn.md 2024-09-06 23:11:48 +08:00
wenjianzhang 8751f34539 perf👌: correct attribute definition 2024-09-05 18:33:11 +08:00
wenjianzhang 4aa0068d2d perf👌: update SysDept Get First to FirstOrInit 2024-09-05 18:29:50 +08:00
keemozhang b954a2f092 fix🐛: declaration of new local variable causes transactions to be ignored 2024-09-05 15:46:20 +08:00
wenjianzhang 9227bd2be1 perf👌: format code 2024-09-04 20:25:02 +08:00
wenjianzhang e70a0b1314 perf👌: update SysConfig Get First to FirstOrInit 2024-09-04 20:22:49 +08:00
wenjianzhang 21c262a31e perf👌: update build file 2024-09-03 22:17:11 +08:00
wenjianzhang f30889bd19 perf👌: update SysApi Get Func First to FirstOrInit 2024-09-03 21:22:09 +08:00
wenjianzhang 23e519999e perf👌: update go mod 2024-08-30 16:00:29 +08:00
wenjianzhang bedf064ace Merge pull request #802 from zhanluxianshen/drop-base-model
replace basemodel by common.model
2024-08-29 16:26:22 +08:00
wenjianzhang 9a8e0cddde Merge pull request #803 from zhanluxianshen/clean-err-use-in-method
clean err define in methods.
2024-08-29 16:23:54 +08:00
wenjianzhang c0c16036d3 Merge pull request #811 from wangle201210/fix/logger
fix🐛: reset default logger fields
2024-08-29 16:17:18 +08:00
wanna dd905a2bed fix🐛: reset default logger fields 2024-08-23 16:49:21 +08:00
zhanluxianshen 2d76430f89 clean err define in methods.
Signed-off-by: zhanluxianshen <zhanluxianshen@163.com>
2024-07-10 15:03:33 +08:00
zhanluxianshen 5dde1d2a00 replace basemodel by common.model
Signed-off-by: zhanluxianshen <zhanluxianshen@163.com>
2024-07-10 11:26:52 +08:00
lwnmengjing 93f25c6cdf Add mss-boot-io link 2023-11-07 23:28:52 +08:00
wenjianzhang d366df372d feat✨: Log file size control and retention days control 2023-11-03 18:59:20 +08:00
wenjianzhang 7281d05efc fix🐛: Fixed system startup Network output problem 2023-11-03 17:42:01 +08:00
wenjianzhang e6d6a65267 Merge remote-tracking branch 'origin/master' 2023-11-03 17:36:21 +08:00
wenjianzhang 9ff094b6f5 fix🐛: Fixed data migration issue during multi-tenant configuration 2023-11-03 17:36:04 +08:00
wenjianzhang fc9c253a9f tag📌: Upgrade go1.21 2023-11-03 17:35:01 +08:00
wenjianzhang 9d735ed5aa docs📝: Update README.md 2023-11-02 17:42:40 +08:00
wenjianzhang d782b00117 tag📌: Change version 2023-11-02 17:10:52 +08:00
wenjianzhang 239159dd2a fix🐛: Fix the problem that el-popconfirm does not take effect 2023-11-02 17:08:58 +08:00
wenjianzhang 0c1e91c3b5 Add files via upload 2023-10-11 21:25:01 -05:00
wenjianzhang c09347b387 Merge pull request #768 from majiayu000/fix-pgerror
[BugFix] 修复一个``引发的bug
2023-10-11 21:18:42 -05:00
lif e49e47c7a1 Delete go.mod 2023-09-22 14:02:38 +08:00
wenjianzhang 98b46535aa Merge pull request #767 from zgxme/fix-gen-0909
[fix](gen) ignore default time type columns in table
2023-09-21 22:00:57 +08:00
lif 014a23aac3 [BugFix] Fix pgsql error with 2023-09-14 16:35:41 +08:00
zgxme f1dfba79e0 [fix](gen) ignore default time type columns in table 2023-09-09 22:59:45 +08:00
wenjianzhang a282e44b1d Merge pull request #753 from Vingurzhou/master
-installsuffix 参数没有指定后缀字符串。它被设定为空,这可能导致一些问题
2023-08-02 09:24:48 +08:00
wenjianzhang d1279e67fb Merge pull request #757 from NipGeihou/master
fix: 修复go generate命令不更新Swagger文档问题
2023-08-02 09:22:25 +08:00
wenjianzhang 37a5963cd6 perf👌: Optimize go warnings 2023-08-01 22:38:41 +08:00
wenjianzhang 69df1b3d34 perf👌: Remove unused attributes 2023-08-01 22:12:18 +08:00
wenjianzhang eae97f7a15 perf👌: update version 2023-08-01 22:08:47 +08:00
wenjianzhang ce0b5ff7bf perf👌: update version 2023-08-01 22:08:43 +08:00
NipGeihou 73118e49b9 fix: 修复go generate命令不更新Swagger文档问题
修复go generate不更新Swagger文档问题,并更新生成后文档文件
2023-06-20 00:37:59 +08:00
Vingurzhou 7b43982595 Update Makefile
fix(makefile): -installsuffix 参数没有指定后缀字符串。它被设定为空,这可能导致一些问题
2023-06-10 15:57:18 +08:00
wenjianzhang 31cd1ee768 Merge pull request #741 from wwhai/patch-2
fix: change 'os.Signal' channel to buffered
2023-05-14 11:04:04 +08:00
wenjianzhang 8df7551946 Merge pull request #740 from llussy/patch
fix setting.yml spelling
2023-05-14 11:03:47 +08:00
wenjianzhang 79c1295a70 Merge pull request #749 from sincatter/master_fix_pg_migrate
处理postgres迁移时insert提示类型不匹配问题
2023-05-14 11:03:07 +08:00
wenjianzhang 26d9a2e9e4 Merge pull request #747 from go-admin-team/dependabot/go_modules/golang.org/x/net-0.7.0
build(deps): bump golang.org/x/net from 0.0.0-20220722155237-a158d28d115b to 0.7.0
2023-05-13 15:38:46 +08:00
wenjianzhang b09b7b6ccc Merge pull request #729 from go-admin-team/dependabot/go_modules/github.com/prometheus/client_golang-1.11.1
build(deps): bump github.com/prometheus/client_golang from 1.11.0 to 1.11.1
2023-05-13 15:38:13 +08:00
dependabot[bot] ef6fdaa221 build(deps): bump golang.org/x/net
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220722155237-a158d28d115b to 0.7.0.
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-13 07:38:07 +00:00
wenjianzhang 23c80f0217 Merge pull request #735 from go-admin-team/dependabot/go_modules/golang.org/x/text-0.3.8
build(deps): bump golang.org/x/text from 0.3.7 to 0.3.8
2023-05-13 15:37:14 +08:00
wenjianzhang 95dc699e2e Merge pull request #746 from haimait/fix_edit_master_role模块添加注释
fix_edit_master_role模块添加注释
2023-05-13 15:36:29 +08:00
wenjianzhang 74b7d62c75 处理postgres迁移时insert提示类型不匹配问题 2023-05-13 00:00:35 +08:00
wanghaima 68accd5448 fix_edit_master_role模块添加注释 2023-05-07 19:33:00 +08:00
wenjianzhang 3edbef8696 Merge pull request #745 from haimait/fix_edit_master_优化api筛选
优化API管理筛选
2023-05-07 19:22:23 +08:00
wanghaima 5527f6386a 优化API管理筛选 2023-05-07 18:47:36 +08:00
wwhai c48f70a7c6 fix: change 'os.Signal' channel to buffered 2023-05-04 23:25:43 +08:00
llussy a078e31664 fix setting.yml 2023-04-26 15:59:19 +08:00
wenjianzhang 04d2d7dde1 format🥚: Exclude empty permission identification 2023-04-19 18:50:35 +08:00
Akiraka b846053bea 恢复 common/middleware/demo.go 2023-04-14 19:51:48 +08:00
Akiraka a1a5634c4e 恢复修改 2023-04-14 19:51:22 +08:00
Akiraka 6036c6e4e3 接受参数位置错误 2023-04-14 19:07:01 +08:00
dependabot[bot] 1bf9f74bdf build(deps): bump golang.org/x/text from 0.3.7 to 0.3.8
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-23 00:08:28 +00:00
dependabot[bot] 79fb2d0bee build(deps): bump github.com/prometheus/client_golang
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-15 01:38:52 +00:00
105 changed files with 4099 additions and 953 deletions
+1 -1
View File
@@ -62,5 +62,5 @@ Describe changes from the user side, and list all potential break changes or oth
- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] TypeScript definition is updated/provided or not needed
- [ ] TypeScript's definition is updated/provided or not needed
- [ ] Changelog is provided or not needed
+10 -4
View File
@@ -16,12 +16,12 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
go-version: 1.26.5
- name: Tidy
run: go mod tidy
@@ -29,7 +29,12 @@ jobs:
- name: Build
run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main .
# 以下推镜像与重启步骤仅在 master 收到 push 时执行。
# pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的
# PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务,
# 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。
- name: Build the Docker image and push
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
run: |
docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
echo "************ docker login end"
@@ -43,7 +48,8 @@ jobs:
echo "************ docker push end"
- name: Restart server # 第五步,重启服务
uses: appleboy/ssh-action@master
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
env:
GITHUB_SHA_X: ${GITHUB_SHA}
with:
+4 -4
View File
@@ -19,11 +19,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -34,7 +34,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -48,4 +48,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@24c7eb380a2dc368f2d129e4c65e51d172983a1e # v4
+15 -10
View File
@@ -3,6 +3,7 @@ name: build
on:
push:
branches: [ master, dev ]
tags: [ 'v*', '[0-9]*' ]
pull_request:
branches: [ master ]
env:
@@ -16,23 +17,27 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.18
uses: actions/setup-go@v3
- name: Set up Go 1.26
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
go-version: 1.26.5
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Get dependencies
run: go mod tidy
- name: Build
run: make build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
if: startsWith(github.ref, 'refs/tags/')
- name: Log in to the Container registry
uses: docker/login-action@v2
if: startsWith(${{github.ref}}, 'refs/tags/')
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
if: startsWith(github.ref, 'refs/tags/')
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -40,8 +45,8 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
if: startsWith(${{github.ref}}, 'refs/tags/')
uses: docker/metadata-action@v4
if: startsWith(github.ref, 'refs/tags/')
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
@@ -52,8 +57,8 @@ jobs:
type=sha,prefix=,format=long,enable=true,priority=100
- name: Build and push Docker image
uses: docker/build-push-action@v3
if: startsWith(${{github.ref}}, 'refs/tags/')
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
if: startsWith(github.ref, 'refs/tags/')
with:
context: .
file: scripts/Dockerfile
@@ -1,22 +0,0 @@
name: Issue Check Inactive
on:
schedule:
- cron: "0 0 */15 * *"
permissions:
contents: read
jobs:
issue-check-inactive:
permissions:
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
steps:
- name: check-inactive
uses: actions-cool/issues-helper@v3
with:
actions: 'check-inactive'
inactive-label: 'Inactive'
inactive-day: 30
-32
View File
@@ -1,32 +0,0 @@
name: Issue Close Require
on:
schedule:
- cron: "0 0 * * *"
permissions:
contents: read
jobs:
issue-close-require:
permissions:
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
steps:
- name: need reproduce
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: '🤔 Need Reproduce'
inactive-day: 3
- name: needs more info
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: 'needs-more-info'
inactive-day: 3
body: |
Since the issue was labeled with `needs-more-info`, but no response in 3 days. This issue will be closed. If you have any questions, you can comment and reply.
由于该 issue 被标记为需要更多信息,却 3 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
-76
View File
@@ -1,76 +0,0 @@
# Origin Source
# https://github.com/ant-design/ant-design/blob/79f566b7f8abb1012ef55b0d2793bfdf5595b85d/.github/workflows/issue-reply.yml
name: Issue Labeled
on:
issues:
types: [labeled]
permissions:
contents: read
jobs:
issue-labeled:
permissions:
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
runs-on: ubuntu-latest
steps:
- name: help wanted
if: github.event.label.name == 'help wanted'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
token: ${{ secrets.ADMIN_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to [send us a Pull Request](https://help.github.com/en/articles/creating-a-pull-request) for it. Please send your Pull Request to proper branch (feature branch for the new feature, master for bugfix and other changes), fill the [Pull Request Template](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE.md) here, provide changelog/TypeScript/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution!
你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎直接在此仓库 [创建一个 Pull Request](https://help.github.com/en/articles/creating-a-pull-request) 来解决这个问题。请将 Pull Request 发到正确的分支(新特性发到 feature 分支,其他发到 master 分支),务必填写 Pull Request 内的[预设模板](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE.md),提供改动所需相应的 changelog、TypeScript 定义、测试用例、文档等,并确保 CI 通过,我们会尽快进行 Review,提前感谢和期待您的贡献。
![giphy](https://user-images.githubusercontent.com/507615/62342668-4735dc00-b51a-11e9-92a7-d46fbb1cc0c7.gif)
- name: 🤔 Need Reproduce
if: github.event.label.name == '🤔 Need Reproduce'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
token: ${{ secrets.ADMIN_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by forking this link https://u.ant.design/codesandbox-repro or a minimal GitHub repository. Issues labeled by `Need Reproduce` will be closed if no activities in 3 days.
你好 @${{ github.event.issue.user.login }}, 我们需要你提供一个在线的重现实例以便于我们帮你排查问题。你可以通过点击 [此处](https://u.ant.design/codesandbox-repro) 创建一个 codesandbox 或者提供一个最小化的 GitHub 仓库。3 天内未跟进此 issue 将会被自动关闭。
![](https://gw.alipayobjects.com/zos/antfincdn/y9kwg7DVCd/reproduce.gif)
- name: Usage
if: github.event.label.name == 'Usage' || github.event.label.name == 'Question'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.ADMIN_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, we use GitHub issues to trace bugs or discuss plans of Ant Design. So, please [don't ask usage questions](https://github.com/ant-design/ant-design/issues/2320) here. You can try to open a new discussion in [antd discussions](https://github.com/ant-design/ant-design/discussions), select `Q&A` to ask questions, also can ask questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/antd) or [Segment Fault](https://segmentfault.com/t/antd), then apply tag `antd` and `react` to your question.
你好 @${{ github.event.issue.user.login }},go-admin Issue 板块是用于 bug 反馈与需求讨论的地方。请[勿询问如何使用的问题](https://github.com/go-admin-te/ant-design/issues/699),你可以试着在 [antd discussions](https://github.com/ant-design/ant-design/discussions) 新开一个 discussion,选择 `Q&A` 类别进行提问,也可以在 [Stack Overflow](http://stackoverflow.com/questions/tagged/antd) 或者 [Segment Fault](https://segmentfault.com/t/antd) 中提问(记得添加 `antd` 和 `react` 标签哦~)。
- name: 3.x
if: github.event.label.name == '3.x'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.ADMIN_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hi @${{ github.event.issue.user.login }}. Current version (3.x) is off the maintenance period. We may not accept pull request or fix bug with it anymore. This topic will be auto closed.
你好 @${{ github.event.issue.user.login }},当前版本(3.x)已经过了维护期。我们不会再接受对其的相关 PR 与 issue。当前 topic 会被自动关闭。
- name: invalid
if: github.event.label.name == 'Invalid'
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.ADMIN_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, your issue has been closed because it does not conform to our issue requirements. Please use the [Issue Helper](https://new-issue.go-admin.dev) to create an issue, thank you!
你好 @${{ github.event.issue.user.login }},为了能够进行高效沟通,我们对 issue 有一定的格式要求,你的 issue 因为不符合要求而被自动关闭。你可以通过 [issue 助手](https://new-issue.go-admin.dev) 来创建 issue 以方便我们定位错误。谢谢配合!
- name: rtl
if: github.event.label.name == 'rtl'
uses: actions-cool/issues-helper@v3
with:
actions: 'add-assignees'
assignees: 'xrkffgg'
+8 -4
View File
@@ -7,9 +7,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v1
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: 'Mirror to gitee'
uses: pixta-dev/repository-mirroring-action@v1
uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1
with:
target_repo_url:
git@gitee.com:go-admin-team/go-admin.git
@@ -19,9 +21,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v1
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: 'Mirror to gitlab'
uses: pixta-dev/repository-mirroring-action@v1
uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1
with:
target_repo_url:
git@gitlab.com:go-admin-team/go-admin.git
+5 -5
View File
@@ -1,6 +1,6 @@
.idea
.vscode
*/.DS_Store
.DS_Store
static/uploadfile
main.exe
*.exe
@@ -10,7 +10,6 @@ temp/
!temp
vendor
config/settings.dev.yml
common/middleware/demo.go
config/settings.dev.*.yml
config/settings.dev.*.yml.log
temp/logs
@@ -18,8 +17,9 @@ config/settings.dev.yml.log
config/settings.b.dev.yml
cmd/migrate/migration/version-local/*
!cmd/migrate/migration/version-local/doc.go
*/.DS_Store
# go sum
go.sum
config/settings.deva.yml
go-admin-server
CLAUDE.md
.claude/
config/settings.local.dev.yml
+206
View File
@@ -0,0 +1,206 @@
# AGENTS.md — go-admin 后端
> 给 AI 编码工具与新贡献者的约定。**只写"不遵守就会出错"的规则**;技术栈版本以
> `go.mod` 为准,命令以 `Makefile` 为准,此处不复述,避免与代码脱节。
>
> 标准 CRUD 模块的完整写法见 **`app/demo/`** —— 那是可编译、有测试、CI 会跑的参照物。
> 本文与它冲突时,以 `app/demo/` 为准。
## 分层
```
Router → Api → Service → Model
路由注册 参数绑定 业务逻辑 GORM 结构体
中间件链 调用 Service 操作数据库 TableName()
```
对应目录:`app/{模块}/router|apis|service|models`,DTO 位于 `service/dto`。
**不可跨层**:Api 不直接操作 `Orm`,Service 不接触 `gin.Context`。
## 优先使用通用 Action
单表 CRUD **不要手写 Handler 与 Service**。`common/actions` 提供的五个
Action 已覆盖参数绑定、数据权限过滤、操作人注入、分页与错误响应:
```go
r := v1.Group("/demo-product").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
m := &models.DemoProduct{}
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
list := make([]models.DemoProduct, 0); return &list
}))
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
return &models.DemoProduct{}
}))
r.POST("", actions.CreateAction(new(dto.DemoProductControl)))
r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl)))
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById)))
}
```
这样一个模块只需 **model + dto + router** 三个文件,完整示例见 `app/demo/`。
使用通用 Action 的前提:
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`)
- 列表 DTO 实现 `dto.Index`,增改删 DTO 实现 `dto.Control`
- **所有 `Generate()` 必须返回副本** —— Action 在并发请求间复用实例,
就地返回会串数据(`app/demo` 的测试锁定了这一点)
- 详情/删除 DTO 内嵌 `dto.ObjectById` 即可继承 `Bind` 与 `GetId`,无需重写
仅当业务超出单表 CRUD(跨表事务、外部调用、复杂校验)时才自行编写 Handler
与 Service,写法见下。
## Api 层(仅在通用 Action 不适用时)
结构体嵌入 `api.Api`,链式初始化后**必须检查 `Errors`**:
```go
func (e SysPost) GetPage(c *gin.Context) {
s := service.SysPost{}
req := dto.SysPostPageReq{}
err := e.MakeContext(c).MakeOrm().Bind(&req, binding.Form).MakeService(&s.Service).Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// ... 调用 s.GetPage(...)
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
```
响应一律走 `e.OK` / `e.PageOK` / `e.Error`,不要自行 `c.JSON`。
## Service 层(仅在通用 Action 不适用时)
结构体嵌入 `service.Service`(持有 `Orm` 与 `Log`)。查询通过 Scopes 组合:
```go
err = e.Orm.Model(&data).Scopes(
cDto.MakeCondition(c.GetNeedSearch()), // 由 search tag 生成 WHERE
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p), // 数据权限,列表/详情必须带
).Find(list).Limit(-1).Offset(-1).Count(count).Error
```
**遗漏 `actions.Permission` 会使数据权限配置静默失效** —— 这是最容易出的错。
错误一律 `return err` 向上传递,日志用 `e.Log.Errorf`,不使用 `panic`。
## DTO
搜索条件由 tag 声明,`MakeCondition` 据此拼 SQL:
```go
type SysPostPageReq struct {
dto.Pagination `search:"-"`
PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post"`
}
func (m *SysPostPageReq) GetNeedSearch() interface{} { return *m }
```
`type` 可选:`exact` `iexact` `contains` `gt` `gte` `lt` `lte` `order` `left`(联表)。
## Model
```go
type SysPost struct {
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"`
// ... 业务字段
models.ControlBy // CreateBy / UpdateBy
models.ModelTime // CreatedAt / UpdatedAt / DeletedAt
}
func (SysPost) TableName() string { return "sys_post" }
```
`TableName()` 必须显式声明(GORM 配置了 `SingularTable`,不会自动推导复数)。
## 路由注册
通过 `init()` 自注册,不在中心文件手工添加:
```go
func init() { routerCheckRole = append(routerCheckRole, registerSysPostRouter) }
func registerSysPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysPost{}
r := v1.Group("/post").
Use(authMiddleware.MiddlewareFunc()).
Use(middleware.AuthCheckRole()). // Casbin 鉴权
Use(actions.PermissionAction()) // 注入数据权限
{ r.GET("", api.GetPage); r.POST("", api.Insert); /* ... */ }
}
```
新增路由文件后,需确认 `cmd/api/` 中已用 `_` 导入该包。
## 命名
| 对象 | 规则 | 示例 |
|---|---|---|
| 数据表 | `sys_` 前缀 + 下划线 | `sys_post` |
| API 路径 | `/api/v1/` + kebab-case | `/api/v1/sys-user` |
| DTO | `{Model}{Action}Req` | `SysPostPageReq` |
| 权限标识 | `模块:资源:操作` | `admin:sysPost:add` |
权限标识需与前端 `v-permisaction` 一致,并写入 `sys_menu` 种子数据。
## Swagger
Handler 必须带完整注解,`go generate` 会据此生成文档:
```go
// @Summary 岗位列表
// @Tags 岗位
// @Success 200 {object} response.Response
// @Router /api/v1/post [get]
// @Security Bearer
```
## 本地运行
**配置 `driver: sqlite3` 时必须带构建标签**,否则启动即 panic:
```bash
go run -tags sqlite3 . migrate -c config/settings.sqlite.yml
go run -tags sqlite3 . server -c config/settings.sqlite.yml
```
原因:`common/database/open.go` 带 `//go:build !sqlite3`,不加标签时编进的是
不含 sqlite3 的版本,`opens["sqlite3"]` 为 nil,调用时在 nil 函数上崩溃。
报错信息不会提到构建标签,容易误判成环境损坏。MySQL / PostgreSQL 无此问题。
对应 `Makefile` 的 `build-sqlite` 目标。
## 数据库迁移
文件名前 13 位为时间戳版本号。**已执行过的迁移文件不可修改** ——
`sys_migration` 表按版本号去重,改动不会重跑,只能新增一个迁移来修正。
放哪个目录取决于身份:
| 目录 | 用途 | 是否入库 |
|---|---|---|
| `version/` | 框架自带迁移,随仓库分发给所有使用者 | 是 |
| `version-local/` | 使用者自己项目的迁移 | 否(已在 `.gitignore`) |
**向本仓库提交迁移必须放 `version/`** —— 放进 `version-local/` 会被忽略掉,
`git status` 看不到,PR 里也不会出现。两个目录的包名分别是 `version` 与
`version_local`(后者与目录名不一致,因为标识符不能含连字符)。
## 提交规范
格式 `type+emoji: 描述`:
`feat✨` `fix🐛` `style💄` `docs📝` `perf👌` `test✅` `refactor🎨` `chore🔧`
一个提交只做一件事。改动跨越多个语义时拆分提交,不要混在一起。
## 红线
- 不使用全局 DB 变量,一律用 `e.Orm`(来自请求上下文,多租户依赖它)
- 不在 Service 中引用 `gin.Context`
- 生产部署前确认 `mode: prod` 且已修改 `jwt.secret`(dev 模式下 token 几乎不过期)
- 不提交 `config/settings.yml` 中的真实凭据
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 go-admin-team
Copyright (c) 2026 go-admin-team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+3 -3
View File
@@ -2,7 +2,7 @@ PROJECT:=go-admin
.PHONY: build
build:
CGO_ENABLED=0 go build -ldflags="-w -s" -a -installsuffix -o go-admin .
CGO_ENABLED=0 go build -ldflags="-w -s" -a -installsuffix "" -o go-admin .
# make build-linux
build-linux:
@@ -21,7 +21,7 @@ run:
# 进入到项目根目录 执行 make run 命令
@docker-compose up -d
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际决对路径
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际绝对路径
#@docker run --name=go-admin -p 8000:8000 -v /home/code/go/src/go-admin/go-admin/config:/go-admin-api/config -v /home/code/go/src/go-admin/go-admin-api/static:/go-admin/static -v /home/code/go/src/go-admin/go-admin/temp:/go-admin-api/temp -d --restart=always go-admin:latest
@echo "go-admin service is running..."
@@ -51,4 +51,4 @@ deploy:
#@git checkout master
#@git pull origin master
make build-linux
make run
make run
+16 -17
View File
@@ -19,13 +19,10 @@
## 🎬 在线体验
Element UI vue体验:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login)
Element Plus vue3 体验:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
antd体验:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
antd 体验(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
## ✨ 特性
@@ -109,11 +106,11 @@ antd体验:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
### 环境要求
go 1.18
go 1.26.5
node版本: v14.16.0
node版本: v22+(推荐 v24 LTS)
npm版本: 6.14.11
包管理器: pnpm v9+(UI 项目使用 pnpm)
### 开发目录创建
@@ -153,14 +150,14 @@ go build
# 修改配置
# 文件路径 go-admin/config/settings.yml
vi ./config/setting.yml
vi ./config/settings.yml
# 1. 配置文件中修改数据库信息
# 注意: settings.database 下对应的配置数据
# 2. 确认log路径
```
:::tip ⚠️注意 在windows环境如果没有安装中CGO,会出现这个问题;
⚠️注意 在windows环境如果没有安装中CGO,会出现这个问题;
```bash
E:\go-admin>go build
@@ -178,7 +175,6 @@ cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
[解决cgo问题进入](https://doc.go-admin.dev/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
:::
#### 初始化数据库,以及服务启动
@@ -238,14 +234,17 @@ env GOOS=linux GOARCH=amd64 go build main.go
### UI交互端启动说明
```bash
# 安装依赖
npm install
# 安装 pnpm(若未安装)
npm install -g pnpm
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npmmirror.com
# 安装依赖
pnpm install
# 国内网络可指定镜像源加速
pnpm install --registry=https://registry.npmmirror.com
# 启动服务
npm run dev
pnpm dev
```
## 📨 互动
@@ -350,4 +349,4 @@ npm run dev
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2022 wenjianzhang
Copyright (c) 2024 wenjianzhang
+15 -14
View File
@@ -1,7 +1,7 @@
# go-admin
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/203c5930b9ed08d5cf2fcb4516b85e412f8e0e60/img/go-admin.svg">
[![Build Status](https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg)](https://github.com/go-admin-team/go-admin)
@@ -20,13 +20,10 @@ The front-end and back-end separation authority management system based on Gin +
## 🎬 Online Demo
Element UI vue demo:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login)
Element Plus vue3 demo:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
> 账号 / 密码: admin / 123456
Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login)
> 账号 / 密码: admin / 123456
antd demo:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
antd demo (go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> 账号 / 密码: admin / 123456
>
## ✨ Feature
@@ -107,11 +104,11 @@ At the same time, a series of tutorials including videos and documents are provi
### Environmental requirements
go 1.18
go 1.26.5
nodejs: v14.16.0
nodejs: v22+ (v24 LTS recommended)
npm: 6.14.11
package manager: pnpm v9+ (the UI project uses pnpm)
### Development directory creation
@@ -151,14 +148,14 @@ go build
# Change setting
# File path go-admin/config/settings.yml
vi ./config/setting.yml
vi ./config/settings.yml
# 1. Modify the database information in the configuration file
# Note: The corresponding configuration data under settings.database
# 2. Confirm the log path
```
:::tip ⚠️Note that this problem will occur if CGO is not installed in the windows environment;
:::tip ⚠️Note that this problem will occur if CGO is not installed in the windows10+ environment;
```bash
E:\go-admin>go build
@@ -229,11 +226,14 @@ env GOOS=linux GOARCH=amd64 go build main.go
### UI interactive terminal startup instructions
```bash
# Install pnpm if you don't have it
npm install -g pnpm
# Installation dependencies
npm install # or cnpm install
pnpm install
# Start service
npm run dev
pnpm dev
```
## 📨 Interactive
@@ -332,7 +332,8 @@ The `go-admin` project has always been developed in the GoLand integrated develo
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## 🤝 Link
[Go developer growth roadmap](http://www.golangroadmap.com/)
- [Go developer growth roadmap](http://www.golangroadmap.com/)
- [mss-boot-io](https://docs.mss-boot-io.top/)
## 🔑 License
+3 -3
View File
@@ -17,17 +17,17 @@ type System struct {
// @Success 200 {object} response.Response{data=string,id=string,msg=string} "{"code": 200, "data": [...]}"
// @Router /api/v1/captcha [get]
func (e System) GenerateCaptchaHandler(c *gin.Context) {
err := e.MakeContext(c).Errors
if err != nil {
if err := e.MakeContext(c).Errors; err != nil {
e.Error(500, err, "服务初始化失败!")
return
}
id, b64s, err := captcha.DriverDigitFunc()
id, b64s, answer, err := captcha.DriverDigitFunc()
if err != nil {
e.Logger.Errorf("DriverDigitFunc error, %s", err.Error())
e.Error(500, err, "验证码获取失败")
return
}
e.Logger.Infof("DriverDigitFunc answer: %s", answer)
e.Custom(gin.H{
"code": 200,
"data": b64s,
+7 -6
View File
@@ -11,13 +11,14 @@ const INDEX = `
<meta charset="utf-8">
<title>GO-ADMIN欢迎您</title>
<style>
body{
margin:0;
padding:0;
overflow-y:hidden
html,body{
margin:0;
padding:0;
height:100%;
overflow-y:hidden;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
<script src="https://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
<script type="text/javascript">
window.onerror=function(){return true;}
$(function(){
@@ -28,7 +29,7 @@ $(function(){
</script>
</head>
<body>
<iframe id="iframe" frameborder="0" src="https://doc.go-admin.dev" style="width:100%;"></iframe>
<iframe id="iframe" frameborder="0" src="https://www.go-admin.pro" style="width:100%;height:100%;"></iframe>
</body>
</html>
`
+2 -2
View File
@@ -187,7 +187,7 @@ func (e SysDept) Get2Tree(c *gin.Context) {
req := dto.SysDeptGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req,binding.Form).
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
@@ -235,4 +235,4 @@ func (e SysDept) GetDeptTreeRoleSelect(c *gin.Context) {
"depts": result,
"checkedKeys": menuIds,
}, "")
}
}
+2 -40
View File
@@ -202,44 +202,6 @@ func (e SysMenu) GetMenuRole(c *gin.Context) {
e.OK(result, "")
}
//// GetMenuIDS 获取角色对应的菜单id数组
//// @Summary 获取角色对应的菜单id数组,设置角色权限使用
//// @Description 获取JSON
//// @Tags 菜单
//// @Param id path int true "id"
//// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
//// @Router /api/v1/menuids/{id} [get]
//// @Security Bearer
//func (e SysMenu) GetMenuIDS(c *gin.Context) {
// s := new(service.SysMenu)
// r := service.SysRole{}
// m := dto.SysRoleByName{}
// err := e.MakeContext(c).
// MakeOrm().
// Bind(&m, binding.JSON).
// MakeService(&s.Service).
// MakeService(&r.Service).
// Errors
// if err != nil {
// e.Logger.Error(err)
// e.Error(500, err, err.Error())
// return
// }
// var data models.SysRole
// err = r.GetWithName(&m, &data).Error
//
// //data.RoleName = c.GetString("role")
// //data.UpdateBy = user.GetUserId(c)
// //result, err := data.GetIDS(s.Orm)
//
// if err != nil {
// e.Logger.Errorf("GetIDS error, %s", err.Error())
// e.Error(500, err, "获取失败")
// return
// }
// e.OK(result, "")
//}
// GetMenuTreeSelect 根据角色ID查询菜单下拉树结构
// @Summary 角色修改使用的菜单列表
// @Description 获取JSON
@@ -253,7 +215,7 @@ func (e SysMenu) GetMenuRole(c *gin.Context) {
func (e SysMenu) GetMenuTreeSelect(c *gin.Context) {
m := service.SysMenu{}
r := service.SysRole{}
req :=dto.SelectRole{}
req := dto.SelectRole{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&m.Service).
@@ -284,4 +246,4 @@ func (e SysMenu) GetMenuTreeSelect(c *gin.Context) {
"menus": result,
"checkedKeys": menuIds,
}, "获取成功")
}
}
+3 -3
View File
@@ -122,7 +122,7 @@ func (e SysRole) Insert(c *gin.Context) {
if req.Status == "" {
req.Status = "2"
}
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
err = s.Insert(&req, cb)
if err != nil {
e.Logger.Error(err)
@@ -161,7 +161,7 @@ func (e SysRole) Update(c *gin.Context) {
e.Error(500, err, err.Error())
return
}
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
req.SetUpdateBy(user.GetUserId(c))
@@ -203,7 +203,7 @@ func (e SysRole) Delete(c *gin.Context) {
return
}
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
err = s.Remove(&req, cb)
if err != nil {
e.Logger.Error(err)
+1 -1
View File
@@ -450,7 +450,7 @@ func (e SysUser) GetInfo(c *gin.Context) {
if sysUser.Avatar != "" {
mp["avatar"] = sysUser.Avatar
}
mp["userName"] = sysUser.NickName
mp["userName"] = sysUser.Username
mp["userId"] = sysUser.UserId
mp["deptId"] = sysUser.DeptId
mp["name"] = sysUser.NickName
-1
View File
@@ -1,6 +1,5 @@
package models
//sys_casbin_rule
type CasbinRule struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
+1 -1
View File
@@ -31,7 +31,7 @@ func ExecSql(db *gorm.DB, filePath string) error {
fmt.Println(sqlList[i])
continue
}
sql := strings.Replace(sqlList[i]+";", "\n", "", 0)
sql := strings.Replace(sqlList[i]+";", "\n", "", -1)
sql = strings.TrimSpace(sql)
if err = db.Exec(sql).Error; err != nil {
log.Printf("error sql: %s", sql)
-11
View File
@@ -1,11 +0,0 @@
package models
import (
"time"
)
type BaseModel struct {
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt"`
}
+4 -4
View File
@@ -27,7 +27,7 @@ type SysApi struct {
models.ControlBy
}
func (SysApi) TableName() string {
func (*SysApi) TableName() string {
return "sys_api"
}
@@ -44,17 +44,17 @@ func SaveSysApi(message storage.Messager) (err error) {
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
fmt.Errorf("json Marshal error, %s", err.Error())
err = fmt.Errorf("json Marshal error, %v", err.Error())
return err
}
var l runtime.Routers
err = json.Unmarshal(rb, &l)
if err != nil {
fmt.Errorf("json Unmarshal error, %s", err.Error())
err = fmt.Errorf("json Unmarshal error, %s", err.Error())
return err
}
dbList := sdk.Runtime.GetDb()
dbList := sdk.Runtime.GetAllDb()
for _, d := range dbList {
for _, v := range l.List {
if v.HttpMethod != "HEAD" ||
+1 -1
View File
@@ -16,7 +16,7 @@ type SysConfig struct {
models.ModelTime
}
func (SysConfig) TableName() string {
func (*SysConfig) TableName() string {
return "sys_config"
}
+2 -2
View File
@@ -7,7 +7,7 @@ type SysDept struct {
ParentId int `json:"parentId" gorm:""` //上级部门
DeptPath string `json:"deptPath" gorm:"size:255;"` //
DeptName string `json:"deptName" gorm:"size:128;"` //部门名称
Sort int `json:"sort" gorm:"size:4;"` //排序
Sort int `json:"sort" gorm:"size:4;"` //排序
Leader string `json:"leader" gorm:"size:128;"` //负责人
Phone string `json:"phone" gorm:"size:11;"` //手机
Email string `json:"email" gorm:"size:64;"` //邮箱
@@ -19,7 +19,7 @@ type SysDept struct {
Children []SysDept `json:"children" gorm:"-"`
}
func (SysDept) TableName() string {
func (*SysDept) TableName() string {
return "sys_dept"
}
+1 -1
View File
@@ -20,7 +20,7 @@ type SysDictData struct {
models.ModelTime
}
func (SysDictData) TableName() string {
func (*SysDictData) TableName() string {
return "sys_dict_data"
}
+1 -1
View File
@@ -14,7 +14,7 @@ type SysDictType struct {
models.ModelTime
}
func (SysDictType) TableName() string {
func (*SysDictType) TableName() string {
return "sys_dict_type"
}
+2 -2
View File
@@ -29,7 +29,7 @@ type SysLoginLog struct {
models.ControlBy
}
func (SysLoginLog) TableName() string {
func (*SysLoginLog) TableName() string {
return "sys_login_log"
}
@@ -45,7 +45,7 @@ func (e *SysLoginLog) GetId() interface{} {
// SaveLoginLog 从队列中获取登录日志
func SaveLoginLog(message storage.Messager) (err error) {
//准备db
db := sdk.Runtime.GetDbByKey(message.GetPrefix())
db := sdk.Runtime.GetDbByTenant(message.GetPrefix())
if db == nil {
err = errors.New("db not exist")
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
+1 -1
View File
@@ -36,7 +36,7 @@ func (x SysMenuSlice) Len() int { return len(x) }
func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort }
func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (SysMenu) TableName() string {
func (*SysMenu) TableName() string {
return "sys_menu"
}
+2 -2
View File
@@ -37,7 +37,7 @@ type SysOperaLog struct {
models.ControlBy
}
func (SysOperaLog) TableName() string {
func (*SysOperaLog) TableName() string {
return "sys_opera_log"
}
@@ -53,7 +53,7 @@ func (e *SysOperaLog) GetId() interface{} {
// SaveOperaLog 从队列中获取操作日志
func SaveOperaLog(message storage.Messager) (err error) {
//准备db
db := sdk.Runtime.GetDbByKey(message.GetPrefix())
db := sdk.Runtime.GetDbByTenant(message.GetPrefix())
if db == nil {
err = errors.New("db not exist")
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
+2 -2
View File
@@ -16,7 +16,7 @@ type SysPost struct {
Params string `gorm:"-" json:"params"`
}
func (SysPost) TableName() string {
func (*SysPost) TableName() string {
return "sys_post"
}
@@ -27,4 +27,4 @@ func (e *SysPost) Generate() models.ActiveRecord {
func (e *SysPost) GetId() interface{} {
return e.PostId
}
}
+15 -15
View File
@@ -3,25 +3,25 @@ package models
import "go-admin/common/models"
type SysRole struct {
RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码
RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称
Status string `json:"status" gorm:"size:4;"` //
RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码
RoleSort int `json:"roleSort" gorm:""` //角色排序
Flag string `json:"flag" gorm:"size:128;"` //
Remark string `json:"remark" gorm:"size:255;"` //备注
Admin bool `json:"admin" gorm:"size:4;"`
DataScope string `json:"dataScope" gorm:"size:128;"`
Params string `json:"params" gorm:"-"`
MenuIds []int `json:"menuIds" gorm:"-"`
DeptIds []int `json:"deptIds" gorm:"-"`
SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"`
RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码
RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称
Status string `json:"status" gorm:"size:4;"` // 状态 1禁用 2正常
RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码
RoleSort int `json:"roleSort" gorm:""` //角色排序
Flag string `json:"flag" gorm:"size:128;"` //
Remark string `json:"remark" gorm:"size:255;"` //备注
Admin bool `json:"admin" gorm:"size:4;"`
DataScope string `json:"dataScope" gorm:"size:128;"`
Params string `json:"params" gorm:"-"`
MenuIds []int `json:"menuIds" gorm:"-"`
DeptIds []int `json:"deptIds" gorm:"-"`
SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"`
SysMenu *[]SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"`
models.ControlBy
models.ModelTime
}
func (SysRole) TableName() string {
func (*SysRole) TableName() string {
return "sys_role"
}
@@ -32,4 +32,4 @@ func (e *SysRole) Generate() models.ActiveRecord {
func (e *SysRole) GetId() interface{} {
return e.RoleId
}
}
+2 -2
View File
@@ -29,7 +29,7 @@ type SysUser struct {
models.ModelTime
}
func (SysUser) TableName() string {
func (*SysUser) TableName() string {
return "sys_user"
}
@@ -42,7 +42,7 @@ func (e *SysUser) GetId() interface{} {
return e.UserId
}
//加密
// Encrypt 加密
func (e *SysUser) Encrypt() (err error) {
if e.Password == "" {
return
+1 -2
View File
@@ -12,7 +12,6 @@ var (
routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0)
)
// 路由示例
func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine {
// 无需认证的路由
@@ -39,4 +38,4 @@ func examplesCheckRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddle
for _, f := range routerCheckRole {
f(v1, authMiddleware)
}
}
}
+9 -2
View File
@@ -69,8 +69,15 @@ func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle
v1 := r.Group("/api/v1")
{
v1.POST("/login", authMiddleware.LoginHandler)
// Refresh time can be longer than token timeout
v1.GET("/refresh_token", authMiddleware.RefreshHandler)
// GET /api/v1/refresh_token 已移除,原因见 issue #820:
// 该接口用业务 token 即可换取新 token,而续期上限 MaxRefresh 依据的
// orig_iat 在每次续期时被一并重置,上限永远无法到达 —— token 一旦泄
// 露即等同于永久访问权。它此前还位于 CasbinExclude 中,任何角色的已
// 登录用户都能调用,不受权限约束。
//
// 官方前端从未调用该接口(store 中的 refreshToken action 无人 dispatch),
// 移除不影响正常使用。若确需无感续期,应在 go-admin-core 中区分
// access token 与 refresh token 后重新实现,而非沿用此路由。
}
registerBaseRouter(v1, authMiddleware)
}
+2 -3
View File
@@ -11,8 +11,9 @@ type SysApiGetPageReq struct {
dto.Pagination `search:"-"`
Title string `form:"title" search:"type:contains;column:title;table:sys_api" comment:"标题"`
Path string `form:"path" search:"type:contains;column:path;table:sys_api" comment:"地址"`
Action string `form:"action" search:"type:exact;column:action;table:sys_api" comment:"类型"`
Action string `form:"action" search:"type:exact;column:action;table:sys_api" comment:"请求方式"`
ParentId string `form:"parentId" search:"type:exact;column:parent_id;table:sys_api" comment:"按钮id"`
Type string `form:"type" search:"-" comment:"类型"`
SysApiOrder
}
@@ -84,7 +85,6 @@ func (s *SysApiGetReq) GetId() interface{} {
return s.Id
}
// SysApiDeleteReq 功能删除请求参数
type SysApiDeleteReq struct {
Ids []int `json:"ids"`
@@ -93,4 +93,3 @@ type SysApiDeleteReq struct {
func (s *SysApiDeleteReq) GetId() interface{} {
return s.Ids
}
+1 -1
View File
@@ -36,7 +36,7 @@ func (m *SysRoleGetPageReq) GetNeedSearch() interface{} {
type SysRoleInsertReq struct {
RoleId int `uri:"id" comment:"角色编码"` // 角色编码
RoleName string `form:"roleName" comment:"角色名称"` // 角色名称
Status string `form:"status" comment:"状态"` // 状态
Status string `form:"status" comment:"状态"` // 状态 1禁用 2正常
RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码
RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序
Flag string `form:"flag" comment:"标记"` // 标记
+22 -11
View File
@@ -6,12 +6,11 @@ import (
"github.com/go-admin-team/go-admin-core/sdk/runtime"
"github.com/go-admin-team/go-admin-core/sdk/service"
"gorm.io/gorm"
"go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/actions"
cDto "go-admin/common/dto"
"go-admin/common/global"
)
type SysApi struct {
@@ -23,13 +22,25 @@ func (e *SysApi) GetPage(c *dto.SysApiGetPageReq, p *actions.DataPermission, lis
var err error
var data models.SysApi
err = e.Orm.Debug().Model(&data).
orm := e.Orm.Debug().Model(&data).
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
actions.Permission(data.TableName(), p),
).
Find(list).Limit(-1).Offset(-1).
)
if c.Type != "" {
qType := c.Type
if qType == "暂无" {
qType = ""
}
if global.Driver == "postgres" {
orm = orm.Where("type = ?", qType)
} else {
orm = orm.Where("`type` = ?", qType)
}
}
err = orm.Find(list).Limit(-1).Offset(-1).
Count(count).Error
if err != nil {
e.Log.Errorf("Service GetSysApiPage error:%s", err)
@@ -45,15 +56,15 @@ func (e *SysApi) Get(d *dto.SysApiGetReq, p *actions.DataPermission, model *mode
Scopes(
actions.Permission(data.TableName(), p),
).
First(model, d.GetId()).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error:%s", err)
FirstOrInit(model, d.GetId()).Error
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return e
}
if err != nil {
e.Log.Errorf("db error:%s", err)
if model.Id == 0 {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error: %s", err)
_ = e.AddError(err)
return e
}
+11 -10
View File
@@ -8,7 +8,6 @@ import (
cDto "go-admin/common/dto"
"github.com/go-admin-team/go-admin-core/sdk/service"
"gorm.io/gorm"
)
type SysConfig struct {
@@ -33,14 +32,18 @@ func (e *SysConfig) GetPage(c *dto.SysConfigGetPageReq, list *[]models.SysConfig
// Get 获取SysConfig对象
func (e *SysConfig) Get(d *dto.SysConfigGetReq, model *models.SysConfig) error {
err := e.Orm.First(model, d.GetId()).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
err := e.Orm.
FirstOrInit(model, d.GetId()).
Error
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return err
}
if err != nil {
e.Log.Errorf("Service GetSysConfig error:%s", err)
if model.Id == 0 {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error: %s", err)
_ = e.AddError(err)
return err
}
return nil
@@ -143,7 +146,6 @@ func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error {
db := e.Orm.Delete(&data, d.Ids)
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Service RemoveSysConfig error:%s", err)
return err
}
@@ -168,8 +170,7 @@ func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigB
}
func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error {
var err error
err = e.Orm.
err := e.Orm.
Scopes(
cDto.MakeCondition(c.GetNeedSearch()),
).
+20 -21
View File
@@ -7,8 +7,6 @@ import (
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"gorm.io/gorm"
"go-admin/app/admin/service/dto"
cDto "go-admin/common/dto"
@@ -41,16 +39,18 @@ func (e *SysDept) Get(d *dto.SysDeptGetReq, model *models.SysDept) error {
var err error
var data models.SysDept
db := e.Orm.Model(&data).
First(model, d.GetId())
err = db.Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
err = errors.New("查看对象不存在或无权查看")
err = e.Orm.Model(&data).
FirstOrInit(model, d.GetId()).
Error
if err != nil {
e.Log.Errorf("db error:%s", err)
_ = e.AddError(err)
return err
}
if err = db.Error; err != nil {
e.Log.Errorf("db error:%s", err)
if model.DeptId == 0 {
err = errors.New("查看对象不存在或无权查看")
e.Log.Errorf("Service GetSysApi error: %s", err)
_ = e.AddError(err)
return err
}
return nil
@@ -84,7 +84,7 @@ func (e *SysDept) Insert(c *dto.SysDeptInsertReq) error {
}
var mp = map[string]string{}
mp["dept_path"] = deptPath
if err := tx.Model(&data).Update("dept_path", deptPath).Error; err != nil {
if err = tx.Model(&data).Update("dept_path", deptPath).Error; err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
@@ -133,7 +133,6 @@ func (e *SysDept) Remove(d *dto.SysDeptDeleteReq) error {
db := e.Orm.Model(&data).Delete(&data, d.GetId())
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Delete error: %s", err)
return err
}
@@ -184,16 +183,16 @@ func (e *SysDept) SetDeptTree(c *dto.SysDeptGetPageReq) (m []dto.DeptLabel, err
// Call 递归构造组织数据
func deptTreeCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel {
list := *deptList
min := make([]dto.DeptLabel, 0)
childrenList := make([]dto.DeptLabel, 0)
for j := 0; j < len(list); j++ {
if dept.Id != list[j].ParentId {
continue
}
mi := dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}}
ms := deptTreeCall(deptList, mi)
min = append(min, ms)
childrenList = append(childrenList, ms)
}
dept.Children = min
dept.Children = childrenList
return dept
}
@@ -213,7 +212,7 @@ func (e *SysDept) SetDeptPage(c *dto.SysDeptGetPageReq) (m []models.SysDept, err
func (e *SysDept) deptPageCall(deptlist *[]models.SysDept, menu models.SysDept) models.SysDept {
list := *deptlist
min := make([]models.SysDept, 0)
childrenList := make([]models.SysDept, 0)
for j := 0; j < len(list); j++ {
if menu.DeptId != list[j].ParentId {
continue
@@ -231,13 +230,13 @@ func (e *SysDept) deptPageCall(deptlist *[]models.SysDept, menu models.SysDept)
mi.CreatedAt = list[j].CreatedAt
mi.Children = []models.SysDept{}
ms := e.deptPageCall(deptlist, mi)
min = append(min, ms)
childrenList = append(childrenList, ms)
}
menu.Children = min
menu.Children = childrenList
return menu
}
// GetRoleDeptId 获取角色的部门ID集合
// GetWithRoleId 获取角色的部门ID集合
func (e *SysDept) GetWithRoleId(roleId int) ([]int, error) {
deptIds := make([]int, 0)
deptList := make([]dto.DeptIdList, 0)
@@ -281,15 +280,15 @@ func (e *SysDept) SetDeptLabel() (m []dto.DeptLabel, err error) {
func deptLabelCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel {
list := *deptList
var mi dto.DeptLabel
min := make([]dto.DeptLabel, 0)
childrenList := make([]dto.DeptLabel, 0)
for j := 0; j < len(list); j++ {
if dept.Id != list[j].ParentId {
continue
}
mi = dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}}
ms := deptLabelCall(deptList, mi)
min = append(min, ms)
childrenList = append(childrenList, ms)
}
dept.Children = min
dept.Children = childrenList
return dept
}
-1
View File
@@ -92,7 +92,6 @@ func (e *SysDictData) Remove(c *dto.SysDictDataDeleteReq) error {
db := e.Orm.Delete(&data, c.GetId())
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Delete error: %s", err)
return err
}
+2 -2
View File
@@ -3,6 +3,7 @@ package service
import (
"errors"
"fmt"
"github.com/go-admin-team/go-admin-core/sdk/service"
"gorm.io/gorm"
@@ -60,7 +61,7 @@ func (e *SysDictType) Insert(c *dto.SysDictTypeInsertReq) error {
var count int64
e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count)
if count > 0 {
return errors.New(fmt.Sprintf("当前字典类型[%s]已经存在!", data.DictType))
return fmt.Errorf("当前字典类型[%s]已经存在!", data.DictType)
}
err = e.Orm.Create(&data).Error
if err != nil {
@@ -95,7 +96,6 @@ func (e *SysDictType) Remove(d *dto.SysDictTypeDeleteReq) error {
db := e.Orm.Delete(&data, d.GetId())
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Delete error: %s", err)
return err
}
-1
View File
@@ -58,7 +58,6 @@ func (e *SysLoginLog) Remove(c *dto.SysLoginLogDeleteReq) error {
db := e.Orm.Delete(&data, c.GetId())
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Delete error: %s", err)
return err
}
-1
View File
@@ -192,7 +192,6 @@ func (e *SysMenu) Remove(d *dto.SysMenuDeleteReq) *SysMenu {
db := e.Orm.Model(&data).Delete(&data, d.Ids)
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Delete error: %s", err)
_ = e.AddError(err)
}
+2 -3
View File
@@ -47,7 +47,7 @@ func (e *SysPost) Get(d *dto.SysPostGetReq, model *models.SysPost) error {
e.Log.Errorf("db error:%s", err)
return err
}
if err = db.Error; err != nil {
if err != nil {
e.Log.Errorf("db error:%s", err)
return err
}
@@ -93,7 +93,6 @@ func (e *SysPost) Remove(d *dto.SysPostDeleteReq) error {
db := e.Orm.Model(&data).Delete(&data, d.GetId())
if err = db.Error; err != nil {
err = db.Error
e.Log.Errorf("Delete error: %s", err)
return err
}
@@ -102,4 +101,4 @@ func (e *SysPost) Remove(d *dto.SysPostDeleteReq) error {
return err
}
return nil
}
}
+19 -7
View File
@@ -6,7 +6,7 @@ import (
"github.com/go-admin-team/go-admin-core/sdk/config"
"gorm.io/gorm/clause"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v3"
"github.com/go-admin-team/go-admin-core/sdk/service"
"gorm.io/gorm"
@@ -75,7 +75,7 @@ func (e *SysRole) Insert(c *dto.SysRoleInsertReq, cb *casbin.SyncedEnforcer) err
c.Generate(&data)
tx := e.Orm
if config.DatabaseConfig.Driver != "sqlite3" {
tx := e.Orm.Begin()
tx = e.Orm.Begin()
defer func() {
if err != nil {
tx.Rollback()
@@ -118,6 +118,7 @@ func (e *SysRole) Insert(c *dto.SysRoleInsertReq, cb *casbin.SyncedEnforcer) err
return nil
}
// 写入 sys_casbin_rule 权限表里 当前角色数据的记录
_, err = cb.AddNamedPolicies("p", polices)
if err != nil {
return err
@@ -131,7 +132,7 @@ func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) err
var err error
tx := e.Orm
if config.DatabaseConfig.Driver != "sqlite3" {
tx := e.Orm.Begin()
tx = e.Orm.Begin()
defer func() {
if err != nil {
tx.Rollback()
@@ -151,6 +152,7 @@ func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) err
}
c.Generate(&model)
model.SysMenu = &mlist
// 更新关联的数据,使用 FullSaveAssociations 模式
db := tx.Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model)
if err = db.Error; err != nil {
@@ -161,6 +163,7 @@ func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) err
return errors.New("无权更新该数据")
}
// 清除 sys_casbin_rule 权限表里 当前角色的所有记录
_, err = cb.RemoveFilteredPolicy(0, model.RoleKey)
if err != nil {
e.Log.Errorf("delete policy error:%s", err)
@@ -180,6 +183,8 @@ func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) err
if len(polices) <= 0 {
return nil
}
// 写入 sys_casbin_rule 权限表里 当前角色数据的记录
_, err = cb.AddNamedPolicies("p", polices)
if err != nil {
return err
@@ -192,7 +197,7 @@ func (e *SysRole) Remove(c *dto.SysRoleDeleteReq, cb *casbin.SyncedEnforcer) err
var err error
tx := e.Orm
if config.DatabaseConfig.Driver != "sqlite3" {
tx := e.Orm.Begin()
tx = e.Orm.Begin()
defer func() {
if err != nil {
tx.Rollback()
@@ -203,6 +208,7 @@ func (e *SysRole) Remove(c *dto.SysRoleDeleteReq, cb *casbin.SyncedEnforcer) err
}
var model = models.SysRole{}
tx.Preload("SysMenu").Preload("SysDept").First(&model, c.GetId())
//删除 SysRole 时,同时删除角色所有 关联其它表 记录 (SysMenu 和 SysMenu)
db := tx.Select(clause.Associations).Delete(&model)
if err = db.Error; err != nil {
@@ -213,6 +219,7 @@ func (e *SysRole) Remove(c *dto.SysRoleDeleteReq, cb *casbin.SyncedEnforcer) err
return errors.New("无权更新该数据")
}
// 清除 sys_casbin_rule 权限表里 当前角色的所有记录
_, _ = cb.RemoveFilteredPolicy(0, model.RoleKey)
return nil
@@ -237,7 +244,7 @@ func (e *SysRole) UpdateDataScope(c *dto.RoleDataScopeReq) *SysRole {
var err error
tx := e.Orm
if config.DatabaseConfig.Driver != "sqlite3" {
tx := e.Orm.Begin()
tx = e.Orm.Begin()
defer func() {
if err != nil {
tx.Rollback()
@@ -250,6 +257,7 @@ func (e *SysRole) UpdateDataScope(c *dto.RoleDataScopeReq) *SysRole {
var model = models.SysRole{}
tx.Preload("SysDept").First(&model, c.RoleId)
tx.Where("dept_id in ?", c.DeptIds).Find(&dlist)
// 删除SysRole 和 SysDept 的关联关系
err = tx.Model(&model).Association("SysDept").Delete(model.SysDept)
if err != nil {
e.Log.Errorf("delete SysDept error:%s", err)
@@ -258,6 +266,7 @@ func (e *SysRole) UpdateDataScope(c *dto.RoleDataScopeReq) *SysRole {
}
c.Generate(&model)
model.SysDept = dlist
// 更新关联的数据,使用 FullSaveAssociations 模式
db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model)
if err = db.Error; err != nil {
e.Log.Errorf("db error:%s", err)
@@ -276,7 +285,7 @@ func (e *SysRole) UpdateStatus(c *dto.UpdateStatusReq) error {
var err error
tx := e.Orm
if config.DatabaseConfig.Driver != "sqlite3" {
tx := e.Orm.Begin()
tx = e.Orm.Begin()
defer func() {
if err != nil {
tx.Rollback()
@@ -288,6 +297,7 @@ func (e *SysRole) UpdateStatus(c *dto.UpdateStatusReq) error {
var model = models.SysRole{}
tx.First(&model, c.GetId())
c.Generate(&model)
// 更新关联的数据,使用 FullSaveAssociations 模式
db := tx.Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model)
if err = db.Error; err != nil {
e.Log.Errorf("db error:%s", err)
@@ -334,7 +344,9 @@ func (e *SysRole) GetById(roleId int) ([]string, error) {
}
l := *model.SysMenu
for i := 0; i < len(l); i++ {
permissions = append(permissions, l[i].Permission)
if l[i].Permission != "" {
permissions = append(permissions, l[i].Permission)
}
}
return permissions, nil
}
+37
View File
@@ -0,0 +1,37 @@
package models
import (
"go-admin/common/models"
)
// DemoProduct 示例模型
//
// 内嵌 ControlBy 与 ModelTime 后,创建人/更新人与时间戳由框架自动维护;
// 数据权限(actions.Permission)正是按 create_by 过滤,缺少 ControlBy 会使其失效。
type DemoProduct struct {
models.Model
Name string `json:"name" gorm:"size:128;comment:名称"`
Code string `json:"code" gorm:"size:64;comment:编码"`
Price float64 `json:"price" gorm:"comment:单价"`
Status string `json:"status" gorm:"size:4;comment:状态"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
models.ControlBy
models.ModelTime
}
func (DemoProduct) TableName() string {
return "demo_product"
}
// Generate 返回副本,供通用 Action 使用。
// 必须返回新实例:Action 在并发请求间复用同一个模型指针,就地返回会串数据。
func (e *DemoProduct) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *DemoProduct) GetId() interface{} {
return e.Id
}
+49
View File
@@ -0,0 +1,49 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"go-admin/app/demo/models"
"go-admin/app/demo/service/dto"
"go-admin/common/actions"
"go-admin/common/middleware"
)
// 路由通过 init 自注册,无需在任何中心文件登记。
// 新建应用时用 `go run main.go app -n <名称>` 生成骨架,
// 它会同时产出 cmd/api/<名称>.go 完成注册。
func init() {
routerCheckRole = append(routerCheckRole, registerDemoProductRouter)
}
// registerDemoProductRouter 标准 CRUD 的推荐写法。
//
// 五个通用 Action 覆盖了增删改查的全部样板逻辑——参数绑定、数据权限过滤、
// 操作人注入、分页、错误响应,因此本模块没有 apis 与 service 文件。
//
// 仅当业务逻辑超出单表 CRUD(如跨表事务、外部调用、复杂校验)时,才需要
// 自行编写 Handler 与 Service,写法参照 app/admin/apis/sys_post.go。
func registerDemoProductRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
r := v1.Group("/demo-product").
Use(authMiddleware.MiddlewareFunc()). // JWT 认证
Use(middleware.AuthCheckRole()) // Casbin 鉴权
{
m := &models.DemoProduct{}
// actions.PermissionAction() 注入数据权限上下文,
// 列表与详情缺少它会绕过 DataScope 过滤
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
list := make([]models.DemoProduct, 0)
return &list
}))
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
return &models.DemoProduct{}
}))
r.POST("", actions.CreateAction(new(dto.DemoProductControl)))
r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl)))
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById)))
}
}
+74
View File
@@ -0,0 +1,74 @@
package router
import (
"github.com/gin-gonic/gin"
_ "github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
// "github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
common "go-admin/common/middleware"
"os"
)
var (
routerNoCheckRole = make([]func(*gin.RouterGroup), 0)
routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0)
)
// InitRouter 路由初始化
func InitRouter() {
var r *gin.Engine
h := sdk.Runtime.GetEngine()
if h == nil {
h = gin.New()
sdk.Runtime.SetEngine(h)
}
switch h.(type) {
case *gin.Engine:
r = h.(*gin.Engine)
default:
log.Fatal("not support other engine")
os.Exit(-1)
}
// the jwt middleware
authMiddleware, err := common.AuthInit()
if err != nil {
log.Fatalf("JWT Init Error, %s", err.Error())
}
// 注册业务路由
InitBusinessRouter(r, authMiddleware)
}
func InitBusinessRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine {
// 无需认证的路由
noCheckRoleRouter(r)
// 需要认证的路由
checkRoleRouter(r, authMiddleware)
return r
}
// noCheckRoleRouter 无需认证的路由
func noCheckRoleRouter(r *gin.Engine) {
// 可根据业务需求来设置接口版本
v := r.Group("/api/v1")
for _, f := range routerNoCheckRole {
f(v)
}
}
// checkRoleRouter 需要认证的路由
func checkRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) {
// 可根据业务需求来设置接口版本
v := r.Group("/api/v1")
for _, f := range routerCheckRole {
f(v, authMiddleware)
}
}
+96
View File
@@ -0,0 +1,96 @@
package dto
import (
"github.com/gin-gonic/gin"
"go-admin/app/demo/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
// DemoProductSearch 列表查询条件
//
// search tag 决定 MakeCondition 拼出的 WHERE:
//
// exact 精确匹配 / icontains 忽略大小写模糊 / gte 大于等于 …
//
// 未打 search tag 的字段不参与查询,可避免无意间开放过滤维度。
type DemoProductSearch struct {
dto.Pagination `search:"-"`
Name string `form:"name" search:"type:icontains;column:name;table:demo_product"`
Code string `form:"code" search:"type:exact;column:code;table:demo_product"`
Status string `form:"status" search:"type:exact;column:status;table:demo_product"`
DemoProductOrder
}
// DemoProductOrder 排序字段单独成组,避免与查询字段混在一起
type DemoProductOrder struct {
CreatedAtOrder string `form:"createdAtOrder" search:"type:order;column:created_at;table:demo_product"`
}
func (m *DemoProductSearch) GetNeedSearch() interface{} { return *m }
func (m *DemoProductSearch) Bind(ctx *gin.Context) error {
return ctx.ShouldBind(m)
}
func (m *DemoProductSearch) Generate() dto.Index {
o := *m
return &o
}
// DemoProductControl 新增与修改共用的入参
//
// 通用 Action(Create / Update)通过 GenerateM 拿到落库对象,
// 因此这里不直接暴露 Model,字段校验用 validate tag 声明。
type DemoProductControl struct {
Id int `json:"id" comment:"主键"`
Name string `json:"name" comment:"名称" validate:"required"`
Code string `json:"code" comment:"编码" validate:"required"`
Price float64 `json:"price" comment:"单价" validate:"gte=0"`
Status string `json:"status" comment:"状态"`
Remark string `json:"remark" comment:"备注"`
}
func (s *DemoProductControl) Bind(ctx *gin.Context) error {
return ctx.ShouldBind(s)
}
func (s *DemoProductControl) Generate() dto.Control {
o := *s
return &o
}
func (s *DemoProductControl) GetId() interface{} { return s.Id }
// GenerateM 组装落库对象。CreateBy / UpdateBy 由通用 Action 在此之后注入,
// 此处不要手动赋值。
func (s *DemoProductControl) GenerateM() (common.ActiveRecord, error) {
return &models.DemoProduct{
Model: common.Model{Id: s.Id},
Name: s.Name,
Code: s.Code,
Price: s.Price,
Status: s.Status,
Remark: s.Remark,
}, nil
}
// DemoProductById 详情与删除共用,支持单个 id 与批量 ids
type DemoProductById struct {
dto.ObjectById
}
// Bind 与 GetId 由内嵌的 dto.ObjectById 提供:它已处理好 uri 绑定、
// DELETE 时的批量 ids 合并与参数校验,无需在此重复实现。
func (s *DemoProductById) Generate() dto.Control {
o := *s
return &o
}
func (s *DemoProductById) GenerateM() (common.ActiveRecord, error) {
return &models.DemoProduct{}, nil
}
+91
View File
@@ -0,0 +1,91 @@
package dto
import (
"testing"
"go-admin/app/demo/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
// 通用 Action 依赖 DTO 与 Model 实现一组接口。这些约束在编译期无法完全覆盖
// (接口是在路由注册处才被要求的),因此用测试锁定,避免改动后在运行时才暴露。
func TestImplementsIndexInterface(t *testing.T) {
var _ dto.Index = (*DemoProductSearch)(nil)
}
func TestImplementsControlInterface(t *testing.T) {
var _ dto.Control = (*DemoProductControl)(nil)
var _ dto.Control = (*DemoProductById)(nil)
}
func TestModelImplementsActiveRecord(t *testing.T) {
var _ common.ActiveRecord = (*models.DemoProduct)(nil)
}
// Generate 必须返回副本:通用 Action 在并发请求间复用同一个实例,
// 就地返回会导致请求之间串数据。
func TestGenerateReturnsCopy(t *testing.T) {
src := &DemoProductControl{Id: 1, Name: "原始"}
got := src.Generate().(*DemoProductControl)
if got == src {
t.Fatal("Generate 返回了同一指针,应返回副本")
}
got.Name = "被修改"
if src.Name != "原始" {
t.Errorf("修改副本影响了原对象:src.Name = %q", src.Name)
}
}
func TestSearchGenerateReturnsCopy(t *testing.T) {
src := &DemoProductSearch{Name: "原始"}
got := src.Generate().(*DemoProductSearch)
if got == src {
t.Fatal("Generate 返回了同一指针,应返回副本")
}
got.Name = "被修改"
if src.Name != "原始" {
t.Errorf("修改副本影响了原对象:src.Name = %q", src.Name)
}
}
func TestModelGenerateReturnsCopy(t *testing.T) {
src := &models.DemoProduct{Name: "原始"}
got := src.Generate().(*models.DemoProduct)
if got == src {
t.Fatal("Generate 返回了同一指针,应返回副本")
}
got.Name = "被修改"
if src.Name != "原始" {
t.Errorf("修改副本影响了原对象:src.Name = %q", src.Name)
}
}
// GenerateM 组装落库对象,主键需正确传递,否则更新会退化成插入。
func TestGenerateMCarriesId(t *testing.T) {
c := &DemoProductControl{Id: 42, Name: "示例", Code: "P-42", Price: 9.9}
m, err := c.GenerateM()
if err != nil {
t.Fatalf("GenerateM 返回错误: %v", err)
}
p, ok := m.(*models.DemoProduct)
if !ok {
t.Fatalf("GenerateM 返回类型错误: %T", m)
}
if p.Id != 42 {
t.Errorf("主键未传递: got %d, want 42", p.Id)
}
if p.Name != "示例" || p.Code != "P-42" || p.Price != 9.9 {
t.Errorf("字段映射有误: %+v", p)
}
}
func TestTableName(t *testing.T) {
if got := (models.DemoProduct{}).TableName(); got != "demo_product" {
t.Errorf("TableName() = %q, want %q", got, "demo_product")
}
}
+2 -2
View File
@@ -29,7 +29,7 @@ func (e SysJob) RemoveJobForService(c *gin.Context) {
return
}
s.Cron = sdk.Runtime.GetCrontabKey(c.Request.Host)
s.Cron = sdk.Runtime.GetCrontabByTenant(c.Request.Host)
err = s.RemoveJob(&v)
if err != nil {
e.Logger.Errorf("RemoveJob error, %s", err.Error())
@@ -58,7 +58,7 @@ func (e SysJob) StartJobForService(c *gin.Context) {
s := service.SysJob{}
s.Orm = db
s.Log = log
s.Cron = sdk.Runtime.GetCrontabKey(c.Request.Host)
s.Cron = sdk.Runtime.GetCrontabByTenant(c.Request.Host)
err = s.StartJob(&v)
if err != nil {
log.Errorf("GetCrontabKey error, %s", err.Error())
+3 -1
View File
@@ -5,15 +5,17 @@ import (
"time"
)
// InitJob
// 需要将定义的struct 添加到字典中;
// 字典 key 可以配置到 自动任务 调用目标 中;
func InitJob() {
jobList = map[string]JobsExec{
jobList = map[string]JobExec{
"ExamplesOne": ExamplesOne{},
// ...
}
}
// ExamplesOne
// 新添加的job 必须按照以下格式定义,并实现Exec函数
type ExamplesOne struct {
}
+16 -16
View File
@@ -6,7 +6,6 @@ import (
"github.com/go-admin-team/go-admin-core/sdk"
models2 "go-admin/app/jobs/models"
"gorm.io/gorm"
"sync"
"time"
"github.com/robfig/cron/v3"
@@ -18,8 +17,9 @@ import (
var timeFormat = "2006-01-02 15:04:05"
var retryCount = 3
var jobList map[string]JobsExec
var lock sync.Mutex
var jobList map[string]JobExec
//var lock sync.Mutex
type JobCore struct {
InvokeTarget string
@@ -30,7 +30,7 @@ type JobCore struct {
Args string
}
// 任务类型 http
// HttpJob 任务类型 http
type HttpJob struct {
JobCore
}
@@ -46,7 +46,7 @@ func (e *ExecJob) Run() {
log.Warn("[Job] ExecJob Run job nil")
return
}
err := CallExec(obj.(JobsExec), e.Args)
err := CallExec(obj.(JobExec), e.Args)
if err != nil {
// 如果失败暂停一段时间重试
fmt.Println(time.Now().Format(timeFormat), " [ERROR] mission failed! ", err)
@@ -59,11 +59,11 @@ func (e *ExecJob) Run() {
//TODO: 待完善部分
//str := time.Now().Format(timeFormat) + " [INFO] JobCore " + string(e.EntryId) + "exec success , spend :" + latencyTime.String()
//ws.SendAll(str)
log.Info("[Job] JobCore %s exec success , spend :%v", e.Name, latencyTime)
log.Infof("[Job] JobCore %s exec success , spend :%v", e.Name, latencyTime)
return
}
//http 任务接口
// Run http 任务接口
func (h *HttpJob) Run() {
startTime := time.Now()
@@ -77,8 +77,8 @@ LOOP:
str, err = pkg.Get(h.InvokeTarget)
if err != nil {
// 如果失败暂停一段时间重试
fmt.Println(time.Now().Format(timeFormat), " [ERROR] mission failed! ", err)
fmt.Printf(time.Now().Format(timeFormat)+" [INFO] Retry after the task fails %d seconds! %s \n", (count+1)*5, str)
log.Warnf("[Job] mission failed! %v", err)
log.Warnf("[Job] Retry after the task fails %d seconds! %s \n", (count+1)*5, str)
time.Sleep(time.Duration(count+1) * 5 * time.Second)
count = count + 1
goto LOOP
@@ -95,19 +95,19 @@ LOOP:
return
}
// 初始化
// Setup 初始化
func Setup(dbs map[string]*gorm.DB) {
fmt.Println(time.Now().Format(timeFormat), " [INFO] JobCore Starting...")
for k, db := range dbs {
sdk.Runtime.SetCrontab(k, cronjob.NewWithSeconds())
sdk.Runtime.SetCrontabByTenant(k, cronjob.NewWithSeconds())
setup(k, db)
}
}
func setup(key string, db *gorm.DB) {
crontab := sdk.Runtime.GetCrontabKey(key)
crontab := sdk.Runtime.GetCrontabByTenant(key)
sysJob := models2.SysJob{}
jobList := make([]models2.SysJob, 0)
err := sysJob.GetList(db, &jobList)
@@ -152,7 +152,7 @@ func setup(key string, db *gorm.DB) {
select {}
}
// 添加任务 AddJob(invokeTarget string, jobId int, jobName string, cronExpression string)
// AddJob 添加任务 AddJob(invokeTarget string, jobId int, jobName string, cronExpression string)
func AddJob(c *cron.Cron, job Job) (int, error) {
if job == nil {
fmt.Println("unknown")
@@ -171,8 +171,8 @@ func (h *HttpJob) addJob(c *cron.Cron) (int, error) {
return EntryId, nil
}
func (h *ExecJob) addJob(c *cron.Cron) (int, error) {
id, err := c.AddJob(h.CronExpression, h)
func (e *ExecJob) addJob(c *cron.Cron) (int, error) {
id, err := c.AddJob(e.CronExpression, e)
if err != nil {
fmt.Println(time.Now().Format(timeFormat), " [ERROR] JobCore AddJob error", err)
return 0, err
@@ -181,7 +181,7 @@ func (h *ExecJob) addJob(c *cron.Cron) (int, error) {
return EntryId, nil
}
// 移除任务
// Remove 移除任务
func Remove(c *cron.Cron, entryID int) chan bool {
ch := make(chan bool)
go func() {
+2 -2
View File
@@ -23,7 +23,7 @@ type SysJob struct {
DataScope string `json:"dataScope" gorm:"-"`
}
func (SysJob) TableName() string {
func (*SysJob) TableName() string {
return "sys_job"
}
@@ -48,7 +48,7 @@ func (e *SysJob) GetList(tx *gorm.DB, list interface{}) (err error) {
return tx.Table(e.TableName()).Where("status = ?", 2).Find(list).Error
}
// 更新SysJob
// Update 更新SysJob
func (e *SysJob) Update(tx *gorm.DB, id interface{}) (err error) {
return tx.Table(e.TableName()).Where(id).Updates(&e).Error
}
+2 -2
View File
@@ -7,10 +7,10 @@ type Job interface {
addJob(*cron.Cron) (int, error)
}
type JobsExec interface {
type JobExec interface {
Exec(arg interface{}) error
}
func CallExec(e JobsExec, arg interface{}) error {
func CallExec(e JobExec, arg interface{}) error {
return e.Exec(arg)
}
+80 -87
View File
@@ -44,63 +44,61 @@ type File struct {
func (e File) UploadFile(c *gin.Context) {
e.MakeContext(c)
tag, _ := c.GetPostForm("type")
urlPrefix := fmt.Sprintf("http://%s/", c.Request.Host)
var fileResponse FileResponse
urlPrefix := fmt.Sprintf("%s://%s/", "http", c.Request.Host)
switch tag {
case "1": // 单图
var done bool
fileResponse, done = e.singleFile(c, fileResponse, urlPrefix)
if done {
return
}
e.OK(fileResponse, "上传成功")
return
e.handleSingleFile(c, urlPrefix)
case "2": // 多图
multipartFile := e.multipleFile(c, urlPrefix)
e.OK(multipartFile, "上传成功")
return
e.handleMultipleFiles(c, urlPrefix)
case "3": // base64
fileResponse = e.baseImg(c, fileResponse, urlPrefix)
e.OK(fileResponse, "上传成功")
e.handleBase64File(c, urlPrefix)
default:
var done bool
fileResponse, done = e.singleFile(c, fileResponse, urlPrefix)
if done {
return
}
e.OK(fileResponse, "上传成功")
return
e.handleSingleFile(c, urlPrefix)
}
}
func (e File) baseImg(c *gin.Context, fileResponse FileResponse, urlPerfix string) FileResponse {
func (e File) handleSingleFile(c *gin.Context, urlPrefix string) {
fileResponse, done := e.singleFile(c, FileResponse{}, urlPrefix)
if done {
return
}
e.OK(fileResponse, "上传成功")
}
func (e File) handleMultipleFiles(c *gin.Context, urlPrefix string) {
multipartFile := e.multipleFile(c, urlPrefix)
e.OK(multipartFile, "上传成功")
}
func (e File) handleBase64File(c *gin.Context, urlPrefix string) {
fileResponse := e.baseImg(c, FileResponse{}, urlPrefix)
e.OK(fileResponse, "上传成功")
}
func (e File) baseImg(c *gin.Context, fileResponse FileResponse, urlPrefix string) FileResponse {
files, _ := c.GetPostForm("file")
file2list := strings.Split(files, ",")
ddd, _ := base64.StdEncoding.DecodeString(file2list[1])
guid := uuid.New().String()
fileName := guid + ".jpg"
err := utils.IsNotExistMkDir(path)
if err != nil {
decodedData, _ := base64.StdEncoding.DecodeString(file2list[1])
fileName := uuid.New().String() + ".jpg"
if err := utils.IsNotExistMkDir(path); err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
return fileResponse
}
base64File := path + fileName
_ = ioutil.WriteFile(base64File, ddd, 0666)
_ = ioutil.WriteFile(base64File, decodedData, 0666)
typeStr := strings.Replace(strings.Replace(file2list[0], "data:", "", -1), ";base64", "", -1)
fileResponse = FileResponse{
Size: pkg.GetFileSize(base64File),
Path: base64File,
FullPath: urlPerfix + base64File,
Name: "",
Type: typeStr,
}
fileResponse = e.buildFileResponse(base64File, urlPrefix, "", typeStr)
source, _ := c.GetPostForm("source")
err = thirdUpload(source, fileName, base64File)
if err != nil {
if err := thirdUpload(source, fileName, base64File); err != nil {
e.Error(200, errors.New(""), "上传第三方失败")
return fileResponse
}
if source != "1" {
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
@@ -108,81 +106,76 @@ func (e File) baseImg(c *gin.Context, fileResponse FileResponse, urlPerfix strin
return fileResponse
}
func (e File) multipleFile(c *gin.Context, urlPerfix string) []FileResponse {
func (e File) multipleFile(c *gin.Context, urlPrefix string) []FileResponse {
files := c.Request.MultipartForm.File["file"]
source, _ := c.GetPostForm("source")
var multipartFile []FileResponse
for _, f := range files {
guid := uuid.New().String()
fileName := guid + utils.GetExt(f.Filename)
err := utils.IsNotExistMkDir(path)
if err != nil {
for _, f := range files {
fileName := uuid.New().String() + utils.GetExt(f.Filename)
if err := utils.IsNotExistMkDir(path); err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
continue
}
multipartFileName := path + fileName
err1 := c.SaveUploadedFile(f, multipartFileName)
fileType, _ := utils.GetType(multipartFileName)
if err1 == nil {
err := thirdUpload(source, fileName, multipartFileName)
if err != nil {
e.Error(500, errors.New(""), "上传第三方失败")
} else {
fileResponse := FileResponse{
Size: pkg.GetFileSize(multipartFileName),
Path: multipartFileName,
FullPath: urlPerfix + multipartFileName,
Name: f.Filename,
Type: fileType,
}
if source != "1" {
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
}
multipartFile = append(multipartFile, fileResponse)
}
if err := c.SaveUploadedFile(f, multipartFileName); err != nil {
continue
}
fileType, _ := utils.GetType(multipartFileName)
if err := thirdUpload(source, fileName, multipartFileName); err != nil {
e.Error(500, errors.New(""), "上传第三方失败")
continue
}
fileResponse := e.buildFileResponse(multipartFileName, urlPrefix, f.Filename, fileType)
if source != "1" {
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
}
multipartFile = append(multipartFile, fileResponse)
}
return multipartFile
}
func (e File) singleFile(c *gin.Context, fileResponse FileResponse, urlPerfix string) (FileResponse, bool) {
func (e File) singleFile(c *gin.Context, fileResponse FileResponse, urlPrefix string) (FileResponse, bool) {
files, err := c.FormFile("file")
if err != nil {
e.Error(200, errors.New(""), "图片不能为空")
return FileResponse{}, true
}
// 上传文件至指定目录
guid := uuid.New().String()
fileName := guid + utils.GetExt(files.Filename)
err = utils.IsNotExistMkDir(path)
if err != nil {
fileName := uuid.New().String() + utils.GetExt(files.Filename)
if err := utils.IsNotExistMkDir(path); err != nil {
e.Error(500, errors.New(""), "初始化文件路径失败")
return FileResponse{}, true
}
singleFile := path + fileName
_ = c.SaveUploadedFile(files, singleFile)
fileType, _ := utils.GetType(singleFile)
fileResponse = FileResponse{
Size: pkg.GetFileSize(singleFile),
Path: singleFile,
FullPath: urlPerfix + singleFile,
Name: files.Filename,
Type: fileType,
if err := c.SaveUploadedFile(files, singleFile); err != nil {
e.Error(500, errors.New(""), "文件保存失败")
return FileResponse{}, true
}
//source, _ := c.GetPostForm("source")
//err = thirdUpload(source, fileName, singleFile)
//if err != nil {
// e.Error(200, errors.New(""), "上传第三方失败")
// return FileResponse{}, true
//}
fileType, _ := utils.GetType(singleFile)
fileResponse = e.buildFileResponse(singleFile, urlPrefix, files.Filename, fileType)
fileResponse.Path = "/static/uploadfile/" + fileName
fileResponse.FullPath = "/static/uploadfile/" + fileName
return fileResponse, false
}
func (e File) buildFileResponse(filePath, urlPrefix, fileName, fileType string) FileResponse {
return FileResponse{
Size: pkg.GetFileSize(filePath),
Path: filePath,
FullPath: urlPrefix + filePath,
Name: fileName,
Type: fileType,
}
}
func thirdUpload(source string, name string, path string) error {
switch source {
case "2":
@@ -201,4 +194,4 @@ func ossUpload(name string, path string) error {
func qiniuUpload(name string, path string) error {
oss := file_store.ALiYunOSS{}
return oss.UpLoad(name, path)
}
}
+98 -102
View File
@@ -3,7 +3,6 @@ package apis
import (
"fmt"
"github.com/shirou/gopsutil/v3/net"
"regexp"
"runtime"
"strconv"
"strings"
@@ -12,7 +11,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
@@ -26,22 +24,9 @@ const (
GB = 1024 * MB
)
var (
Version string
expectDiskFsTypes = []string{
"apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs",
"fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone",
}
excludeNetInterfaces = []string{
"lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
}
getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`)
)
var (
netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdateNetStats uint64
cachedBootTime time.Time
)
var excludeNetInterfaces = []string{
"lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
}
type ServerMonitor struct {
api.Api
@@ -49,137 +34,148 @@ type ServerMonitor struct {
// GetHourDiffer 获取相差时间
func GetHourDiffer(startTime, endTime string) int64 {
var hour int64
t1, err := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
t2, err := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
if err == nil && t1.Before(t2) {
diff := t2.Unix() - t1.Unix() //
hour = diff / 3600
return hour
} else {
return hour
t1, err1 := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local)
t2, err2 := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local)
if err1 != nil || err2 != nil || !t1.Before(t2) {
return 0
}
return (t2.Unix() - t1.Unix()) / 3600
}
// ServerInfo 获取系统信息
// @Summary 系统信息
// @Description 获取JSON
// @Tags 系统信息
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/server-monitor [get]
// @Security Bearer
func (e ServerMonitor) ServerInfo(c *gin.Context) {
e.Context = c
sysInfo, err := host.Info()
osDic := make(map[string]interface{}, 0)
osDic["goOs"] = runtime.GOOS
osDic["arch"] = runtime.GOARCH
osDic["mem"] = runtime.MemProfileRate
osDic["compiler"] = runtime.Compiler
osDic["version"] = runtime.Version()
osDic["numGoroutine"] = runtime.NumGoroutine()
osDic["ip"] = pkg.GetLocaHonst()
osDic["projectDir"] = pkg.GetCurrentPath()
osDic["hostName"] = sysInfo.Hostname
osDic["time"] = time.Now().Format("2006-01-02 15:04:05")
osInfo := getOSInfo()
memInfo := getMemoryInfo()
swapInfo := getSwapInfo()
cpuInfo := getCPUInfo()
diskInfo := getDiskInfo()
netInfo := getNetworkInfo()
mem, _ := mem.VirtualMemory()
memDic := make(map[string]interface{}, 0)
memDic["used"] = mem.Used / MB
memDic["total"] = mem.Total / MB
bootTime, _ := host.BootTime()
cachedBootTime := time.Unix(int64(bootTime), 0)
fmt.Println("mem", int(mem.Total/mem.Used*100))
memDic["percent"] = pkg.Round(mem.UsedPercent, 2)
e.Custom(gin.H{
"code": 200,
"os": osInfo,
"mem": memInfo,
"cpu": cpuInfo,
"disk": diskInfo,
"net": netInfo,
"swap": swapInfo,
"location": "Aliyun",
"bootTime": GetHourDiffer(cachedBootTime.Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05")),
})
}
swapDic := make(map[string]interface{}, 0)
swapDic["used"] = mem.SwapTotal - mem.SwapFree
swapDic["total"] = mem.SwapTotal
func getOSInfo() map[string]interface{} {
sysInfo, _ := host.Info()
return map[string]interface{}{
"goOs": runtime.GOOS,
"arch": runtime.GOARCH,
"mem": runtime.MemProfileRate,
"compiler": runtime.Compiler,
"version": runtime.Version(),
"numGoroutine": runtime.NumGoroutine(),
"ip": pkg.GetLocalHost(),
"projectDir": pkg.GetCurrentPath(),
"hostName": sysInfo.Hostname,
"time": time.Now().Format("2006-01-02 15:04:05"),
}
}
cpuDic := make(map[string]interface{}, 0)
cpuDic["cpuInfo"], _ = cpu.Info()
func getMemoryInfo() map[string]interface{} {
memory, _ := mem.VirtualMemory()
return map[string]interface{}{
"used": memory.Used / MB,
"total": memory.Total / MB,
"percent": pkg.Round(memory.UsedPercent, 2),
}
}
func getSwapInfo() map[string]interface{} {
memory, _ := mem.VirtualMemory()
return map[string]interface{}{
"used": memory.SwapTotal - memory.SwapFree,
"total": memory.SwapTotal,
}
}
func getCPUInfo() map[string]interface{} {
cpuInfo, _ := cpu.Info()
percent, _ := cpu.Percent(0, false)
cpuDic["percent"] = pkg.Round(percent[0], 2)
cpuDic["cpuNum"], _ = cpu.Counts(false)
cpuNum, _ := cpu.Counts(false)
return map[string]interface{}{
"cpuInfo": cpuInfo,
"percent": pkg.Round(percent[0], 2),
"cpuNum": cpuNum,
}
}
//服务器磁盘信息
disklist := make([]disk.UsageStat, 0)
//所有分区
func getDiskInfo() map[string]interface{} {
var diskTotal, diskUsed, diskUsedPercent float64
diskList := make([]disk.UsageStat, 0)
diskInfo, err := disk.Partitions(true)
if err == nil {
for _, p := range diskInfo {
diskDetail, err := disk.Usage(p.Mountpoint)
if err == nil {
diskDetail.UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", diskDetail.UsedPercent), 64)
diskDetail.Total = diskDetail.Total / 1024 / 1024
diskDetail.Used = diskDetail.Used / 1024 / 1024
diskDetail.Free = diskDetail.Free / 1024 / 1024
disklist = append(disklist, *diskDetail)
diskDetail.Total /= MB
diskDetail.Used /= MB
diskDetail.Free /= MB
diskList = append(diskList, *diskDetail)
}
}
}
d, _ := disk.Usage("/")
diskTotal = float64(d.Total / GB)
diskUsed = float64(d.Used / GB)
diskUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", d.UsedPercent), 64)
diskDic := make(map[string]interface{}, 0)
diskDic["total"] = diskTotal
diskDic["used"] = diskUsed
diskDic["percent"] = diskUsedPercent
bootTime, _ := host.BootTime()
cachedBootTime = time.Unix(int64(bootTime), 0)
TrackNetworkSpeed()
netDic := make(map[string]interface{}, 0)
netDic["in"] = pkg.Round(float64(netInSpeed/KB), 2)
netDic["out"] = pkg.Round(float64(netOutSpeed/KB), 2)
e.Custom(gin.H{
"code": 200,
"os": osDic,
"mem": memDic,
"cpu": cpuDic,
"disk": diskDic,
"net": netDic,
"swap": swapDic,
"location": "Aliyun",
"bootTime": GetHourDiffer(cachedBootTime.Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05")),
})
return map[string]interface{}{
"total": diskTotal,
"used": diskUsed,
"percent": diskUsedPercent,
}
}
func TrackNetworkSpeed() {
var innerNetInTransfer, innerNetOutTransfer uint64
func getNetworkInfo() map[string]interface{} {
netInSpeed, netOutSpeed := trackNetworkSpeed()
return map[string]interface{}{
"in": pkg.Round(float64(netInSpeed/KB), 2),
"out": pkg.Round(float64(netOutSpeed/KB), 2),
}
}
func trackNetworkSpeed() (uint64, uint64) {
var netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdateNetStats uint64
nc, err := net.IOCounters(true)
if err == nil {
for _, v := range nc {
if isListContainsStr(excludeNetInterfaces, v.Name) {
continue
}
innerNetInTransfer += v.BytesRecv
innerNetOutTransfer += v.BytesSent
netInTransfer += v.BytesRecv
netOutTransfer += v.BytesSent
}
now := uint64(time.Now().Unix())
diff := now - lastUpdateNetStats
if diff > 0 {
netInSpeed = (innerNetInTransfer - netInTransfer) / diff
fmt.Println("netInSpeed", netInSpeed)
netOutSpeed = (innerNetOutTransfer - netOutTransfer) / diff
fmt.Println("netOutSpeed", netOutSpeed)
netInSpeed = (netInTransfer - netInTransfer) / diff
netOutSpeed = (netOutTransfer - netOutTransfer) / diff
}
netInTransfer = innerNetInTransfer
netOutTransfer = innerNetOutTransfer
lastUpdateNetStats = now
}
return netInSpeed, netOutSpeed
}
func isListContainsStr(list []string, str string) bool {
for i := 0; i < len(list); i++ {
if strings.Contains(str, list[i]) {
for _, item := range list {
if strings.Contains(str, item) {
return true
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
// @Param pageIndex query int false "pageIndex / 页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/db/columns/page [get]
func (e *Gen) GetDBColumnList(c *gin.Context) {
func (e Gen) GetDBColumnList(c *gin.Context) {
e.Context = c
log := e.GetLogger()
var data tools.DBColumns
+1 -1
View File
@@ -19,7 +19,7 @@ import (
// @Param pageIndex query int false "pageIndex / 页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/db/tables/page [get]
func (e *Gen) GetDBTableList(c *gin.Context) {
func (e Gen) GetDBTableList(c *gin.Context) {
//var res response.Response
var data tools.DBTables
var err error
+4 -3
View File
@@ -1,7 +1,8 @@
package tools
import (
"go-admin/app/admin/models"
common "go-admin/common/models"
"gorm.io/gorm"
)
@@ -44,10 +45,10 @@ type SysColumns struct {
CreateBy int `gorm:"column:create_by;size:20;" json:"createBy"`
UpdateBy int `gorm:"column:update_By;size:20;" json:"updateBy"`
models.BaseModel
common.ModelTime
}
func (SysColumns) TableName() string {
func (*SysColumns) TableName() string {
return "sys_columns"
}
+1 -5
View File
@@ -5,8 +5,6 @@ import (
"strings"
"gorm.io/gorm"
"go-admin/app/admin/models"
)
type SysTables struct {
@@ -43,11 +41,9 @@ type SysTables struct {
DataScope string `gorm:"-" json:"dataScope"`
Params Params `gorm:"-" json:"params"`
Columns []SysColumns `gorm:"-" json:"columns"`
models.BaseModel
}
func (SysTables) TableName() string {
func (*SysTables) TableName() string {
return "sys_tables"
}
+8
View File
@@ -0,0 +1,8 @@
package api
import "go-admin/app/demo/router"
func init() {
//注册路由 fixme 其他应用的路由,在本目录新建文件放在init方法
AppRouters = append(AppRouters, router.InitRouter)
}
+21 -19
View File
@@ -3,7 +3,6 @@ package api
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
@@ -11,11 +10,12 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/config/source/file"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/runtime"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"go-admin/app/admin/models"
@@ -66,14 +66,14 @@ func setup() {
storage.Setup,
)
//注册监听函数
queue := sdk.Runtime.GetMemoryQueue("")
queue := sdk.Runtime.GetQueuePrefix("")
queue.Register(global.LoginLog, models.SaveLoginLog)
queue.Register(global.OperateLog, models.SaveOperaLog)
queue.Register(global.ApiCheck, models.SaveSysApi)
go queue.Run()
usageStr := `starting api server...`
log.Println(usageStr)
log.Info(usageStr)
}
func run() error {
@@ -89,27 +89,29 @@ func run() error {
srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", config.ApplicationConfig.Host, config.ApplicationConfig.Port),
Handler: sdk.Runtime.GetEngine(),
ReadTimeout: time.Duration(config.ApplicationConfig.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(config.ApplicationConfig.WriterTimeout) * time.Second,
}
go func() {
jobs.InitJob()
jobs.Setup(sdk.Runtime.GetDb())
jobs.Setup(sdk.Runtime.GetAllDb())
}()
if apiCheck {
var routers = sdk.Runtime.GetRouter()
q := sdk.Runtime.GetMemoryQueue("")
mp := make(map[string]interface{}, 0)
q := sdk.Runtime.GetQueuePrefix("")
mp := make(map[string]interface{})
mp["List"] = routers
message, err := sdk.Runtime.GetStreamMessage("", global.ApiCheck, mp)
if err != nil {
log.Printf("GetStreamMessage error, %s \n", err.Error())
log.Infof("GetStreamMessage error, %s \n", err.Error())
//日志报错错误,不中断请求
} else {
err = q.Append(message)
if err != nil {
log.Printf("Append message error, %s \n", err.Error())
log.Infof("Append message error, %s \n", err.Error())
}
}
}
@@ -117,11 +119,11 @@ func run() error {
go func() {
// 服务连接
if config.SslConfig.Enable {
if err := srv.ListenAndServeTLS(config.SslConfig.Pem, config.SslConfig.KeyStr); err != nil && err != http.ErrServerClosed {
if err := srv.ListenAndServeTLS(config.SslConfig.Pem, config.SslConfig.KeyStr); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal("listen: ", err)
}
} else {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal("listen: ", err)
}
}
@@ -129,30 +131,30 @@ func run() error {
fmt.Println(pkg.Red(string(global.LogoContent)))
tip()
fmt.Println(pkg.Green("Server run at:"))
fmt.Printf("- Local: http://localhost:%d/ \r\n", config.ApplicationConfig.Port)
fmt.Printf("- Network: http://%s:%d/ \r\n", pkg.GetLocaHonst(), config.ApplicationConfig.Port)
fmt.Printf("- Local: %s://localhost:%d/ \r\n", "http", config.ApplicationConfig.Port)
fmt.Printf("- Network: %s://%s:%d/ \r\n", "http", pkg.GetLocalHost(), config.ApplicationConfig.Port)
fmt.Println(pkg.Green("Swagger run at:"))
fmt.Printf("- Local: http://localhost:%d/swagger/admin/index.html \r\n", config.ApplicationConfig.Port)
fmt.Printf("- Network: http://%s:%d/swagger/admin/index.html \r\n", pkg.GetLocaHonst(), config.ApplicationConfig.Port)
fmt.Printf("- Network: %s://%s:%d/swagger/admin/index.html \r\n", "http", pkg.GetLocalHost(), config.ApplicationConfig.Port)
fmt.Printf("%s Enter Control + C Shutdown Server \r\n", pkg.GetCurrentTimeStr())
// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
fmt.Printf("%s Shutdown Server ... \r\n", pkg.GetCurrentTimeStr())
log.Info("Shutdown Server ... ")
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
log.Info("Server exiting")
return nil
}
var Router runtime.Router
//var Router runtime.Router
func tip() {
usageStr := `欢迎使用 ` + pkg.Green(`go-admin `+global.Version) + ` 可以使用 ` + pkg.Red(`-h`) + ` 查看命令`
@@ -171,7 +173,7 @@ func initRouter() {
r = h.(*gin.Engine)
default:
log.Fatal("not support other engine")
os.Exit(-1)
//os.Exit(-1)
}
if config.SslConfig.Enable {
r.Use(handler.TlsHandler())
+1 -1
View File
@@ -1,6 +1,6 @@
package models
//sys_casbin_rule
// CasbinRule sys_casbin_rule
type CasbinRule struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
@@ -1,6 +1,5 @@
package models
//sys_role_dept
type SysRoleDept struct {
RoleId int `gorm:"size:11;primaryKey"`
DeptId int `gorm:"size:11;primaryKey"`
+1 -1
View File
@@ -24,7 +24,7 @@ type SysUser struct {
ModelTime
}
func (SysUser) TableName() string {
func (*SysUser) TableName() string {
return "sys_user"
}
@@ -0,0 +1,42 @@
package version
import (
"runtime"
"gorm.io/gorm"
"go-admin/app/demo/models"
"go-admin/cmd/migrate/migration"
common "go-admin/common/models"
)
// 示例模块的建表迁移。
//
// 迁移文件名前 13 位是版本号(Unix 毫秒时间戳),框架按文件名升序执行,
// 已执行的版本记录在 sys_migration 表中,不会重复运行。
//
// 本文件放在 version/ 是因为它随框架一起分发;**业务项目自己的迁移应放
// version-local/**,该目录已被 .gitignore 忽略,不会与上游冲突。
//
// 生成新的迁移骨架:go run main.go migrate -c config/settings.yml -g
//
// 注意:已执行过的迁移文件不可再修改——版本号已入表,改动不会重跑。
// 需要调整时新建一个迁移。
func init() {
_, fileName, _, _ := runtime.Caller(0)
migration.Migrate.SetVersion(migration.GetFilename(fileName), _1786700000000DemoProduct)
}
func _1786700000000DemoProduct(db *gorm.DB, version string) error {
return db.Transaction(func(tx *gorm.DB) error {
// 1. 建表
if err := tx.Migrator().AutoMigrate(new(models.DemoProduct)); err != nil {
return err
}
// 2. 初始化数据(可选)。此处留空,示例模块不写入业务数据。
// 3. 记录版本号——必须,否则该迁移每次启动都会重跑
return tx.Create(&common.Migration{Version: version}).Error
})
}
@@ -0,0 +1,135 @@
package version
import (
"runtime"
"strconv"
"gorm.io/gorm"
"go-admin/cmd/migrate/migration"
"go-admin/cmd/migrate/migration/models"
common "go-admin/common/models"
)
// 为 app/demo 模块写入菜单、接口与权限种子数据。
//
// 一个业务模块要在界面上可用,需要四类数据:
//
// 1. sys_api —— 后端路由的登记,Casbin 据此判定权限
// 2. sys_menu —— 侧边栏菜单(目录 M / 菜单 C / 按钮 F)
// 3. sys_menu_api_rule —— 菜单与接口的多对多关联,角色保存时据此生成策略
// 4. casbin_rule —— 实际生效的权限策略
//
// 注意 casbin_rule 才是 adapter 实际使用的表;models.CasbinRule 对应的
// sys_casbin_rule 是历史遗留,其 7 列 size:512 唯一索引在 MySQL 下会超出
// 索引长度限制,不要将其纳入 AutoMigrate。
func init() {
_, fileName, _, _ := runtime.Caller(0)
migration.Migrate.SetVersion(migration.GetFilename(fileName), _1786700001000DemoMenu)
}
const (
demoMenuId = 9000 // 目录:示例模块
demoProductId = 9001 // 菜单:商品管理
demoApiBaseId = 9000 // sys_api 起始 id
adminRoleKey = "admin"
)
func _1786700001000DemoMenu(db *gorm.DB, version string) error {
return db.Transaction(func(tx *gorm.DB) error {
// ---- 1. 接口登记 ----
apis := []models.SysApi{
{Id: demoApiBaseId + 1, Handle: "go-admin/common/actions.IndexAction.func1", Title: "示例商品列表", Path: "/api/v1/demo-product", Type: "SYS", Action: "GET"},
{Id: demoApiBaseId + 2, Handle: "go-admin/common/actions.ViewAction.func1", Title: "示例商品详情", Path: "/api/v1/demo-product/:id", Type: "SYS", Action: "GET"},
{Id: demoApiBaseId + 3, Handle: "go-admin/common/actions.CreateAction.func1", Title: "示例商品新增", Path: "/api/v1/demo-product", Type: "SYS", Action: "POST"},
{Id: demoApiBaseId + 4, Handle: "go-admin/common/actions.UpdateAction.func1", Title: "示例商品修改", Path: "/api/v1/demo-product/:id", Type: "SYS", Action: "PUT"},
{Id: demoApiBaseId + 5, Handle: "go-admin/common/actions.DeleteAction.func1", Title: "示例商品删除", Path: "/api/v1/demo-product", Type: "SYS", Action: "DELETE"},
}
for i := range apis {
if err := upsert(tx, &models.SysApi{}, "id = ?", apis[i].Id, &apis[i]); err != nil {
return err
}
}
// ---- 2. 菜单 ----
// 目录本身不对应页面,component 固定为 Layout
dir := models.SysMenu{
MenuId: demoMenuId, MenuName: "Demo", Title: "示例模块", Icon: "example",
Path: "/demo", Paths: "/0/9000", MenuType: "M", ParentId: 0,
Component: "Layout", Sort: 900, Visible: "0", IsFrame: "1",
}
if err := upsert(tx, &models.SysMenu{}, "menu_id = ?", dir.MenuId, &dir); err != nil {
return err
}
// 菜单的 menu_name 需与前端组件 name 一致,否则 keep-alive 缓存无法命中
page := models.SysMenu{
MenuId: demoProductId, MenuName: "DemoProduct", Title: "商品管理", Icon: "documentation",
Path: "/demo/product", Paths: "/0/9000/9001", MenuType: "C", ParentId: demoMenuId,
Component: "/demo/product/index", Sort: 1, Visible: "0", IsFrame: "1",
SysApi: apis,
}
if err := upsert(tx, &models.SysMenu{}, "menu_id = ?", page.MenuId, &page); err != nil {
return err
}
// 按钮级权限,permission 需与前端 v-permisaction 中的标识一致
buttons := []models.SysMenu{
{MenuId: 9002, MenuName: "DemoProductAdd", Title: "新增", MenuType: "F", ParentId: demoProductId, Permission: "demo:product:add", Sort: 1, Visible: "0", IsFrame: "1"},
{MenuId: 9003, MenuName: "DemoProductEdit", Title: "修改", MenuType: "F", ParentId: demoProductId, Permission: "demo:product:edit", Sort: 2, Visible: "0", IsFrame: "1"},
{MenuId: 9004, MenuName: "DemoProductDel", Title: "删除", MenuType: "F", ParentId: demoProductId, Permission: "demo:product:delete", Sort: 3, Visible: "0", IsFrame: "1"},
}
for i := range buttons {
buttons[i].Paths = "/0/9000/9001/" + strconv.Itoa(buttons[i].MenuId)
if err := upsert(tx, &models.SysMenu{}, "menu_id = ?", buttons[i].MenuId, &buttons[i]); err != nil {
return err
}
}
// ---- 3. 授权给 admin 角色 ----
var role models.SysRole
err := tx.Where("role_key = ?", adminRoleKey).First(&role).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
// 尚未初始化角色数据时跳过授权,不阻断迁移
return tx.Create(&common.Migration{Version: version}).Error
}
return err
}
for _, id := range []int{demoMenuId, demoProductId, 9002, 9003, 9004} {
if err = tx.Exec(
"INSERT INTO sys_role_menu (role_id, menu_id) SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu WHERE role_id = ? AND menu_id = ?)",
role.RoleId, id, role.RoleId, id,
).Error; err != nil {
return err
}
}
// ---- 4. Casbin 策略 ----
// admin 角色在中间件中直接放行,此处仍写入策略以便复制该角色配置时可继承
for _, a := range apis {
if err = tx.Exec(
"INSERT INTO casbin_rule (ptype, v0, v1, v2, v3, v4, v5) SELECT 'p', ?, ?, ?, '', '', '' WHERE NOT EXISTS (SELECT 1 FROM casbin_rule WHERE ptype='p' AND v0=? AND v1=? AND v2=?)",
role.RoleKey, a.Path, a.Action, role.RoleKey, a.Path, a.Action,
).Error; err != nil {
return err
}
}
return tx.Create(&common.Migration{Version: version}).Error
})
}
// upsert 存在则更新、不存在则插入。迁移可能在已有数据的库上运行,
// 直接 Create 会因主键冲突失败。
func upsert(tx *gorm.DB, model interface{}, where string, id int, value interface{}) error {
var count int64
if err := tx.Model(model).Where(where, id).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return tx.Model(model).Where(where, id).Updates(value).Error
}
return tx.Create(value).Error
}
@@ -0,0 +1,61 @@
package version
import (
"runtime"
"gorm.io/gorm"
"go-admin/cmd/migrate/migration"
"go-admin/cmd/migrate/migration/models"
common "go-admin/common/models"
)
// 清理 GET /api/v1/refresh_token 的残留权限数据。
//
// 该路由已随 issue #820 的修复移除:它允许用业务 token 换取新 token,而续期
// 上限 MaxRefresh 依据的 orig_iat 每次续期都被重置,上限永远无法到达。
//
// 路由虽已删除(请求会返回 404),但已有部署的库中仍残留三类记录:接口登记、
// 菜单与接口的绑定、Casbin 策略。留着会让「接口管理」列出一个不存在的端点,
// 角色配置里也仍可勾选,造成误解。
//
// 按 path 匹配而非固定 id —— 使用者若执行过 `server -a` 重新注册接口,id 会
// 与官方种子数据不同。
func init() {
_, fileName, _, _ := runtime.Caller(0)
migration.Migrate.SetVersion(migration.GetFilename(fileName), _1786700002000RemoveRefreshTokenApi)
}
const refreshTokenPath = "/api/v1/refresh_token"
func _1786700002000RemoveRefreshTokenApi(db *gorm.DB, version string) error {
return db.Transaction(func(tx *gorm.DB) error {
var apiIds []int
if err := tx.Model(&models.SysApi{}).
Where("path = ? AND action = ?", refreshTokenPath, "GET").
Pluck("id", &apiIds).Error; err != nil {
return err
}
if len(apiIds) > 0 {
// 先断开绑定,再删接口本身,避免遗留悬空外键
// 连接表由 GORM many2many 生成,列名为 sys_api_id 而非 api_id
if err := tx.Exec("DELETE FROM sys_menu_api_rule WHERE sys_api_id IN ?", apiIds).Error; err != nil {
return err
}
if err := tx.Where("id IN ?", apiIds).Delete(&models.SysApi{}).Error; err != nil {
return err
}
}
// casbin_rule 才是 adapter 实际使用的表,策略按路径存储,与 sys_api 的
// id 无关,因此即使上面没匹配到接口也要清理
if err := tx.Exec(
"DELETE FROM casbin_rule WHERE ptype = 'p' AND v1 = ?", refreshTokenPath,
).Error; err != nil {
return err
}
return tx.Create(&common.Migration{Version: version}).Error
})
}
+13 -1
View File
@@ -62,7 +62,19 @@ func migrateModel() error {
if host == "" {
host = "*"
}
db := sdk.Runtime.GetDbByKey(host)
db := sdk.Runtime.GetDbByTenant(host)
if db == nil {
if len(sdk.Runtime.GetAllDb()) == 1 && host == "*" {
for k, v := range sdk.Runtime.GetAllDb() {
db = v
host = k
break
}
}
}
if db == nil {
return fmt.Errorf("未找到数据库配置")
}
if config.DatabasesConfig[host].Driver == "mysql" {
//初始化数据库时候用
db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4")
+7 -7
View File
@@ -27,7 +27,7 @@ func (e *Api) AddError(err error) {
e.Errors = err
} else if err != nil {
e.Logger.Error(err)
e.Errors = fmt.Errorf("%v; %w", e.Error, err)
e.Errors = fmt.Errorf("%v; %w", e.Errors, err)
}
}
@@ -39,7 +39,7 @@ func (e *Api) MakeContext(c *gin.Context) *Api {
}
// GetLogger 获取上下文提供的日志
func (e Api) GetLogger() *logger.Helper {
func (e *Api) GetLogger() *logger.Helper {
return api.GetRequestLogger(e.Context)
}
@@ -81,7 +81,7 @@ func (e *Api) Bind(d interface{}, bindings ...binding.Binding) *Api {
}
// GetOrm 获取Orm DB
func (e Api) GetOrm() (*gorm.DB, error) {
func (e *Api) GetOrm() (*gorm.DB, error) {
db, err := pkg.GetOrm(e.Context)
if err != nil {
e.Error(500, err, "数据库连接获取失败")
@@ -115,21 +115,21 @@ func (e *Api) MakeService(c *service.Service) *Api {
}
// Error 通常错误数据处理
func (e Api) Error(code int, err error, msg string) {
func (e *Api) Error(code int, err error, msg string) {
response.Error(e.Context, code, err, msg)
}
// OK 通常成功数据处理
func (e Api) OK(data interface{}, msg string) {
func (e *Api) OK(data interface{}, msg string) {
response.OK(e.Context, data, msg)
}
// PageOK 分页数据处理
func (e Api) PageOK(result interface{}, count int, pageIndex int, pageSize int, msg string) {
func (e *Api) PageOK(result interface{}, count int, pageIndex int, pageSize int, msg string) {
response.PageOK(e.Context, result, count, pageIndex, pageSize, msg)
}
// Custom 兼容函数
func (e Api) Custom(data gin.H) {
func (e *Api) Custom(data gin.H) {
response.Custum(e.Context, data)
}
+2 -2
View File
@@ -60,6 +60,6 @@ func setupSimpleDatabase(host string, c *toolsConfig.Database) {
e := mycasbin.Setup(db, "")
sdk.Runtime.SetDb(host, db)
sdk.Runtime.SetCasbin(host, e)
sdk.Runtime.SetDbByTenant(host, db)
sdk.Runtime.SetCasbinByTenant(host, e)
}
-1
View File
@@ -1,5 +1,4 @@
//go:build !sqlite3
// +build !sqlite3
package database
+7 -11
View File
@@ -13,21 +13,17 @@ type GeneralDelDto struct {
func (g GeneralDelDto) GetIds() []int {
ids := make([]int, 0)
if g.Id != 0 {
// Id 此前在 else 分支里被重复追加:仅传 Id 时会得到 [5 5],
// 同一条记录被执行两次删除
if g.Id > 0 {
ids = append(ids, g.Id)
}
if len(g.Ids) > 0 {
for _, id := range g.Ids {
if id > 0 {
ids = append(ids, id)
}
}
} else {
if g.Id > 0 {
ids = append(ids, g.Id)
for _, id := range g.Ids {
if id > 0 {
ids = append(ids, id)
}
}
if len(ids) <= 0 {
if len(ids) == 0 {
//方式全部删除
ids = append(ids, 0)
}
+32
View File
@@ -0,0 +1,32 @@
package dto
import (
"reflect"
"testing"
)
// GetIds 曾在 else 分支中重复追加 Id:仅传 Id 时返回 [5 5],
// 导致删除接口对同一条记录执行两次。
func TestGeneralDelDtoGetIds(t *testing.T) {
cases := []struct {
name string
dto GeneralDelDto
want []int
}{
{"仅 Id", GeneralDelDto{Id: 5}, []int{5}},
{"仅 Ids", GeneralDelDto{Ids: []int{1, 2}}, []int{1, 2}},
{"Id 与 Ids 并存", GeneralDelDto{Id: 5, Ids: []int{1, 2}}, []int{5, 1, 2}},
{"Ids 含非正数被过滤", GeneralDelDto{Ids: []int{0, -1, 3}}, []int{3}},
{"Id 为 0 视为未传", GeneralDelDto{Id: 0, Ids: []int{7}}, []int{7}},
{"全部为空时回退到 0(全量删除约定)", GeneralDelDto{}, []int{0}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := c.dto.GetIds()
if !reflect.DeepEqual(got, c.want) {
t.Errorf("GetIds() = %v, want %v", got, c.want)
}
})
}
}
+2 -3
View File
@@ -2,11 +2,10 @@ package global
const (
// Version go-admin version info
Version = "2.1.0"
Version = "2.4.0"
)
var (
Source string
// Driver 数据库驱动
Driver string
DBName string
)
+3 -3
View File
@@ -1,7 +1,7 @@
package global
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v3"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/sdk/api"
@@ -9,8 +9,8 @@ import (
func LoadPolicy(c *gin.Context) (*casbin.SyncedEnforcer, error) {
log := api.GetRequestLogger(c)
if err := sdk.Runtime.GetCasbinKey(c.Request.Host).LoadPolicy(); err == nil {
return sdk.Runtime.GetCasbinKey(c.Request.Host), err
if err := sdk.Runtime.GetCasbinByTenant(c.Request.Host).LoadPolicy(); err == nil {
return sdk.Runtime.GetCasbinByTenant(c.Request.Host), err
} else {
log.Errorf("casbin rbac_model or policy init error, %s ", err.Error())
return nil, err
+12 -11
View File
@@ -6,22 +6,23 @@ import (
)
func GetClientIP(c *gin.Context) string {
ClientIP := c.ClientIP()
//fmt.Println("ClientIP:", ClientIP)
RemoteIP, _ := c.RemoteIP()
//fmt.Println("RemoteIP:", RemoteIP)
// 优先从 X-Forwarded-For 获取 IP
ip := c.Request.Header.Get("X-Forwarded-For")
if strings.Contains(ip, "127.0.0.1") || ip == "" {
if ip == "" || strings.Contains(ip, "127.0.0.1") {
// 如果为空或为本地地址,则尝试从 X-Real-IP 获取
ip = c.Request.Header.Get("X-real-ip")
}
if ip == "" {
// 如果仍为空,则使用 RemoteIP
ip = c.RemoteIP()
}
if ip == "" || ip == "127.0.0.1" {
// 如果仍为空或为本地地址,则使用 ClientIP
ip = c.ClientIP()
}
if ip == "" {
// 最后兜底为本地地址
ip = "127.0.0.1"
}
if RemoteIP.String() != "127.0.0.1" {
ip = RemoteIP.String()
}
if ClientIP != "127.0.0.1" {
ip = ClientIP
}
return ip
}
+1 -1
View File
@@ -6,6 +6,6 @@ import (
)
func WithContextDb(c *gin.Context) {
c.Set("db", sdk.Runtime.GetDbByKey(c.Request.Host).WithContext(c))
c.Set("db", sdk.Runtime.GetDbByTenant(c.Request.Host).WithContext(c))
c.Next()
}
+3 -3
View File
@@ -93,11 +93,11 @@ func Authenticator(c *gin.Context) (interface{}, error) {
return nil, jwt.ErrInvalidVerificationode
}
}
user, role, e := loginVals.GetUser(db)
sysUser, role, e := loginVals.GetUser(db)
if e == nil {
username = loginVals.Username
return map[string]interface{}{"user": user, "role": role}, nil
return map[string]interface{}{"user": sysUser, "role": role}, nil
} else {
msg = "登录失败"
status = "1"
@@ -127,7 +127,7 @@ func LoginLogToDB(c *gin.Context, status string, msg string, username string) {
l["username"] = username
l["msg"] = msg
q := sdk.Runtime.GetMemoryQueue(c.Request.Host)
q := sdk.Runtime.GetQueuePrefix(c.Request.Host)
message, err := sdk.Runtime.GetStreamMessage("", global.LoginLog, l)
if err != nil {
log.Errorf("GetStreamMessage error, %s", err.Error())
+1 -1
View File
@@ -28,7 +28,7 @@ type SysUser struct {
models.ModelTime
}
func (SysUser) TableName() string {
func (*SysUser) TableName() string {
return "sys_user"
}
+5 -3
View File
@@ -91,7 +91,9 @@ func LoggerToFile() gin.HandlerFunc {
"uri": reqUri,
}
log.WithFields(logData).Info()
defer func() {
log.Fields(map[string]interface{}{})
}()
if c.Request.Method != "OPTIONS" && config.LoggerConfig.EnabledDB && statusCode != 404 {
SetDBOperLog(c, clientIP, statusCode, reqUri, reqMethod, latencyTime, body, result, statusBus)
}
@@ -122,11 +124,11 @@ func SetDBOperLog(c *gin.Context, clientIP string, statusCode int, reqUri string
} else {
l["status"] = dto.OperaStatusDisable
}
q := sdk.Runtime.GetMemoryQueue(c.Request.Host)
q := sdk.Runtime.GetQueuePrefix(c.Request.Host)
message, err := sdk.Runtime.GetStreamMessage("", global.OperateLog, l)
if err != nil {
log.Errorf("GetStreamMessage error, %s", err.Error())
//日志报错错误,不中断请求
// 日志报错错误,不中断请求
} else {
err = q.Append(message)
if err != nil {
+2 -2
View File
@@ -1,7 +1,7 @@
package middleware
import (
"github.com/casbin/casbin/v2/util"
"github.com/casbin/casbin/v3/util"
"net/http"
"github.com/gin-gonic/gin"
@@ -17,7 +17,7 @@ func AuthCheckRole() gin.HandlerFunc {
log := api.GetRequestLogger(c)
data, _ := c.Get(jwtauth.JwtPayloadKey)
v := data.(jwtauth.MapClaims)
e := sdk.Runtime.GetCasbinKey(c.Request.Host)
e := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
var res, casbinExclude bool
var err error
//检查权限
-1
View File
@@ -24,7 +24,6 @@ var CasbinExclude = []UrlInfo{
{Url: "/api/v1/menuids", Method: "GET"},
{Url: "/api/v1/roleMenuTreeselect/:roleId", Method: "GET"},
{Url: "/api/v1/roleDeptTreeselect/:roleId", Method: "GET"},
{Url: "/api/v1/refresh_token", Method: "GET"},
{Url: "/api/v1/configKey/:configKey", Method: "GET"},
{Url: "/api/v1/app-config", Method: "GET"},
{Url: "/api/v1/user/profile", Method: "GET"},
+1 -1
View File
@@ -15,7 +15,7 @@ func Trace() gin.HandlerFunc {
opentracing.TextMap,
opentracing.HTTPHeadersCarrier(ctx.Request.Header))
if err != nil {
// If for whatever reason we can't join, go ahead an start a new root span.
// If for whatever reason we can't join, go ahead and start a new root span.
sp = opentracing.StartSpan(opName)
} else {
sp = opentracing.StartSpan(opName, opentracing.ChildOf(wireContext))
+1 -1
View File
@@ -17,7 +17,7 @@ const (
query
)
var constructor = &bindConstructor{}
//var constructor = &bindConstructor{}
type bindConstructor struct {
cache map[string][]uint8
+27 -29
View File
@@ -1,8 +1,8 @@
/*
* @Author: lwnmengjing
* @Date: 2021/6/10 3:39 下午
* @Last Modified by: lwnmengjing
* @Last Modified time: 2021/6/10 3:39 下午
* @Author: zhangwenjian
* @Date: 2025/04/13 22:03
* @Last Modified by: zhangwenjian
* @Last Modified time: 2025/04/13 22:03
*/
package storage
@@ -12,41 +12,39 @@ import (
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/go-admin-team/go-admin-core/sdk/pkg/captcha"
"github.com/go-admin-team/go-admin-core/captcha"
)
// Setup 配置storage组件
func Setup() {
//4. 设置缓存
setupCache()
setupCaptcha()
setupQueue()
}
func setupCache() {
cacheAdapter, err := config.CacheConfig.Setup()
if err != nil {
log.Fatalf("cache setup error, %s\n", err.Error())
}
sdk.Runtime.SetCacheAdapter(cacheAdapter)
//5. 设置验证码store
captcha.SetStore(captcha.NewCacheStore(cacheAdapter, 600))
}
//6. 设置队列
if !config.QueueConfig.Empty() {
if q := sdk.Runtime.GetQueueAdapter(); q != nil {
q.Shutdown()
}
queueAdapter, err := config.QueueConfig.Setup()
if err != nil {
log.Fatalf("queue setup error, %s\n", err.Error())
}
sdk.Runtime.SetQueueAdapter(queueAdapter)
defer func() {
go queueAdapter.Run()
}()
func setupCaptcha() {
captcha.SetStore(captcha.NewCacheStore(sdk.Runtime.GetCacheAdapter(), 600))
}
func setupQueue() {
if config.QueueConfig.Empty() {
return
}
if q := sdk.Runtime.GetQueueAdapter(); q != nil {
q.Shutdown()
}
//7. 设置分布式锁
if !config.LockerConfig.Empty() {
lockerAdapter, err := config.LockerConfig.Setup()
if err != nil {
log.Fatalf("locker setup error, %s\n", err.Error())
}
sdk.Runtime.SetLockerAdapter(lockerAdapter)
queueAdapter, err := config.QueueConfig.Setup()
if err != nil {
log.Fatalf("queue setup error, %s\n", err.Error())
}
sdk.Runtime.SetQueueAdapter(queueAdapter)
go queueAdapter.Run()
}
+1 -1
View File
@@ -5,7 +5,7 @@
settings:
application:
# 项目启动环境
mode: dev # dev开发环境 test测试环境 prod线上环境;
mode: dev # dev开发环境 prod线上环境;
host: 0.0.0.0 # 主机ip 或者域名,默认0.0.0.0
# 服务名称
name: go-admin
-1
View File
@@ -37,7 +37,6 @@ INSERT INTO sys_api (id, handle, title, path, type, "action", created_at, update
(45, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '菜单权限列表【角色配菜单使用】', '/api/v1/roleMenuTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.762', '2021-06-17 11:48:40.732', NULL, 0, 0),
(46, 'go-admin/app/admin/apis.SysDept.GetDeptTreeRoleSelect-fm', '角色部门结构树【自定义数据权限】', '/api/v1/roleDeptTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.809', '2021-06-17 11:48:40.732', NULL, 0, 0),
(47, 'go-admin/app/admin/apis.SysRole.Get-fm', '角色通过id获取', '/api/v1/role/:id', 'BUS', 'GET', '2021-05-13 19:59:02.850', '2021-06-17 11:48:40.732', NULL, 0, 0),
(48, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).RefreshHandler-fm', '刷新token', '/api/v1/refresh_token', 'SYS', 'GET', '2021-05-13 19:59:02.892', '2021-06-13 20:53:49.278', NULL, 0, 0),
(53, 'go-admin/app/admin/apis.SysConfig.GetPage-fm', '参数列表', '/api/v1/config', 'BUS', 'GET', '2021-05-13 19:59:03.116', '2021-06-17 11:48:40.732', NULL, 0, 0),
(54, 'go-admin/app/admin/apis.SysConfig.Get-fm', '参数通过id获取', '/api/v1/config/:id', 'BUS', 'GET', '2021-05-13 19:59:03.157', '2021-06-17 11:48:40.732', NULL, 0, 0),
(55, 'go-admin/app/admin/apis.SysConfig.GetSysConfigByKEYForService-fm', '参数通过键名搜索【基础默认配置】', '/api/v1/configKey/:configKey', 'SYS', 'GET', '2021-05-13 19:59:03.198', '2021-06-13 20:53:49.745', NULL, 0, 0),
+68 -69
View File
@@ -35,7 +35,6 @@ INSERT INTO sys_api VALUES (44, 'go-admin/app/admin/apis.SysRole.GetPage-fm', '
INSERT INTO sys_api VALUES (45, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '菜单权限列表【角色配菜单使用】', '/api/v1/roleMenuTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.762', '2021-06-17 11:48:40.732', NULL, 0, 0);
INSERT INTO sys_api VALUES (46, 'go-admin/app/admin/apis.SysDept.GetDeptTreeRoleSelect-fm', '角色部门结构树【自定义数据权限】', '/api/v1/roleDeptTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.809', '2021-06-17 11:48:40.732', NULL, 0, 0);
INSERT INTO sys_api VALUES (47, 'go-admin/app/admin/apis.SysRole.Get-fm', '角色通过id获取', '/api/v1/role/:id', 'BUS', 'GET', '2021-05-13 19:59:02.850', '2021-06-17 11:48:40.732', NULL, 0, 0);
INSERT INTO sys_api VALUES (48, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).RefreshHandler-fm', '刷新token', '/api/v1/refresh_token', 'SYS', 'GET', '2021-05-13 19:59:02.892', '2021-06-13 20:53:49.278', NULL, 0, 0);
INSERT INTO sys_api VALUES (53, 'go-admin/app/admin/apis.SysConfig.GetPage-fm', '参数列表', '/api/v1/config', 'BUS', 'GET', '2021-05-13 19:59:03.116', '2021-06-17 11:48:40.732', NULL, 0, 0);
INSERT INTO sys_api VALUES (54, 'go-admin/app/admin/apis.SysConfig.Get-fm', '参数通过id获取', '/api/v1/config/:id', 'BUS', 'GET', '2021-05-13 19:59:03.157', '2021-06-17 11:48:40.732', NULL, 0, 0);
INSERT INTO sys_api VALUES (55, 'go-admin/app/admin/apis.SysConfig.GetSysConfigByKEYForService-fm', '参数通过键名搜索【基础默认配置】', '/api/v1/configKey/:configKey', 'SYS', 'GET', '2021-05-13 19:59:03.198', '2021-06-13 20:53:49.745', NULL, 0, 0);
@@ -182,73 +181,73 @@ INSERT INTO sys_dict_type VALUES (11, '内容状态', 'sys_content_status', '2',
INSERT INTO sys_job VALUES (1, '接口测试', 'DEFAULT', 1, '0/5 * * * * ', 'http://localhost:8000', '', 1, 1, 1, 0, '2021-05-13 19:56:37.914', '2021-06-14 20:59:55.417', NULL, 1, 1);
INSERT INTO sys_job VALUES (2, '函数测试', 'DEFAULT', 2, '0/5 * * * * ', 'ExamplesOne', '参数', 1, 1, 1, 0, '2021-05-13 19:56:37.914', '2021-05-31 23:55:37.221', NULL, 1, 1);
INSERT INTO sys_menu VALUES (2, 'Admin', '系统管理', 'api-server', '/admin', '/0/2', 'M', '无', '', 0, 1, '', 'Layout', 10, '0', '1', 0, 1, '2021-05-20 21:58:45.679', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (3, 'SysUserManage', '用户管理', 'user', '/admin/sys-user', '/0/2/3', 'C', '无', 'admin:sysUser:list', 2, 0, '', '/admin/sys-user/index', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (43, '', '新增管理员', 'app-group-fill', '', '/0/2/3/43', 'F', 'POST', 'admin:sysUser:add', 3, 0, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (44, '', '查询管理员', 'app-group-fill', '', '/0/2/3/44', 'F', 'GET', 'admin:sysUser:query', 3, 0, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (45, '', '修改管理员', 'app-group-fill', '', '/0/2/3/45', 'F', 'PUT', 'admin:sysUser:edit', 3, 0, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (46, '', '删除管理员', 'app-group-fill', '', '/0/2/3/46', 'F', 'DELETE', 'admin:sysUser:remove', 3, 0, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (51, 'SysMenuManage', '菜单管理', 'tree-table', '/admin/sys-menu', '/0/2/51', 'C', '无', 'admin:sysMenu:list', 2, 1, '', '/admin/sys-menu/index', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (52, 'SysRoleManage', '角色管理', 'peoples', '/admin/sys-role', '/0/2/52', 'C', '无', 'admin:sysRole:list', 2, 1, '', '/admin/sys-role/index', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (56, 'SysDeptManage', '部门管理', 'tree', '/admin/sys-dept', '/0/2/56', 'C', '无', 'admin:sysDept:list', 2, 0, '', '/admin/sys-dept/index', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (57, 'SysPostManage', '岗位管理', 'pass', '/admin/sys-post', '/0/2/57', 'C', '无', 'admin:sysPost:list', 2, 0, '', '/admin/sys-post/index', 50, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (58, 'Dict', '字典管理', 'education', '/admin/dict', '/0/2/58', 'C', '无', 'admin:sysDictType:list', 2, 0, '', '/admin/dict/index', 60, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (59, 'SysDictDataManage', '字典数据', 'education', '/admin/dict/data/:dictId', '/0/2/59', 'C', '无', 'admin:sysDictData:list', 2, 0, '', '/admin/dict/data', 100, '1', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (60, 'Tools', '开发工具', 'dev-tools', '/dev-tools', '/0/60', 'M', '无', '', 0, 0, '', 'Layout', 40, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (61, 'Swagger', '系统接口', 'guide', '/dev-tools/swagger', '/0/60/61', 'C', '无', '', 60, 0, '', '/dev-tools/swagger/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (62, 'SysConfigManage', '参数管理', 'swagger', '/admin/sys-config', '/0/2/62', 'C', '无', 'admin:sysConfig:list', 2, 0, '', '/admin/sys-config/index', 70, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (211, 'Log', '日志管理', 'log', '/log', '/0/2/211', 'M', '', '', 2, 0, '', '/log/index', 80, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (212, 'SysLoginLogManage', '登录日志', 'logininfor', '/admin/sys-login-log', '/0/2/211/212', 'C', '', 'admin:sysLoginLog:list', 211, 0, '', '/admin/sys-login-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (216, 'OperLog', '操作日志', 'skill', '/admin/sys-oper-log', '/0/2/211/216', 'C', '', 'admin:sysOperLog:list', 211, 0, '', '/admin/sys-oper-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (220, '', '新增菜单', 'app-group-fill', '', '/0/2/51/220', 'F', '', 'admin:sysMenu:add', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (221, '', '修改菜单', 'app-group-fill', '', '/0/2/51/221', 'F', '', 'admin:sysMenu:edit', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (222, '', '查询菜单', 'app-group-fill', '', '/0/2/51/222', 'F', '', 'admin:sysMenu:query', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (223, '', '删除菜单', 'app-group-fill', '', '/0/2/51/223', 'F', '', 'admin:sysMenu:remove', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (224, '', '新增角色', 'app-group-fill', '', '/0/2/52/224', 'F', '', 'admin:sysRole:add', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (225, '', '查询角色', 'app-group-fill', '', '/0/2/52/225', 'F', '', 'admin:sysRole:query', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (226, '', '修改角色', 'app-group-fill', '', '/0/2/52/226', 'F', '', 'admin:sysRole:update', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (227, '', '删除角色', 'app-group-fill', '', '/0/2/52/227', 'F', '', 'admin:sysRole:remove', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (228, '', '查询部门', 'app-group-fill', '', '/0/2/56/228', 'F', '', 'admin:sysDept:query', 56, 0, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (229, '', '新增部门', 'app-group-fill', '', '/0/2/56/229', 'F', '', 'admin:sysDept:add', 56, 0, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (230, '', '修改部门', 'app-group-fill', '', '/0/2/56/230', 'F', '', 'admin:sysDept:edit', 56, 0, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (231, '', '删除部门', 'app-group-fill', '', '/0/2/56/231', 'F', '', 'admin:sysDept:remove', 56, 0, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (232, '', '查询岗位', 'app-group-fill', '', '/0/2/57/232', 'F', '', 'admin:sysPost:query', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (233, '', '新增岗位', 'app-group-fill', '', '/0/2/57/233', 'F', '', 'admin:sysPost:add', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (234, '', '修改岗位', 'app-group-fill', '', '/0/2/57/234', 'F', '', 'admin:sysPost:edit', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (235, '', '删除岗位', 'app-group-fill', '', '/0/2/57/235', 'F', '', 'admin:sysPost:remove', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (236, '', '查询字典', 'app-group-fill', '', '/0/2/58/236', 'F', '', 'admin:sysDictType:query', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (237, '', '新增类型', 'app-group-fill', '', '/0/2/58/237', 'F', '', 'admin:sysDictType:add', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (238, '', '修改类型', 'app-group-fill', '', '/0/2/58/238', 'F', '', 'admin:sysDictType:edit', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (239, '', '删除类型', 'app-group-fill', '', '/0/2/58/239', 'F', '', 'system:sysdicttype:remove', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (240, '', '查询数据', 'app-group-fill', '', '/0/2/59/240', 'F', '', 'admin:sysDictData:query', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (241, '', '新增数据', 'app-group-fill', '', '/0/2/59/241', 'F', '', 'admin:sysDictData:add', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (242, '', '修改数据', 'app-group-fill', '', '/0/2/59/242', 'F', '', 'admin:sysDictData:edit', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (243, '', '删除数据', 'app-group-fill', '', '/0/2/59/243', 'F', '', 'admin:sysDictData:remove', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (244, '', '查询参数', 'app-group-fill', '', '/0/2/62/244', 'F', '', 'admin:sysConfig:query', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (245, '', '新增参数', 'app-group-fill', '', '/0/2/62/245', 'F', '', 'admin:sysConfig:add', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (246, '', '修改参数', 'app-group-fill', '', '/0/2/62/246', 'F', '', 'admin:sysConfig:edit', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (247, '', '删除参数', 'app-group-fill', '', '/0/2/62/247', 'F', '', 'admin:sysConfig:remove', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (248, '', '查询登录日志', 'app-group-fill', '', '/0/2/211/212/248', 'F', '', 'admin:sysLoginLog:query', 212, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (249, '', '删除登录日志', 'app-group-fill', '', '/0/2/211/212/249', 'F', '', 'admin:sysLoginLog:remove', 212, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (250, '', '查询操作日志', 'app-group-fill', '', '/0/2/211/216/250', 'F', '', 'admin:sysOperLog:query', 216, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (251, '', '删除操作日志', 'app-group-fill', '', '/0/2/211/216/251', 'F', '', 'admin:sysOperLog:remove', 216, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (261, 'Gen', '代码生成', 'code', '/dev-tools/gen', '/0/60/261', 'C', '', '', 60, 0, '', '/dev-tools/gen/index', 2, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (262, 'EditTable', '代码生成修改', 'build', '/dev-tools/editTable', '/0/60/262', 'C', '', '', 60, 0, '', '/dev-tools/gen/editTable', 100, '1', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (264, 'Build', '表单构建', 'build', '/dev-tools/build', '/0/60/264', 'C', '', '', 60, 0, '', '/dev-tools/build/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (269, 'ServerMonitor', '服务监控', 'druid', '/sys-tools/monitor', '/0/60/269', 'C', '', 'sysTools:serverMonitor:list', 537, 0, '', '/sys-tools/monitor', 0, '0', '1', 1, 1, '2020-04-14 00:28:19.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (459, 'Schedule', '定时任务', 'time-range', '/schedule', '/0/459', 'M', '无', '', 0, 0, '', 'Layout', 20, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (460, 'ScheduleManage', 'Schedule', 'job', '/schedule/manage', '/0/459/460', 'C', '无', 'job:sysJob:list', 459, 0, '', '/schedule/index', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (461, 'sys_job', '分页获取定时任务', 'app-group-fill', '', '/0/459/460/461', 'F', '无', 'job:sysJob:query', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (462, 'sys_job', '创建定时任务', 'app-group-fill', '', '/0/459/460/462', 'F', '无', 'job:sysJob:add', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (463, 'sys_job', '修改定时任务', 'app-group-fill', '', '/0/459/460/463', 'F', '无', 'job:sysJob:edit', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (464, 'sys_job', '删除定时任务', 'app-group-fill', '', '/0/459/460/464', 'F', '无', 'job:sysJob:remove', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (471, 'JobLog', '日志', 'bug', '/schedule/log', '/0/459/471', 'C', '', '', 459, 0, '', '/schedule/log', 0, '1', '1', 1, 1, '2020-08-05 21:24:46.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (528, 'SysApiManage', '接口管理', 'api-doc', '/admin/sys-api', '/0/527/528', 'C', '无', 'admin:sysApi:list', 2, 0, '', '/admin/sys-api/index', 0, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (529, '', '查询接口', 'app-group-fill', '', '/0/527/528/529', 'F', '无', 'admin:sysApi:query', 528, 0, '', '', 40, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (531, '', '修改接口', 'app-group-fill', '', '/0/527/528/531', 'F', '无', 'admin:sysApi:edit', 528, 0, '', '', 30, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (537, 'SysTools', '系统工具', 'system-tools', '/sys-tools', '', 'M', '', '', 0, 0, '', 'Layout', 30, '0', '1', 1, 1, '2021-05-21 11:13:32.166', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (540, 'SysConfigSet', '参数设置', 'system-tools', '/admin/sys-config/set', '', 'C', '', 'admin:sysConfigSet:list', 2, 0, '', '/admin/sys-config/set', 0, '0', '1', 1, 1, '2021-05-25 16:06:52.560', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (542, '', '修改', 'upload', '', '', 'F', '', 'admin:sysConfigSet:update', 540, 0, '', '', 0, '0', '1', 1, 1, '2021-06-13 11:45:48.670', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (2, 'Admin', '系统管理', 'api-server', '/admin', '/0/2', 'M', '无', '', 0, true, '', 'Layout', 10, '0', '1', 0, 1, '2021-05-20 21:58:45.679', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (3, 'SysUserManage', '用户管理', 'user', '/admin/sys-user', '/0/2/3', 'C', '无', 'admin:sysUser:list', 2, false, '', '/admin/sys-user/index', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (43, '', '新增管理员', 'app-group-fill', '', '/0/2/3/43', 'F', 'POST', 'admin:sysUser:add', 3, false, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (44, '', '查询管理员', 'app-group-fill', '', '/0/2/3/44', 'F', 'GET', 'admin:sysUser:query', 3, false, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (45, '', '修改管理员', 'app-group-fill', '', '/0/2/3/45', 'F', 'PUT', 'admin:sysUser:edit', 3, false, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (46, '', '删除管理员', 'app-group-fill', '', '/0/2/3/46', 'F', 'DELETE', 'admin:sysUser:remove', 3, false, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL);
INSERT INTO sys_menu VALUES (51, 'SysMenuManage', '菜单管理', 'tree-table', '/admin/sys-menu', '/0/2/51', 'C', '无', 'admin:sysMenu:list', 2, true, '', '/admin/sys-menu/index', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (52, 'SysRoleManage', '角色管理', 'peoples', '/admin/sys-role', '/0/2/52', 'C', '无', 'admin:sysRole:list', 2, true, '', '/admin/sys-role/index', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (56, 'SysDeptManage', '部门管理', 'tree', '/admin/sys-dept', '/0/2/56', 'C', '无', 'admin:sysDept:list', 2, false, '', '/admin/sys-dept/index', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (57, 'SysPostManage', '岗位管理', 'pass', '/admin/sys-post', '/0/2/57', 'C', '无', 'admin:sysPost:list', 2, false, '', '/admin/sys-post/index', 50, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (58, 'Dict', '字典管理', 'education', '/admin/dict', '/0/2/58', 'C', '无', 'admin:sysDictType:list', 2, false, '', '/admin/dict/index', 60, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (59, 'SysDictDataManage', '字典数据', 'education', '/admin/dict/data/:dictId', '/0/2/59', 'C', '无', 'admin:sysDictData:list', 2, false, '', '/admin/dict/data', 100, '1', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (60, 'Tools', '开发工具', 'dev-tools', '/dev-tools', '/0/60', 'M', '无', '', 0, false, '', 'Layout', 40, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (61, 'Swagger', '系统接口', 'guide', '/dev-tools/swagger', '/0/60/61', 'C', '无', '', 60, false, '', '/dev-tools/swagger/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (62, 'SysConfigManage', '参数管理', 'swagger', '/admin/sys-config', '/0/2/62', 'C', '无', 'admin:sysConfig:list', 2, false, '', '/admin/sys-config/index', 70, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (211, 'Log', '日志管理', 'log', '/log', '/0/2/211', 'M', '', '', 2, false, '', '/log/index', 80, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (212, 'SysLoginLogManage', '登录日志', 'logininfor', '/admin/sys-login-log', '/0/2/211/212', 'C', '', 'admin:sysLoginLog:list', 211, false, '', '/admin/sys-login-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (216, 'OperLog', '操作日志', 'skill', '/admin/sys-oper-log', '/0/2/211/216', 'C', '', 'admin:sysOperLog:list', 211, false, '', '/admin/sys-oper-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (220, '', '新增菜单', 'app-group-fill', '', '/0/2/51/220', 'F', '', 'admin:sysMenu:add', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (221, '', '修改菜单', 'app-group-fill', '', '/0/2/51/221', 'F', '', 'admin:sysMenu:edit', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (222, '', '查询菜单', 'app-group-fill', '', '/0/2/51/222', 'F', '', 'admin:sysMenu:query', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (223, '', '删除菜单', 'app-group-fill', '', '/0/2/51/223', 'F', '', 'admin:sysMenu:remove', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (224, '', '新增角色', 'app-group-fill', '', '/0/2/52/224', 'F', '', 'admin:sysRole:add', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (225, '', '查询角色', 'app-group-fill', '', '/0/2/52/225', 'F', '', 'admin:sysRole:query', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (226, '', '修改角色', 'app-group-fill', '', '/0/2/52/226', 'F', '', 'admin:sysRole:update', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (227, '', '删除角色', 'app-group-fill', '', '/0/2/52/227', 'F', '', 'admin:sysRole:remove', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (228, '', '查询部门', 'app-group-fill', '', '/0/2/56/228', 'F', '', 'admin:sysDept:query', 56, false, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (229, '', '新增部门', 'app-group-fill', '', '/0/2/56/229', 'F', '', 'admin:sysDept:add', 56, false, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (230, '', '修改部门', 'app-group-fill', '', '/0/2/56/230', 'F', '', 'admin:sysDept:edit', 56, false, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (231, '', '删除部门', 'app-group-fill', '', '/0/2/56/231', 'F', '', 'admin:sysDept:remove', 56, false, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (232, '', '查询岗位', 'app-group-fill', '', '/0/2/57/232', 'F', '', 'admin:sysPost:query', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (233, '', '新增岗位', 'app-group-fill', '', '/0/2/57/233', 'F', '', 'admin:sysPost:add', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (234, '', '修改岗位', 'app-group-fill', '', '/0/2/57/234', 'F', '', 'admin:sysPost:edit', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (235, '', '删除岗位', 'app-group-fill', '', '/0/2/57/235', 'F', '', 'admin:sysPost:remove', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (236, '', '查询字典', 'app-group-fill', '', '/0/2/58/236', 'F', '', 'admin:sysDictType:query', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (237, '', '新增类型', 'app-group-fill', '', '/0/2/58/237', 'F', '', 'admin:sysDictType:add', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (238, '', '修改类型', 'app-group-fill', '', '/0/2/58/238', 'F', '', 'admin:sysDictType:edit', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (239, '', '删除类型', 'app-group-fill', '', '/0/2/58/239', 'F', '', 'system:sysdicttype:remove', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (240, '', '查询数据', 'app-group-fill', '', '/0/2/59/240', 'F', '', 'admin:sysDictData:query', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (241, '', '新增数据', 'app-group-fill', '', '/0/2/59/241', 'F', '', 'admin:sysDictData:add', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (242, '', '修改数据', 'app-group-fill', '', '/0/2/59/242', 'F', '', 'admin:sysDictData:edit', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (243, '', '删除数据', 'app-group-fill', '', '/0/2/59/243', 'F', '', 'admin:sysDictData:remove', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (244, '', '查询参数', 'app-group-fill', '', '/0/2/62/244', 'F', '', 'admin:sysConfig:query', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (245, '', '新增参数', 'app-group-fill', '', '/0/2/62/245', 'F', '', 'admin:sysConfig:add', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (246, '', '修改参数', 'app-group-fill', '', '/0/2/62/246', 'F', '', 'admin:sysConfig:edit', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (247, '', '删除参数', 'app-group-fill', '', '/0/2/62/247', 'F', '', 'admin:sysConfig:remove', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (248, '', '查询登录日志', 'app-group-fill', '', '/0/2/211/212/248', 'F', '', 'admin:sysLoginLog:query', 212, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (249, '', '删除登录日志', 'app-group-fill', '', '/0/2/211/212/249', 'F', '', 'admin:sysLoginLog:remove', 212, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (250, '', '查询操作日志', 'app-group-fill', '', '/0/2/211/216/250', 'F', '', 'admin:sysOperLog:query', 216, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (251, '', '删除操作日志', 'app-group-fill', '', '/0/2/211/216/251', 'F', '', 'admin:sysOperLog:remove', 216, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (261, 'Gen', '代码生成', 'code', '/dev-tools/gen', '/0/60/261', 'C', '', '', 60, false, '', '/dev-tools/gen/index', 2, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (262, 'EditTable', '代码生成修改', 'build', '/dev-tools/editTable', '/0/60/262', 'C', '', '', 60, false, '', '/dev-tools/gen/editTable', 100, '1', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (264, 'Build', '表单构建', 'build', '/dev-tools/build', '/0/60/264', 'C', '', '', 60, false, '', '/dev-tools/build/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (269, 'ServerMonitor', '服务监控', 'druid', '/sys-tools/monitor', '/0/60/269', 'C', '', 'sysTools:serverMonitor:list', 537, false, '', '/sys-tools/monitor', 0, '0', '1', 1, 1, '2020-04-14 00:28:19.000', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (459, 'Schedule', '定时任务', 'time-range', '/schedule', '/0/459', 'M', '无', '', 0, false, '', 'Layout', 20, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (460, 'ScheduleManage', 'Schedule', 'job', '/schedule/manage', '/0/459/460', 'C', '无', 'job:sysJob:list', 459, false, '', '/schedule/index', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (461, 'sys_job', '分页获取定时任务', 'app-group-fill', '', '/0/459/460/461', 'F', '无', 'job:sysJob:query', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (462, 'sys_job', '创建定时任务', 'app-group-fill', '', '/0/459/460/462', 'F', '无', 'job:sysJob:add', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (463, 'sys_job', '修改定时任务', 'app-group-fill', '', '/0/459/460/463', 'F', '无', 'job:sysJob:edit', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (464, 'sys_job', '删除定时任务', 'app-group-fill', '', '/0/459/460/464', 'F', '无', 'job:sysJob:remove', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (471, 'JobLog', '日志', 'bug', '/schedule/log', '/0/459/471', 'C', '', '', 459, false, '', '/schedule/log', 0, '1', '1', 1, 1, '2020-08-05 21:24:46.000', '2021-06-05 22:15:03.465', NULL);
INSERT INTO sys_menu VALUES (528, 'SysApiManage', '接口管理', 'api-doc', '/admin/sys-api', '/0/527/528', 'C', '无', 'admin:sysApi:list', 2, false, '', '/admin/sys-api/index', 0, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (529, '', '查询接口', 'app-group-fill', '', '/0/527/528/529', 'F', '无', 'admin:sysApi:query', 528, false, '', '', 40, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (531, '', '修改接口', 'app-group-fill', '', '/0/527/528/531', 'F', '无', 'admin:sysApi:edit', 528, false, '', '', 30, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (537, 'SysTools', '系统工具', 'system-tools', '/sys-tools', '', 'M', '', '', 0, false, '', 'Layout', 30, '0', '1', 1, 1, '2021-05-21 11:13:32.166', '2021-06-16 21:26:12.446', NULL);
INSERT INTO sys_menu VALUES (540, 'SysConfigSet', '参数设置', 'system-tools', '/admin/sys-config/set', '', 'C', '', 'admin:sysConfigSet:list', 2, false, '', '/admin/sys-config/set', 0, '0', '1', 1, 1, '2021-05-25 16:06:52.560', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu VALUES (542, '', '修改', 'upload', '', '', 'F', '', 'admin:sysConfigSet:update', 540, false, '', '', 0, '0', '1', 1, 1, '2021-06-13 11:45:48.670', '2021-06-17 11:48:40.703', NULL);
INSERT INTO sys_menu_api_rule VALUES (216, 6);
INSERT INTO sys_menu_api_rule VALUES (250, 6);
INSERT INTO sys_menu_api_rule VALUES (58, 21);
@@ -319,6 +318,6 @@ INSERT INTO sys_menu_api_rule VALUES (46, 156);
INSERT INTO sys_post VALUES (1, '首席执行官', 'CEO', 0, '2','首席执行官', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL);
INSERT INTO sys_post VALUES (2, '首席技术执行官', 'CTO', 2, '2','首席技术执行官', 1, 1,'2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL);
INSERT INTO sys_post VALUES (3, '首席运营官', 'COO', 3, '2','测试工程师', 1, 1,'2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL);
INSERT INTO sys_role VALUES (1, '系统管理员', '2', 'admin', 1, '', '', 1, '', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL);
INSERT INTO sys_role VALUES (1, '系统管理员', '2', 'admin', 1, '', '', true, '', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL);
INSERT INTO sys_user VALUES (1, 'admin', '$2a$10$/Glr4g9Svr6O0kvjsRJCXu3f0W8/dsP3XZyVNi1019ratWpSPMyw.', 'zhangwj', '13818888888', 1, '', '', '1', '1@qq.com', 1, 1, '', '2', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:40.205', NULL);
-- 数据完成 ;
+3 -9
View File
@@ -61,14 +61,8 @@ settings:
# redis:
# addr: 127.0.0.1:6379
# password: xxxxxx
# producer:
# streamMaxLength: 100
# approximateMaxLength: true
# consumer:
# visibilityTimeout: 60
# bufferSize: 100
# concurrency: 10
# blockingTimeout: 5
# reclaimInterval: 1
# group: go-admin # 消费组,同一应用的多个实例必须用同一个值
# key_prefix: go-admin # 加在 stream key 前面,多个应用共用一个 redis 时用于隔离
# max_attempts: 5 # 消息投递失败的最大重试次数
locker:
redis:
+575 -4
View File
@@ -11,6 +11,10 @@ const docTemplateadmin = `{
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"license": {
"name": "MIT",
"url": "https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
@@ -83,6 +87,80 @@ const docTemplateadmin = `{
}
}
},
"/api/v1/db/columns/page": {
"get": {
"description": "数据库表列分页列表 / database table column page list",
"tags": [
"工具 / 生成工具"
],
"summary": "分页列表数据 / page list data",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tableName",
"in": "query"
},
{
"type": "integer",
"description": "pageSize / 页条数",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "pageIndex / 页码",
"name": "pageIndex",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/db/tables/page": {
"get": {
"description": "数据库表分页列表 / database table page list",
"tags": [
"工具 / 生成工具"
],
"summary": "分页列表数据 / page list data",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tableName",
"in": "query"
},
{
"type": "integer",
"description": "pageSize / 页条数",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "pageIndex / 页码",
"name": "pageIndex",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/dept": {
"get": {
"security": [
@@ -1172,6 +1250,47 @@ const docTemplateadmin = `{
}
}
},
"/api/v1/public/uploadFile": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "获取JSON",
"consumes": [
"multipart/form-data"
],
"tags": [
"公共接口"
],
"summary": "上传图片",
"parameters": [
{
"type": "string",
"description": "type",
"name": "type",
"in": "query",
"required": true
},
{
"type": "file",
"description": "file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"添加失败\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/role": {
"get": {
"security": [
@@ -1391,6 +1510,28 @@ const docTemplateadmin = `{
}
}
},
"/api/v1/server-monitor": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "获取JSON",
"tags": [
"系统信息"
],
"summary": "系统信息",
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/set-config": {
"get": {
"security": [
@@ -2302,6 +2443,165 @@ const docTemplateadmin = `{
}
}
},
"/api/v1/sys/tables/info": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "修改表结构",
"consumes": [
"application/json"
],
"tags": [
"工具 / 生成工具"
],
"summary": "修改表结构",
"parameters": [
{
"description": "body",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/tools.SysTables"
}
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"添加失败\"}",
"schema": {
"type": "string"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "添加表结构",
"consumes": [
"application/json"
],
"tags": [
"工具 / 生成工具"
],
"summary": "添加表结构",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tables",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"添加失败\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/sys/tables/info/{tableId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "获取JSON",
"tags": [
"工具 / 生成工具"
],
"summary": "获取配置",
"parameters": [
{
"type": "integer",
"description": "configKey",
"name": "configKey",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
},
"delete": {
"description": "删除表结构",
"tags": [
"工具 / 生成工具"
],
"summary": "删除表结构",
"parameters": [
{
"type": "integer",
"description": "tableId",
"name": "tableId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"删除失败\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/sys/tables/page": {
"get": {
"description": "生成表分页列表",
"tags": [
"工具 / 生成工具"
],
"summary": "分页列表数据",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tableName",
"in": "query"
},
{
"type": "integer",
"description": "pageSize / 页条数",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "pageIndex / 页码",
"name": "pageIndex",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/user/avatar": {
"post": {
"security": [
@@ -3268,7 +3568,7 @@ const docTemplateadmin = `{
"type": "integer"
},
"status": {
"description": "状态",
"description": "状态 1禁用 2正常",
"type": "string"
},
"sysDept": {
@@ -3781,18 +4081,289 @@ const docTemplateadmin = `{
"type": "string"
}
}
},
"tools.Params": {
"type": "object",
"properties": {
"treeCode": {
"type": "string"
},
"treeName": {
"type": "string"
},
"treeParentCode": {
"type": "string"
}
}
},
"tools.SysColumns": {
"type": "object",
"properties": {
"columnComment": {
"type": "string"
},
"columnId": {
"type": "integer"
},
"columnName": {
"type": "string"
},
"columnType": {
"type": "string"
},
"createBy": {
"type": "integer"
},
"createdAt": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"dictType": {
"type": "string"
},
"edit": {
"type": "boolean"
},
"fkCol": {
"type": "array",
"items": {
"$ref": "#/definitions/tools.SysColumns"
}
},
"fkLabelId": {
"type": "string"
},
"fkLabelName": {
"type": "string"
},
"fkTableName": {
"type": "string"
},
"fkTableNameClass": {
"type": "string"
},
"fkTableNamePackage": {
"type": "string"
},
"goField": {
"type": "string"
},
"goType": {
"type": "string"
},
"htmlType": {
"type": "string"
},
"increment": {
"type": "boolean"
},
"insert": {
"type": "boolean"
},
"isEdit": {
"type": "string"
},
"isIncrement": {
"type": "string"
},
"isInsert": {
"type": "string"
},
"isList": {
"type": "string"
},
"isPk": {
"type": "string"
},
"isQuery": {
"type": "string"
},
"isRequired": {
"type": "string"
},
"jsonField": {
"type": "string"
},
"list": {
"type": "string"
},
"pk": {
"type": "boolean"
},
"query": {
"type": "boolean"
},
"queryType": {
"type": "string"
},
"remark": {
"type": "string"
},
"required": {
"type": "boolean"
},
"sort": {
"type": "integer"
},
"superColumn": {
"type": "boolean"
},
"tableId": {
"type": "integer"
},
"updateBy": {
"type": "integer"
},
"updatedAt": {
"type": "string"
},
"usableColumn": {
"type": "boolean"
}
}
},
"tools.SysTables": {
"type": "object",
"properties": {
"businessName": {
"type": "string"
},
"className": {
"description": "类名",
"type": "string"
},
"columns": {
"type": "array",
"items": {
"$ref": "#/definitions/tools.SysColumns"
}
},
"createBy": {
"type": "integer"
},
"createdAt": {
"type": "string"
},
"crud": {
"type": "boolean"
},
"dataScope": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"functionAuthor": {
"description": "功能作者",
"type": "string"
},
"functionName": {
"description": "功能名称",
"type": "string"
},
"isActions": {
"type": "integer"
},
"isAuth": {
"type": "integer"
},
"isDataScope": {
"type": "integer"
},
"isLogicalDelete": {
"type": "string"
},
"logicalDelete": {
"type": "boolean"
},
"logicalDeleteColumn": {
"type": "string"
},
"moduleFrontName": {
"description": "前端文件名",
"type": "string"
},
"moduleName": {
"description": "go文件名",
"type": "string"
},
"options": {
"type": "string"
},
"packageName": {
"description": "包名",
"type": "string"
},
"params": {
"$ref": "#/definitions/tools.Params"
},
"pkColumn": {
"type": "string"
},
"pkGoField": {
"type": "string"
},
"pkJsonField": {
"type": "string"
},
"remark": {
"type": "string"
},
"tableComment": {
"description": "表备注",
"type": "string"
},
"tableId": {
"description": "表编码",
"type": "integer"
},
"tableName": {
"description": "表名称",
"type": "string"
},
"tplCategory": {
"type": "string"
},
"tree": {
"type": "boolean"
},
"treeCode": {
"type": "string"
},
"treeName": {
"type": "string"
},
"treeParentCode": {
"type": "string"
},
"updateBy": {
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}`
// SwaggerInfoadmin holds exported Swagger Info so clients can modify it
var SwaggerInfoadmin = &swag.Spec{
Version: "",
Version: "2.0.0",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "",
Description: "",
Title: "go-admin API",
Description: "基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档\n添加qq群: 521386980 进入技术交流群 请先star,谢谢!",
InfoInstanceName: "admin",
SwaggerTemplate: docTemplateadmin,
}
+576 -2
View File
@@ -1,7 +1,14 @@
{
"swagger": "2.0",
"info": {
"contact": {}
"description": "基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档\n添加qq群: 521386980 进入技术交流群 请先star,谢谢!",
"title": "go-admin API",
"contact": {},
"license": {
"name": "MIT",
"url": "https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md"
},
"version": "2.0.0"
},
"paths": {
"/api/v1/app-config": {
@@ -71,6 +78,80 @@
}
}
},
"/api/v1/db/columns/page": {
"get": {
"description": "数据库表列分页列表 / database table column page list",
"tags": [
"工具 / 生成工具"
],
"summary": "分页列表数据 / page list data",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tableName",
"in": "query"
},
{
"type": "integer",
"description": "pageSize / 页条数",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "pageIndex / 页码",
"name": "pageIndex",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/db/tables/page": {
"get": {
"description": "数据库表分页列表 / database table page list",
"tags": [
"工具 / 生成工具"
],
"summary": "分页列表数据 / page list data",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tableName",
"in": "query"
},
{
"type": "integer",
"description": "pageSize / 页条数",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "pageIndex / 页码",
"name": "pageIndex",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/dept": {
"get": {
"security": [
@@ -1160,6 +1241,47 @@
}
}
},
"/api/v1/public/uploadFile": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "获取JSON",
"consumes": [
"multipart/form-data"
],
"tags": [
"公共接口"
],
"summary": "上传图片",
"parameters": [
{
"type": "string",
"description": "type",
"name": "type",
"in": "query",
"required": true
},
{
"type": "file",
"description": "file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"添加失败\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/role": {
"get": {
"security": [
@@ -1379,6 +1501,28 @@
}
}
},
"/api/v1/server-monitor": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "获取JSON",
"tags": [
"系统信息"
],
"summary": "系统信息",
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/set-config": {
"get": {
"security": [
@@ -2290,6 +2434,165 @@
}
}
},
"/api/v1/sys/tables/info": {
"put": {
"security": [
{
"Bearer": []
}
],
"description": "修改表结构",
"consumes": [
"application/json"
],
"tags": [
"工具 / 生成工具"
],
"summary": "修改表结构",
"parameters": [
{
"description": "body",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/tools.SysTables"
}
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"添加失败\"}",
"schema": {
"type": "string"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "添加表结构",
"consumes": [
"application/json"
],
"tags": [
"工具 / 生成工具"
],
"summary": "添加表结构",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tables",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"添加失败\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/sys/tables/info/{tableId}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "获取JSON",
"tags": [
"工具 / 生成工具"
],
"summary": "获取配置",
"parameters": [
{
"type": "integer",
"description": "configKey",
"name": "configKey",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
},
"delete": {
"description": "删除表结构",
"tags": [
"工具 / 生成工具"
],
"summary": "删除表结构",
"parameters": [
{
"type": "integer",
"description": "tableId",
"name": "tableId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "{\"code\": -1, \"message\": \"删除失败\"}",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/sys/tables/page": {
"get": {
"description": "生成表分页列表",
"tags": [
"工具 / 生成工具"
],
"summary": "分页列表数据",
"parameters": [
{
"type": "string",
"description": "tableName / 数据表名称",
"name": "tableName",
"in": "query"
},
{
"type": "integer",
"description": "pageSize / 页条数",
"name": "pageSize",
"in": "query"
},
{
"type": "integer",
"description": "pageIndex / 页码",
"name": "pageIndex",
"in": "query"
}
],
"responses": {
"200": {
"description": "{\"code\": 200, \"data\": [...]}",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/api/v1/user/avatar": {
"post": {
"security": [
@@ -3256,7 +3559,7 @@
"type": "integer"
},
"status": {
"description": "状态",
"description": "状态 1禁用 2正常",
"type": "string"
},
"sysDept": {
@@ -3769,6 +4072,277 @@
"type": "string"
}
}
},
"tools.Params": {
"type": "object",
"properties": {
"treeCode": {
"type": "string"
},
"treeName": {
"type": "string"
},
"treeParentCode": {
"type": "string"
}
}
},
"tools.SysColumns": {
"type": "object",
"properties": {
"columnComment": {
"type": "string"
},
"columnId": {
"type": "integer"
},
"columnName": {
"type": "string"
},
"columnType": {
"type": "string"
},
"createBy": {
"type": "integer"
},
"createdAt": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"dictType": {
"type": "string"
},
"edit": {
"type": "boolean"
},
"fkCol": {
"type": "array",
"items": {
"$ref": "#/definitions/tools.SysColumns"
}
},
"fkLabelId": {
"type": "string"
},
"fkLabelName": {
"type": "string"
},
"fkTableName": {
"type": "string"
},
"fkTableNameClass": {
"type": "string"
},
"fkTableNamePackage": {
"type": "string"
},
"goField": {
"type": "string"
},
"goType": {
"type": "string"
},
"htmlType": {
"type": "string"
},
"increment": {
"type": "boolean"
},
"insert": {
"type": "boolean"
},
"isEdit": {
"type": "string"
},
"isIncrement": {
"type": "string"
},
"isInsert": {
"type": "string"
},
"isList": {
"type": "string"
},
"isPk": {
"type": "string"
},
"isQuery": {
"type": "string"
},
"isRequired": {
"type": "string"
},
"jsonField": {
"type": "string"
},
"list": {
"type": "string"
},
"pk": {
"type": "boolean"
},
"query": {
"type": "boolean"
},
"queryType": {
"type": "string"
},
"remark": {
"type": "string"
},
"required": {
"type": "boolean"
},
"sort": {
"type": "integer"
},
"superColumn": {
"type": "boolean"
},
"tableId": {
"type": "integer"
},
"updateBy": {
"type": "integer"
},
"updatedAt": {
"type": "string"
},
"usableColumn": {
"type": "boolean"
}
}
},
"tools.SysTables": {
"type": "object",
"properties": {
"businessName": {
"type": "string"
},
"className": {
"description": "类名",
"type": "string"
},
"columns": {
"type": "array",
"items": {
"$ref": "#/definitions/tools.SysColumns"
}
},
"createBy": {
"type": "integer"
},
"createdAt": {
"type": "string"
},
"crud": {
"type": "boolean"
},
"dataScope": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"functionAuthor": {
"description": "功能作者",
"type": "string"
},
"functionName": {
"description": "功能名称",
"type": "string"
},
"isActions": {
"type": "integer"
},
"isAuth": {
"type": "integer"
},
"isDataScope": {
"type": "integer"
},
"isLogicalDelete": {
"type": "string"
},
"logicalDelete": {
"type": "boolean"
},
"logicalDeleteColumn": {
"type": "string"
},
"moduleFrontName": {
"description": "前端文件名",
"type": "string"
},
"moduleName": {
"description": "go文件名",
"type": "string"
},
"options": {
"type": "string"
},
"packageName": {
"description": "包名",
"type": "string"
},
"params": {
"$ref": "#/definitions/tools.Params"
},
"pkColumn": {
"type": "string"
},
"pkGoField": {
"type": "string"
},
"pkJsonField": {
"type": "string"
},
"remark": {
"type": "string"
},
"tableComment": {
"description": "表备注",
"type": "string"
},
"tableId": {
"description": "表编码",
"type": "integer"
},
"tableName": {
"description": "表名称",
"type": "string"
},
"tplCategory": {
"type": "string"
},
"tree": {
"type": "boolean"
},
"treeCode": {
"type": "string"
},
"treeName": {
"type": "string"
},
"treeParentCode": {
"type": "string"
},
"updateBy": {
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"Bearer": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}
+379 -1
View File
@@ -529,7 +529,7 @@ definitions:
description: 角色排序
type: integer
status:
description: 状态
description: 状态 1禁用 2正常
type: string
sysDept:
items:
@@ -876,8 +876,194 @@ definitions:
status:
type: string
type: object
tools.Params:
properties:
treeCode:
type: string
treeName:
type: string
treeParentCode:
type: string
type: object
tools.SysColumns:
properties:
columnComment:
type: string
columnId:
type: integer
columnName:
type: string
columnType:
type: string
createBy:
type: integer
createdAt:
type: string
deletedAt:
type: string
dictType:
type: string
edit:
type: boolean
fkCol:
items:
$ref: '#/definitions/tools.SysColumns'
type: array
fkLabelId:
type: string
fkLabelName:
type: string
fkTableName:
type: string
fkTableNameClass:
type: string
fkTableNamePackage:
type: string
goField:
type: string
goType:
type: string
htmlType:
type: string
increment:
type: boolean
insert:
type: boolean
isEdit:
type: string
isIncrement:
type: string
isInsert:
type: string
isList:
type: string
isPk:
type: string
isQuery:
type: string
isRequired:
type: string
jsonField:
type: string
list:
type: string
pk:
type: boolean
query:
type: boolean
queryType:
type: string
remark:
type: string
required:
type: boolean
sort:
type: integer
superColumn:
type: boolean
tableId:
type: integer
updateBy:
type: integer
updatedAt:
type: string
usableColumn:
type: boolean
type: object
tools.SysTables:
properties:
businessName:
type: string
className:
description: 类名
type: string
columns:
items:
$ref: '#/definitions/tools.SysColumns'
type: array
createBy:
type: integer
createdAt:
type: string
crud:
type: boolean
dataScope:
type: string
deletedAt:
type: string
functionAuthor:
description: 功能作者
type: string
functionName:
description: 功能名称
type: string
isActions:
type: integer
isAuth:
type: integer
isDataScope:
type: integer
isLogicalDelete:
type: string
logicalDelete:
type: boolean
logicalDeleteColumn:
type: string
moduleFrontName:
description: 前端文件名
type: string
moduleName:
description: go文件名
type: string
options:
type: string
packageName:
description: 包名
type: string
params:
$ref: '#/definitions/tools.Params'
pkColumn:
type: string
pkGoField:
type: string
pkJsonField:
type: string
remark:
type: string
tableComment:
description: 表备注
type: string
tableId:
description: 表编码
type: integer
tableName:
description: 表名称
type: string
tplCategory:
type: string
tree:
type: boolean
treeCode:
type: string
treeName:
type: string
treeParentCode:
type: string
updateBy:
type: integer
updatedAt:
type: string
type: object
info:
contact: {}
description: |-
基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档
添加qq群: 521386980 进入技术交流群 请先star,谢谢!
license:
name: MIT
url: https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md
title: go-admin API
version: 2.0.0
paths:
/api/v1/app-config:
get:
@@ -917,6 +1103,54 @@ paths:
summary: 获取验证码
tags:
- 登陆
/api/v1/db/columns/page:
get:
description: 数据库表列分页列表 / database table column page list
parameters:
- description: tableName / 数据表名称
in: query
name: tableName
type: string
- description: pageSize / 页条数
in: query
name: pageSize
type: integer
- description: pageIndex / 页码
in: query
name: pageIndex
type: integer
responses:
"200":
description: '{"code": 200, "data": [...]}'
schema:
$ref: '#/definitions/response.Response'
summary: 分页列表数据 / page list data
tags:
- 工具 / 生成工具
/api/v1/db/tables/page:
get:
description: 数据库表分页列表 / database table page list
parameters:
- description: tableName / 数据表名称
in: query
name: tableName
type: string
- description: pageSize / 页条数
in: query
name: pageSize
type: integer
- description: pageIndex / 页码
in: query
name: pageIndex
type: integer
responses:
"200":
description: '{"code": 200, "data": [...]}'
schema:
$ref: '#/definitions/response.Response'
summary: 分页列表数据 / page list data
tags:
- 工具 / 生成工具
/api/v1/dept:
delete:
description: 删除数据
@@ -1595,6 +1829,32 @@ paths:
summary: 获取岗位信息
tags:
- 岗位
/api/v1/public/uploadFile:
post:
consumes:
- multipart/form-data
description: 获取JSON
parameters:
- description: type
in: query
name: type
required: true
type: string
- description: file
in: formData
name: file
required: true
type: file
responses:
"200":
description: '{"code": -1, "message": "添加失败"}'
schema:
type: string
security:
- Bearer: []
summary: 上传图片
tags:
- 公共接口
/api/v1/role:
delete:
description: 删除数据
@@ -1730,6 +1990,19 @@ paths:
summary: 修改用户角色
tags:
- 角色/Role
/api/v1/server-monitor:
get:
description: 获取JSON
responses:
"200":
description: '{"code": 200, "data": [...]}'
schema:
$ref: '#/definitions/response.Response'
security:
- Bearer: []
summary: 系统信息
tags:
- 系统信息
/api/v1/set-config:
get:
consumes:
@@ -2277,6 +2550,106 @@ paths:
summary: 修改用户数据
tags:
- 用户
/api/v1/sys/tables/info:
post:
consumes:
- application/json
description: 添加表结构
parameters:
- description: tableName / 数据表名称
in: query
name: tables
type: string
responses:
"200":
description: '{"code": -1, "message": "添加失败"}'
schema:
type: string
security:
- Bearer: []
summary: 添加表结构
tags:
- 工具 / 生成工具
put:
consumes:
- application/json
description: 修改表结构
parameters:
- description: body
in: body
name: data
required: true
schema:
$ref: '#/definitions/tools.SysTables'
responses:
"200":
description: '{"code": -1, "message": "添加失败"}'
schema:
type: string
security:
- Bearer: []
summary: 修改表结构
tags:
- 工具 / 生成工具
/api/v1/sys/tables/info/{tableId}:
delete:
description: 删除表结构
parameters:
- description: tableId
in: path
name: tableId
required: true
type: integer
responses:
"200":
description: '{"code": -1, "message": "删除失败"}'
schema:
type: string
summary: 删除表结构
tags:
- 工具 / 生成工具
get:
description: 获取JSON
parameters:
- description: configKey
in: path
name: configKey
required: true
type: integer
responses:
"200":
description: '{"code": 200, "data": [...]}'
schema:
$ref: '#/definitions/response.Response'
security:
- Bearer: []
summary: 获取配置
tags:
- 工具 / 生成工具
/api/v1/sys/tables/page:
get:
description: 生成表分页列表
parameters:
- description: tableName / 数据表名称
in: query
name: tableName
type: string
- description: pageSize / 页条数
in: query
name: pageSize
type: integer
- description: pageIndex / 页码
in: query
name: pageIndex
type: integer
responses:
"200":
description: '{"code": 200, "data": [...]}'
schema:
$ref: '#/definitions/response.Response'
summary: 分页列表数据
tags:
- 工具 / 生成工具
/api/v1/user/avatar:
post:
consumes:
@@ -2390,4 +2763,9 @@ paths:
security:
- Bearer: []
summary: 退出登录
securityDefinitions:
Bearer:
in: header
name: Authorization
type: apiKey
swagger: "2.0"
+85
View File
@@ -0,0 +1,85 @@
# 架构说明
> 本文记录**为什么这样设计**与不易从代码直接读出的语义。
> 编码规范见根目录 `AGENTS.md`,标准写法见 `app/demo/`。
## 数据权限(DataScope)
`PermissionAction()` 中间件从数据库取出当前用户的 DataScope 存入 `gin.Context`;
Service 在查询时通过 `actions.Permission(tableName, p)` 这个 GORM Scope 追加 WHERE。
实现见 `common/actions/permission.go:62-79`,五档语义:
| 值 | 含义 | 过滤方式 |
|---|---|---|
| `1` 或其他 | 全部数据 | 不追加条件 |
| `2` | 本角色关联部门的数据 | `create_by` 属于 `sys_role_dept` 关联部门下的用户 |
| `3` | 本部门数据 | `create_by` 属于同部门用户 |
| `4` | 本部门及子部门 | 按 `sys_dept.dept_path` 前缀匹配 |
| `5` | 仅本人 | `create_by = 当前用户` |
**过滤依据是 `create_by` 字段**,因此参与数据权限的表必须内嵌 `models.ControlBy`。
总开关:`config/settings.yml` 的 `application.enabledp`。关闭时 `Permission` 直接返回原
查询,这也意味着**关闭开关后所有数据权限配置立即失效**,排查问题时先确认此项。
## 定时任务
两类任务,配置在 `sys_job` 表:
- **HTTP 任务** —— 按 Cron 表达式请求指定 URL
- **函数任务** —— 调用注册在 `app/jobs` 中的 Go 函数
自定义函数任务需实现 `JobExec` 接口(`app/jobs/type.go:10`):
```go
type JobExec interface {
Exec(arg interface{}) error
}
```
并注册进 `app/jobs/examples.go` 的 `jobList` 映射,键名与 `sys_job` 表中配置的调用目标
对应。
任务内需要数据库连接时,通过 `sdk.Runtime.GetDbByKey("*")` 获取,不要反向 import
`app/admin/service`。
## 配置扩展
业务自定义配置写在 `config/extend.go` 中的结构体,对应 `settings.yml` 的 `extend:`
节点,代码中通过 `config.ExtConfig.Xxx` 访问。
配置值支持环境变量占位:在 yml 中写 `${ENV_NAME}`,由 `go-admin-core` 的
`config/reader/preprocessor.go` 在加载时替换。敏感信息可借此避免写入文件。
## 多数据源
`WithContextDb` 中间件按请求解析出对应的数据库连接放入上下文,Service 通过 `e.Orm`
取用。**不要使用全局 DB 变量** —— 那会绕过多租户隔离。
多库配置见 `settings.yml` 的 `databases` 与 `registers` 节点,后者用于 dbresolver
读写分离。
## 数据库迁移
迁移文件放 `cmd/migrate/migration/version-local/`,文件名前 13 位为 Unix 毫秒时间戳,
框架按文件名升序执行,已执行版本记录在 `sys_migration` 表。
```bash
go run main.go migrate -c config/settings.yml -g # 生成骨架
```
**已执行过的迁移文件不可修改** —— 版本号已入表,改动不会重跑。需要调整时新建迁移。
## 构建注意
- 默认构建禁用 CGO;使用 SQLite 需 `make build-sqlite`(带 `-tags sqlite3`)
- `mode: prod` 时不注册 Swagger 路由
- dev 模式下 JWT 超时被设为极大值,生产部署前务必确认 `mode` 与 `jwt.secret`
## 参考
- 编码规范:根目录 `AGENTS.md`
- 标准 CRUD 模块:`app/demo/`
- API 文档:`go generate` 生成到 `docs/admin/`,dev 模式下访问
`/swagger/admin/index.html`
-28
View File
@@ -1,28 +0,0 @@
// +build examples
package main
import (
"github.com/go-admin-team/go-admin-core/sdk"
"log"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
myCasbin "github.com/go-admin-team/go-admin-core/sdk/pkg/casbin"
"gorm.io/driver/mysql"
)
func main() {
db, err := gorm.Open(mysql.Open("root:123456@tcp/inmg?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{})
if err != nil {
panic(err)
}
syncEnforce := myCasbin.Setup(db, "sys_")
sdk.Runtime.SetDb("*", db)
sdk.Runtime.SetCasbin("*", syncEnforce)
e := gin.Default()
sdk.Runtime.SetEngine(e)
log.Fatal(e.Run(":8000"))
}
+126 -115
View File
@@ -1,143 +1,154 @@
module go-admin
go 1.18
go 1.26.5
require (
github.com/alibaba/sentinel-golang v1.0.4
github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20220808015021-c5f1f1d055c5
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5
github.com/bitly/go-simplejson v0.5.0
github.com/bytedance/go-tagexpr/v2 v2.7.12
github.com/casbin/casbin/v2 v2.51.2
github.com/gin-gonic/gin v1.7.7
github.com/go-admin-team/go-admin-core v1.4.1-0.20220809101213-21187928f7d9
github.com/go-admin-team/go-admin-core/sdk v1.4.1-0.20220809101213-21187928f7d9
github.com/google/uuid v1.3.0
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.12+incompatible
github.com/mssola/user_agent v0.5.2
github.com/opentracing/opentracing-go v1.1.0
github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/bitly/go-simplejson v0.5.1
github.com/bytedance/go-tagexpr/v2 v2.9.11
github.com/casbin/casbin/v3 v3.8.1
github.com/gin-gonic/gin v1.12.0
github.com/go-admin-team/go-admin-core v1.7.1-0.20260818135152-a912b404d5c0
github.com/google/uuid v1.6.0
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible
github.com/mssola/user_agent v0.6.0
github.com/opentracing/opentracing-go v1.2.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
github.com/qiniu/go-sdk/v7 v7.11.1
github.com/prometheus/client_golang v1.24.1
github.com/qiniu/go-sdk/v7 v7.27.0
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.22.1
github.com/spf13/cobra v1.0.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.0
github.com/swaggo/swag v1.8.3
github.com/unrolled/secure v1.0.8
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
gorm.io/driver/mysql v1.3.5
gorm.io/driver/postgres v1.3.8
gorm.io/driver/sqlite v1.3.6
gorm.io/driver/sqlserver v1.3.2
gorm.io/gorm v1.23.8
github.com/shirou/gopsutil/v3 v3.24.5
github.com/spf13/cobra v1.10.2
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.1
github.com/swaggo/swag v1.16.6
github.com/unrolled/secure v1.17.0
golang.org/x/crypto v0.54.0
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.2
gorm.io/driver/sqlite v1.6.0
gorm.io/driver/sqlserver v1.6.4
gorm.io/gorm v1.31.2
)
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/andeya/ameda v1.5.3 // indirect
github.com/andeya/goutil v1.1.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bsm/redislock v0.5.0 // indirect
github.com/casbin/redis-watcher/v2 v2.0.0-20220614104201-0e70bf2be930 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chanxuehong/rand v0.0.0-20201110082127-2f19a1bdd973 // indirect
github.com/chanxuehong/wechat v0.0.0-20201110083048-0180211b69fd // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.2 // indirect
github.com/casbin/gorm-adapter/v3 v3.41.0 // indirect
github.com/casbin/govaluate v1.10.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 // indirect
github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/git-chglog/git-chglog v0.0.0-20190611050339-63a4e637021f // indirect
github.com/go-admin-team/go-admin-core/plugins/logger/zap v0.0.0-20210610020726-2db73adb505d // indirect
github.com/go-admin-team/gorm-adapter/v3 v3.7.8-0.20220809100335-eaf9f67b3d21 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/go-redis/redis/v7 v7.4.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect
github.com/go-openapi/jsonreference v1.0.0 // indirect
github.com/go-openapi/spec v0.22.9 // indirect
github.com/go-openapi/swag/conv v0.28.0 // indirect
github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
github.com/go-openapi/swag/loading v0.28.0 // indirect
github.com/go-openapi/swag/pools v0.28.0 // indirect
github.com/go-openapi/swag/stringutils v0.28.0 // indirect
github.com/go-openapi/swag/typeutils v0.28.0 // indirect
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.3 // indirect
github.com/go-sql-driver/mysql v1.10.0 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/henrylee2cn/ameda v1.4.10 // indirect
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8 // indirect
github.com/imdario/mergo v0.3.9 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.12.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.11.0 // indirect
github.com/jackc/pgx/v4 v4.16.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.10.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/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.5.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/mattn/goveralls v0.0.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-sqlite3 v1.14.49 // indirect
github.com/microsoft/go-mssqldb v1.10.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mojocn/base64Captcha v1.3.1 // indirect
github.com/nsqio/go-nsq v1.0.8 // indirect
github.com/nyaruka/phonenumbers v1.0.55 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mojocn/base64Captcha v1.3.8 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nyaruka/phonenumbers v1.2.2 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/robinjoseph08/redisqueue/v2 v2.1.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.61.0 // indirect
github.com/redis/go-redis/v9 v9.22.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/shamsher31/goimgext v1.0.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.3 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
github.com/urfave/cli v1.22.1 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/atomic v1.6.0 // indirect
go.uber.org/multierr v1.5.0 // indirect
go.uber.org/zap v1.15.0 // indirect
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb // indirect
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.1.10 // indirect
google.golang.org/protobuf v1.26.0-rc.1 // indirect
gopkg.in/AlecAivazis/survey.v1 v1.8.5 // indirect
gopkg.in/kyokomi/emoji.v1 v1.5.1 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/arch v0.30.0 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/image v0.41.0 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.48.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/plugin/dbresolver v1.2.2 // 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.11.0 // indirect
modernc.org/sqlite v1.42.2 // indirect
)
//replace (
// github.com/go-admin-team/go-admin-core v1.4.0 => ../../go-admin-core
// github.com/go-admin-team/go-admin-core/sdk v1.4.0 => ../../go-admin-core/sdk
// github.com/go-admin-team/go-admin-core v1.5.2-0.20231103105356-84418ed9252c => ../go-admin-core
// github.com/go-admin-team/go-admin-core/sdk v1.5.2-0.20231103105356-84418ed9252c => ../go-admin-core/sdk
//)

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