mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-24 19:17:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76c9d1211e | ||
|
|
f5273f5a58 | ||
|
|
54ffaac9c5 | ||
|
|
484de2e698 | ||
|
|
d72ff76aad | ||
|
|
4a523bed92 | ||
|
|
55dc33b865 | ||
|
|
3d13f5856a | ||
|
|
aa2976ba17 | ||
|
|
8e141ff8a0 | ||
|
|
2628ab8e3e | ||
|
|
1b7dcd843c | ||
|
|
f0d91fb763 | ||
|
|
7238c6a26d | ||
|
|
0964cf98e2 | ||
|
|
04c6a081ae | ||
|
|
fcbd9ae02e | ||
|
|
d34d30a197 | ||
|
|
ecfea845c2 | ||
|
|
e05ff7c809 | ||
|
|
28a9626661 | ||
|
|
96b2cb3acf | ||
|
|
87fe6b7d9b | ||
|
|
722de8ea65 | ||
|
|
8ffde94433 | ||
|
|
08eef12bca | ||
|
|
ab0e8e6056 | ||
|
|
2cef52d906 | ||
|
|
95077b116b | ||
|
|
d4cf11d313 | ||
|
|
f201792d8f | ||
|
|
d16f5e7180 | ||
|
|
f8f697af2b | ||
|
|
eb8da38a46 | ||
|
|
f19568c69a | ||
|
|
91bf25e5fe | ||
|
|
85666f160f | ||
|
|
b2baf48dc6 | ||
|
|
c906e1d503 | ||
|
|
3b93ac19f8 | ||
|
|
9914373d45 | ||
|
|
4911730012 | ||
|
|
88bab51056 | ||
|
|
0fd4f68b6c | ||
|
|
c66cb5c6a8 | ||
|
|
b16ec0af77 | ||
|
|
82ea8539eb | ||
|
|
d17d5c1206 | ||
|
|
041d22d0d2 | ||
|
|
5864058a81 | ||
|
|
45035a16e4 | ||
|
|
f4d0108d49 | ||
|
|
b81611ba72 | ||
|
|
bb34108831 | ||
|
|
b7fd92f39b | ||
|
|
63b800a3ba | ||
|
|
ed9450a2d5 | ||
|
|
1d551a10ab | ||
|
|
4d7c9e5a12 | ||
|
|
d1f5fe5681 | ||
|
|
e8c2e0a966 | ||
|
|
cef0a19a9c | ||
|
|
c0e81363dc | ||
|
|
df2e4a2b48 | ||
|
|
9088ebc2e1 | ||
|
|
9f2dec3036 | ||
|
|
ea049f9b06 | ||
|
|
1f1349a685 | ||
|
|
dcef2df38e | ||
|
|
92834d6e39 | ||
|
|
f06540883b | ||
|
|
3c9ce5b6b0 | ||
|
|
ff8a59550a | ||
|
|
7cddef33a2 | ||
|
|
7013c2fa4a | ||
|
|
65bacacb38 | ||
|
|
45587028c3 | ||
|
|
887c9cca4b | ||
|
|
a6ddb113fc | ||
|
|
b83eef8670 | ||
|
|
43dcd61c51 | ||
|
|
1bd64d4562 | ||
|
|
44e81bc72f | ||
|
|
3312f8b7b9 | ||
|
|
d6a2272f9d | ||
|
|
a5cc0a9e29 | ||
|
|
3f995735e9 | ||
|
|
b65b74dee5 | ||
|
|
98cf3ad95a | ||
|
|
8649d8d791 | ||
|
|
817e34c6aa | ||
|
|
952cd92648 | ||
|
|
6b1e961a7f | ||
|
|
762eba5af7 | ||
|
|
e82128f679 | ||
|
|
8f8a197db1 | ||
|
|
364854eda0 | ||
|
|
b259e91f4d | ||
|
|
76411f80bc | ||
|
|
5494353229 | ||
|
|
afe5efbe36 | ||
|
|
db422785fc | ||
|
|
4ac68323da | ||
|
|
44002fcb11 | ||
|
|
5bbd919745 | ||
|
|
54dd3de5b6 | ||
|
|
9540fdfc30 | ||
|
|
937775e2a7 | ||
|
|
84721265dd | ||
|
|
3ab67dfa7d | ||
|
|
6a1941a820 | ||
|
|
3ae7c44585 | ||
|
|
9d809f6392 | ||
|
|
4b477b3103 | ||
|
|
e7ae2fe019 | ||
|
|
d5ba3d9770 | ||
|
|
f3d744f6f5 | ||
|
|
0315631b53 | ||
|
|
48e7ce88ff | ||
|
|
357db6b1c9 | ||
|
|
83e0531f43 | ||
|
|
898ba7d8eb | ||
|
|
8751f34539 | ||
|
|
4aa0068d2d | ||
|
|
b954a2f092 | ||
|
|
9227bd2be1 | ||
|
|
e70a0b1314 | ||
|
|
21c262a31e | ||
|
|
f30889bd19 | ||
|
|
23e519999e | ||
|
|
bedf064ace | ||
|
|
9a8e0cddde | ||
|
|
c0c16036d3 | ||
|
|
dd905a2bed | ||
|
|
2d76430f89 | ||
|
|
5dde1d2a00 | ||
|
|
93f25c6cdf | ||
|
|
d366df372d | ||
|
|
7281d05efc | ||
|
|
e6d6a65267 | ||
|
|
9ff094b6f5 | ||
|
|
fc9c253a9f | ||
|
|
9d735ed5aa | ||
|
|
d782b00117 | ||
|
|
239159dd2a | ||
|
|
0c1e91c3b5 | ||
|
|
c09347b387 | ||
|
|
e49e47c7a1 | ||
|
|
98b46535aa | ||
|
|
014a23aac3 | ||
|
|
f1dfba79e0 | ||
|
|
a282e44b1d | ||
|
|
d1279e67fb | ||
|
|
37a5963cd6 | ||
|
|
69df1b3d34 | ||
|
|
eae97f7a15 | ||
|
|
ce0b5ff7bf | ||
|
|
73118e49b9 | ||
|
|
7b43982595 | ||
|
|
31cd1ee768 | ||
|
|
8df7551946 | ||
|
|
79c1295a70 | ||
|
|
26d9a2e9e4 | ||
|
|
b09b7b6ccc | ||
|
|
ef6fdaa221 | ||
|
|
23c80f0217 | ||
|
|
95dc699e2e | ||
|
|
74b7d62c75 | ||
|
|
68accd5448 | ||
|
|
3edbef8696 | ||
|
|
5527f6386a | ||
|
|
c48f70a7c6 | ||
|
|
a078e31664 | ||
|
|
04d2d7dde1 | ||
|
|
b846053bea | ||
|
|
a1a5634c4e | ||
|
|
6036c6e4e3 | ||
|
|
7d74e6f325 | ||
|
|
1bf9f74bdf | ||
|
|
f61de5beeb | ||
|
|
79fb2d0bee | ||
|
|
c973d6819c | ||
|
|
3d8b879e64 | ||
|
|
ac971bda4b | ||
|
|
44f62abbca | ||
|
|
7d7d8484d4 | ||
|
|
c8b27492eb | ||
|
|
783f79dcb6 | ||
|
|
ddd97d5a9e | ||
|
|
fa73c3d6b1 | ||
|
|
5b2f3e9316 | ||
|
|
42f3025217 | ||
|
|
5df43b4d11 | ||
|
|
eac1bf197e | ||
|
|
5c834939af | ||
|
|
642e86951b | ||
|
|
bc42412e92 | ||
|
|
d9122d29cb | ||
|
|
b532ad994c | ||
|
|
b62fbc803c | ||
|
|
ed8bac8a1b | ||
|
|
d56142463a | ||
|
|
f795a356e7 | ||
|
|
a359c25e36 | ||
|
|
0ef422d309 | ||
|
|
e0519a4d7e | ||
|
|
5043ce5411 | ||
|
|
cc0cdc7d0e | ||
|
|
c004b3d333 | ||
|
|
453dd65cb1 | ||
|
|
b55cbe1992 | ||
|
|
b6e1b7b210 | ||
|
|
3685510d25 | ||
|
|
d7e685536c | ||
|
|
4df859a0ea | ||
|
|
7d1b84e837 | ||
|
|
be1f0be9b2 | ||
|
|
0f742900f5 | ||
|
|
e6f4fac859 | ||
|
|
d39faa1aca | ||
|
|
c3fe13d9dd | ||
|
|
90db381b5a | ||
|
|
508137da4b | ||
|
|
695ac7b29b | ||
|
|
c06df13a75 | ||
|
|
e663de3697 | ||
|
|
10b4f03ff5 | ||
|
|
c7a4434a0e | ||
|
|
1f8babd9e7 | ||
|
|
8e8fe906fd | ||
|
|
386d08a03f | ||
|
|
af38e1694b | ||
|
|
5609e004cc | ||
|
|
5be468308a | ||
|
|
39702abfba | ||
|
|
a97d86e801 | ||
|
|
75582539fe | ||
|
|
466723c55e | ||
|
|
6b3b2125df | ||
|
|
8c8d268708 | ||
|
|
73ac7273a0 | ||
|
|
dcafcdc6ce | ||
|
|
5b1405391e | ||
|
|
0993173b1f | ||
|
|
483ec2bf3e | ||
|
|
60fe272ba0 | ||
|
|
8df2e8190e | ||
|
|
66c8eb5ee8 | ||
|
|
bb65e76219 | ||
|
|
a585e29073 | ||
|
|
7655d0fd38 | ||
|
|
5bec640f20 | ||
|
|
62d9084ee8 | ||
|
|
df8ab39aa0 | ||
|
|
a7d3666811 | ||
|
|
ea9e3d2fe1 | ||
|
|
f14085f3ee | ||
|
|
e8b9db1df5 | ||
|
|
dc625997c4 | ||
|
|
4f458591e8 | ||
|
|
7a074d93cd | ||
|
|
9d1e1f6482 | ||
|
|
19153170bb | ||
|
|
6bf774c463 | ||
|
|
0de5ba77aa | ||
|
|
f4c0134d9c | ||
|
|
2c5c1b69b4 | ||
|
|
d34a33b691 | ||
|
|
869394c898 | ||
|
|
b97bde11b2 | ||
|
|
852cfa66e8 | ||
|
|
289fbba8e0 | ||
|
|
a6ffac657e | ||
|
|
096663ac91 | ||
|
|
d40a0c0837 | ||
|
|
f94b437852 | ||
|
|
7e571b038f | ||
|
|
56968c0bd2 | ||
|
|
ef6b85faec | ||
|
|
0d47fb4e68 | ||
|
|
8bfee8af16 | ||
|
|
0fc7276ccd | ||
|
|
9a596397ca | ||
|
|
1bea64bb6d | ||
|
|
09bfdc3d39 | ||
|
|
fe8b39691b | ||
|
|
1465bfdf15 | ||
|
|
bccbd67450 | ||
|
|
50a3b39666 | ||
|
|
087ba38c24 | ||
|
|
8a3c50ea1a | ||
|
|
86187e4c79 | ||
|
|
98c495abb1 | ||
|
|
c53c4fd9f8 | ||
|
|
b231705a67 | ||
|
|
86f94a2cb9 | ||
|
|
ef41e07550 | ||
|
|
255c72d3f1 | ||
|
|
260eedfcc6 | ||
|
|
f43cd117e3 | ||
|
|
83b219458f | ||
|
|
0f1b9369df | ||
|
|
b31e1c0d58 | ||
|
|
0122024789 | ||
|
|
f469536174 | ||
|
|
70bd8b26ad | ||
|
|
dabc4d88b3 | ||
|
|
214f90b366 | ||
|
|
a19622f6ac | ||
|
|
45c8737601 | ||
|
|
98b60f0564 |
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: new-business-module
|
||||
description: Scaffold a new single-table CRUD business module end to end — migration, Actions-mode model/dto/router, and the sys_menu/sys_api/casbin seed data that makes it show up in the UI with working permissions. Use when the user wants to add a new business table/module to go-admin, not for cross-table or non-CRUD business logic.
|
||||
---
|
||||
|
||||
# 新增业务模块
|
||||
|
||||
给一张新的业务表配齐"能跑、能看见、能授权"的完整闭环:迁移 → 后端代码 → 菜单与权限种子数据。
|
||||
只适用于单表增删改查;跨表事务、外部调用、复杂校验等超出这个范围(见下方"何时不适用")。
|
||||
|
||||
开始前先读 `AGENTS.md`(分层边界、通用 Action 使用前提、命名规则)和 `app/demo/` 下的全部文件——
|
||||
这是可编译、有测试、CI 会跑的参照物,本文与它冲突时以它为准。
|
||||
|
||||
## 何时不适用
|
||||
|
||||
业务超出单表 CRUD(跨表事务、外部服务调用、复杂校验)时,不要用这个 skill 硬套——
|
||||
改成手写 Handler + Service,参照 `app/admin/apis/sys_post.go` 及其 Service,遵守
|
||||
`AGENTS.md` 的分层约束(Api 不碰 Orm,Service 不碰 `gin.Context`,一律用 `e.Orm`)。
|
||||
|
||||
## 步骤
|
||||
|
||||
### 1. 确认表结构
|
||||
|
||||
表结构需符合命名规范:`sys_`/业务前缀 + 下划线(如 `tb_article`)。核对字段是否已有
|
||||
`created_at`/`updated_at`/`deleted_at` 这类约定字段。
|
||||
|
||||
### 2. 写数据库迁移
|
||||
|
||||
放在 `cmd/migrate/migration/version/` 目录(**不是** `version-local/` —— 后者在
|
||||
`.gitignore` 中,提交时会被忽略,`git status` 也看不到)。
|
||||
|
||||
- 文件名前 13 位是时间戳版本号
|
||||
- 已执行过的迁移文件不可修改;需要修正时新增一个迁移
|
||||
- 包名为 `version`
|
||||
|
||||
### 3. 生成 model / dto / router 三个文件(Actions 模式)
|
||||
|
||||
不要手写 Api 与 Service。使用 `common/actions` 的通用 Action,一个模块只需
|
||||
model、dto、router 三个文件,完整写法照抄 `app/demo/` 的结构。
|
||||
|
||||
**关键正确性要求**(这三条是实际出问题最多的地方):
|
||||
|
||||
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`),
|
||||
`TableName()` 必须显式声明——GORM 配置了 `SingularTable`,不会自动推导
|
||||
- **`Generate()` 必须返回副本,不要就地返回**——Action 在并发请求间复用实例,
|
||||
就地返回会导致请求之间串数据;这个问题单人测试时几乎不出现,上线后才暴露
|
||||
- 完成后确认 `cmd/api/` 中已用 `_` 导入新包,否则路由不会被注册
|
||||
|
||||
### 4. 写菜单、接口与权限种子数据
|
||||
|
||||
这一步最容易被漏掉——代码能编译、接口能测通,但界面上看不到菜单、点了按钮说
|
||||
没权限,往往就是漏了这一步。**完整参照 `cmd/migrate/migration/version/1786700001000_demo_menu.go`**
|
||||
——那是可运行、幂等(用 `upsert`,重复跑不会报错)的真实例子,逐字照抄结构,只换 ID 和业务字段。
|
||||
|
||||
一个模块要在界面上可用,需要四类数据,缺一样都不行:
|
||||
|
||||
| 表 | 作用 |
|
||||
|---|---|
|
||||
| `sys_api` | 后端路由登记,Casbin 据此判定权限 |
|
||||
| `sys_menu` | 侧边栏菜单(目录用 `M`、菜单用 `C`、按钮用 `F`) |
|
||||
| `sys_menu_api_rule` | 菜单与接口的多对多关联,角色保存时据此生成策略 |
|
||||
| `casbin_rule` | 实际生效的权限策略(**不是** `sys_casbin_rule`,那张表的唯一索引在 MySQL 下会超长,不要迁移它) |
|
||||
|
||||
必须核对的两处一致性——**错了不会报错,只会在界面上表现为"看不到/点不动"**:
|
||||
|
||||
- `sys_menu.menu_name` 必须与前端组件的 `defineOptions({ name: 'XxxManage' })` 一致,
|
||||
否则 `keep-alive` 缓存静默失效
|
||||
- 按钮级 `sys_menu.permission`(格式 `模块:资源:操作`)必须与前端
|
||||
`v-permisaction="['模块:资源:操作']"` 完全一致,否则按钮权限判断静默失效
|
||||
|
||||
### 5. 收尾检查
|
||||
|
||||
| 检查项 | 出错后果 |
|
||||
| --- | --- |
|
||||
| `Generate()` 是否返回副本 | 并发请求之间串数据 |
|
||||
| 是否使用 `e.Orm` 而非全局 DB | 多租户下拿到错误的数据库连接 |
|
||||
| `TableName()` 是否显式声明 | GORM 不会自动推导 |
|
||||
| 迁移文件是否放在 `version/` | 放进 `version-local/` 会被忽略,别人拉代码看不到 |
|
||||
| `sys_menu.menu_name` 是否与前端组件 `name` 一致 | keep-alive 缓存静默失效 |
|
||||
| `sys_menu.permission` 是否与前端 `v-permisaction` 一致 | 按钮权限静默失效 |
|
||||
|
||||
跑一遍 `go run -tags sqlite3 . migrate -c config/settings.sqlite.yml` 验证迁移可执行,
|
||||
再 `go run -tags sqlite3 . server -c config/settings.sqlite.yml` 启动服务,用 admin
|
||||
账号登录确认新菜单和按钮权限都出现了。
|
||||
|
||||
如果前端页面还没生成,下一步用 go-admin-ui 仓库里的 `new-list-page` skill——两边靠
|
||||
`sys_menu.permission` / `v-permisaction` 这个字符串对齐。
|
||||
|
||||
> 不要把 `config/settings.yml` 的真实内容贴给 AI 工具——`database.source` 含数据库
|
||||
> 账号密码,`jwt.secret` 泄露后可被用来伪造任意用户的 token。
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 🆕 Create new issue
|
||||
url: http://new-issue.go-admin.dev
|
||||
about: The issue which is not created via http://new-issue.go-admin.dev will be closed immediately.
|
||||
- name: 🆕 创建一个新 Issue
|
||||
url: http://new-issue.go-admin.dev
|
||||
about: 不是用 http://new-issue.go-admin.dev 创建的 issue 会被机器人自动关闭。
|
||||
@@ -0,0 +1,66 @@
|
||||
<!--
|
||||
First of all, thank you for your contribution! 😄
|
||||
|
||||
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
|
||||
|
||||
Before submitting your pull request, please make sure the checklist below is confirmed.
|
||||
|
||||
Your pull requests will be merged after one of the collaborators approve.
|
||||
|
||||
Thank you!
|
||||
|
||||
-->
|
||||
|
||||
[[中文版模板 / Chinese template](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
|
||||
|
||||
### 🤔 This is a ...
|
||||
|
||||
- [ ] New feature
|
||||
- [ ] Bug fix
|
||||
- [ ] Site / documentation update
|
||||
- [ ] Demo update
|
||||
- [ ] Component style update
|
||||
- [ ] TypeScript definition update
|
||||
- [ ] Bundle size optimization
|
||||
- [ ] Performance optimization
|
||||
- [ ] Enhancement feature
|
||||
- [ ] Internationalization
|
||||
- [ ] Refactoring
|
||||
- [ ] Code style optimization
|
||||
- [ ] Test Case
|
||||
- [ ] Branch merge
|
||||
- [ ] Other (about what?)
|
||||
|
||||
### 🔗 Related issue link
|
||||
|
||||
<!--
|
||||
1. Put the related issue or discussion links here.
|
||||
-->
|
||||
|
||||
### 💡 Background and solution
|
||||
|
||||
<!--
|
||||
1. Describe the problem and the scenario.
|
||||
2. GIF or snapshot should be provided if includes UI/interactive modification.
|
||||
3. How to fix the problem, and list the final API implementation and usage sample if that is a new feature.
|
||||
-->
|
||||
|
||||
### 📝 Changelog
|
||||
|
||||
<!--
|
||||
Describe changes from the user side, and list all potential break changes or other risks.
|
||||
--->
|
||||
|
||||
| Language | Changelog |
|
||||
| ---------- | --------- |
|
||||
| 🇺🇸 English | |
|
||||
| 🇨🇳 Chinese | |
|
||||
|
||||
### ☑️ Self-Check before Merge
|
||||
|
||||
⚠️ Please check all items below before review. ⚠️
|
||||
|
||||
- [ ] Doc is updated/provided or not needed
|
||||
- [ ] Demo is updated/provided or not needed
|
||||
- [ ] TypeScript's definition is updated/provided or not needed
|
||||
- [ ] Changelog is provided or not needed
|
||||
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
首先,感谢你的贡献!😄
|
||||
|
||||
新特性请提交至 feature 分支,其余可提交至 master 分支。
|
||||
在维护者审核通过后会合并。
|
||||
请确保填写以下 pull request 的信息,谢谢!~
|
||||
-->
|
||||
|
||||
[[English Template / 英文模板](https://github.com/go-admin-team/go-admin/blob/master/.github/PULL_REQUEST_TEMPLATE.md)]
|
||||
|
||||
### 🤔 这个变动的性质是?
|
||||
|
||||
- [ ] 新特性提交
|
||||
- [ ] 日常 bug 修复
|
||||
- [ ] 站点、文档改进
|
||||
- [ ] 演示代码改进
|
||||
- [ ] 组件样式/交互改进
|
||||
- [ ] TypeScript 定义更新
|
||||
- [ ] 包体积优化
|
||||
- [ ] 性能优化
|
||||
- [ ] 功能增强
|
||||
- [ ] 国际化改进
|
||||
- [ ] 重构
|
||||
- [ ] 代码风格优化
|
||||
- [ ] 测试用例
|
||||
- [ ] 分支合并
|
||||
- [ ] 其他改动(是关于什么的改动?)
|
||||
|
||||
### 🔗 相关 Issue
|
||||
|
||||
<!--
|
||||
1. 描述相关需求的来源,如相关的 issue 讨论链接。
|
||||
-->
|
||||
|
||||
### 💡 需求背景和解决方案
|
||||
|
||||
<!--
|
||||
1. 要解决的具体问题。
|
||||
2. 列出最终的 API 实现和用法。
|
||||
3. 涉及UI/交互变动需要有截图或 GIF。
|
||||
-->
|
||||
|
||||
### 📝 更新日志
|
||||
|
||||
<!--
|
||||
从用户角度描述具体变化,以及可能的 breaking change 和其他风险。
|
||||
-->
|
||||
|
||||
| 语言 | 更新描述 |
|
||||
| ------- | -------- |
|
||||
| 🇺🇸 英文 | |
|
||||
| 🇨🇳 中文 | |
|
||||
|
||||
### ☑️ 请求合并前的自查清单
|
||||
|
||||
⚠️ 请自检并全部**勾选全部选项**。⚠️
|
||||
|
||||
- [ ] 文档已补充或无须补充
|
||||
- [ ] 代码演示已提供或无须提供
|
||||
- [ ] TypeScript 定义已补充或无须补充
|
||||
- [ ] Changelog 已提供或无须提供
|
||||
@@ -0,0 +1,129 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
# One deploy at a time. Two merges seconds apart raced here: both runs did
|
||||
# docker rm -f then docker run, the second removed the container the first had
|
||||
# just created, and the first's docker run then failed on a name conflict -
|
||||
# leaving the demo on the older image with a red deploy.
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
IMAGE_NAME: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api # 镜像名称
|
||||
TAG: ${{ github.sha }}
|
||||
IMAGE_NAME_TAG: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api:${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: 1.26.5
|
||||
|
||||
- name: Tidy
|
||||
run: go mod tidy
|
||||
|
||||
- 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"
|
||||
docker build -t go-admin-api:latest .
|
||||
echo "************ docker build end"
|
||||
docker tag go-admin-api ${{ env.IMAGE_NAME_TAG }}
|
||||
echo "************ docker tag end"
|
||||
docker images
|
||||
echo "************ docker images end"
|
||||
docker push ${{ env.IMAGE_NAME_TAG }} # 推送
|
||||
echo "************ docker push end"
|
||||
|
||||
- name: Restart server # 第五步,重启服务
|
||||
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:
|
||||
host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似
|
||||
username: ${{ secrets.SSH_USERNAME }}
|
||||
key: ${{ secrets.DEPLOY_KEY }}
|
||||
# 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器
|
||||
#
|
||||
# 配置从宿主机挂载,不使用镜像里的那份:演示站连的是托管数据库,
|
||||
# 而 config/settings.demo.yml 会随仓库公开、也会打进镜像,凭据不能写在那里。
|
||||
# 镜像里那份保持 sqlite,供 clone 仓库的人开箱即用。
|
||||
#
|
||||
# 路径本身走 secret:它不是凭据,但本仓库公开,没有理由把服务器的
|
||||
# 目录结构一并公布。DEMO_CONFIG_PATH 指向宿主机上那份配置。
|
||||
#
|
||||
# 顺序是有意的:迁移先跑,跑不过就保持现有版本不动;
|
||||
# 旧容器改名保留而不是删除,新容器不健康时能原样恢复。
|
||||
# 健康检查两条都要过——HTTP 活着不代表数据库通了。
|
||||
script: |
|
||||
set -u
|
||||
CFG="${{ secrets.DEMO_CONFIG_PATH }}"
|
||||
IMG="${{ env.IMAGE_NAME_TAG }}"
|
||||
NAME=go-admin-api
|
||||
PREV="$NAME-prev"
|
||||
|
||||
test -f "$CFG" || { echo "宿主机配置缺失,中止部署"; exit 1; }
|
||||
|
||||
sudo docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
||||
sudo docker pull "$IMG" || { echo "拉取镜像失败,中止部署"; exit 1; }
|
||||
|
||||
# 迁移用新镜像跑。失败时线上仍是旧版本配旧 schema,是自洽的;
|
||||
# 硬切过去才会得到代码与表对不上的服务。
|
||||
if ! sudo docker run --rm -v "$CFG":/config/settings.yml:ro "$IMG" \
|
||||
/main migrate -c /config/settings.yml; then
|
||||
echo "迁移失败,保持现有版本"; exit 1
|
||||
fi
|
||||
|
||||
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$NAME"; then
|
||||
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
||||
sudo docker rename "$NAME" "$PREV"
|
||||
sudo docker stop "$PREV" >/dev/null
|
||||
fi
|
||||
|
||||
sudo docker run -d -p 8000:8000 \
|
||||
-v "$CFG":/config/settings.yml:ro \
|
||||
--name "$NAME" "$IMG"
|
||||
|
||||
ok=0
|
||||
for i in $(seq 1 20); do
|
||||
sleep 3
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1:8000/api/v1/captcha 2>/dev/null || true)
|
||||
if [ "$code" = "200" ] && sudo docker logs "$NAME" 2>&1 | grep -q 'connect success'; then
|
||||
ok=1; echo "健康检查通过(第 $i 次探测)"; break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$ok" = "1" ]; then
|
||||
sudo docker rm -f "$PREV" >/dev/null 2>&1 || true
|
||||
else
|
||||
echo "健康检查失败,回滚到上一版本"
|
||||
sudo docker logs --tail 40 "$NAME" 2>&1 || true
|
||||
sudo docker rm -f "$NAME" >/dev/null 2>&1 || true
|
||||
if sudo docker ps -a --format '{{.Names}}' | grep -qx "$PREV"; then
|
||||
sudo docker rename "$PREV" "$NAME"
|
||||
sudo docker start "$NAME" >/dev/null
|
||||
echo "已恢复"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
@@ -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
@@ -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.15
|
||||
uses: actions/setup-go@v3
|
||||
- name: Set up Go 1.26
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: 1.15
|
||||
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,29 +0,0 @@
|
||||
name: 'GitHub Actions Mirror'
|
||||
|
||||
on: [push, delete]
|
||||
|
||||
jobs:
|
||||
mirror_to_gitee:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v1
|
||||
- name: 'Mirror to gitee'
|
||||
uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url:
|
||||
git@gitee.com:go-admin-team/go-admin.git
|
||||
ssh_private_key:
|
||||
${{ secrets.GITEE_KEY }}
|
||||
mirror_to_gitlab:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v1
|
||||
- name: 'Mirror to gitlab'
|
||||
uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url:
|
||||
git@gitlab.com:go-admin-team/go-admin.git
|
||||
ssh_private_key:
|
||||
${{ secrets.GITLAB_KEY }}
|
||||
+13
-6
@@ -1,15 +1,15 @@
|
||||
.idea
|
||||
.vscode
|
||||
*/.DS_Store
|
||||
.DS_Store
|
||||
static/uploadfile
|
||||
main.exe
|
||||
*.exe
|
||||
go-admin
|
||||
go-admin.exe
|
||||
temp/
|
||||
!temp
|
||||
vendor
|
||||
config/settings.dev.yml
|
||||
go-admin
|
||||
common/middleware/demo.go
|
||||
config/settings.dev.*.yml
|
||||
config/settings.dev.*.yml.log
|
||||
temp/logs
|
||||
@@ -17,8 +17,15 @@ 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
|
||||
# Everything under .claude is private by default. Skills meant for people using
|
||||
# go-admin are re-included one directory at a time, so a personal one dropped in
|
||||
# here is never committed by accident.
|
||||
.claude/*
|
||||
!.claude/skills/
|
||||
.claude/skills/*
|
||||
!.claude/skills/new-business-module/
|
||||
config/settings.local.dev.yml
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
# 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` 种子数据——完整可运行的
|
||||
参照见 `cmd/migrate/migration/version/1786700001000_demo_menu.go`(sys_api /
|
||||
sys_menu / sys_menu_api_rule / casbin_rule 四张表如何配齐,用的是幂等 upsert,
|
||||
可以直接照抄结构)。
|
||||
|
||||
## 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` 中的真实凭据
|
||||
+12
-22
@@ -1,28 +1,18 @@
|
||||
FROM golang:alpine as builder
|
||||
|
||||
MAINTAINER lwnmengjing
|
||||
|
||||
ENV GOPROXY https://goproxy.cn/
|
||||
|
||||
WORKDIR /go/release
|
||||
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
RUN apk update && apk add tzdata
|
||||
|
||||
COPY go.mod ./go.mod
|
||||
RUN go mod tidy
|
||||
COPY . .
|
||||
RUN pwd && ls
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -a -installsuffix cgo -o go-admin .
|
||||
|
||||
FROM alpine
|
||||
|
||||
COPY --from=builder /go/release/go-admin /
|
||||
# ENV GOPROXY https://goproxy.cn/
|
||||
|
||||
COPY --from=builder /go/release/config/settings.yml /config/settings.yml
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
|
||||
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
RUN apk update --no-cache
|
||||
RUN apk add --update gcc g++ libc6-compat
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ Asia/Shanghai
|
||||
|
||||
COPY ./main /main
|
||||
COPY ./config/settings.demo.yml /config/settings.yml
|
||||
COPY ./go-admin-db.db /go-admin-db.db
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["/go-admin","server","-c", "/config/settings.yml"]
|
||||
RUN chmod +x /main
|
||||
CMD ["/main","server","-c", "/config/settings.yml"]
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -2,11 +2,41 @@ 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:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -a -installsuffix -o go-admin .
|
||||
@docker build -t go-admin:latest .
|
||||
@echo "build successful"
|
||||
|
||||
build-sqlite:
|
||||
go build -tags sqlite3 -ldflags="-w -s" -a -installsuffix -o go-admin .
|
||||
|
||||
# make run
|
||||
run:
|
||||
# delete go-admin-api container
|
||||
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
|
||||
|
||||
# 启动方法一 run go-admin-api container docker-compose 启动方式
|
||||
# 进入到项目根目录 执行 make run 命令
|
||||
@docker-compose up -d
|
||||
|
||||
# 启动方式二 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..."
|
||||
|
||||
# delete Tag=<none> 的镜像
|
||||
@docker image prune -f
|
||||
@docker ps -a | grep "go-admin"
|
||||
|
||||
stop:
|
||||
# delete go-admin-api container
|
||||
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker-compose down; fi
|
||||
#@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
|
||||
#@echo "go-admin stop success"
|
||||
|
||||
|
||||
#.PHONY: test
|
||||
#test:
|
||||
# go test -v ./... -cover
|
||||
@@ -14,3 +44,11 @@ build-sqlite:
|
||||
#.PHONY: docker
|
||||
#docker:
|
||||
# docker build . -t go-admin:latest
|
||||
|
||||
# make deploy
|
||||
deploy:
|
||||
|
||||
#@git checkout master
|
||||
#@git pull origin master
|
||||
make build-linux
|
||||
make run
|
||||
|
||||
+100
-42
@@ -1,6 +1,6 @@
|
||||
# go-admin
|
||||
|
||||
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/a44d60756c9fdedbd70f6bff076a31cbf314936a/img/go-admin.svg">
|
||||
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
|
||||
|
||||
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
@@ -9,18 +9,22 @@
|
||||
|
||||
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | 简体中文
|
||||
|
||||
基于Gin + Vue + Element UI的前后端分离权限管理系统,系统初始化极度简单,只需要配置文件中,修改数据库连接,系统支持多指令操作,迁移指令可以让初始化数据库信息变得更简单,服务指令可以很简单的启动api服务
|
||||
基于Gin + Vue + Element UI OR Arco Design OR Ant Design的前后端分离权限管理系统,系统初始化极度简单,只需要配置文件中,修改数据库连接,系统支持多指令操作,迁移指令可以让初始化数据库信息变得更简单,服务指令可以很简单的启动api服务
|
||||
|
||||
[在线文档](https://doc.go-admin.dev)
|
||||
|
||||
[github在线文档](https://wenjianzhang.github.io)
|
||||
|
||||
[gitee在线文档](http://mydearzwj.gitee.io/go-admin-doc/)
|
||||
[在线文档](https://www.go-admin.pro)
|
||||
|
||||
[前端项目](https://github.com/go-admin-team/go-admin-ui)
|
||||
|
||||
[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
|
||||
## 🎬 在线体验
|
||||
|
||||
Element Plus vue3 体验:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
antd 体验(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 遵循 RESTful API 设计规范
|
||||
@@ -74,9 +78,9 @@
|
||||
|
||||
### 轻松实现go-admin写出第一个应用 - 文档教程
|
||||
|
||||
[步骤一 - 基础内容介绍](https://doc.zhangwj.com/guide/intro/tutorial01.html)
|
||||
[步骤一 - 基础内容介绍](https://doc.go-admin.dev/guide/intro/tutorial01.html)
|
||||
|
||||
[步骤二 - 实际应用 - 编写增删改查](https://doc.zhangwj.com/guide/intro/tutorial02.html)
|
||||
[步骤二 - 实际应用 - 编写增删改查](https://doc.go-admin.dev/guide/intro/tutorial02.html)
|
||||
|
||||
### 手把手教你从入门到放弃 - 视频教程
|
||||
|
||||
@@ -100,6 +104,14 @@
|
||||
|
||||
## 📦 本地开发
|
||||
|
||||
### 环境要求
|
||||
|
||||
go 1.26.5
|
||||
|
||||
node版本: v22+(推荐 v24 LTS)
|
||||
|
||||
包管理器: pnpm v9+(UI 项目使用 pnpm)
|
||||
|
||||
### 开发目录创建
|
||||
|
||||
```bash
|
||||
@@ -130,19 +142,22 @@ git clone https://github.com/go-admin-team/go-admin-ui.git
|
||||
# 进入 go-admin 后端项目
|
||||
cd ./go-admin
|
||||
|
||||
# 更新整理依赖
|
||||
go mod tidy
|
||||
|
||||
# 编译项目
|
||||
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
|
||||
@@ -160,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)
|
||||
|
||||
:::
|
||||
|
||||
#### 初始化数据库,以及服务启动
|
||||
|
||||
@@ -220,43 +234,83 @@ 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.npm.taobao.org
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
|
||||
# 国内网络可指定镜像源加速
|
||||
pnpm install --registry=https://registry.npmmirror.com
|
||||
|
||||
# 启动服务
|
||||
npm run dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 🎬 在线体验
|
||||
|
||||
> admin / 123456
|
||||
|
||||
演示地址:[http://www.go-admin.dev](http://www.go-admin.dev/#/login)
|
||||
|
||||
## 📨 互动
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq.png" width="200px"></td>
|
||||
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
|
||||
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>微信</td>
|
||||
<td><a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=I8ZMqsExqCHpyu8SL4rbya700rBBXYLO&jump_from=webapi"><img border="0" src="//pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流甲号" title="go-admin技术交流甲号"></a></td>
|
||||
<td>公众号🔥🔥🔥</td>
|
||||
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
|
||||
<td>哔哩哔哩🔥🔥🔥</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 💎 主要成员
|
||||
## 💎 贡献者
|
||||
|
||||
<a href="https://github.com/wenjianzhang"> <img src="https://avatars.githubusercontent.com/u/3890175?s=460&u=20eac63daef81588fbac611da676b99859319251&v=4" width="80px"></a>
|
||||
<a href="https://github.com/lwnmengjing"> <img src="https://avatars.githubusercontent.com/u/12806223?s=400&u=a89272dce50100b77b4c0d5c81c718bf78ebb580&v=4" width="80px"></a>
|
||||
<a href="https://github.com/chengxiao"> <img src="https://avatars.githubusercontent.com/u/1379545?s=460&u=557da5503d0ac4a8628df6b4075b17853d5edcd9&v=4" width="80px"></a>
|
||||
<a href="https://github.com/bing127"> <img src="https://avatars.githubusercontent.com/u/31166183?s=460&u=c085bff88df10bb7676c8c0351ba9dcd031d1fb3&v=4" width="80px"></a>
|
||||
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
|
||||
## JetBrains 开源证书支持
|
||||
|
||||
@@ -266,16 +320,20 @@ npm run dev
|
||||
|
||||
## 🤝 特别感谢
|
||||
|
||||
1. [chengxiao](https://github.com/chengxiao)
|
||||
2. [gin](https://github.com/gin-gonic/gin)
|
||||
2. [casbin](https://github.com/casbin/casbin)
|
||||
2. [spf13/viper](https://github.com/spf13/viper)
|
||||
2. [gorm](https://github.com/jinzhu/gorm)
|
||||
2. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
2. [jwt-go](https://github.com/dgrijalva/jwt-go)
|
||||
2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
2. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
2. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
1. [ant-design](https://github.com/ant-design/ant-design)
|
||||
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
|
||||
2. [arco-design](https://github.com/arco-design/arco-design)
|
||||
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
|
||||
4. [gin](https://github.com/gin-gonic/gin)
|
||||
5. [casbin](https://github.com/casbin/casbin)
|
||||
6. [spf13/viper](https://github.com/spf13/viper)
|
||||
7. [gorm](https://github.com/jinzhu/gorm)
|
||||
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
9. [golang-jwt](https://github.com/golang-jwt/jwt)
|
||||
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
12. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
|
||||
|
||||
## 🤟 打赏
|
||||
|
||||
@@ -285,10 +343,10 @@ npm run dev
|
||||
|
||||
## 🤝 链接
|
||||
|
||||
[Go开发者成长线路图](http://www.golangroadmap.com/)
|
||||
- [mss-boot-io](https://docs.mss-boot-io.top/)
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2020 wenjianzhang
|
||||
Copyright (c) 2026 wenjianzhang
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# go-admin
|
||||
|
||||
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/a44d60756c9fdedbd70f6bff076a31cbf314936a/img/go-admin.svg">
|
||||
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/203c5930b9ed08d5cf2fcb4516b85e412f8e0e60/img/go-admin.svg">
|
||||
|
||||
|
||||
[](https://github.com/go-admin-team/go-admin)
|
||||
@@ -10,14 +10,22 @@
|
||||
|
||||
English | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md)
|
||||
|
||||
The front-end and back-end separation authority management system based on Gin + Vue + Element UI is extremely simple to initialize the system. You only need to modify the database connection in the configuration file. The system supports multi-instruction operations. Migration instructions can make it easier to initialize database information. Service instructions It's easy to start the api service.
|
||||
The front-end and back-end separation authority management system based on Gin + Vue + Element UI OR Arco Design is extremely simple to initialize the system. You only need to modify the database connection in the configuration file. The system supports multi-instruction operations. Migration instructions can make it easier to initialize database information. Service instructions It's easy to start the api service.
|
||||
|
||||
[documentation](https://doc.go-admin.dev)
|
||||
[documentation](https://www.go-admin.dev)
|
||||
|
||||
[Front-end project](https://github.com/go-admin-team/go-admin-ui)
|
||||
|
||||
[Video tutorial](https://space.bilibili.com/565616721/channel/detail?cid=125737)
|
||||
|
||||
## 🎬 Online Demo
|
||||
|
||||
Element Plus vue3 demo:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> Account / Password: admin / 123456
|
||||
|
||||
antd demo (go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> Account / Password: admin / 123456
|
||||
>
|
||||
## ✨ Feature
|
||||
|
||||
- Follow RESTful API design specifications
|
||||
@@ -68,9 +76,9 @@ At the same time, a series of tutorials including videos and documents are provi
|
||||
|
||||
### Easily implement go-admin to write the first application-documentation tutorial
|
||||
|
||||
[Step 1 - basic content introduction](https://doc.zhangwj.com/guide/intro/tutorial01.html)
|
||||
[Step 1 - basic content introduction](https://doc.go-admin.dev/guide/intro/tutorial01.html)
|
||||
|
||||
[Step 2 - Practical application - writing database operations](https://doc.zhangwj.com/guide/intro/tutorial02.html)
|
||||
[Step 2 - Practical application - writing database operations](https://doc.go-admin.dev/guide/intro/tutorial02.html)
|
||||
|
||||
### Teach you from getting started to giving up-video tutorial
|
||||
|
||||
@@ -94,6 +102,14 @@ At the same time, a series of tutorials including videos and documents are provi
|
||||
|
||||
## 📦 Local development
|
||||
|
||||
### Environmental requirements
|
||||
|
||||
go 1.26.5
|
||||
|
||||
nodejs: v22+ (v24 LTS recommended)
|
||||
|
||||
package manager: pnpm v9+ (the UI project uses pnpm)
|
||||
|
||||
### Development directory creation
|
||||
|
||||
```bash
|
||||
@@ -124,19 +140,22 @@ git clone https://github.com/go-admin-team/go-admin-ui.git
|
||||
# Enter the go-admin backend project
|
||||
cd ./go-admin
|
||||
|
||||
# Update dependencies
|
||||
go mod tidy
|
||||
|
||||
# Compile the project
|
||||
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
|
||||
@@ -207,38 +226,79 @@ 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
|
||||
```
|
||||
|
||||
## 🎬 Online Demo
|
||||
> admin / 123456
|
||||
|
||||
演示地址:[http://www.go-admin.dev](http://www.go-admin.dev/#/login)
|
||||
|
||||
|
||||
## 📨 Interactive
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
|
||||
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
|
||||
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
|
||||
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wechat</td>
|
||||
<td>Wechat公众号🔥🔥🔥</td>
|
||||
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
|
||||
<td>bilibili🔥🔥🔥</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 💎 Members
|
||||
## 💎 Contributors
|
||||
|
||||
<a href="https://github.com/wenjianzhang"> <img src="https://avatars.githubusercontent.com/u/3890175?s=460&u=20eac63daef81588fbac611da676b99859319251&v=4" width="80px"></a>
|
||||
<a href="https://github.com/lwnmengjing"> <img src="https://avatars.githubusercontent.com/u/12806223?s=400&u=a89272dce50100b77b4c0d5c81c718bf78ebb580&v=4" width="80px"></a>
|
||||
<a href="https://github.com/chengxiao"> <img src="https://avatars.githubusercontent.com/u/1379545?s=460&u=557da5503d0ac4a8628df6b4075b17853d5edcd9&v=4" width="80px"></a>
|
||||
<a href="https://github.com/bing127"> <img src="https://avatars.githubusercontent.com/u/31166183?s=460&u=c085bff88df10bb7676c8c0351ba9dcd031d1fb3&v=4" width="80px"></a>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
|
||||
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
|
||||
|
||||
|
||||
@@ -250,13 +310,17 @@ The `go-admin` project has always been developed in the GoLand integrated develo
|
||||
|
||||
|
||||
## 🤝 Thanks
|
||||
1. [chengxiao](https://github.com/chengxiao)
|
||||
|
||||
1. [ant-design](https://github.com/ant-design/ant-design)
|
||||
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
|
||||
2. [arco-design](https://github.com/arco-design/arco-design)
|
||||
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
|
||||
2. [gin](https://github.com/gin-gonic/gin)
|
||||
2. [casbin](https://github.com/casbin/casbin)
|
||||
2. [spf13/viper](https://github.com/spf13/viper)
|
||||
2. [gorm](https://github.com/jinzhu/gorm)
|
||||
2. [gin-swagger](https://github.com/swaggo/gin-swagger)
|
||||
2. [jwt-go](https://github.com/dgrijalva/jwt-go)
|
||||
2. [golang-jwt](https://github.com/golang-jwt/jwt)
|
||||
2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
2. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
|
||||
2. [form-generator](https://github.com/JakHuang/form-generator)
|
||||
@@ -268,10 +332,10 @@ 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/)
|
||||
- [mss-boot-io](https://docs.mss-boot-io.top/)
|
||||
|
||||
## 🔑 License
|
||||
|
||||
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
|
||||
|
||||
Copyright (c) 2020 wenjianzhang
|
||||
Copyright (c) 2026 wenjianzhang
|
||||
|
||||
@@ -2,8 +2,8 @@ package apis
|
||||
|
||||
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/captcha"
|
||||
"github.com/go-admin-team/go-admin-core/v2/captcha"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
)
|
||||
|
||||
type System struct {
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -3,9 +3,9 @@ package apis
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
|
||||
@@ -3,8 +3,8 @@ package apis
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
@@ -198,7 +198,7 @@ func (e SysConfig) Get2SysApp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
// 控制只读前台的数据
|
||||
req.IsFrontend = 1
|
||||
req.IsFrontend = "1"
|
||||
list := make([]models.SysConfig, 0)
|
||||
err = s.GetWithKeyList(&req, &list)
|
||||
if err != nil {
|
||||
@@ -310,4 +310,4 @@ func (e SysConfig) GetSysConfigByKEYForService(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
e.OK(resp, s.Msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package apis
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"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/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
@@ -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,
|
||||
}, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package apis
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
|
||||
@@ -3,7 +3,7 @@ package apis
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
|
||||
@@ -3,8 +3,8 @@ package apis
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
@@ -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,
|
||||
}, "获取成功")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service"
|
||||
|
||||
@@ -2,16 +2,17 @@ package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-admin/common/global"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
"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/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
"go-admin/app/admin/service/dto"
|
||||
@@ -121,14 +122,19 @@ 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)
|
||||
e.Error(500, err, "创建失败,"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err = global.LoadPolicy(c)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, "创建失败,"+err.Error())
|
||||
return
|
||||
}
|
||||
e.OK(req.GetId(), "创建成功")
|
||||
}
|
||||
|
||||
@@ -155,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))
|
||||
|
||||
@@ -165,6 +171,13 @@ func (e SysRole) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = global.LoadPolicy(c)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, "更新失败,"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
@@ -190,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)
|
||||
|
||||
+12
-20
@@ -2,14 +2,14 @@ package apis
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
"go-admin/app/admin/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
|
||||
"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/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go-admin/app/admin/service"
|
||||
@@ -312,19 +312,12 @@ func (e SysUser) ResetPwd(c *gin.Context) {
|
||||
//数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
if req.UserId == 1 && config.ApplicationConfig.Mode == "demo" {
|
||||
req.Password = "123456"
|
||||
}
|
||||
err = s.ResetPwd(&req, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
return
|
||||
}
|
||||
if req.UserId == 1 && config.ApplicationConfig.Mode == "demo" {
|
||||
e.OK(req.GetId(), "admin:现在使用的预览环境,休想改掉我!否则会影响其他朋友体验的哦!可以创建其他用户体验该功能!")
|
||||
} else {
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
e.OK(req.GetId(), "更新成功")
|
||||
}
|
||||
|
||||
// UpdatePwd
|
||||
@@ -353,20 +346,19 @@ func (e SysUser) UpdatePwd(c *gin.Context) {
|
||||
|
||||
// 数据权限检查
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
if user.GetUserId(c) == 1 && config.ApplicationConfig.Mode == "demo" {
|
||||
req.NewPassword = "123456"
|
||||
var hash []byte
|
||||
if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil {
|
||||
req.NewPassword = string(hash)
|
||||
}
|
||||
|
||||
err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(http.StatusForbidden, err, "密码修改失败")
|
||||
return
|
||||
}
|
||||
if user.GetUserId(c) == 1 && config.ApplicationConfig.Mode == "demo" {
|
||||
e.OK(nil, "admin:现在使用的预览环境,休想改掉我!否则会影响其他朋友体验的哦!可以创建其他用户体验该功能!")
|
||||
} else {
|
||||
e.OK(nil, "密码修改成功")
|
||||
}
|
||||
|
||||
e.OK(nil, "密码修改成功")
|
||||
}
|
||||
|
||||
// GetProfile
|
||||
@@ -458,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,14 +1,14 @@
|
||||
package models
|
||||
|
||||
//sys_casbin_rule
|
||||
type CasbinRule struct {
|
||||
PType string `json:"p_type" gorm:"size:100;"`
|
||||
V0 string `json:"v0" gorm:"size:100;"`
|
||||
V1 string `json:"v1" gorm:"size:100;"`
|
||||
V2 string `json:"v2" gorm:"size:100;"`
|
||||
V3 string `json:"v3" gorm:"size:100;"`
|
||||
V4 string `json:"v4" gorm:"size:100;"`
|
||||
V5 string `json:"v5" gorm:"size:100;"`
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V0 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V1 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V2 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V3 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V4 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
V5 string `gorm:"size:512;uniqueIndex:unique_index"`
|
||||
}
|
||||
|
||||
func (CasbinRule) TableName() string {
|
||||
|
||||
@@ -2,11 +2,11 @@ package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"gorm.io/gorm"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
)
|
||||
|
||||
type DataPermission struct {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/runtime"
|
||||
"github.com/go-admin-team/go-admin-core/storage"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/runtime"
|
||||
"github.com/go-admin-team/go-admin-core/v2/storage"
|
||||
|
||||
"go-admin/common/models"
|
||||
)
|
||||
@@ -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" ||
|
||||
|
||||
@@ -10,13 +10,13 @@ type SysConfig struct {
|
||||
ConfigKey string `json:"configKey" gorm:"size:128;comment:ConfigKey"` //
|
||||
ConfigValue string `json:"configValue" gorm:"size:255;comment:ConfigValue"` //
|
||||
ConfigType string `json:"configType" gorm:"size:64;comment:ConfigType"`
|
||||
IsFrontend int `json:"isFrontend" gorm:"size:64;comment:是否前台"` //
|
||||
IsFrontend string `json:"isFrontend" gorm:"size:64;comment:是否前台"` //
|
||||
Remark string `json:"remark" gorm:"size:128;comment:Remark"` //
|
||||
models.ControlBy
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (SysConfig) TableName() string {
|
||||
func (*SysConfig) TableName() string {
|
||||
return "sys_config"
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ type SysDictData struct {
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (SysDictData) TableName() string {
|
||||
func (*SysDictData) TableName() string {
|
||||
return "sys_dict_data"
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ type SysDictType struct {
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (SysDictType) TableName() string {
|
||||
func (*SysDictType) TableName() string {
|
||||
return "sys_dict_type"
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
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/storage"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/storage"
|
||||
|
||||
"go-admin/common/models"
|
||||
)
|
||||
@@ -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())
|
||||
|
||||
@@ -30,7 +30,13 @@ type SysMenu struct {
|
||||
models.ModelTime
|
||||
}
|
||||
|
||||
func (SysMenu) TableName() string {
|
||||
type SysMenuSlice []SysMenu
|
||||
|
||||
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 {
|
||||
return "sys_menu"
|
||||
}
|
||||
|
||||
@@ -41,4 +47,4 @@ func (e *SysMenu) Generate() models.ActiveRecord {
|
||||
|
||||
func (e *SysMenu) GetId() interface{} {
|
||||
return e.MenuId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
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/storage"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/storage"
|
||||
|
||||
"go-admin/common/models"
|
||||
)
|
||||
@@ -18,15 +18,15 @@ type SysOperaLog struct {
|
||||
BusinessType string `json:"businessType" gorm:"size:128;comment:操作类型"`
|
||||
BusinessTypes string `json:"businessTypes" gorm:"size:128;comment:BusinessTypes"`
|
||||
Method string `json:"method" gorm:"size:128;comment:函数"`
|
||||
RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式"`
|
||||
RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式 GET POST PUT DELETE"`
|
||||
OperatorType string `json:"operatorType" gorm:"size:128;comment:操作类型"`
|
||||
OperName string `json:"operName" gorm:"size:128;comment:操作者"`
|
||||
DeptName string `json:"deptName" gorm:"size:128;comment:部门名称"`
|
||||
OperUrl string `json:"operUrl" gorm:"size:255;comment:访问地址"`
|
||||
OperIp string `json:"operIp" gorm:"size:128;comment:客户端ip"`
|
||||
OperLocation string `json:"operLocation" gorm:"size:128;comment:访问位置"`
|
||||
OperParam string `json:"operParam" gorm:"size:255;comment:请求参数"`
|
||||
Status string `json:"status" gorm:"size:4;comment:操作状态"`
|
||||
OperParam string `json:"operParam" gorm:"text;comment:请求参数"`
|
||||
Status string `json:"status" gorm:"size:4;comment:操作状态 1:正常 2:关闭"`
|
||||
OperTime time.Time `json:"operTime" gorm:"comment:操作时间"`
|
||||
JsonResult string `json:"jsonResult" gorm:"size:255;comment:返回数据"`
|
||||
Remark string `json:"remark" gorm:"size:255;comment:备注"`
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
common "go-admin/common/middleware"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package router
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"go-admin/common/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/middleware"
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
"go-admin/app/admin/apis"
|
||||
"mime"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/ws"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg/ws"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"github.com/swaggo/gin-swagger/swaggerFiles"
|
||||
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
|
||||
"go-admin/common/middleware"
|
||||
"go-admin/common/middleware/handler"
|
||||
_ "go-admin/docs"
|
||||
_ "go-admin/docs/admin"
|
||||
)
|
||||
|
||||
func InitSysRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterGroup {
|
||||
@@ -54,11 +56,11 @@ func sysStaticFileRouter(r *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func sysSwaggerRouter(r *gin.RouterGroup) {
|
||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
r.GET("/swagger/admin/*any", ginSwagger.WrapHandler(swaggerfiles.NewHandler(), ginSwagger.InstanceName("admin")))
|
||||
}
|
||||
|
||||
func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
wss:=r.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
wss := r.Group("").Use(authMiddleware.MiddlewareFunc())
|
||||
{
|
||||
wss.GET("/ws/:id/:channel", ws.WebsocketManager.WsClient)
|
||||
wss.GET("/wslogout/:id/:channel", ws.WebsocketManager.UnWsClient)
|
||||
@@ -67,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,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/admin/apis"
|
||||
"go-admin/common/actions"
|
||||
"go-admin/common/middleware"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ type SysConfigGetPageReq struct {
|
||||
ConfigName string `form:"configName" search:"type:contains;column:config_name;table:sys_config"`
|
||||
ConfigKey string `form:"configKey" search:"type:contains;column:config_key;table:sys_config"`
|
||||
ConfigType string `form:"configType" search:"type:exact;column:config_type;table:sys_config"`
|
||||
IsFrontend int `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
|
||||
IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
|
||||
SysConfigOrder
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func (m *SysConfigGetPageReq) GetNeedSearch() interface{} {
|
||||
}
|
||||
|
||||
type SysConfigGetToSysAppReq struct {
|
||||
IsFrontend int `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
|
||||
IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"`
|
||||
}
|
||||
|
||||
func (m *SysConfigGetToSysAppReq) GetNeedSearch() interface{} {
|
||||
@@ -43,7 +43,7 @@ type SysConfigControl struct {
|
||||
ConfigKey string `uri:"configKey" json:"configKey" comment:""`
|
||||
ConfigValue string `json:"configValue" comment:""`
|
||||
ConfigType string `json:"configType" comment:""`
|
||||
IsFrontend int `json:"isFrontend"`
|
||||
IsFrontend string `json:"isFrontend"`
|
||||
Remark string `json:"remark" comment:""`
|
||||
common.ControlBy
|
||||
}
|
||||
|
||||
@@ -8,16 +8,21 @@ import (
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
const (
|
||||
OperaStatusEnabel = "1" // 状态-正常
|
||||
OperaStatusDisable = "2" // 状态-关闭
|
||||
)
|
||||
|
||||
type SysOperaLogGetPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
Title string `form:"title" search:"type:contains;column:title;table:sys_opera_log" comment:"操作模块"`
|
||||
Method string `form:"method" search:"type:contains;column:method;table:sys_opera_log" comment:"函数"`
|
||||
RequestMethod string `form:"requestMethod" search:"type:contains;column:request_method;table:sys_opera_log" comment:"请求方式"`
|
||||
RequestMethod string `form:"requestMethod" search:"type:contains;column:request_method;table:sys_opera_log" comment:"请求方式: GET POST PUT DELETE"`
|
||||
OperUrl string `form:"operUrl" search:"type:contains;column:oper_url;table:sys_opera_log" comment:"访问地址"`
|
||||
OperIp string `form:"operIp" search:"type:exact;column:oper_ip;table:sys_opera_log" comment:"客户端ip"`
|
||||
Status int `form:"status" search:"type:exact;column:status;table:sys_opera_log" comment:"状态"`
|
||||
BeginTime string `form:"beginTime" search:"type:gte;column:ctime;table:sys_opera_log" comment:"创建时间"`
|
||||
EndTime string `form:"endTime" search:"type:lte;column:ctime;table:sys_opera_log" comment:"创建时间"`
|
||||
Status int `form:"status" search:"type:exact;column:status;table:sys_opera_log" comment:"状态 1:正常 2:关闭"`
|
||||
BeginTime string `form:"beginTime" search:"type:gte;column:created_at;table:sys_opera_log" comment:"创建时间"`
|
||||
EndTime string `form:"endTime" search:"type:lte;column:created_at;table:sys_opera_log" comment:"更新时间"`
|
||||
SysOperaLogOrder
|
||||
}
|
||||
|
||||
|
||||
@@ -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:"标记"` // 标记
|
||||
|
||||
@@ -121,6 +121,7 @@ func (s *SysUserInsertReq) Generate(model *models.SysUser) {
|
||||
model.PostId = s.PostId
|
||||
model.Remark = s.Remark
|
||||
model.Status = s.Status
|
||||
model.CreateBy = s.CreateBy
|
||||
}
|
||||
|
||||
func (s *SysUserInsertReq) GetId() interface{} {
|
||||
@@ -185,4 +186,4 @@ func (s *SysUserById) GenerateM() (common.ActiveRecord, error) {
|
||||
type PassWord struct {
|
||||
NewPassword string `json:"newPassword" vd:"len($)>0"`
|
||||
OldPassword string `json:"oldPassword" vd:"len($)>0"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/runtime"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/runtime"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"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
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import (
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
type SysConfig struct {
|
||||
@@ -17,13 +16,14 @@ type SysConfig struct {
|
||||
|
||||
// GetPage 获取SysConfig列表
|
||||
func (e *SysConfig) GetPage(c *dto.SysConfigGetPageReq, list *[]models.SysConfig, count *int64) error {
|
||||
if err := e.Orm.
|
||||
err := e.Orm.
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
).
|
||||
Find(list).Limit(-1).Offset(-1).
|
||||
Count(count).Error; err != nil {
|
||||
Count(count).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -32,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 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
|
||||
@@ -50,7 +54,8 @@ func (e *SysConfig) Insert(c *dto.SysConfigControl) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
c.Generate(&data)
|
||||
if err = e.Orm.Create(&data).Error; err != nil {
|
||||
err = e.Orm.Create(&data).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service InsertSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -85,7 +90,8 @@ func (e *SysConfig) SetSysConfig(c *[]dto.GetSetSysConfigReq) error {
|
||||
if model.Id != 0 {
|
||||
req.Generate(&model)
|
||||
db := e.Orm.Save(&model)
|
||||
if err = db.Error; err != nil {
|
||||
err = db.Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service SetSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -101,8 +107,9 @@ func (e *SysConfig) GetForSet(c *[]dto.GetSetSysConfigReq) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
|
||||
if err = e.Orm.Model(&data).
|
||||
Find(c).Error; err != nil {
|
||||
err = e.Orm.Model(&data).
|
||||
Find(c).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigPage error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -134,25 +141,27 @@ func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error {
|
||||
|
||||
// Remove 删除SysConfig
|
||||
func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
|
||||
db := e.Orm.Delete(&data, d.Ids)
|
||||
if db.Error != nil {
|
||||
err := db.Error
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Service RemoveSysConfig error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.RowsAffected == 0 {
|
||||
return errors.New("无权删除该数据")
|
||||
err = errors.New("无权删除该数据")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWithKey 根据Key获取SysConfig
|
||||
func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigByKEYForServiceResp) error {
|
||||
var err error
|
||||
var data models.SysConfig
|
||||
if err := e.Orm.Table(data.TableName()).Where("config_key = ?",
|
||||
c.ConfigKey).First(resp).Error; err != nil {
|
||||
err = e.Orm.Table(data.TableName()).Where("config_key = ?", c.ConfigKey).First(resp).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("At Service GetSysConfigByKEY Error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -161,11 +170,12 @@ func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigB
|
||||
}
|
||||
|
||||
func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error {
|
||||
if err := e.Orm.
|
||||
err := e.Orm.
|
||||
Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()),
|
||||
).
|
||||
Find(list).Error; err != nil {
|
||||
Find(list).Error
|
||||
if err != nil {
|
||||
e.Log.Errorf("Service GetSysConfigByKey error:%s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,15 +4,13 @@ import (
|
||||
"errors"
|
||||
"go-admin/app/admin/models"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
|
||||
"gorm.io/gorm"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
type SysDept struct {
|
||||
@@ -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 db.Error != 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
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func (e *SysDept) Update(c *dto.SysDeptUpdateReq) error {
|
||||
}
|
||||
model.DeptPath = deptPath
|
||||
db := tx.Save(&model)
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("UpdateSysDept error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -132,8 +132,7 @@ func (e *SysDept) Remove(d *dto.SysDeptDeleteReq) error {
|
||||
var data models.SysDept
|
||||
|
||||
db := e.Orm.Model(&data).Delete(&data, d.GetId())
|
||||
if db.Error != nil {
|
||||
err = db.Error
|
||||
if err = db.Error; err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
@@ -47,7 +47,7 @@ func (e *SysDictData) Get(d *dto.SysDictDataGetReq, model *models.SysDictData) e
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
@@ -91,8 +91,7 @@ func (e *SysDictData) Remove(c *dto.SysDictDataDeleteReq) error {
|
||||
var data models.SysDictData
|
||||
|
||||
db := e.Orm.Delete(&data, c.GetId())
|
||||
if db.Error != nil {
|
||||
err = db.Error
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
@@ -45,7 +46,7 @@ func (e *SysDictType) Get(d *dto.SysDictTypeGetReq, model *models.SysDictType) e
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
@@ -58,9 +59,14 @@ func (e *SysDictType) Insert(c *dto.SysDictTypeInsertReq) error {
|
||||
var data models.SysDictType
|
||||
c.Generate(&data)
|
||||
var count int64
|
||||
e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count)
|
||||
// The error was dropped, so a query that failed left count at zero and the
|
||||
// insert went ahead as though the name were free.
|
||||
if err = e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count).Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
@@ -77,7 +83,7 @@ func (e *SysDictType) Update(c *dto.SysDictTypeUpdateReq) error {
|
||||
e.Orm.First(&model, c.GetId())
|
||||
c.Generate(&model)
|
||||
db := e.Orm.Save(&model)
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
}
|
||||
@@ -94,8 +100,7 @@ func (e *SysDictType) Remove(d *dto.SysDictTypeDeleteReq) error {
|
||||
var data models.SysDictType
|
||||
|
||||
db := e.Orm.Delete(&data, d.GetId())
|
||||
if db.Error != nil {
|
||||
err = db.Error
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
@@ -44,7 +44,7 @@ func (e *SysLoginLog) Get(d *dto.SysLoginLogGetReq, model *models.SysLoginLog) e
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -57,8 +57,7 @@ func (e *SysLoginLog) Remove(c *dto.SysLoginLogDeleteReq) error {
|
||||
var data models.SysLoginLog
|
||||
|
||||
db := e.Orm.Delete(&data, c.GetId())
|
||||
if db.Error != nil {
|
||||
err = db.Error
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
+100
-49
@@ -1,8 +1,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
@@ -10,7 +14,7 @@ import (
|
||||
cDto "go-admin/common/dto"
|
||||
cModels "go-admin/common/models"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
type SysMenu struct {
|
||||
@@ -86,21 +90,46 @@ func (e *SysMenu) Insert(c *dto.SysMenuInsertReq) *SysMenu {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
c.Generate(&data)
|
||||
err = e.Orm.Create(&data).Error
|
||||
tx := e.Orm.Debug().Begin()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}()
|
||||
err = tx.Where("id in ?", c.Apis).Find(&data.SysApi).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
err = tx.Create(&data).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
c.MenuId = data.MenuId
|
||||
err = e.initPaths(tx, &data)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
tx.Commit()
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *SysMenu) initPaths(menu *models.SysMenu) error {
|
||||
func (e *SysMenu) initPaths(tx *gorm.DB, menu *models.SysMenu) error {
|
||||
var err error
|
||||
var data models.SysMenu
|
||||
parentMenu := new(models.SysMenu)
|
||||
if menu.ParentId != 0 {
|
||||
e.Orm.Model(&data).First(parentMenu, menu.ParentId)
|
||||
err = tx.Model(&data).First(parentMenu, menu.ParentId).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parentMenu.Paths == "" {
|
||||
err = errors.New("父级paths异常,请尝试对当前节点父级菜单进行更新操作!")
|
||||
return err
|
||||
@@ -109,7 +138,7 @@ func (e *SysMenu) initPaths(menu *models.SysMenu) error {
|
||||
} else {
|
||||
menu.Paths = "/0/" + pkg.IntToString(menu.MenuId)
|
||||
}
|
||||
e.Orm.Model(&data).Where("menu_id = ?", menu.MenuId).Update("paths", menu.Paths)
|
||||
err = tx.Model(&data).Where("menu_id = ?", menu.MenuId).Update("paths", menu.Paths).Error
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -127,6 +156,7 @@ func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu {
|
||||
var alist = make([]models.SysApi, 0)
|
||||
var model = models.SysMenu{}
|
||||
tx.Preload("SysApi").First(&model, c.GetId())
|
||||
oldPath := model.Paths
|
||||
tx.Where("id in ?", c.Apis).Find(&alist)
|
||||
err = tx.Model(&model).Association("SysApi").Delete(model.SysApi)
|
||||
if err != nil {
|
||||
@@ -137,7 +167,7 @@ func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu {
|
||||
c.Generate(&model)
|
||||
model.SysApi = alist
|
||||
db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model)
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
@@ -146,6 +176,12 @@ func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu {
|
||||
_ = e.AddError(errors.New("无权更新该数据"))
|
||||
return e
|
||||
}
|
||||
var menuList []models.SysMenu
|
||||
tx.Where("paths like ?", oldPath+"%").Find(&menuList)
|
||||
for _, v := range menuList {
|
||||
v.Paths = strings.Replace(v.Paths, oldPath, model.Paths, 1)
|
||||
tx.Model(&v).Update("paths", v.Paths)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -155,8 +191,7 @@ func (e *SysMenu) Remove(d *dto.SysMenuDeleteReq) *SysMenu {
|
||||
var data models.SysMenu
|
||||
|
||||
db := e.Orm.Model(&data).Delete(&data, d.Ids)
|
||||
if db.Error != nil {
|
||||
err = db.Error
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("Delete error: %s", err)
|
||||
_ = e.AddError(err)
|
||||
}
|
||||
@@ -306,6 +341,40 @@ func menuCall(menuList *[]models.SysMenu, menu models.SysMenu) models.SysMenu {
|
||||
return menu
|
||||
}
|
||||
|
||||
func menuDistinct(menuList []models.SysMenu) (result []models.SysMenu) {
|
||||
distinctMap := make(map[int]struct{}, len(menuList))
|
||||
for _, menu := range menuList {
|
||||
if _, ok := distinctMap[menu.MenuId]; !ok {
|
||||
distinctMap[menu.MenuId] = struct{}{}
|
||||
result = append(result, menu)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func recursiveSetMenu(orm *gorm.DB, mIds []int, menus *[]models.SysMenu) error {
|
||||
if len(mIds) == 0 || menus == nil {
|
||||
return nil
|
||||
}
|
||||
var subMenus []models.SysMenu
|
||||
err := orm.Where(fmt.Sprintf(" menu_type in ('%s', '%s', '%s') and menu_id in ?",
|
||||
cModels.Directory, cModels.Menu, cModels.Button), mIds).Order("sort").Find(&subMenus).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subIds := make([]int, 0)
|
||||
for _, menu := range subMenus {
|
||||
if menu.ParentId != 0 {
|
||||
subIds = append(subIds, menu.ParentId)
|
||||
}
|
||||
if menu.MenuType != cModels.Button {
|
||||
*menus = append(*menus, menu)
|
||||
}
|
||||
}
|
||||
return recursiveSetMenu(orm, subIds, menus)
|
||||
}
|
||||
|
||||
// SetMenuRole 获取左侧菜单树使用
|
||||
func (e *SysMenu) SetMenuRole(roleName string) (m []models.SysMenu, err error) {
|
||||
menus, err := e.getByRoleName(roleName)
|
||||
@@ -321,55 +390,37 @@ func (e *SysMenu) SetMenuRole(roleName string) (m []models.SysMenu, err error) {
|
||||
}
|
||||
|
||||
func (e *SysMenu) getByRoleName(roleName string) ([]models.SysMenu, error) {
|
||||
var MenuList []models.SysMenu
|
||||
var role models.SysRole
|
||||
var err error
|
||||
data := make([]models.SysMenu, 0)
|
||||
|
||||
if roleName == "admin" {
|
||||
var data []models.SysMenu
|
||||
err = e.Orm.Where(" menu_type in ('M','C')").Order("sort").Find(&data).Error
|
||||
MenuList = data
|
||||
// The soft-delete condition is GORM's to add: it appends one for the
|
||||
// model's DeletedAt field on every query. Writing it by hand duplicates
|
||||
// that and hard-codes what "deleted" looks like — a column that stops
|
||||
// being nullable turns this clause into one that matches nothing.
|
||||
err = e.Orm.Where("menu_type in ('M','C')").
|
||||
Order("sort").
|
||||
Find(&data).
|
||||
Error
|
||||
err = errors.WithStack(err)
|
||||
} else {
|
||||
role.RoleKey = roleName
|
||||
buttons := make([]models.SysMenu,0)
|
||||
err = e.Orm.Debug().Model(&role).Where("role_key = ? ", roleName).Preload("SysMenu", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where(" menu_type in ('F')").Order("sort")
|
||||
}).Find(&role).Error
|
||||
err = e.Orm.Model(&role).Where("role_key = ? ", roleName).Preload("SysMenu").First(&role).Error
|
||||
|
||||
if role.SysMenu != nil {
|
||||
buttons = *role.SysMenu
|
||||
}
|
||||
mIds := make([]int, 0)
|
||||
for _, menu := range buttons {
|
||||
if menu.ParentId != 0 {
|
||||
mIds = append(mIds, menu.ParentId)
|
||||
mIds := make([]int, 0)
|
||||
for _, menu := range *role.SysMenu {
|
||||
mIds = append(mIds, menu.MenuId)
|
||||
}
|
||||
}
|
||||
var dataC []models.SysMenu
|
||||
err = e.Orm.Where(" menu_type in ('C') and menu_id in ?", mIds).Order("sort").Find(&dataC).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, datum := range dataC {
|
||||
MenuList = append(MenuList, datum)
|
||||
}
|
||||
cIds := make([]int, 0)
|
||||
for _, menu := range MenuList {
|
||||
if menu.ParentId != 0 {
|
||||
cIds = append(cIds, menu.ParentId)
|
||||
if err := recursiveSetMenu(e.Orm, mIds, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var dataM []models.SysMenu
|
||||
err = e.Orm.Where(" menu_type in ('M') and menu_id in ?", cIds).Order("sort").Find(&dataM).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, datum := range dataM {
|
||||
MenuList = append(MenuList, datum)
|
||||
|
||||
data = menuDistinct(data)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
}
|
||||
return MenuList, err
|
||||
sort.Sort(models.SysMenuSlice(data))
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
)
|
||||
|
||||
// The admin branch of getSysMenuByRoleName carried "deleted_at is null" in its
|
||||
// where clause. GORM adds that condition itself for a model with a DeletedAt
|
||||
// field, so the clause was a duplicate — and one written in terms of a column
|
||||
// being null, which stops being true the moment the column stops being
|
||||
// nullable. This pins the behaviour the clause was there for.
|
||||
func TestSoftDeletedMenusAreNotReturned(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.SysMenu{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
|
||||
live := models.SysMenu{MenuName: "live", MenuType: "M"}
|
||||
gone := models.SysMenu{MenuName: "gone", MenuType: "M"}
|
||||
if err := db.Create(&live).Error; err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if err := db.Create(&gone).Error; err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
if err := db.Delete(&gone).Error; err != nil {
|
||||
t.Fatalf("delete: %v", err)
|
||||
}
|
||||
|
||||
// Through getByRoleName rather than a copy of its query: a test that
|
||||
// reissues the statement passes whether or not the production line still
|
||||
// says what it is supposed to, which is what the first version of this
|
||||
// test did.
|
||||
e := &SysMenu{}
|
||||
e.Orm = db
|
||||
got, err := e.getByRoleName("admin")
|
||||
if err != nil {
|
||||
t.Fatalf("getByRoleName: %v", err)
|
||||
}
|
||||
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("got %d rows, want 1", len(got))
|
||||
}
|
||||
if got[0].MenuName != "live" {
|
||||
t.Errorf("got %q, want the row that was not deleted", got[0].MenuName)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"go-admin/app/admin/service/dto"
|
||||
cDto "go-admin/common/dto"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
@@ -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 db.Error != nil {
|
||||
if err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func (e *SysPost) Update(c *dto.SysPostUpdateReq) error {
|
||||
c.Generate(&model)
|
||||
|
||||
db := e.Orm.Save(&model)
|
||||
if db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -92,8 +92,7 @@ func (e *SysPost) Remove(d *dto.SysPostDeleteReq) error {
|
||||
var data models.SysPost
|
||||
|
||||
db := e.Orm.Model(&data).Delete(&data, d.GetId())
|
||||
if db.Error != nil {
|
||||
err = db.Error
|
||||
if err = db.Error; err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/v2/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"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/admin/models"
|
||||
@@ -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()
|
||||
@@ -113,21 +113,17 @@ func (e *SysRole) Insert(c *dto.SysRoleInsertReq, cb *casbin.SyncedEnforcer) err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(polices) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 写入 sys_casbin_rule 权限表里 当前角色数据的记录
|
||||
_, err = cb.AddNamedPolicies("p", polices)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//if len(c.MenuIds) > 0 {
|
||||
// s := SysRoleMenu{}
|
||||
// s.Orm = e.Orm
|
||||
// s.Log = e.Log
|
||||
// err = s.ReloadRule(tx, c.RoleId, c.MenuIds)
|
||||
// if err != nil {
|
||||
// e.Log.Errorf("reload casbin rule error, %", err.Error())
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,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()
|
||||
@@ -156,9 +152,10 @@ 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 db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -166,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)
|
||||
@@ -182,11 +180,15 @@ 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
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -195,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()
|
||||
@@ -206,9 +208,10 @@ 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 db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -216,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
|
||||
@@ -240,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()
|
||||
@@ -253,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)
|
||||
@@ -261,8 +266,9 @@ 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 db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
_ = e.AddError(err)
|
||||
return e
|
||||
@@ -279,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()
|
||||
@@ -291,8 +297,9 @@ 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 db.Error != nil {
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error:%s", err)
|
||||
return err
|
||||
}
|
||||
@@ -337,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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
)
|
||||
|
||||
// SysRoleMenu 即将弃用结构体
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"go-admin/app/admin/models"
|
||||
"go-admin/app/admin/service/dto"
|
||||
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/common/actions"
|
||||
@@ -233,7 +233,9 @@ func (e *SysUser) UpdatePwd(id int, oldPassword, newPassword string, p *actions.
|
||||
return err
|
||||
}
|
||||
c.Password = newPassword
|
||||
db := e.Orm.Model(c).Where("user_id = ?", id).Select("Password", "Salt").Updates(c)
|
||||
db := e.Orm.Model(c).Where("user_id = ?", id).
|
||||
Select("Password", "Salt").
|
||||
Updates(c)
|
||||
if err = db.Error; err != nil {
|
||||
e.Log.Errorf("db error: %s", err)
|
||||
return err
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/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)))
|
||||
}
|
||||
}
|
||||
@@ -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/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
// "github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"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/v2/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
|
||||
"go-admin/app/jobs/service"
|
||||
"go-admin/common/dto"
|
||||
@@ -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())
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
+20
-20
@@ -2,24 +2,24 @@ package jobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
models2 "go-admin/app/jobs/models"
|
||||
"gorm.io/gorm"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/cronjob"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg/cronjob"
|
||||
)
|
||||
|
||||
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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
//"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
//"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk"
|
||||
common "go-admin/common/middleware"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,7 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/v2/jwtauth"
|
||||
"go-admin/app/jobs/apis"
|
||||
models2 "go-admin/app/jobs/models"
|
||||
dto2 "go-admin/app/jobs/service/dto"
|
||||
|
||||
@@ -2,7 +2,7 @@ package dto
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"go-admin/app/jobs/models"
|
||||
|
||||
"go-admin/common/dto"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"go-admin/app/jobs"
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+112
-104
@@ -8,12 +8,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/utils"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg/utils"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go-admin/common/file_store"
|
||||
"go-admin/config"
|
||||
)
|
||||
|
||||
type FileResponse struct {
|
||||
@@ -44,63 +45,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(""), "初始化文件路径失败")
|
||||
}
|
||||
base64File := path + fileName
|
||||
_ = ioutil.WriteFile(base64File, ddd, 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,
|
||||
}
|
||||
source, _ := c.GetPostForm("source")
|
||||
err = thirdUpload(source, fileName, base64File)
|
||||
if err != nil {
|
||||
e.Error(200, errors.New(""), "上传第三方失败")
|
||||
return fileResponse
|
||||
}
|
||||
|
||||
base64File := path + fileName
|
||||
_ = ioutil.WriteFile(base64File, decodedData, 0666)
|
||||
typeStr := strings.Replace(strings.Replace(file2list[0], "data:", "", -1), ";base64", "", -1)
|
||||
|
||||
fileResponse = e.buildFileResponse(base64File, urlPrefix, "", typeStr)
|
||||
source, _ := c.GetPostForm("source")
|
||||
|
||||
if err := thirdUpload(source, fileName, base64File); err != nil {
|
||||
e.Error(200, err, "上传第三方失败")
|
||||
return fileResponse
|
||||
}
|
||||
|
||||
if source != "1" {
|
||||
fileResponse.Path = "/static/uploadfile/" + fileName
|
||||
fileResponse.FullPath = "/static/uploadfile/" + fileName
|
||||
@@ -108,97 +107,106 @@ 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, err, "上传第三方失败")
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// thirdUpload copies the file that was already stored locally to the object
|
||||
// store the request asked for. source "1", and anything unrecognised, keeps the
|
||||
// local copy only.
|
||||
//
|
||||
// Both branches used to construct a zero-value ALiYunOSS and call UpLoad on it,
|
||||
// which panicked - and the qiniu branch constructed the aliyun client, so
|
||||
// source=3 never reached qiniu even in principle.
|
||||
func thirdUpload(source string, name string, path string) error {
|
||||
switch source {
|
||||
case "2":
|
||||
return ossUpload("img/"+name, path)
|
||||
return upload(file_store.AliYunOSS, config.ExtConfig.FileStore.AliYun, "img/"+name, path)
|
||||
case "3":
|
||||
return qiniuUpload("img/"+name, path)
|
||||
return upload(file_store.QiNiuKodo, config.ExtConfig.FileStore.QiNiu, "img/"+name, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ossUpload(name string, path string) error {
|
||||
oss := file_store.ALiYunOSS{}
|
||||
return oss.UpLoad(name, path)
|
||||
func upload(driver file_store.DriverType, store config.ObjectStore, name, path string) error {
|
||||
if !store.Configured() {
|
||||
return fmt.Errorf("file store %s is not configured; set it under extend.fileStore", driver)
|
||||
}
|
||||
oxs := file_store.OXS{
|
||||
Endpoint: store.Endpoint,
|
||||
AccessKeyID: store.AccessKeyID,
|
||||
AccessKeySecret: store.AccessKeySecret,
|
||||
BucketName: store.BucketName,
|
||||
}
|
||||
client, err := oxs.Setup(driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.UpLoad(name, path)
|
||||
}
|
||||
|
||||
func qiniuUpload(name string, path string) error {
|
||||
oss := file_store.ALiYunOSS{}
|
||||
return oss.UpLoad(name, path)
|
||||
}
|
||||
@@ -3,16 +3,14 @@ package apis
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package tools
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
|
||||
"go-admin/app/other/models/tools"
|
||||
)
|
||||
@@ -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
|
||||
@@ -41,7 +41,7 @@ func (e *Gen) GetDBColumnList(c *gin.Context) {
|
||||
}
|
||||
|
||||
data.TableName = c.Request.FormValue("tableName")
|
||||
pkg.Assert(data.TableName == "", "table name cannot be empty!", 500)
|
||||
pkg.Assert(data.TableName != "", "table name cannot be empty!", 500)
|
||||
result, count, err := data.GetPage(db, pageSize, pageIndex)
|
||||
if err != nil {
|
||||
log.Errorf("GetPage error, %s", err.Error())
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-admin-team/go-admin-core/v2/logger"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
const emptyTableNameMsg = "table name cannot be empty!"
|
||||
|
||||
// bodyOf covers both the success and the CustomError shape: both carry msg.
|
||||
type bodyOf struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// newColumnListEngine wires the handler the way the router does, including the
|
||||
// middleware that turns pkg.Assert's panic into a response.
|
||||
func newColumnListEngine(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite: %v", err)
|
||||
}
|
||||
|
||||
// The query targets MySQL's information_schema; the driver setting only has
|
||||
// to select that branch, the statement itself is never expected to succeed.
|
||||
previous := config.DatabaseConfig.Driver
|
||||
config.DatabaseConfig.Driver = "mysql"
|
||||
t.Cleanup(func() { config.DatabaseConfig.Driver = previous })
|
||||
|
||||
r := gin.New()
|
||||
r.Use(middleware.CustomError)
|
||||
r.GET("/db/columns/page", func(c *gin.Context) {
|
||||
c.Set("db", db)
|
||||
c.Set(pkg.LoggerKey, logger.NewHelper(logger.DefaultLogger))
|
||||
Gen{}.GetDBColumnList(c)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func columnListMsg(t *testing.T, r *gin.Engine, query string) bodyOf {
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/db/columns/page"+query, nil))
|
||||
|
||||
var body bodyOf
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("decode %q: %v", w.Body.String(), err)
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
func TestGetDBColumnList_AcceptsATableName(t *testing.T) {
|
||||
body := columnListMsg(t, newColumnListEngine(t), "?tableName=sys_user")
|
||||
if body.Msg == emptyTableNameMsg {
|
||||
t.Fatalf("request carried a table name and was still rejected as empty: %+v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDBColumnList_RejectsAMissingTableName(t *testing.T) {
|
||||
body := columnListMsg(t, newColumnListEngine(t), "")
|
||||
if body.Msg != emptyTableNameMsg {
|
||||
t.Fatalf("missing table name should be rejected, got %+v", body)
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ package tools
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"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/pkg/response"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
|
||||
"go-admin/app/other/models/tools"
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"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/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
|
||||
"go-admin/app/other/models/tools"
|
||||
)
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/go-admin-team/go-admin-core/v2/sdk/api"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/other/models/tools"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go-admin/config"
|
||||
)
|
||||
|
||||
// Both branches used to construct a zero-value ALiYunOSS and call UpLoad on it,
|
||||
// which panicked; the qiniu branch built the aliyun client, so source=3 could
|
||||
// not have reached qiniu even with credentials. Unconfigured now reports which
|
||||
// store is missing.
|
||||
func TestThirdUploadReportsAnUnconfiguredStore(t *testing.T) {
|
||||
previous := config.ExtConfig.FileStore
|
||||
config.ExtConfig.FileStore = config.FileStore{}
|
||||
t.Cleanup(func() { config.ExtConfig.FileStore = previous })
|
||||
|
||||
for source, want := range map[string]string{"2": "AliYunOSS", "3": "QiNiuKodo"} {
|
||||
err := thirdUpload(source, "x.png", "/tmp/x.png")
|
||||
if err == nil {
|
||||
t.Errorf("source=%s: no error from an unconfigured store", source)
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("source=%s: error names %q, want it to mention %s", source, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// source 1 and anything unrecognised keep the local copy and do nothing else.
|
||||
func TestThirdUploadIgnoresLocalAndUnknownSources(t *testing.T) {
|
||||
for _, source := range []string{"", "1", "9"} {
|
||||
if err := thirdUpload(source, "x.png", "/tmp/x.png"); err != nil {
|
||||
t.Errorf("source=%q returned %v, want nil", source, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package tools
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"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/v2/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -24,47 +24,38 @@ type DBColumns struct {
|
||||
}
|
||||
|
||||
func (e *DBColumns) GetPage(tx *gorm.DB, pageSize int, pageIndex int) ([]DBColumns, int, error) {
|
||||
pkg.Assert(config.DatabaseConfig.Driver == "mysql", "目前只支持mysql数据库", 500)
|
||||
|
||||
var doc []DBColumns
|
||||
var count int64
|
||||
table := new(gorm.DB)
|
||||
|
||||
if config.DatabaseConfig.Driver == "mysql" {
|
||||
table = tx.Table("information_schema.`COLUMNS`")
|
||||
table = table.Where("table_schema= ? ", config.GenConfig.DBName)
|
||||
|
||||
if e.TableName != "" {
|
||||
return nil, 0, errors.New("table name cannot be empty!")
|
||||
}
|
||||
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName)
|
||||
if e.TableName == "" {
|
||||
return nil, 0, errors.New("table name cannot be empty!")
|
||||
}
|
||||
|
||||
table := tx.Table("information_schema.`COLUMNS`")
|
||||
table = table.Where("table_schema= ? ", config.GenConfig.DBName)
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName)
|
||||
|
||||
if err := table.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
//table.Count(&count)
|
||||
return doc, int(count), nil
|
||||
|
||||
}
|
||||
|
||||
func (e *DBColumns) GetList(tx *gorm.DB) ([]DBColumns, error) {
|
||||
var doc []DBColumns
|
||||
table := new(gorm.DB)
|
||||
pkg.Assert(config.DatabaseConfig.Driver == "mysql", "目前只支持mysql数据库", 500)
|
||||
|
||||
var doc []DBColumns
|
||||
if e.TableName == "" {
|
||||
return nil, errors.New("table name cannot be empty!")
|
||||
}
|
||||
|
||||
if config.DatabaseConfig.Driver == "mysql" {
|
||||
table = tx.Table("information_schema.columns")
|
||||
table = table.Where("table_schema= ? ", config.GenConfig.DBName)
|
||||
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName).Order("ORDINAL_POSITION asc")
|
||||
} else {
|
||||
pkg.Assert(true, "目前只支持mysql数据库", 500)
|
||||
}
|
||||
table := tx.Table("information_schema.columns")
|
||||
table = table.Where("table_schema= ? ", config.GenConfig.DBName)
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName).Order("ORDINAL_POSITION asc")
|
||||
if err := table.Find(&doc).Error; err != nil {
|
||||
return doc, err
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package tools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
config2 "github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
config2 "github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
)
|
||||
|
||||
type DBTables struct {
|
||||
@@ -20,43 +20,49 @@ type DBTables struct {
|
||||
}
|
||||
|
||||
func (e *DBTables) GetPage(tx *gorm.DB, pageSize int, pageIndex int) ([]DBTables, int, error) {
|
||||
pkg.Assert(config2.DatabaseConfig.Driver == "mysql", "目前只支持mysql数据库", 500)
|
||||
|
||||
var doc []DBTables
|
||||
table := new(gorm.DB)
|
||||
var count int64
|
||||
|
||||
if config2.DatabaseConfig.Driver == "mysql" {
|
||||
table = tx.Table("information_schema.tables")
|
||||
table = table.Where("TABLE_NAME not in (select table_name from `" + config2.GenConfig.DBName + "`.sys_tables) ")
|
||||
table = table.Where("table_schema= ? ", config2.GenConfig.DBName)
|
||||
|
||||
if e.TableName != "" {
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName)
|
||||
}
|
||||
if err := table.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
pkg.Assert(true, "目前只支持mysql数据库", 500)
|
||||
// Tables already registered with the generator are not candidates. Read them
|
||||
// through the model on this connection: the subquery used to spell the
|
||||
// schema out by hand, so it only resolved when sys_tables happened to live
|
||||
// in the schema being generated from, and it counted soft-deleted rows.
|
||||
var generated []string
|
||||
if err := tx.Model(&SysTables{}).Pluck("table_name", &generated).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
//table.Count(&count)
|
||||
table := tx.Table("information_schema.tables")
|
||||
table = table.Where("table_schema= ? ", config2.GenConfig.DBName)
|
||||
if len(generated) > 0 {
|
||||
// NOT IN (NULL) is unknown for every row, so an empty list has to skip
|
||||
// the clause instead of rendering it.
|
||||
table = table.Where("TABLE_NAME not in (?)", generated)
|
||||
}
|
||||
|
||||
if e.TableName != "" {
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName)
|
||||
}
|
||||
if err := table.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return doc, int(count), nil
|
||||
}
|
||||
|
||||
func (e *DBTables) Get(tx *gorm.DB) (DBTables, error) {
|
||||
pkg.Assert(config2.DatabaseConfig.Driver == "mysql", "目前只支持mysql数据库", 500)
|
||||
|
||||
var doc DBTables
|
||||
if config2.DatabaseConfig.Driver == "mysql" {
|
||||
table := tx.Table("information_schema.tables")
|
||||
table = table.Where("table_schema= ? ", config2.GenConfig.DBName)
|
||||
if e.TableName == "" {
|
||||
return doc, errors.New("table name cannot be empty!")
|
||||
}
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName)
|
||||
if err := table.First(&doc).Error; err != nil {
|
||||
return doc, err
|
||||
}
|
||||
} else {
|
||||
pkg.Assert(true, "目前只支持mysql数据库", 500)
|
||||
if e.TableName == "" {
|
||||
return doc, errors.New("table name cannot be empty!")
|
||||
}
|
||||
table := tx.Table("information_schema.tables")
|
||||
table = table.Where("table_schema= ? ", config2.GenConfig.DBName)
|
||||
table = table.Where("TABLE_NAME = ?", e.TableName)
|
||||
if err := table.First(&doc).Error; err != nil {
|
||||
return doc, err
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
config2 "github.com/go-admin-team/go-admin-core/v2/sdk/config"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const generatorSchema = "go_admin_test"
|
||||
|
||||
// newCandidateDB stands in for MySQL: sqlite is given an attached database
|
||||
// called information_schema so the same query runs, and sys_tables lives on the
|
||||
// connection the way it does in production.
|
||||
func newCandidateDB(t *testing.T, tables ...string) *gorm.DB {
|
||||
t.Helper()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite: %v", err)
|
||||
}
|
||||
if err := db.Exec(`ATTACH DATABASE ':memory:' AS information_schema`).Error; err != nil {
|
||||
t.Fatalf("attach information_schema: %v", err)
|
||||
}
|
||||
if err := db.Exec("CREATE TABLE information_schema.`tables` (" +
|
||||
"TABLE_NAME text, TABLE_SCHEMA text, `ENGINE` text, TABLE_ROWS text," +
|
||||
"TABLE_COLLATION text, CREATE_TIME text, UPDATE_TIME text, TABLE_COMMENT text)").Error; err != nil {
|
||||
t.Fatalf("create information_schema.tables: %v", err)
|
||||
}
|
||||
for _, name := range tables {
|
||||
if err := db.Exec("INSERT INTO information_schema.`tables` (TABLE_NAME, TABLE_SCHEMA) VALUES (?, ?)",
|
||||
name, generatorSchema).Error; err != nil {
|
||||
t.Fatalf("insert %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
if err := db.AutoMigrate(new(SysTables)); err != nil {
|
||||
t.Fatalf("migrate sys_tables: %v", err)
|
||||
}
|
||||
|
||||
previousDriver := config2.DatabaseConfig.Driver
|
||||
previousName := config2.GenConfig.DBName
|
||||
config2.DatabaseConfig.Driver = "mysql"
|
||||
config2.GenConfig.DBName = generatorSchema
|
||||
t.Cleanup(func() {
|
||||
config2.DatabaseConfig.Driver = previousDriver
|
||||
config2.GenConfig.DBName = previousName
|
||||
})
|
||||
return db
|
||||
}
|
||||
|
||||
func candidateNames(t *testing.T, db *gorm.DB) []string {
|
||||
t.Helper()
|
||||
found, _, err := new(DBTables).GetPage(db, 100, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPage: %v", err)
|
||||
}
|
||||
names := make([]string, 0, len(found))
|
||||
for _, row := range found {
|
||||
names = append(names, row.TableName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func contains(names []string, want string) bool {
|
||||
for _, name := range names {
|
||||
if name == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An empty sys_tables must not filter everything out - that is what a bare
|
||||
// NOT IN (empty set) does, and a fresh install is exactly the case where the
|
||||
// list matters most.
|
||||
func TestGetPageListsEveryTableWhenNoneAreRegistered(t *testing.T) {
|
||||
db := newCandidateDB(t, "sys_user", "sys_role")
|
||||
|
||||
names := candidateNames(t, db)
|
||||
if len(names) != 2 {
|
||||
t.Fatalf("want both tables offered on a fresh install, got %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPageSkipsAlreadyRegisteredTables(t *testing.T) {
|
||||
db := newCandidateDB(t, "sys_user", "sys_role")
|
||||
if err := db.Create(&SysTables{TBName: "sys_user"}).Error; err != nil {
|
||||
t.Fatalf("register sys_user: %v", err)
|
||||
}
|
||||
|
||||
names := candidateNames(t, db)
|
||||
if contains(names, "sys_user") {
|
||||
t.Errorf("sys_user is already registered and was offered again: %v", names)
|
||||
}
|
||||
if !contains(names, "sys_role") {
|
||||
t.Errorf("sys_role is not registered and was withheld: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
// Deleting the generator entry has to hand the table back, which the raw
|
||||
// subquery never did: it read the row whether or not it was soft-deleted.
|
||||
func TestGetPageOffersTablesWhoseEntryWasDeleted(t *testing.T) {
|
||||
db := newCandidateDB(t, "sys_user")
|
||||
registered := SysTables{TBName: "sys_user"}
|
||||
if err := db.Create(®istered).Error; err != nil {
|
||||
t.Fatalf("register sys_user: %v", err)
|
||||
}
|
||||
if err := db.Delete(®istered).Error; err != nil {
|
||||
t.Fatalf("delete the entry: %v", err)
|
||||
}
|
||||
|
||||
if names := candidateNames(t, db); !contains(names, "sys_user") {
|
||||
t.Errorf("the generator entry is deleted, sys_user should be a candidate again: %v", names)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user