优化结构
This commit is contained in:
parent
491ed21d48
commit
44ab14448a
|
|
@ -30,7 +30,8 @@
|
||||||
- `initialize`:首次安装、配置迁移、种子编排和运行时重载。
|
- `initialize`:首次安装、配置迁移、种子编排和运行时重载。
|
||||||
- `integration`:Redis、邮件、存储、支付、WebSocket、EMQX 和 RabbitMQ 的 provider 生命周期。
|
- `integration`:Redis、邮件、存储、支付、WebSocket、EMQX 和 RabbitMQ 的 provider 生命周期。
|
||||||
- `security`:JWT claims、签发/解析和后台安全实现。
|
- `security`:JWT claims、签发/解析和后台安全实现。
|
||||||
- `service`:HTTP DTO(`service/dto`)、DTO 与 DO 转换、应用服务和路由元数据。
|
- `routecatalog`:HTTP 公开性、操作审计、请求体策略和 API 分组/说明的统一目录。
|
||||||
|
- `service`:HTTP DTO(`service/dto`)、DTO 与 DO 转换和应用服务。
|
||||||
- `server`:Gin 生命周期;handler、middleware、router、HTTP 适配按子包维护。
|
- `server`:Gin 生命周期;handler、middleware、router、HTTP 适配按子包维护。
|
||||||
- `worker`:任务调度、执行器、SSE 订阅及其并发状态。
|
- `worker`:任务调度、执行器、SSE 订阅及其并发状态。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,11 @@
|
||||||
- `data`:数据库生命周期、系统仓储、系统表和支付持久化
|
- `data`:数据库生命周期、系统仓储、系统表和支付持久化
|
||||||
- `initialize`:数据库首次初始化和系统种子数据编排
|
- `initialize`:数据库首次初始化和系统种子数据编排
|
||||||
- `integration`:Redis、邮件、对象存储、支付、WebSocket、EMQX 和 RabbitMQ 适配器
|
- `integration`:Redis、邮件、对象存储、支付、WebSocket、EMQX 和 RabbitMQ 适配器
|
||||||
|
- `routecatalog`:统一声明 HTTP 路由的公开性、操作审计、请求体策略和 API 元数据
|
||||||
- `security`:后台 JWT 等安全实现
|
- `security`:后台 JWT 等安全实现
|
||||||
- `server`:Gin server 组合与生命周期;横切 HTTP 代码按子包维护:
|
- `server`:Gin server 组合与生命周期;横切 HTTP 代码按子包维护:
|
||||||
`server/handler`、`server/middleware`、`server/router`、`server/httpx`
|
`server/handler`、`server/middleware`、`server/router`、`server/httpx`
|
||||||
- `service`:应用服务、DTO 与领域对象转换和路由元数据;DTO 集中在
|
- `service`:应用服务、DTO 与领域对象转换;DTO 集中在
|
||||||
`service/dto`,根包中的 `dto_aliases.go` 只负责兼容旧调用方
|
`service/dto`,根包中的 `dto_aliases.go` 只负责兼容旧调用方
|
||||||
- `worker`:定时任务执行与调度
|
- `worker`:定时任务执行与调度
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
package payment
|
package payment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
datapayment "kra/internal/data/payment"
|
datapayment "kra/internal/data/payment"
|
||||||
|
"kra/internal/routecatalog"
|
||||||
"kra/pkg/module"
|
"kra/pkg/module"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -17,18 +20,18 @@ func Definition() module.Definition {
|
||||||
{Name: "paymentOrders", Path: "paymentOrders", ParentName: "extensions", Component: "view/systemTools/payment/orders.vue", Title: "支付订单", Icon: "wallet", Sort: 6},
|
{Name: "paymentOrders", Path: "paymentOrders", ParentName: "extensions", Component: "view/systemTools/payment/orders.vue", Title: "支付订单", Icon: "wallet", Sort: 6},
|
||||||
{Name: "paymentConfig", Path: "paymentConfig", ParentName: "extensions", Component: "view/systemTools/payment/config.vue", Title: "支付配置", Icon: "credit-card", Sort: 7},
|
{Name: "paymentConfig", Path: "paymentConfig", ParentName: "extensions", Component: "view/systemTools/payment/config.vue", Title: "支付配置", Icon: "credit-card", Sort: 7},
|
||||||
},
|
},
|
||||||
APIs: []module.API{
|
APIs: paymentAPIs(),
|
||||||
{Path: "/payment/orders", Method: "GET", Group: "支付", Description: "分页查询支付订单"},
|
|
||||||
{Path: "/payment/order", Method: "POST", Group: "支付", Description: "查询支付订单"},
|
|
||||||
{Path: "/payment/orders/:provider/:tradeNo", Method: "GET", Group: "支付", Description: "按路径查询支付订单"},
|
|
||||||
{Path: "/payment/create", Method: "POST", Group: "支付", Description: "创建支付订单"},
|
|
||||||
{Path: "/payment/query", Method: "POST", Group: "支付", Description: "同步支付订单状态"},
|
|
||||||
{Path: "/payment/refund", Method: "POST", Group: "支付", Description: "申请支付订单退款"},
|
|
||||||
{Path: "/payment/orders/:provider/:tradeNo/refund", Method: "POST", Group: "支付", Description: "按路径申请支付订单退款"},
|
|
||||||
{Path: "/payment/fulfill", Method: "POST", Group: "支付", Description: "重试支付订单发货"},
|
|
||||||
{Path: "/payment/orders/:provider/:tradeNo/fulfill", Method: "POST", Group: "支付", Description: "按路径重试支付订单发货"},
|
|
||||||
{Path: "/payment/providers/:provider/test", Method: "POST", Group: "支付", Description: "测试支付渠道"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func paymentAPIs() []module.API {
|
||||||
|
items := make([]module.API, 0, 10)
|
||||||
|
for _, descriptor := range routecatalog.Descriptors() {
|
||||||
|
if descriptor.Public || !strings.HasPrefix(descriptor.Path, "/payment") || descriptor.Description == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, module.API{Path: descriptor.Path, Method: descriptor.Method, Group: descriptor.Group, Description: descriptor.Description})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
datasystem "kra/internal/data/repository"
|
datasystem "kra/internal/data/repository"
|
||||||
|
"kra/internal/routecatalog"
|
||||||
"kra/pkg/module"
|
"kra/pkg/module"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,13 +15,7 @@ import (
|
||||||
func Definition() module.Definition {
|
func Definition() module.Definition {
|
||||||
communication := module.Surface{
|
communication := module.Surface{
|
||||||
Menus: []module.Menu{{Name: "integrationConfig", Path: "integrationConfig", ParentName: "extensions", Component: "view/systemTools/integration/config.vue", Title: "通信集成", Icon: "connection", Sort: 8}},
|
Menus: []module.Menu{{Name: "integrationConfig", Path: "integrationConfig", ParentName: "extensions", Component: "view/systemTools/integration/config.vue", Title: "通信集成", Icon: "connection", Sort: 8}},
|
||||||
APIs: []module.API{
|
APIs: integrationAPIs(),
|
||||||
{Path: "/integration/configs/:kind", Method: "GET", Group: "集成配置", Description: "按类型获取集成配置"},
|
|
||||||
{Path: "/integration/configs/:kind/:provider", Method: "GET", Group: "集成配置", Description: "获取指定集成配置"},
|
|
||||||
{Path: "/integration/configs/:kind/:provider", Method: "PUT", Group: "集成配置", Description: "保存集成配置"},
|
|
||||||
{Path: "/integration/configs/:kind/:provider/test", Method: "POST", Group: "集成配置", Description: "测试通信集成连接"},
|
|
||||||
{Path: "/integration/configs/:kind/:provider", Method: "DELETE", Group: "集成配置", Description: "删除集成配置"},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return module.Definition{
|
return module.Definition{
|
||||||
Name: "system",
|
Name: "system",
|
||||||
|
|
@ -30,3 +27,14 @@ func Definition() module.Definition {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func integrationAPIs() []module.API {
|
||||||
|
items := make([]module.API, 0, 5)
|
||||||
|
for _, descriptor := range routecatalog.Descriptors() {
|
||||||
|
if descriptor.Public || !strings.HasPrefix(descriptor.Path, "/integration/configs") || descriptor.Description == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, module.API{Path: descriptor.Path, Method: descriptor.Method, Group: descriptor.Group, Description: descriptor.Description})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,96 @@
|
||||||
package service
|
package routecatalog
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type apiMetadataValue struct{ group, description string }
|
// RouteBodyPolicy controls request-body handling for sensitive routes.
|
||||||
|
type RouteBodyPolicy string
|
||||||
|
|
||||||
var apiMetadata = map[string]apiMetadataValue{
|
const (
|
||||||
"DELETE /api/deleteApisByIds": {group: "api", description: "批量删除api"},
|
BodyPolicyDefault RouteBodyPolicy = "default"
|
||||||
"DELETE /dataAccessLog/deleteDataAccessLogByIds": {group: "数据权限审计", description: "批量删除数据权限审计日志"},
|
BodyPolicyIntegrationConfig RouteBodyPolicy = "integration_config"
|
||||||
"DELETE /department/deleteDepartment": {group: "部门", description: "删除部门"},
|
BodyPolicyPaymentConfig RouteBodyPolicy = "payment_config"
|
||||||
"DELETE /info/deleteInfo": {group: "公告", description: "删除公告"},
|
BodyPolicyPaymentCallback RouteBodyPolicy = "payment_callback"
|
||||||
"DELETE /info/deleteInfoByIds": {group: "公告", description: "批量删除公告"},
|
)
|
||||||
|
|
||||||
|
// Descriptor is the shared HTTP contract consumed by service bootstrap,
|
||||||
|
// Swagger generation and audit middleware.
|
||||||
|
type Descriptor struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Public bool
|
||||||
|
Audit bool
|
||||||
|
Group string
|
||||||
|
Description string
|
||||||
|
BodyPolicy RouteBodyPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
type routeValue struct {
|
||||||
|
group string
|
||||||
|
description string
|
||||||
|
public bool
|
||||||
|
audit bool
|
||||||
|
bodyPolicy RouteBodyPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
// routes is the single declaration point for cross-cutting route policy.
|
||||||
|
// Gin router files still bind handlers; tests keep those registrations aligned
|
||||||
|
// with this catalog while public, audit and API metadata live only here.
|
||||||
|
var routes = map[string]routeValue{
|
||||||
|
"DELETE /api/deleteApisByIds": {group: "api", description: "批量删除api", audit: true},
|
||||||
|
"DELETE /dataAccessLog/deleteDataAccessLogByIds": {group: "数据权限审计", description: "批量删除数据权限审计日志", audit: true},
|
||||||
|
"DELETE /department/deleteDepartment": {group: "部门", description: "删除部门", audit: true},
|
||||||
|
"DELETE /info/deleteInfo": {group: "公告", description: "删除公告", audit: true},
|
||||||
|
"DELETE /info/deleteInfoByIds": {group: "公告", description: "批量删除公告", audit: true},
|
||||||
|
"DELETE /integration/configs/:kind/:provider": {group: "集成配置", description: "删除集成配置", audit: true},
|
||||||
"DELETE /mediaUpload/:uploadId": {group: "媒体上传", description: "取消大文件上传"},
|
"DELETE /mediaUpload/:uploadId": {group: "媒体上传", description: "取消大文件上传"},
|
||||||
"DELETE /position/deletePosition": {group: "岗位", description: "删除岗位"},
|
"DELETE /position/deletePosition": {group: "岗位", description: "删除岗位", audit: true},
|
||||||
"DELETE /sysDictionary/deleteSysDictionary": {group: "系统字典", description: "删除字典"},
|
"DELETE /sysDictionary/deleteSysDictionary": {group: "系统字典", description: "删除字典", audit: true},
|
||||||
"DELETE /sysDictionaryDetail/deleteSysDictionaryDetail": {group: "系统字典详情", description: "删除字典内容"},
|
"DELETE /sysDictionaryDetail/deleteSysDictionaryDetail": {group: "系统字典详情", description: "删除字典内容", audit: true},
|
||||||
"DELETE /sysError/deleteSysError": {group: "错误日志", description: "删除错误日志"},
|
"DELETE /sysError/deleteSysError": {group: "错误日志", description: "删除错误日志", audit: true},
|
||||||
"DELETE /sysError/deleteSysErrorByIds": {group: "错误日志", description: "批量删除错误日志"},
|
"DELETE /sysError/deleteSysErrorByIds": {group: "错误日志", description: "批量删除错误日志", audit: true},
|
||||||
"DELETE /sysExportTemplate/deleteSysExportTemplate": {group: "导出模板", description: "删除导出模板"},
|
"DELETE /sysExportTemplate/deleteSysExportTemplate": {group: "导出模板", description: "删除导出模板", audit: true},
|
||||||
"DELETE /sysExportTemplate/deleteSysExportTemplateByIds": {group: "导出模板", description: "批量删除导出模板"},
|
"DELETE /sysExportTemplate/deleteSysExportTemplateByIds": {group: "导出模板", description: "批量删除导出模板", audit: true},
|
||||||
"DELETE /sysLoginLog/deleteLoginLog": {group: "登录日志", description: "删除登录日志"},
|
"DELETE /sysLoginLog/deleteLoginLog": {group: "登录日志", description: "删除登录日志", audit: true},
|
||||||
"DELETE /sysLoginLog/deleteLoginLogByIds": {group: "登录日志", description: "批量删除登录日志"},
|
"DELETE /sysLoginLog/deleteLoginLogByIds": {group: "登录日志", description: "批量删除登录日志", audit: true},
|
||||||
"DELETE /sysOperationRecord/deleteSysOperationRecord": {group: "操作记录", description: "删除操作记录"},
|
"DELETE /sysOperationRecord/deleteSysOperationRecord": {group: "操作记录", description: "删除操作记录"},
|
||||||
"DELETE /sysOperationRecord/deleteSysOperationRecordByIds": {group: "操作记录", description: "批量删除操作历史"},
|
"DELETE /sysOperationRecord/deleteSysOperationRecordByIds": {group: "操作记录", description: "批量删除操作历史"},
|
||||||
"DELETE /sysParams/deleteSysParams": {group: "参数管理", description: "删除参数"},
|
"DELETE /sysParams/deleteSysParams": {group: "参数管理", description: "删除参数", audit: true},
|
||||||
"DELETE /sysParams/deleteSysParamsByIds": {group: "参数管理", description: "批量删除参数"},
|
"DELETE /sysParams/deleteSysParamsByIds": {group: "参数管理", description: "批量删除参数", audit: true},
|
||||||
"DELETE /sysVersion/deleteSysVersion": {group: "版本控制", description: "删除版本"},
|
"DELETE /sysVersion/deleteSysVersion": {group: "版本控制", description: "删除版本", audit: true},
|
||||||
"DELETE /sysVersion/deleteSysVersionByIds": {group: "版本控制", description: "批量删除版本"},
|
"DELETE /sysVersion/deleteSysVersionByIds": {group: "版本控制", description: "批量删除版本", audit: true},
|
||||||
"DELETE /timedTask/deleteTimedTask": {group: "定时任务", description: "删除定时任务"},
|
"DELETE /timedTask/deleteTimedTask": {group: "定时任务", description: "删除定时任务", audit: true},
|
||||||
"DELETE /user/deleteUser": {group: "系统用户", description: "删除用户"},
|
"DELETE /user/deleteUser": {group: "系统用户", description: "删除用户", audit: true},
|
||||||
"GET /api/getApiGroups": {group: "api", description: "获取路由组"},
|
"GET /api/getApiGroups": {group: "api", description: "获取路由组", audit: true},
|
||||||
"GET /api/getApiRoles": {group: "api", description: "获取指定API关联角色列表"},
|
"GET /api/getApiRoles": {group: "api", description: "获取指定API关联角色列表"},
|
||||||
"GET /api/syncApi": {group: "api", description: "获取待同步API"},
|
"GET /api/syncApi": {group: "api", description: "获取待同步API", audit: true},
|
||||||
|
"GET /api/freshCasbin": {group: "api", description: "刷新 Casbin 缓存"},
|
||||||
"GET /attachmentCategory/getCategoryList": {group: "媒体库分类", description: "分类列表"},
|
"GET /attachmentCategory/getCategoryList": {group: "媒体库分类", description: "分类列表"},
|
||||||
"GET /authority/getDataScopeDepts": {group: "角色", description: "获取角色自定义部门集"},
|
"GET /authority/getDataScopeDepts": {group: "角色", description: "获取角色自定义部门集"},
|
||||||
"GET /authority/getUsersByAuthority": {group: "角色", description: "获取角色关联用户ID列表"},
|
"GET /authority/getUsersByAuthority": {group: "角色", description: "获取角色关联用户ID列表"},
|
||||||
"GET /department/findDepartment": {group: "部门", description: "根据ID获取部门"},
|
"GET /department/findDepartment": {group: "部门", description: "根据ID获取部门"},
|
||||||
"GET /department/getDepartmentUsers": {group: "部门", description: "获取部门成员ID列表"},
|
"GET /department/getDepartmentUsers": {group: "部门", description: "获取部门成员ID列表"},
|
||||||
|
"GET /fileUploadAndDownload/findFile": {group: "文件上传与下载", description: "获取文件详情"},
|
||||||
|
"GET /health": {group: "base", description: "健康检查", public: true},
|
||||||
"GET /info/findInfo": {group: "公告", description: "根据ID获取公告"},
|
"GET /info/findInfo": {group: "公告", description: "根据ID获取公告"},
|
||||||
|
"GET /info/getInfoDataSource": {group: "公告", description: "获取公告数据源"},
|
||||||
"GET /info/getInfoList": {group: "公告", description: "获取公告列表"},
|
"GET /info/getInfoList": {group: "公告", description: "获取公告列表"},
|
||||||
|
"GET /info/getInfoPublic": {group: "公告", description: "获取公开公告", public: true},
|
||||||
|
"GET /integration/configs/:kind": {group: "集成配置", description: "按类型获取集成配置"},
|
||||||
|
"GET /integration/configs/:kind/:provider": {group: "集成配置", description: "获取指定集成配置"},
|
||||||
"GET /logViewer/content": {group: "文件日志", description: "分块读取日志文件内容"},
|
"GET /logViewer/content": {group: "文件日志", description: "分块读取日志文件内容"},
|
||||||
"GET /logViewer/dates": {group: "文件日志", description: "获取存在日志的日期"},
|
"GET /logViewer/dates": {group: "文件日志", description: "获取存在日志的日期"},
|
||||||
"GET /logViewer/files": {group: "文件日志", description: "获取日期下的日志文件"},
|
"GET /logViewer/files": {group: "文件日志", description: "获取日期下的日志文件"},
|
||||||
"GET /menu/getMenuRoles": {group: "菜单", description: "获取菜单关联角色列表"},
|
"GET /menu/getMenuRoles": {group: "菜单", description: "获取菜单关联角色列表"},
|
||||||
"GET /position/findPosition": {group: "岗位", description: "根据ID获取岗位"},
|
|
||||||
"GET /position/getPositionUsers": {group: "岗位", description: "获取岗位成员ID列表"},
|
|
||||||
"GET /integration/configs/:kind": {group: "集成配置", description: "按类型获取集成配置"},
|
|
||||||
"GET /integration/configs/:kind/:provider": {group: "集成配置", description: "获取指定集成配置"},
|
|
||||||
"GET /payment/orders": {group: "支付", description: "分页查询支付订单"},
|
"GET /payment/orders": {group: "支付", description: "分页查询支付订单"},
|
||||||
"GET /payment/orders/:provider/:tradeNo": {group: "支付", description: "按路径查询支付订单"},
|
"GET /payment/orders/:provider/:tradeNo": {group: "支付", description: "按路径查询支付订单"},
|
||||||
"POST /payment/create": {group: "支付", description: "创建支付订单"},
|
"GET /position/findPosition": {group: "岗位", description: "根据ID获取岗位"},
|
||||||
"POST /payment/query": {group: "支付", description: "同步支付订单状态"},
|
"GET /position/getPositionUsers": {group: "岗位", description: "获取岗位成员ID列表"},
|
||||||
"POST /payment/refund": {group: "支付", description: "申请支付订单退款"},
|
|
||||||
"POST /payment/orders/:provider/:tradeNo/refund": {group: "支付", description: "按路径申请支付订单退款"},
|
|
||||||
"POST /payment/fulfill": {group: "支付", description: "重试支付订单发货"},
|
|
||||||
"POST /payment/orders/:provider/:tradeNo/fulfill": {group: "支付", description: "按路径重试支付订单发货"},
|
|
||||||
"POST /payment/providers/:provider/test": {group: "支付", description: "测试支付渠道配置与沙箱交易链路"},
|
|
||||||
"GET /securityConfig/getSecurityConfig": {group: "安全配置", description: "获取安全配置"},
|
"GET /securityConfig/getSecurityConfig": {group: "安全配置", description: "获取安全配置"},
|
||||||
"GET /sysDictionary/exportSysDictionary": {group: "系统字典", description: "导出字典JSON"},
|
"GET /swagger/*any": {group: "base", description: "Swagger 文档", public: true},
|
||||||
|
"GET /sysDictionary/exportSysDictionary": {group: "系统字典", description: "导出字典JSON", audit: true},
|
||||||
"GET /sysDictionary/findSysDictionary": {group: "系统字典", description: "根据ID获取字典(建议选择)"},
|
"GET /sysDictionary/findSysDictionary": {group: "系统字典", description: "根据ID获取字典(建议选择)"},
|
||||||
"GET /sysDictionary/getSysDictionaryList": {group: "系统字典", description: "获取字典列表"},
|
"GET /sysDictionary/getSysDictionaryList": {group: "系统字典", description: "获取字典列表"},
|
||||||
"GET /sysDictionary/getSysDictionaryListWithDetails": {group: "系统字典", description: "获取字典列表(含明细)"},
|
"GET /sysDictionary/getSysDictionaryListWithDetails": {group: "系统字典", description: "获取字典列表(含明细)"},
|
||||||
|
|
@ -69,7 +103,9 @@ var apiMetadata = map[string]apiMetadataValue{
|
||||||
"GET /sysError/findSysError": {group: "错误日志", description: "根据ID获取错误日志"},
|
"GET /sysError/findSysError": {group: "错误日志", description: "根据ID获取错误日志"},
|
||||||
"GET /sysError/getSysErrorList": {group: "错误日志", description: "获取错误日志列表"},
|
"GET /sysError/getSysErrorList": {group: "错误日志", description: "获取错误日志列表"},
|
||||||
"GET /sysExportTemplate/exportExcel": {group: "导出模板", description: "导出Excel"},
|
"GET /sysExportTemplate/exportExcel": {group: "导出模板", description: "导出Excel"},
|
||||||
|
"GET /sysExportTemplate/exportExcelByToken": {group: "导出模板", description: "按令牌导出Excel", public: true},
|
||||||
"GET /sysExportTemplate/exportTemplate": {group: "导出模板", description: "下载模板"},
|
"GET /sysExportTemplate/exportTemplate": {group: "导出模板", description: "下载模板"},
|
||||||
|
"GET /sysExportTemplate/exportTemplateByToken": {group: "导出模板", description: "按令牌下载模板", public: true},
|
||||||
"GET /sysExportTemplate/findSysExportTemplate": {group: "导出模板", description: "根据ID获取导出模板"},
|
"GET /sysExportTemplate/findSysExportTemplate": {group: "导出模板", description: "根据ID获取导出模板"},
|
||||||
"GET /sysExportTemplate/getSysExportTemplateList": {group: "导出模板", description: "获取导出模板列表"},
|
"GET /sysExportTemplate/getSysExportTemplateList": {group: "导出模板", description: "获取导出模板列表"},
|
||||||
"GET /sysExportTemplate/previewSQL": {group: "导出模板", description: "预览SQL"},
|
"GET /sysExportTemplate/previewSQL": {group: "导出模板", description: "预览SQL"},
|
||||||
|
|
@ -88,114 +124,251 @@ var apiMetadata = map[string]apiMetadataValue{
|
||||||
"GET /timedTask/getTimedTaskList": {group: "定时任务", description: "获取定时任务列表"},
|
"GET /timedTask/getTimedTaskList": {group: "定时任务", description: "获取定时任务列表"},
|
||||||
"GET /timedTask/getTimedTaskLogList": {group: "定时任务", description: "获取定时任务执行日志"},
|
"GET /timedTask/getTimedTaskLogList": {group: "定时任务", description: "获取定时任务执行日志"},
|
||||||
"GET /user/getUserInfo": {group: "系统用户", description: "获取自身信息(必选)"},
|
"GET /user/getUserInfo": {group: "系统用户", description: "获取自身信息(必选)"},
|
||||||
"POST /api/createApi": {group: "api", description: "创建api"},
|
"POST /api/createApi": {group: "api", description: "创建api", audit: true},
|
||||||
"POST /api/deleteApi": {group: "api", description: "删除Api"},
|
"POST /api/deleteApi": {group: "api", description: "删除Api", audit: true},
|
||||||
"POST /api/enterSyncApi": {group: "api", description: "确认同步API"},
|
"POST /api/enterSyncApi": {group: "api", description: "确认同步API", audit: true},
|
||||||
"POST /api/getAllApis": {group: "api", description: "获取所有api"},
|
"POST /api/getAllApis": {group: "api", description: "获取所有api"},
|
||||||
"POST /api/getApiById": {group: "api", description: "获取api详细信息"},
|
"POST /api/getApiById": {group: "api", description: "获取api详细信息", audit: true},
|
||||||
"POST /api/getApiList": {group: "api", description: "获取api列表"},
|
"POST /api/getApiList": {group: "api", description: "获取api列表"},
|
||||||
"POST /api/ignoreApi": {group: "api", description: "忽略API"},
|
"POST /api/ignoreApi": {group: "api", description: "忽略API", audit: true},
|
||||||
"POST /api/setApiRoles": {group: "api", description: "全量覆盖API关联角色列表"},
|
"POST /api/setApiRoles": {group: "api", description: "全量覆盖API关联角色列表", audit: true},
|
||||||
"POST /api/updateApi": {group: "api", description: "更新Api"},
|
"POST /api/updateApi": {group: "api", description: "更新Api", audit: true},
|
||||||
"POST /attachmentCategory/addCategory": {group: "媒体库分类", description: "添加/编辑分类"},
|
"POST /attachmentCategory/addCategory": {group: "媒体库分类", description: "添加/编辑分类"},
|
||||||
"POST /attachmentCategory/deleteCategory": {group: "媒体库分类", description: "删除分类"},
|
"POST /attachmentCategory/deleteCategory": {group: "媒体库分类", description: "删除分类"},
|
||||||
"POST /authority/copyAuthority": {group: "角色", description: "拷贝角色"},
|
"POST /authority/copyAuthority": {group: "角色", description: "拷贝角色", audit: true},
|
||||||
"POST /authority/createAuthority": {group: "角色", description: "创建角色"},
|
"POST /authority/createAuthority": {group: "角色", description: "创建角色", audit: true},
|
||||||
"POST /authority/deleteAuthority": {group: "角色", description: "删除角色"},
|
"POST /authority/deleteAuthority": {group: "角色", description: "删除角色", audit: true},
|
||||||
"POST /authority/getAuthorityList": {group: "角色", description: "获取角色列表"},
|
"POST /authority/getAuthorityList": {group: "角色", description: "获取角色列表"},
|
||||||
"POST /authority/setDataScope": {group: "角色", description: "设置角色数据权限"},
|
"POST /authority/setDataScope": {group: "角色", description: "设置角色数据权限", audit: true},
|
||||||
"POST /authority/setRoleUsers": {group: "角色", description: "全量覆盖角色关联用户"},
|
"POST /authority/setRoleUsers": {group: "角色", description: "全量覆盖角色关联用户", audit: true},
|
||||||
"POST /authorityBtn/canRemoveAuthorityBtn": {group: "按钮权限", description: "删除按钮"},
|
"POST /authorityBtn/canRemoveAuthorityBtn": {group: "按钮权限", description: "删除按钮"},
|
||||||
"POST /authorityBtn/getAuthorityBtn": {group: "按钮权限", description: "获取已有按钮权限"},
|
"POST /authorityBtn/getAuthorityBtn": {group: "按钮权限", description: "获取已有按钮权限"},
|
||||||
"POST /authorityBtn/setAuthorityBtn": {group: "按钮权限", description: "设置按钮权限"},
|
"POST /authorityBtn/setAuthorityBtn": {group: "按钮权限", description: "设置按钮权限"},
|
||||||
|
"POST /base/captcha": {group: "base", description: "获取验证码", public: true},
|
||||||
|
"POST /base/login": {group: "base", description: "登录", public: true},
|
||||||
"POST /casbin/getPolicyPathByAuthorityId": {group: "casbin", description: "获取权限列表"},
|
"POST /casbin/getPolicyPathByAuthorityId": {group: "casbin", description: "获取权限列表"},
|
||||||
"POST /casbin/updateCasbin": {group: "casbin", description: "更改角色api权限"},
|
"POST /casbin/updateCasbin": {group: "casbin", description: "更改角色api权限", audit: true},
|
||||||
"POST /dataAccessLog/getDataAccessLogList": {group: "数据权限审计", description: "获取数据权限审计日志"},
|
"POST /dataAccessLog/getDataAccessLogList": {group: "数据权限审计", description: "获取数据权限审计日志"},
|
||||||
"POST /department/createDepartment": {group: "部门", description: "创建部门"},
|
"POST /department/createDepartment": {group: "部门", description: "创建部门", audit: true},
|
||||||
"POST /department/getDepartmentList": {group: "部门", description: "获取部门树"},
|
"POST /department/getDepartmentList": {group: "部门", description: "获取部门树"},
|
||||||
"POST /department/setDepartmentUsers": {group: "部门", description: "设置部门成员(反向分配)"},
|
"POST /department/setDepartmentUsers": {group: "部门", description: "设置部门成员(反向分配)", audit: true},
|
||||||
"POST /email/emailTest": {group: "email", description: "发送测试邮件"},
|
"POST /email/emailTest": {group: "email", description: "发送测试邮件", audit: true},
|
||||||
"POST /email/sendEmail": {group: "email", description: "发送邮件"},
|
"POST /email/sendEmail": {group: "email", description: "发送邮件", audit: true},
|
||||||
"POST /fileUploadAndDownload/deleteFile": {group: "文件上传与下载", description: "删除文件"},
|
"POST /fileUploadAndDownload/deleteFile": {group: "文件上传与下载", description: "删除文件"},
|
||||||
|
"POST /fileUploadAndDownload/deleteFiles": {group: "文件上传与下载", description: "批量删除文件"},
|
||||||
"POST /fileUploadAndDownload/editFileName": {group: "文件上传与下载", description: "文件名或者备注编辑"},
|
"POST /fileUploadAndDownload/editFileName": {group: "文件上传与下载", description: "文件名或者备注编辑"},
|
||||||
"POST /fileUploadAndDownload/getFileList": {group: "文件上传与下载", description: "获取上传文件列表"},
|
"POST /fileUploadAndDownload/getFileList": {group: "文件上传与下载", description: "获取上传文件列表"},
|
||||||
"POST /fileUploadAndDownload/importURL": {group: "文件上传与下载", description: "导入URL"},
|
"POST /fileUploadAndDownload/importURL": {group: "文件上传与下载", description: "导入URL"},
|
||||||
|
"POST /fileUploadAndDownload/listOssFiles": {group: "文件上传与下载", description: "获取对象存储文件列表"},
|
||||||
"POST /fileUploadAndDownload/upload": {group: "文件上传与下载", description: "文件上传(建议选择)"},
|
"POST /fileUploadAndDownload/upload": {group: "文件上传与下载", description: "文件上传(建议选择)"},
|
||||||
"POST /info/createInfo": {group: "公告", description: "新建公告"},
|
"POST /info/createInfo": {group: "公告", description: "新建公告", audit: true},
|
||||||
|
"POST /init/checkdb": {group: "初始化", description: "检查数据库", public: true},
|
||||||
|
"POST /init/initdb": {group: "初始化", description: "初始化数据库", public: true},
|
||||||
|
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接", audit: true},
|
||||||
"POST /jwt/jsonInBlacklist": {group: "jwt", description: "jwt加入黑名单(退出,必选)"},
|
"POST /jwt/jsonInBlacklist": {group: "jwt", description: "jwt加入黑名单(退出,必选)"},
|
||||||
"POST /mediaUpload/chunk": {group: "媒体上传", description: "上传分片"},
|
"POST /mediaUpload/chunk": {group: "媒体上传", description: "上传分片"},
|
||||||
"POST /mediaUpload/complete": {group: "媒体上传", description: "完成大文件上传"},
|
"POST /mediaUpload/complete": {group: "媒体上传", description: "完成大文件上传"},
|
||||||
"POST /mediaUpload/init": {group: "媒体上传", description: "初始化大文件上传"},
|
"POST /mediaUpload/init": {group: "媒体上传", description: "初始化大文件上传"},
|
||||||
"POST /menu/addBaseMenu": {group: "菜单", description: "新增菜单"},
|
"POST /menu/addBaseMenu": {group: "菜单", description: "新增菜单", audit: true},
|
||||||
"POST /menu/addMenuAuthority": {group: "菜单", description: "增加menu和角色关联关系"},
|
"POST /menu/addMenuAuthority": {group: "菜单", description: "增加menu和角色关联关系", audit: true},
|
||||||
"POST /menu/deleteBaseMenu": {group: "菜单", description: "删除菜单"},
|
"POST /menu/deleteBaseMenu": {group: "菜单", description: "删除菜单", audit: true},
|
||||||
"POST /menu/getBaseMenuById": {group: "菜单", description: "根据id获取菜单"},
|
"POST /menu/getBaseMenuById": {group: "菜单", description: "根据id获取菜单"},
|
||||||
"POST /menu/getBaseMenuTree": {group: "菜单", description: "获取用户动态路由"},
|
"POST /menu/getBaseMenuTree": {group: "菜单", description: "获取用户动态路由"},
|
||||||
"POST /menu/getMenu": {group: "菜单", description: "获取菜单树(必选)"},
|
"POST /menu/getMenu": {group: "菜单", description: "获取菜单树(必选)"},
|
||||||
"POST /menu/getMenuAuthority": {group: "菜单", description: "获取指定角色menu"},
|
"POST /menu/getMenuAuthority": {group: "菜单", description: "获取指定角色menu"},
|
||||||
"POST /menu/getMenuList": {group: "菜单", description: "分页获取基础menu列表"},
|
"POST /menu/getMenuList": {group: "菜单", description: "分页获取基础menu列表"},
|
||||||
"POST /menu/setMenuRoles": {group: "菜单", description: "全量覆盖菜单关联角色列表"},
|
"POST /menu/setMenuRoles": {group: "菜单", description: "全量覆盖菜单关联角色列表", audit: true},
|
||||||
"POST /menu/updateBaseMenu": {group: "菜单", description: "更新菜单"},
|
"POST /menu/updateBaseMenu": {group: "菜单", description: "更新菜单", audit: true},
|
||||||
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接"},
|
"POST /payment/callback/:provider": {group: "支付", description: "处理支付回调", public: true, bodyPolicy: BodyPolicyPaymentCallback},
|
||||||
"PUT /integration/configs/:kind/:provider": {group: "集成配置", description: "保存集成配置"},
|
"POST /payment/create": {group: "支付", description: "创建支付订单", audit: true},
|
||||||
"DELETE /integration/configs/:kind/:provider": {group: "集成配置", description: "删除集成配置"},
|
"POST /payment/fulfill": {group: "支付", description: "重试支付订单发货", audit: true},
|
||||||
"POST /payment/order": {group: "支付", description: "查询支付订单"},
|
"POST /payment/order": {group: "支付", description: "查询支付订单"},
|
||||||
"POST /position/createPosition": {group: "岗位", description: "创建岗位"},
|
"POST /payment/orders/:provider/:tradeNo/fulfill": {group: "支付", description: "按路径重试支付订单发货", audit: true},
|
||||||
|
"POST /payment/orders/:provider/:tradeNo/refund": {group: "支付", description: "按路径申请支付订单退款", audit: true},
|
||||||
|
"POST /payment/providers/:provider/test": {group: "支付", description: "测试支付渠道配置与沙箱交易链路", audit: true},
|
||||||
|
"POST /payment/query": {group: "支付", description: "同步支付订单状态", audit: true},
|
||||||
|
"POST /payment/refund": {group: "支付", description: "申请支付订单退款", audit: true},
|
||||||
|
"POST /position/createPosition": {group: "岗位", description: "创建岗位", audit: true},
|
||||||
"POST /position/getPositionList": {group: "岗位", description: "获取岗位列表"},
|
"POST /position/getPositionList": {group: "岗位", description: "获取岗位列表"},
|
||||||
"POST /position/setPositionUsers": {group: "岗位", description: "设置岗位成员(反向分配)"},
|
"POST /position/setPositionUsers": {group: "岗位", description: "设置岗位成员(反向分配)", audit: true},
|
||||||
"POST /securityConfig/setSecurityConfig": {group: "安全配置", description: "设置安全配置"},
|
"POST /securityConfig/setSecurityConfig": {group: "安全配置", description: "设置安全配置", audit: true},
|
||||||
"POST /sysApiToken/createApiToken": {group: "API Token", description: "签发API Token"},
|
"POST /sysApiToken/createApiToken": {group: "API Token", description: "签发API Token", audit: true},
|
||||||
"POST /sysApiToken/deleteApiToken": {group: "API Token", description: "作废API Token"},
|
"POST /sysApiToken/deleteApiToken": {group: "API Token", description: "作废API Token", audit: true},
|
||||||
"POST /sysApiToken/getApiTokenList": {group: "API Token", description: "获取API Token列表"},
|
"POST /sysApiToken/getApiTokenList": {group: "API Token", description: "获取API Token列表", audit: true},
|
||||||
"POST /sysDictionary/createSysDictionary": {group: "系统字典", description: "新增字典"},
|
"POST /sysDictionary/createSysDictionary": {group: "系统字典", description: "新增字典", audit: true},
|
||||||
"POST /sysDictionary/importSysDictionary": {group: "系统字典", description: "导入字典JSON"},
|
"POST /sysDictionary/importSysDictionary": {group: "系统字典", description: "导入字典JSON", audit: true},
|
||||||
"POST /sysDictionaryDetail/createSysDictionaryDetail": {group: "系统字典详情", description: "新增字典内容"},
|
"POST /sysDictionaryDetail/createSysDictionaryDetail": {group: "系统字典详情", description: "新增字典内容", audit: true},
|
||||||
"POST /sysError/createSysError": {group: "错误日志", description: "新建错误日志"},
|
"POST /sysError/createSysError": {group: "错误日志", description: "新建错误日志", public: true},
|
||||||
"POST /sysExportTemplate/createSysExportTemplate": {group: "导出模板", description: "新增导出模板"},
|
"POST /sysExportTemplate/createSysExportTemplate": {group: "导出模板", description: "新增导出模板", audit: true},
|
||||||
"POST /sysExportTemplate/importExcel": {group: "导出模板", description: "导入Excel"},
|
"POST /sysExportTemplate/importExcel": {group: "导出模板", description: "导入Excel", audit: true},
|
||||||
"POST /sysOperationRecord/createSysOperationRecord": {group: "操作记录", description: "新增操作记录"},
|
"POST /sysParams/createSysParams": {group: "参数管理", description: "新建参数", audit: true},
|
||||||
"POST /sysParams/createSysParams": {group: "参数管理", description: "新建参数"},
|
|
||||||
"POST /system/getServerInfo": {group: "系统服务", description: "获取服务器信息"},
|
"POST /system/getServerInfo": {group: "系统服务", description: "获取服务器信息"},
|
||||||
"POST /system/getSystemConfig": {group: "系统服务", description: "获取配置文件内容"},
|
"POST /system/getSystemConfig": {group: "系统服务", description: "获取配置文件内容"},
|
||||||
"POST /system/setSystemConfig": {group: "系统服务", description: "设置配置文件内容"},
|
"POST /system/reloadSystem": {group: "系统服务", description: "重载系统配置", audit: true},
|
||||||
"POST /sysVersion/exportVersion": {group: "版本控制", description: "创建版本"},
|
"POST /system/setSystemConfig": {group: "系统服务", description: "设置配置文件内容", audit: true},
|
||||||
"POST /sysVersion/importVersion": {group: "版本控制", description: "同步版本"},
|
"POST /sysVersion/exportVersion": {group: "版本控制", description: "创建版本", audit: true},
|
||||||
"POST /timedTask/createTimedTask": {group: "定时任务", description: "创建定时任务"},
|
"POST /sysVersion/importVersion": {group: "版本控制", description: "同步版本", audit: true},
|
||||||
"POST /timedTask/toggleTimedTask": {group: "定时任务", description: "启用/停用定时任务"},
|
"POST /timedTask/createTimedTask": {group: "定时任务", description: "创建定时任务", audit: true},
|
||||||
"POST /timedTask/triggerTimedTask": {group: "定时任务", description: "手动触发定时任务"},
|
"POST /timedTask/toggleTimedTask": {group: "定时任务", description: "启用/停用定时任务", audit: true},
|
||||||
"POST /user/admin_register": {group: "系统用户", description: "用户注册"},
|
"POST /timedTask/triggerTimedTask": {group: "定时任务", description: "手动触发定时任务", audit: true},
|
||||||
"POST /user/changePassword": {group: "系统用户", description: "修改密码(建议选择)"},
|
"POST /user/admin_register": {group: "系统用户", description: "用户注册", audit: true},
|
||||||
|
"POST /user/changePassword": {group: "系统用户", description: "修改密码(建议选择)", audit: true},
|
||||||
"POST /user/getUserList": {group: "系统用户", description: "获取用户列表"},
|
"POST /user/getUserList": {group: "系统用户", description: "获取用户列表"},
|
||||||
"POST /user/resetPassword": {group: "系统用户", description: "重置用户密码"},
|
"POST /user/resetPassword": {group: "系统用户", description: "重置用户密码", audit: true},
|
||||||
"POST /user/setUserAuthorities": {group: "系统用户", description: "设置权限组"},
|
"POST /user/setUserAuthorities": {group: "系统用户", description: "设置权限组", audit: true},
|
||||||
"POST /user/setUserAuthority": {group: "系统用户", description: "修改用户角色(必选)"},
|
"POST /user/setUserAuthority": {group: "系统用户", description: "修改用户角色(必选)", audit: true},
|
||||||
"POST /user/setUserDepartments": {group: "系统用户", description: "设置用户归属部门"},
|
"POST /user/setUserDepartments": {group: "系统用户", description: "设置用户归属部门", audit: true},
|
||||||
"POST /user/setUserPositions": {group: "系统用户", description: "设置用户岗位"},
|
"POST /user/setUserPositions": {group: "系统用户", description: "设置用户岗位", audit: true},
|
||||||
"PUT /authority/updateAuthority": {group: "角色", description: "更新角色信息"},
|
"PUT /authority/updateAuthority": {group: "角色", description: "更新角色信息", audit: true},
|
||||||
"PUT /department/updateDepartment": {group: "部门", description: "更新部门"},
|
"PUT /department/updateDepartment": {group: "部门", description: "更新部门", audit: true},
|
||||||
"PUT /info/updateInfo": {group: "公告", description: "更新公告"},
|
"PUT /info/updateInfo": {group: "公告", description: "更新公告", audit: true},
|
||||||
"PUT /position/updatePosition": {group: "岗位", description: "更新岗位"},
|
"PUT /integration/configs/:kind/:provider": {group: "集成配置", description: "保存集成配置", audit: true, bodyPolicy: BodyPolicyIntegrationConfig},
|
||||||
"PUT /sysDictionary/updateSysDictionary": {group: "系统字典", description: "更新字典"},
|
"PUT /position/updatePosition": {group: "岗位", description: "更新岗位", audit: true},
|
||||||
"PUT /sysDictionaryDetail/updateSysDictionaryDetail": {group: "系统字典详情", description: "更新字典内容"},
|
"PUT /sysDictionary/updateSysDictionary": {group: "系统字典", description: "更新字典", audit: true},
|
||||||
"PUT /sysError/updateSysError": {group: "错误日志", description: "更新错误日志"},
|
"PUT /sysDictionaryDetail/updateSysDictionaryDetail": {group: "系统字典详情", description: "更新字典内容", audit: true},
|
||||||
"PUT /sysExportTemplate/updateSysExportTemplate": {group: "导出模板", description: "更新导出模板"},
|
"PUT /sysError/updateSysError": {group: "错误日志", description: "更新错误日志", audit: true},
|
||||||
"PUT /sysParams/updateSysParams": {group: "参数管理", description: "更新参数"},
|
"PUT /sysExportTemplate/updateSysExportTemplate": {group: "导出模板", description: "更新导出模板", audit: true},
|
||||||
"PUT /timedTask/updateTimedTask": {group: "定时任务", description: "更新定时任务"},
|
"PUT /sysParams/updateSysParams": {group: "参数管理", description: "更新参数", audit: true},
|
||||||
"PUT /user/setSelfInfo": {group: "系统用户", description: "设置自身信息(必选)"},
|
"PUT /timedTask/updateTimedTask": {group: "定时任务", description: "更新定时任务", audit: true},
|
||||||
"PUT /user/setSelfSetting": {group: "系统用户", description: "用户界面配置"},
|
"PUT /user/setSelfInfo": {group: "系统用户", description: "设置自身信息(必选)", audit: true},
|
||||||
"PUT /user/setUserInfo": {group: "系统用户", description: "设置用户信息"},
|
"PUT /user/setSelfSetting": {group: "系统用户", description: "用户界面配置", audit: true},
|
||||||
|
"PUT /user/setUserInfo": {group: "系统用户", description: "设置用户信息", audit: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata returns the administration group and description for a route.
|
func splitKey(key string) (string, string) {
|
||||||
func RouteMetadata(method, path string) (string, string) {
|
parts := strings.SplitN(key, " ", 2)
|
||||||
if value, ok := apiMetadata[strings.ToUpper(method)+" "+path]; ok {
|
if len(parts) != 2 {
|
||||||
return value.group, value.description
|
return "", key
|
||||||
}
|
}
|
||||||
return routeGroup(path), ""
|
return strings.ToUpper(parts[0]), normalizePath(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeGroup(path string) string {
|
func descriptorKey(method, path string) string {
|
||||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
return strings.ToUpper(strings.TrimSpace(method)) + " " + normalizePath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePath(path string) string {
|
||||||
|
path = strings.TrimSpace(path)
|
||||||
|
if path == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
return "/" + strings.Trim(path, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func descriptorFrom(key string, value routeValue) Descriptor {
|
||||||
|
method, path := splitKey(key)
|
||||||
|
group := value.group
|
||||||
|
if group == "" {
|
||||||
|
group = RouteGroup(path)
|
||||||
|
}
|
||||||
|
return Descriptor{
|
||||||
|
Method: method, Path: path, Public: value.public, Audit: value.audit,
|
||||||
|
Group: group, Description: value.description, BodyPolicy: value.bodyPolicy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathMatches(pattern, path string) bool {
|
||||||
|
patternParts := strings.Split(strings.Trim(normalizePath(pattern), "/"), "/")
|
||||||
|
pathParts := strings.Split(strings.Trim(normalizePath(path), "/"), "/")
|
||||||
|
if len(patternParts) == 1 && patternParts[0] == "" {
|
||||||
|
return len(pathParts) == 1 && pathParts[0] == ""
|
||||||
|
}
|
||||||
|
if len(pathParts) < len(patternParts) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
offset := len(pathParts) - len(patternParts)
|
||||||
|
for index, patternPart := range patternParts {
|
||||||
|
if strings.HasPrefix(patternPart, "*") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
actual := pathParts[offset+index]
|
||||||
|
if strings.HasPrefix(patternPart, ":") {
|
||||||
|
if actual == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if patternPart != actual {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup accepts either a canonical Gin route template or an actual URL. A
|
||||||
|
// configured router prefix is tolerated by matching canonical paths by suffix.
|
||||||
|
func Lookup(method, path string) (Descriptor, bool) {
|
||||||
|
key := descriptorKey(method, path)
|
||||||
|
if value, ok := routes[key]; ok {
|
||||||
|
return descriptorFrom(key, value), true
|
||||||
|
}
|
||||||
|
for key, value := range routes {
|
||||||
|
descriptor := descriptorFrom(key, value)
|
||||||
|
if strings.EqualFold(descriptor.Method, method) && pathMatches(descriptor.Path, path) {
|
||||||
|
return descriptor, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Descriptor{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Describe(method, path string) Descriptor {
|
||||||
|
if descriptor, ok := Lookup(method, path); ok {
|
||||||
|
return descriptor
|
||||||
|
}
|
||||||
|
return Descriptor{
|
||||||
|
Method: strings.ToUpper(strings.TrimSpace(method)),
|
||||||
|
Path: normalizePath(path),
|
||||||
|
Group: RouteGroup(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShouldAudit(method, path string) bool {
|
||||||
|
descriptor, ok := Lookup(method, path)
|
||||||
|
return ok && descriptor.Audit
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsPublic(method, path string) bool {
|
||||||
|
descriptor, ok := Lookup(method, path)
|
||||||
|
return ok && descriptor.Public
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptors returns an immutable, stable-order snapshot for startup and
|
||||||
|
// contract tests.
|
||||||
|
func Descriptors() []Descriptor {
|
||||||
|
values := make([]Descriptor, 0, len(routes))
|
||||||
|
for key, value := range routes {
|
||||||
|
values = append(values, descriptorFrom(key, value))
|
||||||
|
}
|
||||||
|
sort.Slice(values, func(i, j int) bool {
|
||||||
|
if values[i].Path == values[j].Path {
|
||||||
|
return values[i].Method < values[j].Method
|
||||||
|
}
|
||||||
|
return values[i].Path < values[j].Path
|
||||||
|
})
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyPolicyFor resolves generic integration configuration writes against the
|
||||||
|
// actual URL so only kind=payment receives the payment-config summary policy.
|
||||||
|
func BodyPolicyFor(method, path string) RouteBodyPolicy {
|
||||||
|
descriptor, ok := Lookup(method, path)
|
||||||
|
if !ok {
|
||||||
|
return BodyPolicyDefault
|
||||||
|
}
|
||||||
|
if descriptor.BodyPolicy != BodyPolicyIntegrationConfig {
|
||||||
|
return descriptor.BodyPolicy
|
||||||
|
}
|
||||||
|
parts := strings.Split(strings.Trim(normalizePath(path), "/"), "/")
|
||||||
|
for index := 0; index+2 < len(parts); index++ {
|
||||||
|
if parts[index] == "integration" && parts[index+1] == "configs" && parts[index+2] == "payment" {
|
||||||
|
return BodyPolicyPaymentConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BodyPolicyDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func RouteGroup(path string) string {
|
||||||
|
parts := strings.Split(strings.Trim(normalizePath(path), "/"), "/")
|
||||||
if len(parts) > 0 && parts[0] != "" {
|
if len(parts) > 0 && parts[0] != "" {
|
||||||
return parts[0]
|
return parts[0]
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package routecatalog
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLookupMatchesRouteTemplatesAndPrefixes(t *testing.T) {
|
||||||
|
descriptor, ok := Lookup("POST", "/admin/payment/orders/alipay/order-1/refund")
|
||||||
|
if !ok || !descriptor.Audit || descriptor.Path != "/payment/orders/:provider/:tradeNo/refund" {
|
||||||
|
t.Fatalf("descriptor = %#v, ok=%v", descriptor, ok)
|
||||||
|
}
|
||||||
|
if descriptor, ok = Lookup("GET", "/admin/payment/orders/alipay/order-1"); !ok || descriptor.Audit || descriptor.Public {
|
||||||
|
t.Fatalf("private query descriptor = %#v, ok=%v", descriptor, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoutePoliciesShareOneDescriptor(t *testing.T) {
|
||||||
|
callback := Describe("POST", "/payment/callback/:provider")
|
||||||
|
if !callback.Public || callback.Audit || callback.BodyPolicy != BodyPolicyPaymentCallback {
|
||||||
|
t.Fatalf("callback descriptor = %#v", callback)
|
||||||
|
}
|
||||||
|
if policy := BodyPolicyFor("PUT", "/admin/integration/configs/payment/saobei"); policy != BodyPolicyPaymentConfig {
|
||||||
|
t.Fatalf("payment policy = %q", policy)
|
||||||
|
}
|
||||||
|
if policy := BodyPolicyFor("PUT", "/admin/integration/configs/mq/emqx"); policy != BodyPolicyDefault {
|
||||||
|
t.Fatalf("mq policy = %q", policy)
|
||||||
|
}
|
||||||
|
if descriptor := Describe("POST", "/system/reloadSystem"); !descriptor.Audit || descriptor.Group != "系统服务" {
|
||||||
|
t.Fatalf("reload descriptor = %#v", descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEveryRouteHasAPIMetadata(t *testing.T) {
|
||||||
|
for _, descriptor := range Descriptors() {
|
||||||
|
if descriptor.Group == "" || descriptor.Description == "" {
|
||||||
|
t.Errorf("route metadata is incomplete: %#v", descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,5 +8,10 @@ grouped by role:
|
||||||
- `router/`: resource route registration and the system route registrar
|
- `router/`: resource route registration and the system route registrar
|
||||||
- `httpx/`: system adapter for shared response and cookie helpers
|
- `httpx/`: system adapter for shared response and cookie helpers
|
||||||
|
|
||||||
|
Cross-cutting route policy lives in `internal/routecatalog`: public/private
|
||||||
|
Swagger security, operation-audit flags, sensitive request-body handling and
|
||||||
|
API group/description metadata must be declared there. Router contract tests
|
||||||
|
keep the catalog aligned with the Gin registrations.
|
||||||
|
|
||||||
Keep new files in the matching role directory instead of adding transport files
|
Keep new files in the matching role directory instead of adding transport files
|
||||||
to the root package.
|
to the root package.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"kra/internal/conf"
|
"kra/internal/conf"
|
||||||
|
"kra/internal/routecatalog"
|
||||||
"kra/internal/server/handler"
|
"kra/internal/server/handler"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -64,6 +65,24 @@ func TestGinRouteContract(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGinRoutesMatchSharedCatalog(t *testing.T) {
|
||||||
|
engine := NewGinEngine(conf.NewRuntime(nil, &conf.AdminBackend{}), nil, emptyHandlers(), nil, nil, nil, nil, "test")
|
||||||
|
registered := make(map[string]struct{}, len(engine.Routes()))
|
||||||
|
for _, route := range engine.Routes() {
|
||||||
|
key := route.Method + " " + route.Path
|
||||||
|
registered[key] = struct{}{}
|
||||||
|
if _, ok := routecatalog.Lookup(route.Method, route.Path); !ok {
|
||||||
|
t.Errorf("registered route has no descriptor: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, descriptor := range routecatalog.Descriptors() {
|
||||||
|
key := descriptor.Method + " " + descriptor.Path
|
||||||
|
if _, ok := registered[key]; !ok {
|
||||||
|
t.Errorf("route descriptor is not registered: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGinStartupLogsEveryRegisteredRoute(t *testing.T) {
|
func TestGinStartupLogsEveryRegisteredRoute(t *testing.T) {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
logger := slog.New(slog.NewJSONHandler(&output, nil))
|
logger := slog.New(slog.NewJSONHandler(&output, nil))
|
||||||
|
|
@ -229,196 +248,3 @@ func TestLocalStorageRouteIsNotRegisteredForRemoteStorage(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedGinRouteContract = `DELETE /api/deleteApisByIds
|
|
||||||
DELETE /dataAccessLog/deleteDataAccessLogByIds
|
|
||||||
DELETE /department/deleteDepartment
|
|
||||||
DELETE /info/deleteInfo
|
|
||||||
DELETE /info/deleteInfoByIds
|
|
||||||
DELETE /mediaUpload/:uploadId
|
|
||||||
DELETE /position/deletePosition
|
|
||||||
DELETE /sysDictionary/deleteSysDictionary
|
|
||||||
DELETE /sysDictionaryDetail/deleteSysDictionaryDetail
|
|
||||||
DELETE /sysError/deleteSysError
|
|
||||||
DELETE /sysError/deleteSysErrorByIds
|
|
||||||
DELETE /sysExportTemplate/deleteSysExportTemplate
|
|
||||||
DELETE /sysExportTemplate/deleteSysExportTemplateByIds
|
|
||||||
DELETE /sysLoginLog/deleteLoginLog
|
|
||||||
DELETE /sysLoginLog/deleteLoginLogByIds
|
|
||||||
DELETE /sysOperationRecord/deleteSysOperationRecord
|
|
||||||
DELETE /sysOperationRecord/deleteSysOperationRecordByIds
|
|
||||||
DELETE /sysParams/deleteSysParams
|
|
||||||
DELETE /sysParams/deleteSysParamsByIds
|
|
||||||
DELETE /sysVersion/deleteSysVersion
|
|
||||||
DELETE /sysVersion/deleteSysVersionByIds
|
|
||||||
DELETE /timedTask/deleteTimedTask
|
|
||||||
DELETE /user/deleteUser
|
|
||||||
GET /api/freshCasbin
|
|
||||||
GET /api/getApiGroups
|
|
||||||
GET /api/getApiRoles
|
|
||||||
GET /api/syncApi
|
|
||||||
GET /attachmentCategory/getCategoryList
|
|
||||||
GET /authority/getDataScopeDepts
|
|
||||||
GET /authority/getUsersByAuthority
|
|
||||||
GET /department/findDepartment
|
|
||||||
GET /department/getDepartmentUsers
|
|
||||||
GET /fileUploadAndDownload/findFile
|
|
||||||
GET /health
|
|
||||||
GET /info/findInfo
|
|
||||||
GET /info/getInfoDataSource
|
|
||||||
GET /info/getInfoList
|
|
||||||
GET /info/getInfoPublic
|
|
||||||
GET /logViewer/content
|
|
||||||
GET /logViewer/dates
|
|
||||||
GET /logViewer/files
|
|
||||||
GET /menu/getMenuRoles
|
|
||||||
GET /integration/configs/:kind
|
|
||||||
GET /integration/configs/:kind/:provider
|
|
||||||
GET /payment/orders
|
|
||||||
GET /payment/orders/:provider/:tradeNo
|
|
||||||
GET /position/findPosition
|
|
||||||
GET /position/getPositionUsers
|
|
||||||
GET /securityConfig/getSecurityConfig
|
|
||||||
GET /swagger/*any
|
|
||||||
GET /sysDictionary/exportSysDictionary
|
|
||||||
GET /sysDictionary/findSysDictionary
|
|
||||||
GET /sysDictionary/getSysDictionaryList
|
|
||||||
GET /sysDictionary/getSysDictionaryListWithDetails
|
|
||||||
GET /sysDictionaryDetail/findSysDictionaryDetail
|
|
||||||
GET /sysDictionaryDetail/getDictionaryDetailsByParent
|
|
||||||
GET /sysDictionaryDetail/getDictionaryPath
|
|
||||||
GET /sysDictionaryDetail/getDictionaryTreeList
|
|
||||||
GET /sysDictionaryDetail/getDictionaryTreeListByType
|
|
||||||
GET /sysDictionaryDetail/getSysDictionaryDetailList
|
|
||||||
GET /sysError/findSysError
|
|
||||||
GET /sysError/getSysErrorList
|
|
||||||
GET /sysExportTemplate/exportExcel
|
|
||||||
GET /sysExportTemplate/exportExcelByToken
|
|
||||||
GET /sysExportTemplate/exportTemplate
|
|
||||||
GET /sysExportTemplate/exportTemplateByToken
|
|
||||||
GET /sysExportTemplate/findSysExportTemplate
|
|
||||||
GET /sysExportTemplate/getSysExportTemplateList
|
|
||||||
GET /sysExportTemplate/previewSQL
|
|
||||||
GET /sysLoginLog/findLoginLog
|
|
||||||
GET /sysLoginLog/getLoginLogList
|
|
||||||
GET /sysOperationRecord/findSysOperationRecord
|
|
||||||
GET /sysOperationRecord/getSysOperationRecordList
|
|
||||||
GET /sysParams/findSysParams
|
|
||||||
GET /sysParams/getSysParam
|
|
||||||
GET /sysParams/getSysParamsList
|
|
||||||
GET /sysVersion/downloadVersionJson
|
|
||||||
GET /sysVersion/findSysVersion
|
|
||||||
GET /sysVersion/getSysVersionList
|
|
||||||
GET /timedTask/alertStream
|
|
||||||
GET /timedTask/getRegisteredMethods
|
|
||||||
GET /timedTask/getTimedTaskList
|
|
||||||
GET /timedTask/getTimedTaskLogList
|
|
||||||
GET /user/getUserInfo
|
|
||||||
POST /api/createApi
|
|
||||||
POST /api/deleteApi
|
|
||||||
POST /api/enterSyncApi
|
|
||||||
POST /api/getAllApis
|
|
||||||
POST /api/getApiById
|
|
||||||
POST /api/getApiList
|
|
||||||
POST /api/ignoreApi
|
|
||||||
POST /api/setApiRoles
|
|
||||||
POST /api/updateApi
|
|
||||||
POST /attachmentCategory/addCategory
|
|
||||||
POST /attachmentCategory/deleteCategory
|
|
||||||
POST /authority/copyAuthority
|
|
||||||
POST /authority/createAuthority
|
|
||||||
POST /authority/deleteAuthority
|
|
||||||
POST /authority/getAuthorityList
|
|
||||||
POST /authority/setDataScope
|
|
||||||
POST /authority/setRoleUsers
|
|
||||||
POST /authorityBtn/canRemoveAuthorityBtn
|
|
||||||
POST /authorityBtn/getAuthorityBtn
|
|
||||||
POST /authorityBtn/setAuthorityBtn
|
|
||||||
POST /base/captcha
|
|
||||||
POST /base/login
|
|
||||||
POST /casbin/getPolicyPathByAuthorityId
|
|
||||||
POST /casbin/updateCasbin
|
|
||||||
POST /dataAccessLog/getDataAccessLogList
|
|
||||||
POST /department/createDepartment
|
|
||||||
POST /department/getDepartmentList
|
|
||||||
POST /department/setDepartmentUsers
|
|
||||||
POST /email/emailTest
|
|
||||||
POST /email/sendEmail
|
|
||||||
POST /fileUploadAndDownload/deleteFile
|
|
||||||
POST /fileUploadAndDownload/deleteFiles
|
|
||||||
POST /fileUploadAndDownload/editFileName
|
|
||||||
POST /fileUploadAndDownload/getFileList
|
|
||||||
POST /fileUploadAndDownload/importURL
|
|
||||||
POST /fileUploadAndDownload/listOssFiles
|
|
||||||
POST /fileUploadAndDownload/upload
|
|
||||||
POST /info/createInfo
|
|
||||||
POST /init/checkdb
|
|
||||||
POST /init/initdb
|
|
||||||
POST /integration/configs/:kind/:provider/test
|
|
||||||
POST /jwt/jsonInBlacklist
|
|
||||||
POST /mediaUpload/chunk
|
|
||||||
POST /mediaUpload/complete
|
|
||||||
POST /mediaUpload/init
|
|
||||||
POST /menu/addBaseMenu
|
|
||||||
POST /menu/addMenuAuthority
|
|
||||||
POST /menu/deleteBaseMenu
|
|
||||||
POST /menu/getBaseMenuById
|
|
||||||
POST /menu/getBaseMenuTree
|
|
||||||
POST /menu/getMenu
|
|
||||||
POST /menu/getMenuAuthority
|
|
||||||
POST /menu/getMenuList
|
|
||||||
POST /menu/setMenuRoles
|
|
||||||
POST /menu/updateBaseMenu
|
|
||||||
POST /payment/callback/:provider
|
|
||||||
POST /payment/create
|
|
||||||
POST /payment/fulfill
|
|
||||||
POST /payment/order
|
|
||||||
POST /payment/orders/:provider/:tradeNo/fulfill
|
|
||||||
POST /payment/orders/:provider/:tradeNo/refund
|
|
||||||
POST /payment/query
|
|
||||||
POST /payment/refund
|
|
||||||
POST /position/createPosition
|
|
||||||
POST /position/getPositionList
|
|
||||||
POST /position/setPositionUsers
|
|
||||||
POST /securityConfig/setSecurityConfig
|
|
||||||
POST /sysApiToken/createApiToken
|
|
||||||
POST /sysApiToken/deleteApiToken
|
|
||||||
POST /sysApiToken/getApiTokenList
|
|
||||||
POST /sysDictionary/createSysDictionary
|
|
||||||
POST /sysDictionary/importSysDictionary
|
|
||||||
POST /sysDictionaryDetail/createSysDictionaryDetail
|
|
||||||
POST /sysError/createSysError
|
|
||||||
POST /sysExportTemplate/createSysExportTemplate
|
|
||||||
POST /sysExportTemplate/importExcel
|
|
||||||
POST /sysParams/createSysParams
|
|
||||||
POST /sysVersion/exportVersion
|
|
||||||
POST /sysVersion/importVersion
|
|
||||||
POST /system/getServerInfo
|
|
||||||
POST /system/getSystemConfig
|
|
||||||
POST /system/reloadSystem
|
|
||||||
POST /system/setSystemConfig
|
|
||||||
POST /timedTask/createTimedTask
|
|
||||||
POST /timedTask/toggleTimedTask
|
|
||||||
POST /timedTask/triggerTimedTask
|
|
||||||
POST /user/admin_register
|
|
||||||
POST /user/changePassword
|
|
||||||
POST /user/getUserList
|
|
||||||
POST /user/resetPassword
|
|
||||||
POST /user/setUserAuthorities
|
|
||||||
POST /user/setUserAuthority
|
|
||||||
POST /user/setUserDepartments
|
|
||||||
POST /user/setUserPositions
|
|
||||||
PUT /authority/updateAuthority
|
|
||||||
PUT /department/updateDepartment
|
|
||||||
PUT /info/updateInfo
|
|
||||||
PUT /integration/configs/:kind/:provider
|
|
||||||
PUT /position/updatePosition
|
|
||||||
PUT /sysDictionary/updateSysDictionary
|
|
||||||
PUT /sysDictionaryDetail/updateSysDictionaryDetail
|
|
||||||
PUT /sysError/updateSysError
|
|
||||||
PUT /sysExportTemplate/updateSysExportTemplate
|
|
||||||
PUT /sysParams/updateSysParams
|
|
||||||
PUT /timedTask/updateTimedTask
|
|
||||||
PUT /user/setSelfInfo
|
|
||||||
PUT /user/setSelfSetting
|
|
||||||
PUT /user/setUserInfo`
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/internal/conf"
|
"kra/internal/conf"
|
||||||
|
"kra/internal/routecatalog"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
@ -75,8 +76,9 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H
|
||||||
if config != nil && config.Zap != nil && config.Zap.AccessLogMaxBytes > 0 {
|
if config != nil && config.Zap != nil && config.Zap.AccessLogMaxBytes > 0 {
|
||||||
logLimit = int(config.Zap.AccessLogMaxBytes)
|
logLimit = int(config.Zap.AccessLogMaxBytes)
|
||||||
}
|
}
|
||||||
paymentCallback := isPaymentCallbackPath(c.Request.URL.Path)
|
bodyPolicy := routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path)
|
||||||
paymentConfigWrite := isPaymentIntegrationConfigWrite(c.Request.Method, c.Request.URL.Path)
|
paymentCallback := bodyPolicy == routecatalog.BodyPolicyPaymentCallback
|
||||||
|
paymentConfigWrite := bodyPolicy == routecatalog.BodyPolicyPaymentConfig
|
||||||
requestText := ""
|
requestText := ""
|
||||||
if paymentCallback {
|
if paymentCallback {
|
||||||
requestText = paymentCallbackSummary(requestBody, c.GetHeader("Content-Type"))
|
requestText = paymentCallbackSummary(requestBody, c.GetHeader("Content-Type"))
|
||||||
|
|
@ -159,16 +161,6 @@ func isMediaUploadRoute(route string) bool {
|
||||||
strings.HasSuffix(route, "/mediaUpload/chunk")
|
strings.HasSuffix(route, "/mediaUpload/chunk")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPaymentCallbackPath(path string) bool {
|
|
||||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
for index := 0; index+1 < len(parts); index++ {
|
|
||||||
if parts[index] == "payment" && parts[index+1] == "callback" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func paymentCallbackProvider(path string) string {
|
func paymentCallbackProvider(path string) string {
|
||||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||||
for index := 0; index+2 < len(parts); index++ {
|
for index := 0; index+2 < len(parts); index++ {
|
||||||
|
|
@ -179,6 +171,12 @@ func paymentCallbackProvider(path string) string {
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isPaymentIntegrationConfigWrite is kept as a compatibility seam for
|
||||||
|
// middleware tests; policy ownership lives in routecatalog.
|
||||||
|
func isPaymentIntegrationConfigWrite(method, path string) bool {
|
||||||
|
return routecatalog.BodyPolicyFor(method, path) == routecatalog.BodyPolicyPaymentConfig
|
||||||
|
}
|
||||||
|
|
||||||
func paymentCallbackSummary(body []byte, contentType string) string {
|
func paymentCallbackSummary(body []byte, contentType string) string {
|
||||||
mediaType, _, err := mime.ParseMediaType(contentType)
|
mediaType, _, err := mime.ParseMediaType(contentType)
|
||||||
if err != nil || mediaType == "" {
|
if err != nil || mediaType == "" {
|
||||||
|
|
@ -191,19 +189,6 @@ func paymentCallbackSummary(body []byte, contentType string) string {
|
||||||
return "[支付回调正文已省略 body_bytes=" + strconv.Itoa(len(body)) + " body_sha256=" + hex.EncodeToString(digest[:]) + " content_type=" + mediaType + "]"
|
return "[支付回调正文已省略 body_bytes=" + strconv.Itoa(len(body)) + " body_sha256=" + hex.EncodeToString(digest[:]) + " content_type=" + mediaType + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPaymentIntegrationConfigWrite(method, path string) bool {
|
|
||||||
if method != http.MethodPut {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
for index := 0; index+3 < len(parts); index++ {
|
|
||||||
if parts[index] == "integration" && parts[index+1] == "configs" && parts[index+2] == "payment" && parts[index+3] != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func paymentConfigSummary(body []byte) string {
|
func paymentConfigSummary(body []byte) string {
|
||||||
digest := sha256.Sum256(body)
|
digest := sha256.Sum256(body)
|
||||||
return "[支付配置正文已省略 body_bytes=" + strconv.Itoa(len(body)) + " body_sha256=" + hex.EncodeToString(digest[:]) + "]"
|
return "[支付配置正文已省略 body_bytes=" + strconv.Itoa(len(body)) + " body_sha256=" + hex.EncodeToString(digest[:]) + "]"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/internal/conf"
|
"kra/internal/conf"
|
||||||
|
"kra/internal/routecatalog"
|
||||||
"kra/internal/service"
|
"kra/internal/service"
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
|
||||||
|
|
@ -27,8 +28,12 @@ func OperationAudit(runtime *conf.Runtime, recorder *service.AuditRecorder) gin.
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
routePath := c.FullPath()
|
||||||
|
if routePath == "" {
|
||||||
|
routePath = c.Request.URL.Path
|
||||||
|
}
|
||||||
path := c.Request.URL.Path
|
path := c.Request.URL.Path
|
||||||
if !recordsOperation(c.Request.Method, path) {
|
if !routecatalog.ShouldAudit(c.Request.Method, routePath) {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +91,7 @@ func OperationAudit(runtime *conf.Runtime, recorder *service.AuditRecorder) gin.
|
||||||
}
|
}
|
||||||
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||||
operationBody := operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes)
|
operationBody := operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes)
|
||||||
if isPaymentIntegrationConfigWrite(c.Request.Method, path) {
|
if routecatalog.BodyPolicyFor(c.Request.Method, path) == routecatalog.BodyPolicyPaymentConfig {
|
||||||
operationBody = paymentConfigSummary(requestBody)
|
operationBody = paymentConfigSummary(requestBody)
|
||||||
}
|
}
|
||||||
if err := recorder.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationBody, Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil {
|
if err := recorder.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationBody, Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil {
|
||||||
|
|
@ -161,62 +166,8 @@ func isDownloadResponse(c *gin.Context) bool {
|
||||||
strings.Contains(header.Get("Content-Transfer-Encoding"), "binary")
|
strings.Contains(header.Get("Content-Transfer-Encoding"), "binary")
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordsOperation mirrors the routes on which operation records are enabled.
|
// recordsOperation remains as a small compatibility helper for tests and
|
||||||
// Matching by suffix keeps the behavior stable when router-prefix is configured.
|
// custom middleware chains that do not have a Gin context.
|
||||||
func recordsOperation(method, path string) bool {
|
func recordsOperation(method, path string) bool {
|
||||||
for route := range operationRoutes {
|
return routecatalog.ShouldAudit(method, path)
|
||||||
parts := strings.SplitN(route, " ", 2)
|
|
||||||
if len(parts) != 2 || parts[0] != method || !operationPathMatches(parts[1], path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func operationPathMatches(pattern, path string) bool {
|
|
||||||
patternParts := strings.Split(strings.Trim(pattern, "/"), "/")
|
|
||||||
pathParts := strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
if len(pathParts) < len(patternParts) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
pathParts = pathParts[len(pathParts)-len(patternParts):]
|
|
||||||
for index, patternPart := range patternParts {
|
|
||||||
if strings.HasPrefix(patternPart, "*") {
|
|
||||||
return index <= len(pathParts)
|
|
||||||
}
|
|
||||||
if index >= len(pathParts) || (strings.HasPrefix(patternPart, ":") == false && patternPart != pathParts[index]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(patternParts) == len(pathParts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var operationRoutes = func() map[string]struct{} {
|
|
||||||
values := []string{
|
|
||||||
"POST /user/admin_register", "POST /user/changePassword", "POST /user/setUserAuthority", "DELETE /user/deleteUser", "PUT /user/setUserInfo", "PUT /user/setSelfInfo", "POST /user/setUserAuthorities", "POST /user/setUserDepartments", "POST /user/setUserPositions", "POST /user/resetPassword", "PUT /user/setSelfSetting",
|
|
||||||
"GET /api/getApiGroups", "GET /api/syncApi", "POST /api/ignoreApi", "POST /api/enterSyncApi", "POST /api/createApi", "POST /api/deleteApi", "POST /api/getApiById", "POST /api/updateApi", "DELETE /api/deleteApisByIds", "POST /api/setApiRoles", "POST /casbin/updateCasbin",
|
|
||||||
"POST /authority/createAuthority", "POST /authority/deleteAuthority", "PUT /authority/updateAuthority", "POST /authority/copyAuthority", "POST /authority/setDataScope", "POST /authority/setRoleUsers",
|
|
||||||
"POST /menu/addBaseMenu", "POST /menu/addMenuAuthority", "POST /menu/deleteBaseMenu", "POST /menu/updateBaseMenu", "POST /menu/setMenuRoles",
|
|
||||||
"POST /department/createDepartment", "PUT /department/updateDepartment", "DELETE /department/deleteDepartment", "POST /department/setDepartmentUsers",
|
|
||||||
"POST /position/createPosition", "PUT /position/updatePosition", "DELETE /position/deletePosition", "POST /position/setPositionUsers",
|
|
||||||
"POST /sysDictionary/createSysDictionary", "DELETE /sysDictionary/deleteSysDictionary", "PUT /sysDictionary/updateSysDictionary", "POST /sysDictionary/importSysDictionary", "GET /sysDictionary/exportSysDictionary",
|
|
||||||
"POST /sysDictionaryDetail/createSysDictionaryDetail", "DELETE /sysDictionaryDetail/deleteSysDictionaryDetail", "PUT /sysDictionaryDetail/updateSysDictionaryDetail",
|
|
||||||
"POST /sysParams/createSysParams", "DELETE /sysParams/deleteSysParams", "DELETE /sysParams/deleteSysParamsByIds", "PUT /sysParams/updateSysParams",
|
|
||||||
"POST /securityConfig/setSecurityConfig", "POST /system/setSystemConfig", "POST /system/reloadSystem",
|
|
||||||
"POST /sysApiToken/createApiToken", "POST /sysApiToken/getApiTokenList", "POST /sysApiToken/deleteApiToken",
|
|
||||||
"DELETE /sysVersion/deleteSysVersion", "DELETE /sysVersion/deleteSysVersionByIds", "POST /sysVersion/exportVersion", "POST /sysVersion/importVersion",
|
|
||||||
"POST /sysExportTemplate/createSysExportTemplate", "DELETE /sysExportTemplate/deleteSysExportTemplate", "DELETE /sysExportTemplate/deleteSysExportTemplateByIds", "PUT /sysExportTemplate/updateSysExportTemplate", "POST /sysExportTemplate/importExcel",
|
|
||||||
"DELETE /sysError/deleteSysError", "DELETE /sysError/deleteSysErrorByIds", "PUT /sysError/updateSysError",
|
|
||||||
"DELETE /sysLoginLog/deleteLoginLog", "DELETE /sysLoginLog/deleteLoginLogByIds", "DELETE /dataAccessLog/deleteDataAccessLogByIds",
|
|
||||||
"POST /timedTask/createTimedTask", "PUT /timedTask/updateTimedTask", "DELETE /timedTask/deleteTimedTask", "POST /timedTask/toggleTimedTask", "POST /timedTask/triggerTimedTask",
|
|
||||||
"POST /info/createInfo", "DELETE /info/deleteInfo", "DELETE /info/deleteInfoByIds", "PUT /info/updateInfo", "POST /email/emailTest", "POST /email/sendEmail",
|
|
||||||
"PUT /integration/configs/:kind/:provider", "POST /integration/configs/:kind/:provider/test", "DELETE /integration/configs/:kind/:provider",
|
|
||||||
"POST /payment/create", "POST /payment/query", "POST /payment/refund", "POST /payment/orders/:provider/:tradeNo/refund", "POST /payment/fulfill", "POST /payment/orders/:provider/:tradeNo/fulfill", "POST /payment/providers/:provider/test",
|
|
||||||
}
|
|
||||||
out := make(map[string]struct{}, len(values))
|
|
||||||
for _, value := range values {
|
|
||||||
out[value] = struct{}{}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}()
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"kra/internal/service"
|
"kra/internal/routecatalog"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
swaggerFiles "github.com/swaggo/files"
|
swaggerFiles "github.com/swaggo/files"
|
||||||
|
|
@ -87,7 +87,8 @@ func buildSwaggerDocument(routes []gin.RouteInfo, prefix, version string) string
|
||||||
apiPath = "/"
|
apiPath = "/"
|
||||||
}
|
}
|
||||||
documentPath := swaggerPathParameter.ReplaceAllString(apiPath, `{$1}`)
|
documentPath := swaggerPathParameter.ReplaceAllString(apiPath, `{$1}`)
|
||||||
group, description := service.RouteMetadata(route.Method, apiPath)
|
descriptor := routecatalog.Describe(route.Method, apiPath)
|
||||||
|
group, description := descriptor.Group, descriptor.Description
|
||||||
if description == "" {
|
if description == "" {
|
||||||
description = route.Method + " " + apiPath
|
description = route.Method + " " + apiPath
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +104,7 @@ func buildSwaggerDocument(routes []gin.RouteInfo, prefix, version string) string
|
||||||
if parameters := swaggerPathParameters(apiPath); len(parameters) > 0 {
|
if parameters := swaggerPathParameters(apiPath); len(parameters) > 0 {
|
||||||
operation["parameters"] = parameters
|
operation["parameters"] = parameters
|
||||||
}
|
}
|
||||||
if !swaggerPublicPath(apiPath) {
|
if !descriptor.Public {
|
||||||
operation["security"] = []map[string][]string{{"ApiKeyAuth": {}}}
|
operation["security"] = []map[string][]string{{"ApiKeyAuth": {}}}
|
||||||
}
|
}
|
||||||
if paths[documentPath] == nil {
|
if paths[documentPath] == nil {
|
||||||
|
|
@ -156,16 +157,3 @@ func swaggerPathParameters(path string) []map[string]any {
|
||||||
}
|
}
|
||||||
return parameters
|
return parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
func swaggerPublicPath(path string) bool {
|
|
||||||
for _, marker := range []string{
|
|
||||||
"/health", "/base/login", "/base/captcha", "/init/checkdb", "/init/initdb",
|
|
||||||
"/sysExportTemplate/exportExcelByToken", "/sysExportTemplate/exportTemplateByToken",
|
|
||||||
"/sysError/createSysError", "/info/getInfoPublic",
|
|
||||||
} {
|
|
||||||
if path == marker {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ func TestSwaggerMarksOnlyPublicRoutesWithoutAuth(t *testing.T) {
|
||||||
{Method: "GET", Path: "/api/freshCasbin"},
|
{Method: "GET", Path: "/api/freshCasbin"},
|
||||||
{Method: "GET", Path: "/info/getInfoDataSource"},
|
{Method: "GET", Path: "/info/getInfoDataSource"},
|
||||||
{Method: "GET", Path: "/info/getInfoPublic"},
|
{Method: "GET", Path: "/info/getInfoPublic"},
|
||||||
|
{Method: "POST", Path: "/payment/callback/:provider"},
|
||||||
}, "", "test")
|
}, "", "test")
|
||||||
var payload struct {
|
var payload struct {
|
||||||
Paths map[string]map[string]map[string]any `json:"paths"`
|
Paths map[string]map[string]map[string]any `json:"paths"`
|
||||||
|
|
@ -24,7 +25,9 @@ func TestSwaggerMarksOnlyPublicRoutesWithoutAuth(t *testing.T) {
|
||||||
t.Fatalf("private route %s was marked public", path)
|
t.Fatalf("private route %s was marked public", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := payload.Paths["/info/getInfoPublic"]["get"]["security"]; ok {
|
for path, method := range map[string]string{"/info/getInfoPublic": "get", "/payment/callback/{provider}": "post"} {
|
||||||
t.Fatal("public route /info/getInfoPublic requires authentication")
|
if _, ok := payload.Paths[path][method]["security"]; ok {
|
||||||
|
t.Fatalf("public route %s requires authentication", path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
`service` adapts HTTP-facing DTOs to business usecases and owns application
|
`service` adapts HTTP-facing DTOs to business usecases and owns application
|
||||||
orchestration. Request/response/filter contracts live in `dto/`; the root
|
orchestration. Request/response/filter contracts live in `dto/`; the root
|
||||||
package contains resource services, conversions, route metadata, and security
|
package contains resource services, conversions, and security
|
||||||
coordination.
|
coordination.
|
||||||
|
|
||||||
`dto_alias.go` is a compatibility bridge for existing service callers. New
|
`dto_alias.go` is a compatibility bridge for existing service callers. New
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"kra/internal/biz/system"
|
|
||||||
|
|
||||||
|
"kra/internal/biz/system"
|
||||||
|
"kra/internal/routecatalog"
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,7 +29,8 @@ func (s *SystemConfigService) InitializeRoutes(ctx context.Context, input *dto.D
|
||||||
apis := make([]*system.API, 0, len(routes))
|
apis := make([]*system.API, 0, len(routes))
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
path := NormalizeRoutePath(route.Path, s.settings.RouterPrefix())
|
path := NormalizeRoutePath(route.Path, s.settings.RouterPrefix())
|
||||||
group, description := RouteMetadata(route.Method, path)
|
descriptor := routecatalog.Describe(route.Method, path)
|
||||||
|
group, description := descriptor.Group, descriptor.Description
|
||||||
apis = append(apis, &system.API{Path: path, Method: route.Method, APIGroup: group, Description: description})
|
apis = append(apis, &system.API{Path: path, Method: route.Method, APIGroup: group, Description: description})
|
||||||
}
|
}
|
||||||
return s.Initialize(ctx, input, apis)
|
return s.Initialize(ctx, input, apis)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue