mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-25 03:21:46 +00:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -29,7 +29,12 @@ jobs:
|
||||
- name: Build
|
||||
run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main .
|
||||
|
||||
# 以下推镜像与重启步骤仅在 master 收到 push 时执行。
|
||||
# pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的
|
||||
# PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务,
|
||||
# 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。
|
||||
- name: Build the Docker image and push
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }}
|
||||
echo "************ docker login end"
|
||||
@@ -43,6 +48,7 @@ jobs:
|
||||
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}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
.idea
|
||||
.vscode
|
||||
*/.DS_Store
|
||||
.DS_Store
|
||||
static/uploadfile
|
||||
main.exe
|
||||
*.exe
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
# AGENTS.md — go-admin 后端
|
||||
|
||||
> 给 AI 编码工具与新贡献者的约定。**只写"不遵守就会出错"的规则**;技术栈版本以
|
||||
> `go.mod` 为准,命令以 `Makefile` 为准,此处不复述,避免与代码脱节。
|
||||
>
|
||||
> 标准 CRUD 模块的完整写法见 **`app/demo/`** —— 那是可编译、有测试、CI 会跑的参照物。
|
||||
> 本文与它冲突时,以 `app/demo/` 为准。
|
||||
|
||||
## 分层
|
||||
|
||||
```
|
||||
Router → Api → Service → Model
|
||||
路由注册 参数绑定 业务逻辑 GORM 结构体
|
||||
中间件链 调用 Service 操作数据库 TableName()
|
||||
```
|
||||
|
||||
对应目录:`app/{模块}/router|apis|service|models`,DTO 位于 `service/dto`。
|
||||
|
||||
**不可跨层**:Api 不直接操作 `Orm`,Service 不接触 `gin.Context`。
|
||||
|
||||
## 优先使用通用 Action
|
||||
|
||||
单表 CRUD **不要手写 Handler 与 Service**。`common/actions` 提供的五个
|
||||
Action 已覆盖参数绑定、数据权限过滤、操作人注入、分页与错误响应:
|
||||
|
||||
```go
|
||||
r := v1.Group("/demo-product").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
|
||||
{
|
||||
m := &models.DemoProduct{}
|
||||
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
|
||||
list := make([]models.DemoProduct, 0); return &list
|
||||
}))
|
||||
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
|
||||
return &models.DemoProduct{}
|
||||
}))
|
||||
r.POST("", actions.CreateAction(new(dto.DemoProductControl)))
|
||||
r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl)))
|
||||
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById)))
|
||||
}
|
||||
```
|
||||
|
||||
这样一个模块只需 **model + dto + router** 三个文件,完整示例见 `app/demo/`。
|
||||
|
||||
使用通用 Action 的前提:
|
||||
|
||||
- Model 实现 `models.ActiveRecord`(`Generate` / `GetId` / `TableName`)
|
||||
- 列表 DTO 实现 `dto.Index`,增改删 DTO 实现 `dto.Control`
|
||||
- **所有 `Generate()` 必须返回副本** —— Action 在并发请求间复用实例,
|
||||
就地返回会串数据(`app/demo` 的测试锁定了这一点)
|
||||
- 详情/删除 DTO 内嵌 `dto.ObjectById` 即可继承 `Bind` 与 `GetId`,无需重写
|
||||
|
||||
仅当业务超出单表 CRUD(跨表事务、外部调用、复杂校验)时才自行编写 Handler
|
||||
与 Service,写法见下。
|
||||
|
||||
## Api 层(仅在通用 Action 不适用时)
|
||||
|
||||
结构体嵌入 `api.Api`,链式初始化后**必须检查 `Errors`**:
|
||||
|
||||
```go
|
||||
func (e SysPost) GetPage(c *gin.Context) {
|
||||
s := service.SysPost{}
|
||||
req := dto.SysPostPageReq{}
|
||||
err := e.MakeContext(c).MakeOrm().Bind(&req, binding.Form).MakeService(&s.Service).Errors
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
// ... 调用 s.GetPage(...)
|
||||
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
||||
}
|
||||
```
|
||||
|
||||
响应一律走 `e.OK` / `e.PageOK` / `e.Error`,不要自行 `c.JSON`。
|
||||
|
||||
## Service 层(仅在通用 Action 不适用时)
|
||||
|
||||
结构体嵌入 `service.Service`(持有 `Orm` 与 `Log`)。查询通过 Scopes 组合:
|
||||
|
||||
```go
|
||||
err = e.Orm.Model(&data).Scopes(
|
||||
cDto.MakeCondition(c.GetNeedSearch()), // 由 search tag 生成 WHERE
|
||||
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
||||
actions.Permission(data.TableName(), p), // 数据权限,列表/详情必须带
|
||||
).Find(list).Limit(-1).Offset(-1).Count(count).Error
|
||||
```
|
||||
|
||||
**遗漏 `actions.Permission` 会使数据权限配置静默失效** —— 这是最容易出的错。
|
||||
|
||||
错误一律 `return err` 向上传递,日志用 `e.Log.Errorf`,不使用 `panic`。
|
||||
|
||||
## DTO
|
||||
|
||||
搜索条件由 tag 声明,`MakeCondition` 据此拼 SQL:
|
||||
|
||||
```go
|
||||
type SysPostPageReq struct {
|
||||
dto.Pagination `search:"-"`
|
||||
PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post"`
|
||||
}
|
||||
func (m *SysPostPageReq) GetNeedSearch() interface{} { return *m }
|
||||
```
|
||||
|
||||
`type` 可选:`exact` `iexact` `contains` `gt` `gte` `lt` `lte` `order` `left`(联表)。
|
||||
|
||||
## Model
|
||||
|
||||
```go
|
||||
type SysPost struct {
|
||||
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"`
|
||||
// ... 业务字段
|
||||
models.ControlBy // CreateBy / UpdateBy
|
||||
models.ModelTime // CreatedAt / UpdatedAt / DeletedAt
|
||||
}
|
||||
func (SysPost) TableName() string { return "sys_post" }
|
||||
```
|
||||
|
||||
`TableName()` 必须显式声明(GORM 配置了 `SingularTable`,不会自动推导复数)。
|
||||
|
||||
## 路由注册
|
||||
|
||||
通过 `init()` 自注册,不在中心文件手工添加:
|
||||
|
||||
```go
|
||||
func init() { routerCheckRole = append(routerCheckRole, registerSysPostRouter) }
|
||||
|
||||
func registerSysPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
api := apis.SysPost{}
|
||||
r := v1.Group("/post").
|
||||
Use(authMiddleware.MiddlewareFunc()).
|
||||
Use(middleware.AuthCheckRole()). // Casbin 鉴权
|
||||
Use(actions.PermissionAction()) // 注入数据权限
|
||||
{ r.GET("", api.GetPage); r.POST("", api.Insert); /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
新增路由文件后,需确认 `cmd/api/` 中已用 `_` 导入该包。
|
||||
|
||||
## 命名
|
||||
|
||||
| 对象 | 规则 | 示例 |
|
||||
|---|---|---|
|
||||
| 数据表 | `sys_` 前缀 + 下划线 | `sys_post` |
|
||||
| API 路径 | `/api/v1/` + kebab-case | `/api/v1/sys-user` |
|
||||
| DTO | `{Model}{Action}Req` | `SysPostPageReq` |
|
||||
| 权限标识 | `模块:资源:操作` | `admin:sysPost:add` |
|
||||
|
||||
权限标识需与前端 `v-permisaction` 一致,并写入 `sys_menu` 种子数据。
|
||||
|
||||
## Swagger
|
||||
|
||||
Handler 必须带完整注解,`go generate` 会据此生成文档:
|
||||
|
||||
```go
|
||||
// @Summary 岗位列表
|
||||
// @Tags 岗位
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /api/v1/post [get]
|
||||
// @Security Bearer
|
||||
```
|
||||
|
||||
## 本地运行
|
||||
|
||||
**配置 `driver: sqlite3` 时必须带构建标签**,否则启动即 panic:
|
||||
|
||||
```bash
|
||||
go run -tags sqlite3 . migrate -c config/settings.sqlite.yml
|
||||
go run -tags sqlite3 . server -c config/settings.sqlite.yml
|
||||
```
|
||||
|
||||
原因:`common/database/open.go` 带 `//go:build !sqlite3`,不加标签时编进的是
|
||||
不含 sqlite3 的版本,`opens["sqlite3"]` 为 nil,调用时在 nil 函数上崩溃。
|
||||
报错信息不会提到构建标签,容易误判成环境损坏。MySQL / PostgreSQL 无此问题。
|
||||
|
||||
对应 `Makefile` 的 `build-sqlite` 目标。
|
||||
|
||||
## 数据库迁移
|
||||
|
||||
文件名前 13 位为时间戳版本号。**已执行过的迁移文件不可修改** ——
|
||||
`sys_migration` 表按版本号去重,改动不会重跑,只能新增一个迁移来修正。
|
||||
|
||||
放哪个目录取决于身份:
|
||||
|
||||
| 目录 | 用途 | 是否入库 |
|
||||
|---|---|---|
|
||||
| `version/` | 框架自带迁移,随仓库分发给所有使用者 | 是 |
|
||||
| `version-local/` | 使用者自己项目的迁移 | 否(已在 `.gitignore`) |
|
||||
|
||||
**向本仓库提交迁移必须放 `version/`** —— 放进 `version-local/` 会被忽略掉,
|
||||
`git status` 看不到,PR 里也不会出现。两个目录的包名分别是 `version` 与
|
||||
`version_local`(后者与目录名不一致,因为标识符不能含连字符)。
|
||||
|
||||
## 提交规范
|
||||
|
||||
格式 `type+emoji: 描述`:
|
||||
|
||||
`feat✨` `fix🐛` `style💄` `docs📝` `perf👌` `test✅` `refactor🎨` `chore🔧`
|
||||
|
||||
一个提交只做一件事。改动跨越多个语义时拆分提交,不要混在一起。
|
||||
|
||||
## 红线
|
||||
|
||||
- 不使用全局 DB 变量,一律用 `e.Orm`(来自请求上下文,多租户依赖它)
|
||||
- 不在 Service 中引用 `gin.Context`
|
||||
- 生产部署前确认 `mode: prod` 且已修改 `jwt.secret`(dev 模式下 token 几乎不过期)
|
||||
- 不提交 `config/settings.yml` 中的真实凭据
|
||||
@@ -21,7 +21,7 @@ run:
|
||||
# 进入到项目根目录 执行 make run 命令
|
||||
@docker-compose up -d
|
||||
|
||||
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际决对路径
|
||||
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际绝对路径
|
||||
#@docker run --name=go-admin -p 8000:8000 -v /home/code/go/src/go-admin/go-admin/config:/go-admin-api/config -v /home/code/go/src/go-admin/go-admin-api/static:/go-admin/static -v /home/code/go/src/go-admin/go-admin/temp:/go-admin-api/temp -d --restart=always go-admin:latest
|
||||
|
||||
@echo "go-admin service is running..."
|
||||
|
||||
+2
-5
@@ -19,13 +19,10 @@
|
||||
|
||||
## 🎬 在线体验
|
||||
|
||||
Element UI vue体验:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login)
|
||||
Element Plus vue3 体验:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
antd体验:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
antd 体验(go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
@@ -20,13 +20,10 @@ The front-end and back-end separation authority management system based on Gin +
|
||||
|
||||
## 🎬 Online Demo
|
||||
|
||||
Element UI vue demo:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login)
|
||||
Element Plus vue3 demo:[https://vue.go-admin.pro](https://vue.go-admin.pro/#/login)
|
||||
> 账号 / 密码: admin / 123456
|
||||
|
||||
Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login)
|
||||
> 账号 / 密码: admin / 123456
|
||||
|
||||
antd demo:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
antd demo (go-admin-pro):[https://antd.go-admin.pro](https://antd.go-admin.pro/)
|
||||
> 账号 / 密码: admin / 123456
|
||||
>
|
||||
## ✨ Feature
|
||||
|
||||
@@ -11,10 +11,11 @@ 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="https://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
|
||||
@@ -28,7 +29,7 @@ $(function(){
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe" frameborder="0" src="https://www.go-admin.pro" style="width:100%;"></iframe>
|
||||
<iframe id="iframe" frameborder="0" src="https://www.go-admin.pro" style="width:100%;height:100%;"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
@@ -122,7 +122,7 @@ func (e SysRole) Insert(c *gin.Context) {
|
||||
if req.Status == "" {
|
||||
req.Status = "2"
|
||||
}
|
||||
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
|
||||
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
err = s.Insert(&req, cb)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
@@ -161,7 +161,7 @@ func (e SysRole) Update(c *gin.Context) {
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
|
||||
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
|
||||
req.SetUpdateBy(user.GetUserId(c))
|
||||
|
||||
@@ -203,7 +203,7 @@ func (e SysRole) Delete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
|
||||
cb := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
err = s.Remove(&req, cb)
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
|
||||
@@ -54,7 +54,7 @@ func SaveSysApi(message storage.Messager) (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" ||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -69,8 +69,15 @@ func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
v1.POST("/login", authMiddleware.LoginHandler)
|
||||
// Refresh time can be longer than token timeout
|
||||
v1.GET("/refresh_token", authMiddleware.RefreshHandler)
|
||||
// GET /api/v1/refresh_token 已移除,原因见 issue #820:
|
||||
// 该接口用业务 token 即可换取新 token,而续期上限 MaxRefresh 依据的
|
||||
// orig_iat 在每次续期时被一并重置,上限永远无法到达 —— token 一旦泄
|
||||
// 露即等同于永久访问权。它此前还位于 CasbinExclude 中,任何角色的已
|
||||
// 登录用户都能调用,不受权限约束。
|
||||
//
|
||||
// 官方前端从未调用该接口(store 中的 refreshToken action 无人 dispatch),
|
||||
// 移除不影响正常使用。若确需无感续期,应在 go-admin-core 中区分
|
||||
// access token 与 refresh token 后重新实现,而非沿用此路由。
|
||||
}
|
||||
registerBaseRouter(v1, authMiddleware)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v3"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk/service"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -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/sdk/pkg/jwtauth"
|
||||
|
||||
"go-admin/app/demo/models"
|
||||
"go-admin/app/demo/service/dto"
|
||||
"go-admin/common/actions"
|
||||
"go-admin/common/middleware"
|
||||
)
|
||||
|
||||
// 路由通过 init 自注册,无需在任何中心文件登记。
|
||||
// 新建应用时用 `go run main.go app -n <名称>` 生成骨架,
|
||||
// 它会同时产出 cmd/api/<名称>.go 完成注册。
|
||||
func init() {
|
||||
routerCheckRole = append(routerCheckRole, registerDemoProductRouter)
|
||||
}
|
||||
|
||||
// registerDemoProductRouter 标准 CRUD 的推荐写法。
|
||||
//
|
||||
// 五个通用 Action 覆盖了增删改查的全部样板逻辑——参数绑定、数据权限过滤、
|
||||
// 操作人注入、分页、错误响应,因此本模块没有 apis 与 service 文件。
|
||||
//
|
||||
// 仅当业务逻辑超出单表 CRUD(如跨表事务、外部调用、复杂校验)时,才需要
|
||||
// 自行编写 Handler 与 Service,写法参照 app/admin/apis/sys_post.go。
|
||||
func registerDemoProductRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
|
||||
r := v1.Group("/demo-product").
|
||||
Use(authMiddleware.MiddlewareFunc()). // JWT 认证
|
||||
Use(middleware.AuthCheckRole()) // Casbin 鉴权
|
||||
{
|
||||
m := &models.DemoProduct{}
|
||||
|
||||
// actions.PermissionAction() 注入数据权限上下文,
|
||||
// 列表与详情缺少它会绕过 DataScope 过滤
|
||||
r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} {
|
||||
list := make([]models.DemoProduct, 0)
|
||||
return &list
|
||||
}))
|
||||
|
||||
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} {
|
||||
return &models.DemoProduct{}
|
||||
}))
|
||||
|
||||
r.POST("", actions.CreateAction(new(dto.DemoProductControl)))
|
||||
r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl)))
|
||||
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/gin-gonic/gin"
|
||||
log "github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
// "github.com/go-admin-team/go-admin-core/sdk/pkg"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
|
||||
common "go-admin/common/middleware"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
routerNoCheckRole = make([]func(*gin.RouterGroup), 0)
|
||||
routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0)
|
||||
)
|
||||
|
||||
// InitRouter 路由初始化
|
||||
func InitRouter() {
|
||||
var r *gin.Engine
|
||||
h := sdk.Runtime.GetEngine()
|
||||
if h == nil {
|
||||
h = gin.New()
|
||||
sdk.Runtime.SetEngine(h)
|
||||
}
|
||||
switch h.(type) {
|
||||
case *gin.Engine:
|
||||
r = h.(*gin.Engine)
|
||||
default:
|
||||
log.Fatal("not support other engine")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// the jwt middleware
|
||||
authMiddleware, err := common.AuthInit()
|
||||
if err != nil {
|
||||
log.Fatalf("JWT Init Error, %s", err.Error())
|
||||
}
|
||||
|
||||
// 注册业务路由
|
||||
InitBusinessRouter(r, authMiddleware)
|
||||
}
|
||||
|
||||
func InitBusinessRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine {
|
||||
|
||||
// 无需认证的路由
|
||||
noCheckRoleRouter(r)
|
||||
// 需要认证的路由
|
||||
checkRoleRouter(r, authMiddleware)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// noCheckRoleRouter 无需认证的路由
|
||||
func noCheckRoleRouter(r *gin.Engine) {
|
||||
// 可根据业务需求来设置接口版本
|
||||
v := r.Group("/api/v1")
|
||||
|
||||
for _, f := range routerNoCheckRole {
|
||||
f(v)
|
||||
}
|
||||
}
|
||||
|
||||
// checkRoleRouter 需要认证的路由
|
||||
func checkRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) {
|
||||
// 可根据业务需求来设置接口版本
|
||||
v := r.Group("/api/v1")
|
||||
|
||||
for _, f := range routerCheckRole {
|
||||
f(v, authMiddleware)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
+2
-2
@@ -101,13 +101,13 @@ 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)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package api
|
||||
|
||||
import "go-admin/app/demo/router"
|
||||
|
||||
func init() {
|
||||
//注册路由 fixme 其他应用的路由,在本目录新建文件放在init方法
|
||||
AppRouters = append(AppRouters, router.InitRouter)
|
||||
}
|
||||
+3
-3
@@ -66,7 +66,7 @@ func setup() {
|
||||
storage.Setup,
|
||||
)
|
||||
//注册监听函数
|
||||
queue := sdk.Runtime.GetMemoryQueue("")
|
||||
queue := sdk.Runtime.GetQueuePrefix("")
|
||||
queue.Register(global.LoginLog, models.SaveLoginLog)
|
||||
queue.Register(global.OperateLog, models.SaveOperaLog)
|
||||
queue.Register(global.ApiCheck, models.SaveSysApi)
|
||||
@@ -95,13 +95,13 @@ func run() error {
|
||||
|
||||
go func() {
|
||||
jobs.InitJob()
|
||||
jobs.Setup(sdk.Runtime.GetDb())
|
||||
jobs.Setup(sdk.Runtime.GetAllDb())
|
||||
|
||||
}()
|
||||
|
||||
if apiCheck {
|
||||
var routers = sdk.Runtime.GetRouter()
|
||||
q := sdk.Runtime.GetMemoryQueue("")
|
||||
q := sdk.Runtime.GetQueuePrefix("")
|
||||
mp := make(map[string]interface{})
|
||||
mp["List"] = routers
|
||||
message, err := sdk.Runtime.GetStreamMessage("", global.ApiCheck, mp)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/app/demo/models"
|
||||
"go-admin/cmd/migrate/migration"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// 示例模块的建表迁移。
|
||||
//
|
||||
// 迁移文件名前 13 位是版本号(Unix 毫秒时间戳),框架按文件名升序执行,
|
||||
// 已执行的版本记录在 sys_migration 表中,不会重复运行。
|
||||
//
|
||||
// 本文件放在 version/ 是因为它随框架一起分发;**业务项目自己的迁移应放
|
||||
// version-local/**,该目录已被 .gitignore 忽略,不会与上游冲突。
|
||||
//
|
||||
// 生成新的迁移骨架:go run main.go migrate -c config/settings.yml -g
|
||||
//
|
||||
// 注意:已执行过的迁移文件不可再修改——版本号已入表,改动不会重跑。
|
||||
// 需要调整时新建一个迁移。
|
||||
func init() {
|
||||
_, fileName, _, _ := runtime.Caller(0)
|
||||
migration.Migrate.SetVersion(migration.GetFilename(fileName), _1786700000000DemoProduct)
|
||||
}
|
||||
|
||||
func _1786700000000DemoProduct(db *gorm.DB, version string) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 建表
|
||||
if err := tx.Migrator().AutoMigrate(new(models.DemoProduct)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 初始化数据(可选)。此处留空,示例模块不写入业务数据。
|
||||
|
||||
// 3. 记录版本号——必须,否则该迁移每次启动都会重跑
|
||||
return tx.Create(&common.Migration{Version: version}).Error
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/cmd/migrate/migration"
|
||||
"go-admin/cmd/migrate/migration/models"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// 为 app/demo 模块写入菜单、接口与权限种子数据。
|
||||
//
|
||||
// 一个业务模块要在界面上可用,需要四类数据:
|
||||
//
|
||||
// 1. sys_api —— 后端路由的登记,Casbin 据此判定权限
|
||||
// 2. sys_menu —— 侧边栏菜单(目录 M / 菜单 C / 按钮 F)
|
||||
// 3. sys_menu_api_rule —— 菜单与接口的多对多关联,角色保存时据此生成策略
|
||||
// 4. casbin_rule —— 实际生效的权限策略
|
||||
//
|
||||
// 注意 casbin_rule 才是 adapter 实际使用的表;models.CasbinRule 对应的
|
||||
// sys_casbin_rule 是历史遗留,其 7 列 size:512 唯一索引在 MySQL 下会超出
|
||||
// 索引长度限制,不要将其纳入 AutoMigrate。
|
||||
func init() {
|
||||
_, fileName, _, _ := runtime.Caller(0)
|
||||
migration.Migrate.SetVersion(migration.GetFilename(fileName), _1786700001000DemoMenu)
|
||||
}
|
||||
|
||||
const (
|
||||
demoMenuId = 9000 // 目录:示例模块
|
||||
demoProductId = 9001 // 菜单:商品管理
|
||||
demoApiBaseId = 9000 // sys_api 起始 id
|
||||
adminRoleKey = "admin"
|
||||
)
|
||||
|
||||
func _1786700001000DemoMenu(db *gorm.DB, version string) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// ---- 1. 接口登记 ----
|
||||
apis := []models.SysApi{
|
||||
{Id: demoApiBaseId + 1, Handle: "go-admin/common/actions.IndexAction.func1", Title: "示例商品列表", Path: "/api/v1/demo-product", Type: "SYS", Action: "GET"},
|
||||
{Id: demoApiBaseId + 2, Handle: "go-admin/common/actions.ViewAction.func1", Title: "示例商品详情", Path: "/api/v1/demo-product/:id", Type: "SYS", Action: "GET"},
|
||||
{Id: demoApiBaseId + 3, Handle: "go-admin/common/actions.CreateAction.func1", Title: "示例商品新增", Path: "/api/v1/demo-product", Type: "SYS", Action: "POST"},
|
||||
{Id: demoApiBaseId + 4, Handle: "go-admin/common/actions.UpdateAction.func1", Title: "示例商品修改", Path: "/api/v1/demo-product/:id", Type: "SYS", Action: "PUT"},
|
||||
{Id: demoApiBaseId + 5, Handle: "go-admin/common/actions.DeleteAction.func1", Title: "示例商品删除", Path: "/api/v1/demo-product", Type: "SYS", Action: "DELETE"},
|
||||
}
|
||||
for i := range apis {
|
||||
if err := upsert(tx, &models.SysApi{}, "id = ?", apis[i].Id, &apis[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 2. 菜单 ----
|
||||
// 目录本身不对应页面,component 固定为 Layout
|
||||
dir := models.SysMenu{
|
||||
MenuId: demoMenuId, MenuName: "Demo", Title: "示例模块", Icon: "example",
|
||||
Path: "/demo", Paths: "/0/9000", MenuType: "M", ParentId: 0,
|
||||
Component: "Layout", Sort: 900, Visible: "0", IsFrame: "1",
|
||||
}
|
||||
if err := upsert(tx, &models.SysMenu{}, "menu_id = ?", dir.MenuId, &dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 菜单的 menu_name 需与前端组件 name 一致,否则 keep-alive 缓存无法命中
|
||||
page := models.SysMenu{
|
||||
MenuId: demoProductId, MenuName: "DemoProduct", Title: "商品管理", Icon: "documentation",
|
||||
Path: "/demo/product", Paths: "/0/9000/9001", MenuType: "C", ParentId: demoMenuId,
|
||||
Component: "/demo/product/index", Sort: 1, Visible: "0", IsFrame: "1",
|
||||
SysApi: apis,
|
||||
}
|
||||
if err := upsert(tx, &models.SysMenu{}, "menu_id = ?", page.MenuId, &page); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 按钮级权限,permission 需与前端 v-permisaction 中的标识一致
|
||||
buttons := []models.SysMenu{
|
||||
{MenuId: 9002, MenuName: "DemoProductAdd", Title: "新增", MenuType: "F", ParentId: demoProductId, Permission: "demo:product:add", Sort: 1, Visible: "0", IsFrame: "1"},
|
||||
{MenuId: 9003, MenuName: "DemoProductEdit", Title: "修改", MenuType: "F", ParentId: demoProductId, Permission: "demo:product:edit", Sort: 2, Visible: "0", IsFrame: "1"},
|
||||
{MenuId: 9004, MenuName: "DemoProductDel", Title: "删除", MenuType: "F", ParentId: demoProductId, Permission: "demo:product:delete", Sort: 3, Visible: "0", IsFrame: "1"},
|
||||
}
|
||||
for i := range buttons {
|
||||
buttons[i].Paths = "/0/9000/9001/" + strconv.Itoa(buttons[i].MenuId)
|
||||
if err := upsert(tx, &models.SysMenu{}, "menu_id = ?", buttons[i].MenuId, &buttons[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 3. 授权给 admin 角色 ----
|
||||
var role models.SysRole
|
||||
err := tx.Where("role_key = ?", adminRoleKey).First(&role).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 尚未初始化角色数据时跳过授权,不阻断迁移
|
||||
return tx.Create(&common.Migration{Version: version}).Error
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range []int{demoMenuId, demoProductId, 9002, 9003, 9004} {
|
||||
if err = tx.Exec(
|
||||
"INSERT INTO sys_role_menu (role_id, menu_id) SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu WHERE role_id = ? AND menu_id = ?)",
|
||||
role.RoleId, id, role.RoleId, id,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 4. Casbin 策略 ----
|
||||
// admin 角色在中间件中直接放行,此处仍写入策略以便复制该角色配置时可继承
|
||||
for _, a := range apis {
|
||||
if err = tx.Exec(
|
||||
"INSERT INTO casbin_rule (ptype, v0, v1, v2, v3, v4, v5) SELECT 'p', ?, ?, ?, '', '', '' WHERE NOT EXISTS (SELECT 1 FROM casbin_rule WHERE ptype='p' AND v0=? AND v1=? AND v2=?)",
|
||||
role.RoleKey, a.Path, a.Action, role.RoleKey, a.Path, a.Action,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Create(&common.Migration{Version: version}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// upsert 存在则更新、不存在则插入。迁移可能在已有数据的库上运行,
|
||||
// 直接 Create 会因主键冲突失败。
|
||||
func upsert(tx *gorm.DB, model interface{}, where string, id int, value interface{}) error {
|
||||
var count int64
|
||||
if err := tx.Model(model).Where(where, id).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return tx.Model(model).Where(where, id).Updates(value).Error
|
||||
}
|
||||
return tx.Create(value).Error
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go-admin/cmd/migrate/migration"
|
||||
"go-admin/cmd/migrate/migration/models"
|
||||
common "go-admin/common/models"
|
||||
)
|
||||
|
||||
// 清理 GET /api/v1/refresh_token 的残留权限数据。
|
||||
//
|
||||
// 该路由已随 issue #820 的修复移除:它允许用业务 token 换取新 token,而续期
|
||||
// 上限 MaxRefresh 依据的 orig_iat 每次续期都被重置,上限永远无法到达。
|
||||
//
|
||||
// 路由虽已删除(请求会返回 404),但已有部署的库中仍残留三类记录:接口登记、
|
||||
// 菜单与接口的绑定、Casbin 策略。留着会让「接口管理」列出一个不存在的端点,
|
||||
// 角色配置里也仍可勾选,造成误解。
|
||||
//
|
||||
// 按 path 匹配而非固定 id —— 使用者若执行过 `server -a` 重新注册接口,id 会
|
||||
// 与官方种子数据不同。
|
||||
func init() {
|
||||
_, fileName, _, _ := runtime.Caller(0)
|
||||
migration.Migrate.SetVersion(migration.GetFilename(fileName), _1786700002000RemoveRefreshTokenApi)
|
||||
}
|
||||
|
||||
const refreshTokenPath = "/api/v1/refresh_token"
|
||||
|
||||
func _1786700002000RemoveRefreshTokenApi(db *gorm.DB, version string) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
var apiIds []int
|
||||
if err := tx.Model(&models.SysApi{}).
|
||||
Where("path = ? AND action = ?", refreshTokenPath, "GET").
|
||||
Pluck("id", &apiIds).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(apiIds) > 0 {
|
||||
// 先断开绑定,再删接口本身,避免遗留悬空外键
|
||||
// 连接表由 GORM many2many 生成,列名为 sys_api_id 而非 api_id
|
||||
if err := tx.Exec("DELETE FROM sys_menu_api_rule WHERE sys_api_id IN ?", apiIds).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("id IN ?", apiIds).Delete(&models.SysApi{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// casbin_rule 才是 adapter 实际使用的表,策略按路径存储,与 sys_api 的
|
||||
// id 无关,因此即使上面没匹配到接口也要清理
|
||||
if err := tx.Exec(
|
||||
"DELETE FROM casbin_rule WHERE ptype = 'p' AND v1 = ?", refreshTokenPath,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Create(&common.Migration{Version: version}).Error
|
||||
})
|
||||
}
|
||||
@@ -62,10 +62,10 @@ func migrateModel() error {
|
||||
if host == "" {
|
||||
host = "*"
|
||||
}
|
||||
db := sdk.Runtime.GetDbByKey(host)
|
||||
db := sdk.Runtime.GetDbByTenant(host)
|
||||
if db == nil {
|
||||
if len(sdk.Runtime.GetDb()) == 1 && host == "*" {
|
||||
for k, v := range sdk.Runtime.GetDb() {
|
||||
if len(sdk.Runtime.GetAllDb()) == 1 && host == "*" {
|
||||
for k, v := range sdk.Runtime.GetAllDb() {
|
||||
db = v
|
||||
host = k
|
||||
break
|
||||
|
||||
@@ -60,6 +60,6 @@ func setupSimpleDatabase(host string, c *toolsConfig.Database) {
|
||||
|
||||
e := mycasbin.Setup(db, "")
|
||||
|
||||
sdk.Runtime.SetDb(host, db)
|
||||
sdk.Runtime.SetCasbin(host, e)
|
||||
sdk.Runtime.SetDbByTenant(host, db)
|
||||
sdk.Runtime.SetCasbinByTenant(host, e)
|
||||
}
|
||||
|
||||
+7
-11
@@ -13,21 +13,17 @@ type GeneralDelDto struct {
|
||||
|
||||
func (g GeneralDelDto) GetIds() []int {
|
||||
ids := make([]int, 0)
|
||||
if g.Id != 0 {
|
||||
// Id 此前在 else 分支里被重复追加:仅传 Id 时会得到 [5 5],
|
||||
// 同一条记录被执行两次删除
|
||||
if g.Id > 0 {
|
||||
ids = append(ids, g.Id)
|
||||
}
|
||||
if len(g.Ids) > 0 {
|
||||
for _, id := range g.Ids {
|
||||
if id > 0 {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if g.Id > 0 {
|
||||
ids = append(ids, g.Id)
|
||||
for _, id := range g.Ids {
|
||||
if id > 0 {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
if len(ids) <= 0 {
|
||||
if len(ids) == 0 {
|
||||
//方式全部删除
|
||||
ids = append(ids, 0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// GetIds 曾在 else 分支中重复追加 Id:仅传 Id 时返回 [5 5],
|
||||
// 导致删除接口对同一条记录执行两次。
|
||||
func TestGeneralDelDtoGetIds(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
dto GeneralDelDto
|
||||
want []int
|
||||
}{
|
||||
{"仅 Id", GeneralDelDto{Id: 5}, []int{5}},
|
||||
{"仅 Ids", GeneralDelDto{Ids: []int{1, 2}}, []int{1, 2}},
|
||||
{"Id 与 Ids 并存", GeneralDelDto{Id: 5, Ids: []int{1, 2}}, []int{5, 1, 2}},
|
||||
{"Ids 含非正数被过滤", GeneralDelDto{Ids: []int{0, -1, 3}}, []int{3}},
|
||||
{"Id 为 0 视为未传", GeneralDelDto{Id: 0, Ids: []int{7}}, []int{7}},
|
||||
{"全部为空时回退到 0(全量删除约定)", GeneralDelDto{}, []int{0}},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got := c.dto.GetIds()
|
||||
if !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("GetIds() = %v, want %v", got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package global
|
||||
|
||||
const (
|
||||
// Version go-admin version info
|
||||
Version = "2.3.0"
|
||||
Version = "2.4.0"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v3"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/api"
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
func LoadPolicy(c *gin.Context) (*casbin.SyncedEnforcer, error) {
|
||||
log := api.GetRequestLogger(c)
|
||||
if err := sdk.Runtime.GetCasbinKey(c.Request.Host).LoadPolicy(); err == nil {
|
||||
return sdk.Runtime.GetCasbinKey(c.Request.Host), err
|
||||
if err := sdk.Runtime.GetCasbinByTenant(c.Request.Host).LoadPolicy(); err == nil {
|
||||
return sdk.Runtime.GetCasbinByTenant(c.Request.Host), err
|
||||
} else {
|
||||
log.Errorf("casbin rbac_model or policy init error, %s ", err.Error())
|
||||
return nil, err
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
func WithContextDb(c *gin.Context) {
|
||||
c.Set("db", sdk.Runtime.GetDbByKey(c.Request.Host).WithContext(c))
|
||||
c.Set("db", sdk.Runtime.GetDbByTenant(c.Request.Host).WithContext(c))
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func LoginLogToDB(c *gin.Context, status string, msg string, username string) {
|
||||
l["username"] = username
|
||||
l["msg"] = msg
|
||||
|
||||
q := sdk.Runtime.GetMemoryQueue(c.Request.Host)
|
||||
q := sdk.Runtime.GetQueuePrefix(c.Request.Host)
|
||||
message, err := sdk.Runtime.GetStreamMessage("", global.LoginLog, l)
|
||||
if err != nil {
|
||||
log.Errorf("GetStreamMessage error, %s", err.Error())
|
||||
|
||||
@@ -124,7 +124,7 @@ func SetDBOperLog(c *gin.Context, clientIP string, statusCode int, reqUri string
|
||||
} else {
|
||||
l["status"] = dto.OperaStatusDisable
|
||||
}
|
||||
q := sdk.Runtime.GetMemoryQueue(c.Request.Host)
|
||||
q := sdk.Runtime.GetQueuePrefix(c.Request.Host)
|
||||
message, err := sdk.Runtime.GetStreamMessage("", global.OperateLog, l)
|
||||
if err != nil {
|
||||
log.Errorf("GetStreamMessage error, %s", err.Error())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/casbin/casbin/v2/util"
|
||||
"github.com/casbin/casbin/v3/util"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -17,7 +17,7 @@ func AuthCheckRole() gin.HandlerFunc {
|
||||
log := api.GetRequestLogger(c)
|
||||
data, _ := c.Get(jwtauth.JwtPayloadKey)
|
||||
v := data.(jwtauth.MapClaims)
|
||||
e := sdk.Runtime.GetCasbinKey(c.Request.Host)
|
||||
e := sdk.Runtime.GetCasbinByTenant(c.Request.Host)
|
||||
var res, casbinExclude bool
|
||||
var err error
|
||||
//检查权限
|
||||
|
||||
@@ -24,7 +24,6 @@ var CasbinExclude = []UrlInfo{
|
||||
{Url: "/api/v1/menuids", Method: "GET"},
|
||||
{Url: "/api/v1/roleMenuTreeselect/:roleId", Method: "GET"},
|
||||
{Url: "/api/v1/roleDeptTreeselect/:roleId", Method: "GET"},
|
||||
{Url: "/api/v1/refresh_token", Method: "GET"},
|
||||
{Url: "/api/v1/configKey/:configKey", Method: "GET"},
|
||||
{Url: "/api/v1/app-config", Method: "GET"},
|
||||
{Url: "/api/v1/user/profile", Method: "GET"},
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/sdk"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/config"
|
||||
"github.com/go-admin-team/go-admin-core/sdk/pkg/captcha"
|
||||
"github.com/go-admin-team/go-admin-core/captcha"
|
||||
)
|
||||
|
||||
// Setup 配置storage组件
|
||||
|
||||
@@ -37,7 +37,6 @@ INSERT INTO sys_api (id, handle, title, path, type, "action", created_at, update
|
||||
(45, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '菜单权限列表【角色配菜单使用】', '/api/v1/roleMenuTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.762', '2021-06-17 11:48:40.732', NULL, 0, 0),
|
||||
(46, 'go-admin/app/admin/apis.SysDept.GetDeptTreeRoleSelect-fm', '角色部门结构树【自定义数据权限】', '/api/v1/roleDeptTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.809', '2021-06-17 11:48:40.732', NULL, 0, 0),
|
||||
(47, 'go-admin/app/admin/apis.SysRole.Get-fm', '角色通过id获取', '/api/v1/role/:id', 'BUS', 'GET', '2021-05-13 19:59:02.850', '2021-06-17 11:48:40.732', NULL, 0, 0),
|
||||
(48, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).RefreshHandler-fm', '刷新token', '/api/v1/refresh_token', 'SYS', 'GET', '2021-05-13 19:59:02.892', '2021-06-13 20:53:49.278', NULL, 0, 0),
|
||||
(53, 'go-admin/app/admin/apis.SysConfig.GetPage-fm', '参数列表', '/api/v1/config', 'BUS', 'GET', '2021-05-13 19:59:03.116', '2021-06-17 11:48:40.732', NULL, 0, 0),
|
||||
(54, 'go-admin/app/admin/apis.SysConfig.Get-fm', '参数通过id获取', '/api/v1/config/:id', 'BUS', 'GET', '2021-05-13 19:59:03.157', '2021-06-17 11:48:40.732', NULL, 0, 0),
|
||||
(55, 'go-admin/app/admin/apis.SysConfig.GetSysConfigByKEYForService-fm', '参数通过键名搜索【基础默认配置】', '/api/v1/configKey/:configKey', 'SYS', 'GET', '2021-05-13 19:59:03.198', '2021-06-13 20:53:49.745', NULL, 0, 0),
|
||||
|
||||
@@ -35,7 +35,6 @@ INSERT INTO sys_api VALUES (44, 'go-admin/app/admin/apis.SysRole.GetPage-fm', '
|
||||
INSERT INTO sys_api VALUES (45, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '菜单权限列表【角色配菜单使用】', '/api/v1/roleMenuTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.762', '2021-06-17 11:48:40.732', NULL, 0, 0);
|
||||
INSERT INTO sys_api VALUES (46, 'go-admin/app/admin/apis.SysDept.GetDeptTreeRoleSelect-fm', '角色部门结构树【自定义数据权限】', '/api/v1/roleDeptTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.809', '2021-06-17 11:48:40.732', NULL, 0, 0);
|
||||
INSERT INTO sys_api VALUES (47, 'go-admin/app/admin/apis.SysRole.Get-fm', '角色通过id获取', '/api/v1/role/:id', 'BUS', 'GET', '2021-05-13 19:59:02.850', '2021-06-17 11:48:40.732', NULL, 0, 0);
|
||||
INSERT INTO sys_api VALUES (48, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).RefreshHandler-fm', '刷新token', '/api/v1/refresh_token', 'SYS', 'GET', '2021-05-13 19:59:02.892', '2021-06-13 20:53:49.278', NULL, 0, 0);
|
||||
INSERT INTO sys_api VALUES (53, 'go-admin/app/admin/apis.SysConfig.GetPage-fm', '参数列表', '/api/v1/config', 'BUS', 'GET', '2021-05-13 19:59:03.116', '2021-06-17 11:48:40.732', NULL, 0, 0);
|
||||
INSERT INTO sys_api VALUES (54, 'go-admin/app/admin/apis.SysConfig.Get-fm', '参数通过id获取', '/api/v1/config/:id', 'BUS', 'GET', '2021-05-13 19:59:03.157', '2021-06-17 11:48:40.732', NULL, 0, 0);
|
||||
INSERT INTO sys_api VALUES (55, 'go-admin/app/admin/apis.SysConfig.GetSysConfigByKEYForService-fm', '参数通过键名搜索【基础默认配置】', '/api/v1/configKey/:configKey', 'SYS', 'GET', '2021-05-13 19:59:03.198', '2021-06-13 20:53:49.745', NULL, 0, 0);
|
||||
|
||||
+3
-9
@@ -61,14 +61,8 @@ settings:
|
||||
# redis:
|
||||
# addr: 127.0.0.1:6379
|
||||
# password: xxxxxx
|
||||
# producer:
|
||||
# streamMaxLength: 100
|
||||
# approximateMaxLength: true
|
||||
# consumer:
|
||||
# visibilityTimeout: 60
|
||||
# bufferSize: 100
|
||||
# concurrency: 10
|
||||
# blockingTimeout: 5
|
||||
# reclaimInterval: 1
|
||||
# group: go-admin # 消费组,同一应用的多个实例必须用同一个值
|
||||
# key_prefix: go-admin # 加在 stream key 前面,多个应用共用一个 redis 时用于隔离
|
||||
# max_attempts: 5 # 消息投递失败的最大重试次数
|
||||
locker:
|
||||
redis:
|
||||
@@ -0,0 +1,85 @@
|
||||
# 架构说明
|
||||
|
||||
> 本文记录**为什么这样设计**与不易从代码直接读出的语义。
|
||||
> 编码规范见根目录 `AGENTS.md`,标准写法见 `app/demo/`。
|
||||
|
||||
## 数据权限(DataScope)
|
||||
|
||||
`PermissionAction()` 中间件从数据库取出当前用户的 DataScope 存入 `gin.Context`;
|
||||
Service 在查询时通过 `actions.Permission(tableName, p)` 这个 GORM Scope 追加 WHERE。
|
||||
|
||||
实现见 `common/actions/permission.go:62-79`,五档语义:
|
||||
|
||||
| 值 | 含义 | 过滤方式 |
|
||||
|---|---|---|
|
||||
| `1` 或其他 | 全部数据 | 不追加条件 |
|
||||
| `2` | 本角色关联部门的数据 | `create_by` 属于 `sys_role_dept` 关联部门下的用户 |
|
||||
| `3` | 本部门数据 | `create_by` 属于同部门用户 |
|
||||
| `4` | 本部门及子部门 | 按 `sys_dept.dept_path` 前缀匹配 |
|
||||
| `5` | 仅本人 | `create_by = 当前用户` |
|
||||
|
||||
**过滤依据是 `create_by` 字段**,因此参与数据权限的表必须内嵌 `models.ControlBy`。
|
||||
|
||||
总开关:`config/settings.yml` 的 `application.enabledp`。关闭时 `Permission` 直接返回原
|
||||
查询,这也意味着**关闭开关后所有数据权限配置立即失效**,排查问题时先确认此项。
|
||||
|
||||
## 定时任务
|
||||
|
||||
两类任务,配置在 `sys_job` 表:
|
||||
|
||||
- **HTTP 任务** —— 按 Cron 表达式请求指定 URL
|
||||
- **函数任务** —— 调用注册在 `app/jobs` 中的 Go 函数
|
||||
|
||||
自定义函数任务需实现 `JobExec` 接口(`app/jobs/type.go:10`):
|
||||
|
||||
```go
|
||||
type JobExec interface {
|
||||
Exec(arg interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
并注册进 `app/jobs/examples.go` 的 `jobList` 映射,键名与 `sys_job` 表中配置的调用目标
|
||||
对应。
|
||||
|
||||
任务内需要数据库连接时,通过 `sdk.Runtime.GetDbByKey("*")` 获取,不要反向 import
|
||||
`app/admin/service`。
|
||||
|
||||
## 配置扩展
|
||||
|
||||
业务自定义配置写在 `config/extend.go` 中的结构体,对应 `settings.yml` 的 `extend:`
|
||||
节点,代码中通过 `config.ExtConfig.Xxx` 访问。
|
||||
|
||||
配置值支持环境变量占位:在 yml 中写 `${ENV_NAME}`,由 `go-admin-core` 的
|
||||
`config/reader/preprocessor.go` 在加载时替换。敏感信息可借此避免写入文件。
|
||||
|
||||
## 多数据源
|
||||
|
||||
`WithContextDb` 中间件按请求解析出对应的数据库连接放入上下文,Service 通过 `e.Orm`
|
||||
取用。**不要使用全局 DB 变量** —— 那会绕过多租户隔离。
|
||||
|
||||
多库配置见 `settings.yml` 的 `databases` 与 `registers` 节点,后者用于 dbresolver
|
||||
读写分离。
|
||||
|
||||
## 数据库迁移
|
||||
|
||||
迁移文件放 `cmd/migrate/migration/version-local/`,文件名前 13 位为 Unix 毫秒时间戳,
|
||||
框架按文件名升序执行,已执行版本记录在 `sys_migration` 表。
|
||||
|
||||
```bash
|
||||
go run main.go migrate -c config/settings.yml -g # 生成骨架
|
||||
```
|
||||
|
||||
**已执行过的迁移文件不可修改** —— 版本号已入表,改动不会重跑。需要调整时新建迁移。
|
||||
|
||||
## 构建注意
|
||||
|
||||
- 默认构建禁用 CGO;使用 SQLite 需 `make build-sqlite`(带 `-tags sqlite3`)
|
||||
- `mode: prod` 时不注册 Swagger 路由
|
||||
- dev 模式下 JWT 超时被设为极大值,生产部署前务必确认 `mode` 与 `jwt.secret`
|
||||
|
||||
## 参考
|
||||
|
||||
- 编码规范:根目录 `AGENTS.md`
|
||||
- 标准 CRUD 模块:`app/demo/`
|
||||
- API 文档:`go generate` 生成到 `docs/admin/`,dev 模式下访问
|
||||
`/swagger/admin/index.html`
|
||||
@@ -8,10 +8,9 @@ require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/bytedance/go-tagexpr/v2 v2.9.11
|
||||
github.com/casbin/casbin/v2 v2.135.0
|
||||
github.com/casbin/casbin/v3 v3.8.1
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/go-admin-team/go-admin-core v1.5.3-rc.3.0.20250408121721-2763de5dcdf4
|
||||
github.com/go-admin-team/go-admin-core/sdk v1.5.3-rc.3.0.20250408121721-2763de5dcdf4
|
||||
github.com/go-admin-team/go-admin-core v1.7.1-0.20260818135152-a912b404d5c0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible
|
||||
github.com/mssola/user_agent v0.6.0
|
||||
@@ -35,7 +34,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
@@ -47,7 +46,7 @@ require (
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic v1.15.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.2 // indirect
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0 // indirect
|
||||
github.com/casbin/gorm-adapter/v3 v3.41.0 // indirect
|
||||
github.com/casbin/govaluate v1.10.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 // indirect
|
||||
@@ -60,7 +59,6 @@ require (
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||
github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.5.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
@@ -102,7 +100,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mojocn/base64Captcha v1.3.8 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/nyaruka/phonenumbers v1.2.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
@@ -111,10 +109,12 @@ require (
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.61.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.22.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/shamsher31/goimgext v1.0.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
@@ -123,11 +123,12 @@ require (
|
||||
github.com/ugorji/go/codec v1.3.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/arch v0.30.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
|
||||
golang.org/x/image v0.41.0 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
@@ -137,13 +138,14 @@ require (
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/plugin/dbresolver v1.6.2 // indirect
|
||||
modernc.org/fileutil v1.3.0 // indirect
|
||||
modernc.org/libc v1.62.1 // indirect
|
||||
modernc.org/fileutil v1.3.40 // indirect
|
||||
modernc.org/libc v1.67.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.9.1 // indirect
|
||||
modernc.org/sqlite v1.37.0 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.42.2 // indirect
|
||||
)
|
||||
|
||||
//replace (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=
|
||||
@@ -65,6 +65,10 @@ github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/go-tagexpr/v2 v2.9.11 h1:jJgmoDKPKacGl0llPYbYL/+/2N+Ng0vV0ipbnVssXHY=
|
||||
github.com/bytedance/go-tagexpr/v2 v2.9.11/go.mod h1:UAyKh4ZRLBPGsyTRFZoPqTni1TlojMdOJXQnEIPCX84=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
@@ -74,10 +78,10 @@ github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4
|
||||
github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo=
|
||||
github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=
|
||||
github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus=
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI=
|
||||
github.com/casbin/casbin/v3 v3.8.1 h1:D4dEY4knePPR4YgNP5WZtWNaOxD0UK0LpPy9+zxtBwo=
|
||||
github.com/casbin/casbin/v3 v3.8.1/go.mod h1:5rJbQr2e6AuuDDNxnPc5lQlC9nIgg6nS1zYwKXhpHC8=
|
||||
github.com/casbin/gorm-adapter/v3 v3.41.0 h1:Xhpi0tfRP9aKPDWDf6dgBxHZ9UM6IophxxPIEGWqCNM=
|
||||
github.com/casbin/gorm-adapter/v3 v3.41.0/go.mod h1:BQZRJhwUnwMpI+pT2m7/cUJwXxrHfzpBpPcNTyMGeGA=
|
||||
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
|
||||
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||
@@ -141,12 +145,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-admin-team/go-admin-core v1.5.3-rc.3.0.20250408121721-2763de5dcdf4 h1:UI9ppj+iWl/0Y6kp9w06dwGUdLsdxWRC3p0J0gLHApI=
|
||||
github.com/go-admin-team/go-admin-core v1.5.3-rc.3.0.20250408121721-2763de5dcdf4/go.mod h1:uWX7fPisJ6DluUP9vR3m3818RkDpZb/4dnwbZdmZN6Q=
|
||||
github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.5.2 h1:cPTLzpvvyh8kyB24jblB+2W0QZBugP+8VYN3R34Pb4s=
|
||||
github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.5.2/go.mod h1:ejtJ3aohd6EznZ9Q+KZVA3NwPU/2qIm0gayIGM3tIXw=
|
||||
github.com/go-admin-team/go-admin-core/sdk v1.5.3-rc.3.0.20250408121721-2763de5dcdf4 h1:gU2OBSCsfSrqWId2monoEQhPKXZqhZ8W9ol9f7A4DAE=
|
||||
github.com/go-admin-team/go-admin-core/sdk v1.5.3-rc.3.0.20250408121721-2763de5dcdf4/go.mod h1:va1lNEXHGnV161Avr0lzi5gnT8OazJ/wmN9xnsY9N/s=
|
||||
github.com/go-admin-team/go-admin-core v1.7.1-0.20260818135152-a912b404d5c0 h1:FLBk5E1kG+3WP/+tzOzTLsYmLiUIEchH6PwgiQDOowg=
|
||||
github.com/go-admin-team/go-admin-core v1.7.1-0.20260818135152-a912b404d5c0/go.mod h1:OZ1R2+zabIo2k5XrdBeaNBbwf1itxa2PO1LR8mKQWzo=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
@@ -222,8 +222,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -285,7 +283,10 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
@@ -394,8 +395,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
|
||||
github.com/nyaruka/phonenumbers v1.2.2 h1:OwVjf7Y4uHoK9VJUrA8ebR0ha2yc6sEYbfrwkq0asCY=
|
||||
github.com/nyaruka/phonenumbers v1.2.2/go.mod h1:wzk2qq7qwsaBKrfbkWKdgHYOOH+QFTesSpIq53ELw8M=
|
||||
@@ -476,14 +477,16 @@ github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9q
|
||||
github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
|
||||
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
|
||||
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
@@ -505,6 +508,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -573,6 +578,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
|
||||
@@ -583,6 +590,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
@@ -595,8 +604,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
@@ -618,8 +627,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
|
||||
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
@@ -748,7 +757,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -802,6 +810,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
@@ -833,26 +843,30 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
|
||||
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
|
||||
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
|
||||
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg=
|
||||
modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
|
||||
modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||
modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74=
|
||||
modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
Reference in New Issue
Block a user