diff --git a/cmd/gendocs/main.go b/cmd/gendocs/main.go new file mode 100644 index 0000000..105c564 --- /dev/null +++ b/cmd/gendocs/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "log" + "path/filepath" + + "github.com/gofiber/fiber/v2" + + "github.com/break/junhong_cmp_fiber/internal/bootstrap" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" + "github.com/break/junhong_cmp_fiber/internal/routes" + "github.com/break/junhong_cmp_fiber/pkg/openapi" +) + +func main() { + // 1. 创建生成器 + adminDoc := openapi.NewGenerator("Admin API", "1.0") + + // 2. 模拟 Fiber App + app := fiber.New() + + // 3. 模拟 Handler + // 我们创建一个伪造的 handler。因为我们不执行请求,nil 依赖是可以的。 + accHandler := admin.NewAccountHandler(nil) + roleHandler := admin.NewRoleHandler(nil) + permHandler := admin.NewPermissionHandler(nil) + + handlers := &bootstrap.Handlers{ + Account: accHandler, + Role: roleHandler, + Permission: permHandler, + } + + // 4. 注册路由 + adminGroup := app.Group("/api/admin") + routes.RegisterAdminRoutes(adminGroup, handlers, adminDoc, "/api/admin") + + // 5. 保存规范 + outputFile := "./docs/admin-openapi.yaml" + if err := adminDoc.Save(outputFile); err != nil { + log.Fatalf("保存规范失败: %v", err) + } + + absPath, _ := filepath.Abs(outputFile) + log.Printf("成功在以下位置生成 OpenAPI 规范: %s", absPath) +} \ No newline at end of file diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml new file mode 100644 index 0000000..f5f3eab --- /dev/null +++ b/docs/admin-openapi.yaml @@ -0,0 +1,1019 @@ +components: + schemas: + ModelAccountPageResult: + properties: + items: + description: 账号列表 + items: + $ref: '#/components/schemas/ModelAccountResponse' + nullable: true + type: array + page: + description: 当前页码 + type: integer + size: + description: 每页数量 + type: integer + total: + description: 总记录数 + type: integer + type: object + ModelAccountResponse: + properties: + created_at: + description: 创建时间 + type: string + creator: + description: 创建人ID + minimum: 0 + type: integer + id: + description: 账号ID + minimum: 0 + type: integer + parent_id: + description: 父账号ID + minimum: 0 + nullable: true + type: integer + phone: + description: 手机号 + type: string + shop_id: + description: 关联店铺ID + minimum: 0 + nullable: true + type: integer + status: + description: 状态 + type: integer + updated_at: + description: 更新时间 + type: string + updater: + description: 更新人ID + minimum: 0 + type: integer + user_type: + description: 用户类型 + type: integer + username: + description: 用户名 + type: string + type: object + ModelAssignPermissionsParams: + properties: + perm_ids: + description: 权限ID列表 + items: + minimum: 0 + type: integer + minItems: 1 + nullable: true + type: array + required: + - perm_ids + type: object + ModelAssignRolesParams: + properties: + role_ids: + description: 角色ID列表 + items: + minimum: 0 + type: integer + minItems: 1 + nullable: true + type: array + required: + - role_ids + type: object + ModelCreateAccountRequest: + properties: + parent_id: + description: 父账号ID + minimum: 0 + nullable: true + type: integer + password: + description: 密码 + maxLength: 32 + minLength: 8 + type: string + phone: + description: 手机号 + maxLength: 11 + minLength: 11 + type: string + shop_id: + description: 关联店铺ID + minimum: 0 + nullable: true + type: integer + user_type: + description: 用户类型 (1:Root, 2:Admin, 3:Agent, 4:Merchant) + maximum: 4 + minimum: 1 + type: integer + username: + description: 用户名 + maxLength: 50 + minLength: 3 + type: string + required: + - username + - phone + - password + - user_type + type: object + ModelCreatePermissionRequest: + properties: + parent_id: + description: 父权限ID + minimum: 0 + nullable: true + type: integer + perm_code: + description: 权限编码 + maxLength: 100 + minLength: 1 + type: string + perm_name: + description: 权限名称 + maxLength: 50 + minLength: 1 + type: string + perm_type: + description: 权限类型 (1:菜单, 2:按钮) + maximum: 2 + minimum: 1 + type: integer + sort: + description: 排序值 + minimum: 0 + type: integer + url: + description: 请求路径 + maxLength: 255 + type: string + required: + - perm_name + - perm_code + - perm_type + type: object + ModelCreateRoleRequest: + properties: + role_desc: + description: 角色描述 + maxLength: 255 + type: string + role_name: + description: 角色名称 + maxLength: 50 + minLength: 1 + type: string + role_type: + description: 角色类型 (1:超级管理员, 2:普通管理员, 3:操作员) + maximum: 3 + minimum: 1 + type: integer + required: + - role_name + - role_type + type: object + ModelPermission: + properties: + creator: + minimum: 0 + type: integer + parent_id: + minimum: 0 + nullable: true + type: integer + perm_code: + type: string + perm_name: + type: string + perm_type: + type: integer + sort: + type: integer + status: + type: integer + updater: + minimum: 0 + type: integer + url: + type: string + type: object + ModelPermissionPageResult: + properties: + items: + description: 权限列表 + items: + $ref: '#/components/schemas/ModelPermissionResponse' + nullable: true + type: array + page: + description: 当前页码 + type: integer + size: + description: 每页数量 + type: integer + total: + description: 总记录数 + type: integer + type: object + ModelPermissionResponse: + properties: + created_at: + description: 创建时间 + type: string + creator: + description: 创建人ID + minimum: 0 + type: integer + id: + description: 权限ID + minimum: 0 + type: integer + parent_id: + description: 父权限ID + minimum: 0 + nullable: true + type: integer + perm_code: + description: 权限编码 + type: string + perm_name: + description: 权限名称 + type: string + perm_type: + description: 权限类型 + type: integer + sort: + description: 排序值 + type: integer + status: + description: 状态 + type: integer + updated_at: + description: 更新时间 + type: string + updater: + description: 更新人ID + minimum: 0 + type: integer + url: + description: 请求路径 + type: string + type: object + ModelPermissionTreeNode: + properties: + children: + description: 子权限列表 + items: + $ref: '#/components/schemas/ModelPermissionTreeNode' + type: array + id: + description: 权限ID + minimum: 0 + type: integer + perm_code: + description: 权限编码 + type: string + perm_name: + description: 权限名称 + type: string + perm_type: + description: 权限类型 + type: integer + sort: + description: 排序值 + type: integer + url: + description: 请求路径 + type: string + type: object + ModelRole: + properties: + creator: + minimum: 0 + type: integer + role_desc: + type: string + role_name: + type: string + role_type: + type: integer + status: + type: integer + updater: + minimum: 0 + type: integer + type: object + ModelRolePageResult: + properties: + items: + description: 角色列表 + items: + $ref: '#/components/schemas/ModelRoleResponse' + nullable: true + type: array + page: + description: 当前页码 + type: integer + size: + description: 每页数量 + type: integer + total: + description: 总记录数 + type: integer + type: object + ModelRoleResponse: + properties: + created_at: + description: 创建时间 + type: string + creator: + description: 创建人ID + minimum: 0 + type: integer + id: + description: 角色ID + minimum: 0 + type: integer + role_desc: + description: 角色描述 + type: string + role_name: + description: 角色名称 + type: string + role_type: + description: 角色类型 + type: integer + status: + description: 状态 + type: integer + updated_at: + description: 更新时间 + type: string + updater: + description: 更新人ID + minimum: 0 + type: integer + type: object + ModelUpdateAccountParams: + properties: + password: + description: 密码 + maxLength: 32 + minLength: 8 + nullable: true + type: string + phone: + description: 手机号 + maxLength: 11 + minLength: 11 + nullable: true + type: string + status: + description: 状态 (0:禁用, 1:启用) + maximum: 1 + minimum: 0 + nullable: true + type: integer + username: + description: 用户名 + maxLength: 50 + minLength: 3 + nullable: true + type: string + type: object + ModelUpdatePermissionParams: + properties: + parent_id: + description: 父权限ID + minimum: 0 + nullable: true + type: integer + perm_code: + description: 权限编码 + maxLength: 100 + minLength: 1 + nullable: true + type: string + perm_name: + description: 权限名称 + maxLength: 50 + minLength: 1 + nullable: true + type: string + sort: + description: 排序值 + minimum: 0 + nullable: true + type: integer + status: + description: 状态 (0:禁用, 1:启用) + maximum: 1 + minimum: 0 + nullable: true + type: integer + url: + description: 请求路径 + maxLength: 255 + nullable: true + type: string + type: object + ModelUpdateRoleParams: + properties: + role_desc: + description: 角色描述 + maxLength: 255 + nullable: true + type: string + role_name: + description: 角色名称 + maxLength: 50 + minLength: 1 + nullable: true + type: string + status: + description: 状态 (0:禁用, 1:启用) + maximum: 1 + minimum: 0 + nullable: true + type: integer + type: object +info: + title: Admin API + version: "1.0" +openapi: 3.0.3 +paths: + /api/admin/accounts: + get: + parameters: + - description: 页码 + in: query + name: page + schema: + description: 页码 + minimum: 1 + type: integer + - description: 每页数量 + in: query + name: page_size + schema: + description: 每页数量 + maximum: 100 + minimum: 1 + type: integer + - description: 用户名模糊查询 + in: query + name: username + schema: + description: 用户名模糊查询 + maxLength: 50 + type: string + - description: 手机号模糊查询 + in: query + name: phone + schema: + description: 手机号模糊查询 + maxLength: 20 + type: string + - description: 用户类型 + in: query + name: user_type + schema: + description: 用户类型 + maximum: 4 + minimum: 1 + nullable: true + type: integer + - description: 状态 + in: query + name: status + schema: + description: 状态 + maximum: 1 + minimum: 0 + nullable: true + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelAccountPageResult' + description: OK + summary: 账号列表 + tags: + - Account + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelCreateAccountRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelAccountResponse' + description: OK + summary: 创建账号 + tags: + - Account + /api/admin/accounts/{account_id}/roles/{role_id}: + delete: + parameters: + - description: 账号ID + in: path + name: account_id + required: true + schema: + description: 账号ID + minimum: 0 + type: integer + - description: 角色ID + in: path + name: role_id + required: true + schema: + description: 角色ID + minimum: 0 + type: integer + responses: + "204": + description: No Content + summary: 移除角色 + tags: + - Account + /api/admin/accounts/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "204": + description: No Content + summary: 删除账号 + tags: + - Account + get: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelAccountResponse' + description: OK + summary: 获取账号详情 + tags: + - Account + put: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelUpdateAccountParams' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelAccountResponse' + description: OK + summary: 更新账号 + tags: + - Account + /api/admin/accounts/{id}/roles: + get: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ModelRole' + type: array + description: OK + summary: 获取账号角色 + tags: + - Account + post: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelAssignRolesParams' + responses: + "204": + description: No Content + summary: 分配角色 + tags: + - Account + /api/admin/permissions: + get: + parameters: + - description: 页码 + in: query + name: page + schema: + description: 页码 + minimum: 1 + type: integer + - description: 每页数量 + in: query + name: page_size + schema: + description: 每页数量 + maximum: 100 + minimum: 1 + type: integer + - description: 权限名称模糊查询 + in: query + name: perm_name + schema: + description: 权限名称模糊查询 + maxLength: 50 + type: string + - description: 权限编码模糊查询 + in: query + name: perm_code + schema: + description: 权限编码模糊查询 + maxLength: 100 + type: string + - description: 权限类型 + in: query + name: perm_type + schema: + description: 权限类型 + maximum: 2 + minimum: 1 + nullable: true + type: integer + - description: 父权限ID + in: query + name: parent_id + schema: + description: 父权限ID + minimum: 0 + nullable: true + type: integer + - description: 状态 + in: query + name: status + schema: + description: 状态 + maximum: 1 + minimum: 0 + nullable: true + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelPermissionPageResult' + description: OK + summary: 权限列表 + tags: + - Permission + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelCreatePermissionRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelPermissionResponse' + description: OK + summary: 创建权限 + tags: + - Permission + /api/admin/permissions/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "204": + description: No Content + summary: 删除权限 + tags: + - Permission + get: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelPermissionResponse' + description: OK + summary: 获取权限详情 + tags: + - Permission + put: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelUpdatePermissionParams' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelPermissionResponse' + description: OK + summary: 更新权限 + tags: + - Permission + /api/admin/permissions/tree: + get: + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ModelPermissionTreeNode' + type: array + description: OK + summary: 获取权限树 + tags: + - Permission + /api/admin/roles: + get: + parameters: + - description: 页码 + in: query + name: page + schema: + description: 页码 + minimum: 1 + type: integer + - description: 每页数量 + in: query + name: page_size + schema: + description: 每页数量 + maximum: 100 + minimum: 1 + type: integer + - description: 角色名称模糊查询 + in: query + name: role_name + schema: + description: 角色名称模糊查询 + maxLength: 50 + type: string + - description: 角色类型 + in: query + name: role_type + schema: + description: 角色类型 + maximum: 3 + minimum: 1 + nullable: true + type: integer + - description: 状态 + in: query + name: status + schema: + description: 状态 + maximum: 1 + minimum: 0 + nullable: true + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelRolePageResult' + description: OK + summary: 角色列表 + tags: + - Role + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelCreateRoleRequest' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelRoleResponse' + description: OK + summary: 创建角色 + tags: + - Role + /api/admin/roles/{id}: + delete: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "204": + description: No Content + summary: 删除角色 + tags: + - Role + get: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelRoleResponse' + description: OK + summary: 获取角色详情 + tags: + - Role + put: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelUpdateRoleParams' + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ModelRoleResponse' + description: OK + summary: 更新角色 + tags: + - Role + /api/admin/roles/{id}/permissions: + get: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + responses: + "200": + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ModelPermission' + type: array + description: OK + summary: 获取角色权限 + tags: + - Role + post: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModelAssignPermissionsParams' + responses: + "204": + description: No Content + summary: 分配权限 + tags: + - Role + /api/admin/roles/{role_id}/permissions/{perm_id}: + delete: + parameters: + - description: 角色ID + in: path + name: role_id + required: true + schema: + description: 角色ID + minimum: 0 + type: integer + - description: 权限ID + in: path + name: perm_id + required: true + schema: + description: 权限ID + minimum: 0 + type: integer + responses: + "204": + description: No Content + summary: 移除权限 + tags: + - Role diff --git a/docs/待沟通/2025-12-23T17:39:57沟通.md b/docs/待沟通/2025-12-23T17:39:57沟通.md new file mode 100644 index 0000000..897d446 --- /dev/null +++ b/docs/待沟通/2025-12-23T17:39:57沟通.md @@ -0,0 +1,11 @@ +## 2025-12-23T17:39:31 沟通 + +1. 一次性佣金(组合佣金)冲突问题 + +2. 阻断不合理的问题(以下都是以同为一个套餐系列下的大前提) +- 一次性佣金套餐与长期佣金不能共存(分配时阻断) +- 组合佣金与另外两种佣金不能共存(分配时应当阻断) +- 次月生效的其他套餐系列怎么处理(疑问?) + + +3. 聚水潭,可能存在入库是20位/19位 出库时变成19位/20位 diff --git a/docs/待沟通/一次性佣金出现两个套餐的情况.excalidraw b/docs/待沟通/一次性佣金出现两个套餐的情况.excalidraw new file mode 100644 index 0000000..b9868a7 --- /dev/null +++ b/docs/待沟通/一次性佣金出现两个套餐的情况.excalidraw @@ -0,0 +1,845 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://mindmap.so", + "elements": [ + { + "id": "7R70pfEhLX0PX16Qkn1Pf", + "type": "rectangle", + "x": 331.5, + "y": 158, + "width": 450, + "height": 503, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 661345629, + "version": 58, + "versionNonce": 1402242749, + "isDeleted": false, + "boundElements": null, + "updated": 1766478975006, + "link": null, + "locked": false + }, + { + "id": "xMvve7y3VXeV0E3Xho43W", + "type": "rectangle", + "x": 373, + "y": 231, + "width": 358, + "height": 91, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RStTR2i2emmHx8XucgL0k" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 676835485, + "version": 51, + "versionNonce": 740750611, + "isDeleted": false, + "boundElements": [ + { + "id": "nUMun3kvf0N6NybORV9Kt", + "type": "arrow" + } + ], + "updated": 1766479178090, + "link": null, + "locked": false + }, + { + "id": "XUgJvaNQeNV_XQIP0xk4f", + "type": "text", + "x": 412, + "y": 251, + "width": 229.10000610351562, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "RStTR2i2emmHx8XucgL0k" + ], + "frameId": null, + "roundness": null, + "seed": 935124733, + "version": 55, + "versionNonce": 858142429, + "isDeleted": false, + "boundElements": null, + "updated": 1766479038207, + "link": null, + "locked": false, + "text": "套餐A\n预存 100 返代理佣金 50", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "套餐A\n预存 100 返代理佣金 50", + "lineHeight": 1.25 + }, + { + "id": "bUH4Ir1WYfhzpfRHnPBKI", + "type": "rectangle", + "x": 382.54442367331444, + "y": 446, + "width": 346, + "height": 100.5, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "jIcVk7sEqssxoWdPXbFaF" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 258241363, + "version": 63, + "versionNonce": 351461469, + "isDeleted": false, + "boundElements": [ + { + "id": "n1FvokZgL_6Z0aYcbltDk", + "type": "arrow" + } + ], + "updated": 1766479197157, + "link": null, + "locked": false + }, + { + "id": "wSwWH9NlU8QysDyvFKvgu", + "type": "text", + "x": 420.04442367331444, + "y": 473, + "width": 229.53334045410156, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [ + "jIcVk7sEqssxoWdPXbFaF" + ], + "frameId": null, + "roundness": null, + "seed": 177000307, + "version": 43, + "versionNonce": 734560637, + "isDeleted": false, + "boundElements": null, + "updated": 1766479197157, + "link": null, + "locked": false, + "text": "套餐B\n预存 100 返代理佣金 60", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "套餐B\n预存 100 返代理佣金 60", + "lineHeight": 1.25 + }, + { + "id": "-glD1Y1WuHzxh3lyXYsW_", + "type": "rectangle", + "x": 1003.071680819518, + "y": 119.33225782014824, + "width": 586.5467583232241, + "height": 486.72780698207305, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1780605853, + "version": 151, + "versionNonce": 232493277, + "isDeleted": false, + "boundElements": [ + { + "id": "qSXz0n40ttVNf8QFsDzza", + "type": "arrow" + } + ], + "updated": 1766479193903, + "link": null, + "locked": false + }, + { + "id": "l0EWorlcCk5lYVs8G09pL", + "type": "text", + "x": 1243.3438468796337, + "y": 219.96233993799694, + "width": 103.76667022705078, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2005419091, + "version": 20, + "versionNonce": 1626040947, + "isDeleted": false, + "boundElements": null, + "updated": 1766479056726, + "link": null, + "locked": false, + "text": "余额 0 元", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "余额 0 元", + "lineHeight": 1.25 + }, + { + "id": "5L_NILlIufWZdwpkYJLtR", + "type": "rectangle", + "x": 1199.1761692950536, + "y": 338.33171586467176, + "width": 217.30497371613433, + "height": 110.41919396145033, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 936455635, + "version": 48, + "versionNonce": 471636563, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "k2gj7fxntIiWGBdYZLRDK" + } + ], + "updated": 1766479065620, + "link": null, + "locked": false + }, + { + "id": "k2gj7fxntIiWGBdYZLRDK", + "type": "text", + "x": 1287.8286561531208, + "y": 381.04131284539693, + "width": 40, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1694248957, + "version": 6, + "versionNonce": 915898387, + "isDeleted": false, + "boundElements": null, + "updated": 1766479067105, + "link": null, + "locked": false, + "text": "充值", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "5L_NILlIufWZdwpkYJLtR", + "originalText": "充值", + "lineHeight": 1.25 + }, + { + "id": "qSXz0n40ttVNf8QFsDzza", + "type": "arrow", + "x": 1293.634684275165, + "y": 614.0102467674457, + "width": 28.18478618454128, + "height": 225.18293290636473, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 772212819, + "version": 101, + "versionNonce": 1187560861, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "KFI__zjuOAnhhEL14zurl" + } + ], + "updated": 1766479193904, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -28.18478618454128, + 225.18293290636473 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "-glD1Y1WuHzxh3lyXYsW_", + "focus": -0.0888623571550402, + "gap": 7.950181965224374 + }, + "endBinding": { + "elementId": "r85LyxyV3LekZp3zyMcmX", + "focus": -0.13056110404939236, + "gap": 5.300121310149507 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "KFI__zjuOAnhhEL14zurl", + "type": "text", + "x": 1238.0780089113427, + "y": 713.6239250572853, + "width": 82.96666717529297, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2065498877, + "version": 12, + "versionNonce": 607232115, + "isDeleted": false, + "boundElements": null, + "updated": 1766479094170, + "link": null, + "locked": false, + "text": "充值 100", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "qSXz0n40ttVNf8QFsDzza", + "originalText": "充值 100", + "lineHeight": 1.25 + }, + { + "id": "r85LyxyV3LekZp3zyMcmX", + "type": "rectangle", + "x": 1000.4216201644431, + "y": 844.4933009839599, + "width": 570.6463943927752, + "height": 299.4568540234534, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 2113391891, + "version": 114, + "versionNonce": 570390515, + "isDeleted": false, + "boundElements": [ + { + "id": "qSXz0n40ttVNf8QFsDzza", + "type": "arrow" + } + ], + "updated": 1766479085841, + "link": null, + "locked": false + }, + { + "id": "GK3W5X5r5RJIASgt0of4s", + "type": "text", + "x": 1109.0741070225101, + "y": 985.0187384779189, + "width": 367.6666564941406, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1629507315, + "version": 91, + "versionNonce": 134830867, + "isDeleted": false, + "boundElements": null, + "updated": 1766479150857, + "link": null, + "locked": false, + "text": "这时候应该是套餐A 还是 套餐 B 的返佣", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "这时候应该是套餐A 还是 套餐 B 的返佣", + "lineHeight": 1.25 + }, + { + "id": "eOglLhGtYymUk0TgFMeRF", + "type": "text", + "x": 1057.0410279697514, + "y": 812.0799974504081, + "width": 140, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1869180381, + "version": 19, + "versionNonce": 786890365, + "isDeleted": false, + "boundElements": null, + "updated": 1766479150301, + "link": null, + "locked": false, + "text": "代理到账的佣金", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "代理到账的佣金", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 194, + "versionNonce": 1120688563, + "isDeleted": false, + "id": "khEXHiUFbo8xYH20Q9Pz7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1773.811826114777, + "y": 105.2615755168747, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 586.5467583232241, + "height": 486.72780698207305, + "seed": 958733939, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "nUMun3kvf0N6NybORV9Kt", + "type": "arrow" + }, + { + "id": "n1FvokZgL_6Z0aYcbltDk", + "type": "arrow" + } + ], + "updated": 1766479182525, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 70, + "versionNonce": 1851133235, + "isDeleted": false, + "id": "f4Jm-0ni3hXHS_wC8CuF5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2014.0839921748925, + "y": 206.84723396140896, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 122.96666717529297, + "height": 25, + "seed": 914282515, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1766479171523, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "余额 100 元", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "余额 100 元", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 90, + "versionNonce": 2000876371, + "isDeleted": false, + "id": "txF94hHhoDTDYdnK2Rpud", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1969.9163145903126, + "y": 325.2166098880838, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 217.30497371613433, + "height": 110.41919396145033, + "seed": 2142264755, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "4XMPCNO1ejl96mLC1C01G" + } + ], + "updated": 1766479168359, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 48, + "versionNonce": 533663987, + "isDeleted": false, + "id": "4XMPCNO1ejl96mLC1C01G", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2058.56880144838, + "y": 367.92620686880895, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 40, + "height": 25, + "seed": 1608527699, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1766479168359, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "充值", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "txF94hHhoDTDYdnK2Rpud", + "originalText": "充值", + "lineHeight": 1.25 + }, + { + "id": "nUMun3kvf0N6NybORV9Kt", + "type": "arrow", + "x": 1762.2563570636948, + "y": 86.79756549606759, + "width": 1029.155703840349, + "height": 268.51694779864243, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 779606077, + "version": 171, + "versionNonce": 1315938387, + "isDeleted": false, + "boundElements": null, + "updated": 1766479343039, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -609.6576964253877, + -97.4687853219271 + ], + [ + -1029.155703840349, + 171.04816247671533 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "khEXHiUFbo8xYH20Q9Pz7", + "focus": 0.7341701363472869, + "gap": 18.46401002080711 + }, + "endBinding": { + "elementId": "xMvve7y3VXeV0E3Xho43W", + "focus": 0.6076267460111122, + "gap": 2.100653223345944 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "n1FvokZgL_6Z0aYcbltDk", + "type": "arrow", + "x": 1793.994491291626, + "y": 611.5600639208494, + "width": 1059.7378903038618, + "height": 248.44984493824586, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1878971997, + "version": 409, + "versionNonce": 298474365, + "isDeleted": false, + "boundElements": null, + "updated": 1766479361268, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -585.9724037055564, + 179.49725434247648 + ], + [ + -1059.7378903038618, + -68.95259059576938 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "khEXHiUFbo8xYH20Q9Pz7", + "focus": -0.5380556689521092, + "gap": 19.570681421901668 + }, + "endBinding": { + "elementId": "bUH4Ir1WYfhzpfRHnPBKI", + "focus": -0.33596246021680487, + "gap": 5.712177314449718 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "Fnsk0wbTUHGz9Oq7sUe0o", + "type": "text", + "x": 1190.8217137057295, + "y": 267.4014912396386, + "width": 99.06666564941406, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1386888435, + "version": 18, + "versionNonce": 2062378163, + "isDeleted": false, + "boundElements": null, + "updated": 1766479223488, + "link": null, + "locked": false, + "text": "套餐A 90\n", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "套餐A 90\n", + "lineHeight": 1.25 + }, + { + "id": "K8sqFrWDj3dAJJ0w8zIJU", + "type": "text", + "x": 369.0260727561474, + "y": 99.22005774297998, + "width": 205.06666564941406, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1868189053, + "version": 26, + "versionNonce": 14547869, + "isDeleted": false, + "boundElements": null, + "updated": 1766479255684, + "link": null, + "locked": false, + "text": "套餐系列 (一次性佣金)\n", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "套餐系列 (一次性佣金)\n", + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/待沟通/关于分佣.md b/docs/待沟通/关于分佣.md index 6b10a05..1765df0 100644 --- a/docs/待沟通/关于分佣.md +++ b/docs/待沟通/关于分佣.md @@ -265,3 +265,34 @@ flowchart TD 1. 确认上面的东西是不是对的 2. 确认一下是否还有别的业务 3. 跟我讲一下我们的实际业务 + + + + + +1. 代理的销售价格不能大于平台给他的成本价两倍(奇成是写死的) +2. 物联网卡行业中的佣金=实际售价-平台给的成本价(这个其实是长期分佣)(阴阳菜单) +3. 一次性佣金关于客户的逻辑 客户充值100 只能≤一百(流量卡) +(号卡 可能≥100) + + + +A 用户买了一个A产品,那么现在给代理的成本价60 售价 90 (一次性佣金)(首次购买时预存100 佣金10块) + +当一个套餐被设置一次性佣金后他第一次去购买只能通过钱包付款 + +组合佣金(一次性佣金+长期分佣)(1.某个时间点后,2. 使用套餐个数(只作用于一个物联网卡 例如 某个套餐的使用套餐个数是10,那么这个张卡需要达到10个套餐周期后才能开始分佣,只有这一张卡才会分佣)) + + +阶梯分佣基于一次性佣金以及长期佣金之上,当到达某个条件后,变更分佣值 +阶梯分佣(提货量/激活量(实名+历史存在过套餐)/保证金(未来做的)) + + +1. 激活量根据当前时间态统计 +2. 如果是进行时统计,例如年底汇报 1-12月 每月的激活量时 应该是固定的,例如 1月的激活是10 2月的激活是20,3月的激活是30,4月的激活是40,5月的激活是50,6月的激活是60,7月的激活是70,8月的激活是80,9月的激活是90,10月的激活是100,11月的激活是110,12月的激活是120 + +可能会存在1月的激活中因为是历史数据,会存在同一个iccid在2月的激活量中存在,这是可以接受的,需要业务方知道 + +1. 一次性佣金满足 激活(实名) + 达到累计/首次充值金额 = 产生佣金(冻结) (可能是[7]天后 状态变成解冻中 同步产生一条佣金解冻审批等待审批) +2. 长期佣金满足 激活(实名) + 达到累计/首次充值金额 + 在网状态(必须是正常的)(能不能拿到在网状态 存疑) + 三无(能不能拿到 存疑) = 产生佣金(冻结 必须通过excel导入, 状态 变成 解冻中 同步产生对应的佣金解冻审批 等待审批) +3. 阶梯分佣满足 激活(实名 + 达到累计/首次充值金额 + 在网状态(必须是正常的)(能不能拿到在网状态 存疑) ) = 激活 diff --git a/go.mod b/go.mod index f266478..3d7ad67 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/redis/go-redis/v9 v9.16.0 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 + github.com/swaggest/openapi-go v0.2.60 github.com/testcontainers/testcontainers-go v0.40.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 github.com/testcontainers/testcontainers-go/modules/redis v0.38.0 @@ -21,6 +22,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/crypto v0.44.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 @@ -102,6 +104,8 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggest/jsonschema-go v0.3.74 // indirect + github.com/swaggest/refl v1.3.1 // indirect github.com/tinylib/msgp v1.2.5 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect @@ -123,5 +127,5 @@ require ( golang.org/x/time v0.14.0 // indirect google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 0cd5a77..f9135db 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,10 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= +github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 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= @@ -100,6 +104,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw= github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -181,6 +187,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -210,6 +218,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.74 h1:hkAZBK3RxNWU013kPqj0Q/GHGzYCCm9WcUTnfg2yPp0= +github.com/swaggest/jsonschema-go v0.3.74/go.mod h1:qp+Ym2DIXHlHzch3HKz50gPf2wJhKOrAB/VYqLS2oJU= +github.com/swaggest/openapi-go v0.2.60 h1:kglHH/WIfqAglfuWL4tu0LPakqNYySzklUWx06SjSKo= +github.com/swaggest/openapi-go v0.2.60/go.mod h1:jmFOuYdsWGtHU0BOuILlHZQJxLqHiAE6en+baE+QQUk= +github.com/swaggest/refl v1.3.1 h1:XGplEkYftR7p9cz1lsiwXMM2yzmOymTE9vneVVpaOh4= +github.com/swaggest/refl v1.3.1/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk= @@ -230,6 +246,10 @@ github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNu github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -297,6 +317,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/bootstrap/handlers.go b/internal/bootstrap/handlers.go index 294f088..ebfa2f3 100644 --- a/internal/bootstrap/handlers.go +++ b/internal/bootstrap/handlers.go @@ -1,15 +1,15 @@ package bootstrap import ( - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" ) // initHandlers 初始化所有 Handler 实例 func initHandlers(svc *services) *Handlers { return &Handlers{ - Account: handler.NewAccountHandler(svc.Account), - Role: handler.NewRoleHandler(svc.Role), - Permission: handler.NewPermissionHandler(svc.Permission), + Account: admin.NewAccountHandler(svc.Account), + Role: admin.NewRoleHandler(svc.Role), + Permission: admin.NewPermissionHandler(svc.Permission), // TODO: 新增 Handler 在此初始化 } } diff --git a/internal/bootstrap/types.go b/internal/bootstrap/types.go index c051c77..a196bed 100644 --- a/internal/bootstrap/types.go +++ b/internal/bootstrap/types.go @@ -1,14 +1,14 @@ package bootstrap import ( - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" ) // Handlers 封装所有 HTTP 处理器 // 用于路由注册 type Handlers struct { - Account *handler.AccountHandler - Role *handler.RoleHandler - Permission *handler.PermissionHandler + Account *admin.AccountHandler + Role *admin.RoleHandler + Permission *admin.PermissionHandler // TODO: 新增 Handler 在此添加字段 } diff --git a/internal/handler/account.go b/internal/handler/admin/account.go similarity index 99% rename from internal/handler/account.go rename to internal/handler/admin/account.go index 00fca67..b936caf 100644 --- a/internal/handler/account.go +++ b/internal/handler/admin/account.go @@ -1,4 +1,4 @@ -package handler +package admin import ( "strconv" diff --git a/internal/handler/permission.go b/internal/handler/admin/permission.go similarity index 99% rename from internal/handler/permission.go rename to internal/handler/admin/permission.go index ef8e109..d1311f7 100644 --- a/internal/handler/permission.go +++ b/internal/handler/admin/permission.go @@ -1,4 +1,4 @@ -package handler +package admin import ( "strconv" diff --git a/internal/handler/role.go b/internal/handler/admin/role.go similarity index 99% rename from internal/handler/role.go rename to internal/handler/admin/role.go index 9220543..46269a1 100644 --- a/internal/handler/role.go +++ b/internal/handler/admin/role.go @@ -1,4 +1,4 @@ -package handler +package admin import ( "strconv" diff --git a/internal/handler/task.go b/internal/handler/admin/task.go similarity index 99% rename from internal/handler/task.go rename to internal/handler/admin/task.go index 6451353..dd5d023 100644 --- a/internal/handler/task.go +++ b/internal/handler/admin/task.go @@ -1,4 +1,4 @@ -package handler +package admin import ( "fmt" diff --git a/internal/model/account_dto.go b/internal/model/account_dto.go index e8665ce..930795b 100644 --- a/internal/model/account_dto.go +++ b/internal/model/account_dto.go @@ -2,48 +2,56 @@ package model // CreateAccountRequest 创建账号请求 type CreateAccountRequest struct { - Username string `json:"username" validate:"required,min=3,max=50"` - Phone string `json:"phone" validate:"required,len=11"` - Password string `json:"password" validate:"required,min=8,max=32"` - UserType int `json:"user_type" validate:"required,min=1,max=4"` - ShopID *uint `json:"shop_id"` - ParentID *uint `json:"parent_id"` + Username string `json:"username" validate:"required,min=3,max=50" required:"true" minLength:"3" maxLength:"50" description:"用户名"` + Phone string `json:"phone" validate:"required,len=11" required:"true" minLength:"11" maxLength:"11" description:"手机号"` + Password string `json:"password" validate:"required,min=8,max=32" required:"true" minLength:"8" maxLength:"32" description:"密码"` + UserType int `json:"user_type" validate:"required,min=1,max=4" required:"true" minimum:"1" maximum:"4" description:"用户类型 (1:Root, 2:Admin, 3:Agent, 4:Merchant)"` + ShopID *uint `json:"shop_id" description:"关联店铺ID"` + ParentID *uint `json:"parent_id" description:"父账号ID"` } // UpdateAccountRequest 更新账号请求 type UpdateAccountRequest struct { - Username *string `json:"username" validate:"omitempty,min=3,max=50"` - Phone *string `json:"phone" validate:"omitempty,len=11"` - Password *string `json:"password" validate:"omitempty,min=8,max=32"` - Status *int `json:"status" validate:"omitempty,min=0,max=1"` + Username *string `json:"username" validate:"omitempty,min=3,max=50" minLength:"3" maxLength:"50" description:"用户名"` + Phone *string `json:"phone" validate:"omitempty,len=11" minLength:"11" maxLength:"11" description:"手机号"` + Password *string `json:"password" validate:"omitempty,min=8,max=32" minLength:"8" maxLength:"32" description:"密码"` + Status *int `json:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态 (0:禁用, 1:启用)"` } // AccountListRequest 账号列表查询请求 type AccountListRequest struct { - Page int `json:"page" query:"page" validate:"omitempty,min=1"` - PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100"` - Username string `json:"username" query:"username" validate:"omitempty,max=50"` - Phone string `json:"phone" query:"phone" validate:"omitempty,max=20"` - UserType *int `json:"user_type" query:"user_type" validate:"omitempty,min=1,max=4"` - Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"` + Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"` + PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"` + Username string `json:"username" query:"username" validate:"omitempty,max=50" maxLength:"50" description:"用户名模糊查询"` + Phone string `json:"phone" query:"phone" validate:"omitempty,max=20" maxLength:"20" description:"手机号模糊查询"` + UserType *int `json:"user_type" query:"user_type" validate:"omitempty,min=1,max=4" minimum:"1" maximum:"4" description:"用户类型"` + Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态"` } // AccountResponse 账号响应 type AccountResponse struct { - ID uint `json:"id"` - Username string `json:"username"` - Phone string `json:"phone"` - UserType int `json:"user_type"` - ShopID *uint `json:"shop_id,omitempty"` - ParentID *uint `json:"parent_id,omitempty"` - Status int `json:"status"` - Creator uint `json:"creator"` - Updater uint `json:"updater"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + ID uint `json:"id" description:"账号ID"` + Username string `json:"username" description:"用户名"` + Phone string `json:"phone" description:"手机号"` + UserType int `json:"user_type" description:"用户类型"` + ShopID *uint `json:"shop_id,omitempty" description:"关联店铺ID"` + ParentID *uint `json:"parent_id,omitempty" description:"父账号ID"` + Status int `json:"status" description:"状态"` + Creator uint `json:"creator" description:"创建人ID"` + Updater uint `json:"updater" description:"更新人ID"` + CreatedAt string `json:"created_at" description:"创建时间"` + UpdatedAt string `json:"updated_at" description:"更新时间"` } // AssignRolesRequest 分配角色请求 type AssignRolesRequest struct { - RoleIDs []uint `json:"role_ids" validate:"required,min=1"` + RoleIDs []uint `json:"role_ids" validate:"required,min=1" required:"true" minItems:"1" description:"角色ID列表"` +} + +// AccountPageResult 账号分页响应 +type AccountPageResult struct { + Items []AccountResponse `json:"items" description:"账号列表"` + Total int64 `json:"total" description:"总记录数"` + Page int `json:"page" description:"当前页码"` + Size int `json:"size" description:"每页数量"` } diff --git a/internal/model/common.go b/internal/model/common.go new file mode 100644 index 0000000..f536430 --- /dev/null +++ b/internal/model/common.go @@ -0,0 +1,24 @@ +package model + +// IDReq 通用 ID 路径参数请求 +type IDReq struct { + ID uint `path:"id" description:"ID" required:"true"` +} + +// UpdateAccountParams 更新账号聚合参数 (用于文档生成) +type UpdateAccountParams struct { + IDReq + UpdateAccountRequest +} + +// AssignRolesParams 分配角色聚合参数 (用于文档生成) +type AssignRolesParams struct { + IDReq + AssignRolesRequest +} + +// RemoveRoleParams 移除角色聚合参数 +type RemoveRoleParams struct { + AccountID uint `path:"account_id" description:"账号ID" required:"true"` + RoleID uint `path:"role_id" description:"角色ID" required:"true"` +} diff --git a/internal/model/permission_dto.go b/internal/model/permission_dto.go index 59bb0b2..eadb5fc 100644 --- a/internal/model/permission_dto.go +++ b/internal/model/permission_dto.go @@ -2,58 +2,72 @@ package model // CreatePermissionRequest 创建权限请求 type CreatePermissionRequest struct { - PermName string `json:"perm_name" validate:"required,min=1,max=50"` - PermCode string `json:"perm_code" validate:"required,min=1,max=100"` - PermType int `json:"perm_type" validate:"required,min=1,max=2"` - URL string `json:"url" validate:"omitempty,max=255"` - ParentID *uint `json:"parent_id"` - Sort int `json:"sort" validate:"omitempty,min=0"` + PermName string `json:"perm_name" validate:"required,min=1,max=50" required:"true" minLength:"1" maxLength:"50" description:"权限名称"` + PermCode string `json:"perm_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"权限编码"` + PermType int `json:"perm_type" validate:"required,min=1,max=2" required:"true" minimum:"1" maximum:"2" description:"权限类型 (1:菜单, 2:按钮)"` + URL string `json:"url" validate:"omitempty,max=255" maxLength:"255" description:"请求路径"` + ParentID *uint `json:"parent_id" description:"父权限ID"` + Sort int `json:"sort" validate:"omitempty,min=0" minimum:"0" description:"排序值"` } // UpdatePermissionRequest 更新权限请求 type UpdatePermissionRequest struct { - PermName *string `json:"perm_name" validate:"omitempty,min=1,max=50"` - PermCode *string `json:"perm_code" validate:"omitempty,min=1,max=100"` - URL *string `json:"url" validate:"omitempty,max=255"` - ParentID *uint `json:"parent_id"` - Sort *int `json:"sort" validate:"omitempty,min=0"` - Status *int `json:"status" validate:"omitempty,min=0,max=1"` + PermName *string `json:"perm_name" validate:"omitempty,min=1,max=50" minLength:"1" maxLength:"50" description:"权限名称"` + PermCode *string `json:"perm_code" validate:"omitempty,min=1,max=100" minLength:"1" maxLength:"100" description:"权限编码"` + URL *string `json:"url" validate:"omitempty,max=255" maxLength:"255" description:"请求路径"` + ParentID *uint `json:"parent_id" description:"父权限ID"` + Sort *int `json:"sort" validate:"omitempty,min=0" minimum:"0" description:"排序值"` + Status *int `json:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态 (0:禁用, 1:启用)"` +} + +// UpdatePermissionParams 更新权限参数聚合 +type UpdatePermissionParams struct { + IDReq + UpdatePermissionRequest } // PermissionListRequest 权限列表查询请求 type PermissionListRequest struct { - Page int `json:"page" query:"page" validate:"omitempty,min=1"` - PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100"` - PermName string `json:"perm_name" query:"perm_name" validate:"omitempty,max=50"` - PermCode string `json:"perm_code" query:"perm_code" validate:"omitempty,max=100"` - PermType *int `json:"perm_type" query:"perm_type" validate:"omitempty,min=1,max=2"` - ParentID *uint `json:"parent_id" query:"parent_id"` - Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"` + Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"` + PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"` + PermName string `json:"perm_name" query:"perm_name" validate:"omitempty,max=50" maxLength:"50" description:"权限名称模糊查询"` + PermCode string `json:"perm_code" query:"perm_code" validate:"omitempty,max=100" maxLength:"100" description:"权限编码模糊查询"` + PermType *int `json:"perm_type" query:"perm_type" validate:"omitempty,min=1,max=2" minimum:"1" maximum:"2" description:"权限类型"` + ParentID *uint `json:"parent_id" query:"parent_id" description:"父权限ID"` + Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态"` } // PermissionResponse 权限响应 type PermissionResponse struct { - ID uint `json:"id"` - PermName string `json:"perm_name"` - PermCode string `json:"perm_code"` - PermType int `json:"perm_type"` - URL string `json:"url,omitempty"` - ParentID *uint `json:"parent_id,omitempty"` - Sort int `json:"sort"` - Status int `json:"status"` - Creator uint `json:"creator"` - Updater uint `json:"updater"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + ID uint `json:"id" description:"权限ID"` + PermName string `json:"perm_name" description:"权限名称"` + PermCode string `json:"perm_code" description:"权限编码"` + PermType int `json:"perm_type" description:"权限类型"` + URL string `json:"url,omitempty" description:"请求路径"` + ParentID *uint `json:"parent_id,omitempty" description:"父权限ID"` + Sort int `json:"sort" description:"排序值"` + Status int `json:"status" description:"状态"` + Creator uint `json:"creator" description:"创建人ID"` + Updater uint `json:"updater" description:"更新人ID"` + CreatedAt string `json:"created_at" description:"创建时间"` + UpdatedAt string `json:"updated_at" description:"更新时间"` +} + +// PermissionPageResult 权限分页响应 +type PermissionPageResult struct { + Items []PermissionResponse `json:"items" description:"权限列表"` + Total int64 `json:"total" description:"总记录数"` + Page int `json:"page" description:"当前页码"` + Size int `json:"size" description:"每页数量"` } // PermissionTreeNode 权限树节点(用于层级展示) type PermissionTreeNode struct { - ID uint `json:"id"` - PermName string `json:"perm_name"` - PermCode string `json:"perm_code"` - PermType int `json:"perm_type"` - URL string `json:"url,omitempty"` - Sort int `json:"sort"` - Children []*PermissionTreeNode `json:"children,omitempty"` -} + ID uint `json:"id" description:"权限ID"` + PermName string `json:"perm_name" description:"权限名称"` + PermCode string `json:"perm_code" description:"权限编码"` + PermType int `json:"perm_type" description:"权限类型"` + URL string `json:"url,omitempty" description:"请求路径"` + Sort int `json:"sort" description:"排序值"` + Children []*PermissionTreeNode `json:"children,omitempty" description:"子权限列表"` +} \ No newline at end of file diff --git a/internal/model/role_dto.go b/internal/model/role_dto.go index 6a4bece..84be4b6 100644 --- a/internal/model/role_dto.go +++ b/internal/model/role_dto.go @@ -2,41 +2,67 @@ package model // CreateRoleRequest 创建角色请求 type CreateRoleRequest struct { - RoleName string `json:"role_name" validate:"required,min=1,max=50"` - RoleDesc string `json:"role_desc" validate:"omitempty,max=255"` - RoleType int `json:"role_type" validate:"required,min=1,max=3"` + RoleName string `json:"role_name" validate:"required,min=1,max=50" required:"true" minLength:"1" maxLength:"50" description:"角色名称"` + RoleDesc string `json:"role_desc" validate:"omitempty,max=255" maxLength:"255" description:"角色描述"` + RoleType int `json:"role_type" validate:"required,min=1,max=3" required:"true" minimum:"1" maximum:"3" description:"角色类型 (1:超级管理员, 2:普通管理员, 3:操作员)"` } // UpdateRoleRequest 更新角色请求 type UpdateRoleRequest struct { - RoleName *string `json:"role_name" validate:"omitempty,min=1,max=50"` - RoleDesc *string `json:"role_desc" validate:"omitempty,max=255"` - Status *int `json:"status" validate:"omitempty,min=0,max=1"` + RoleName *string `json:"role_name" validate:"omitempty,min=1,max=50" minLength:"1" maxLength:"50" description:"角色名称"` + RoleDesc *string `json:"role_desc" validate:"omitempty,max=255" maxLength:"255" description:"角色描述"` + Status *int `json:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态 (0:禁用, 1:启用)"` +} + +// UpdateRoleParams 更新角色参数聚合 +type UpdateRoleParams struct { + IDReq + UpdateRoleRequest } // RoleListRequest 角色列表查询请求 type RoleListRequest struct { - Page int `json:"page" query:"page" validate:"omitempty,min=1"` - PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100"` - RoleName string `json:"role_name" query:"role_name" validate:"omitempty,max=50"` - RoleType *int `json:"role_type" query:"role_type" validate:"omitempty,min=1,max=3"` - Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"` + Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"` + PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"` + RoleName string `json:"role_name" query:"role_name" validate:"omitempty,max=50" maxLength:"50" description:"角色名称模糊查询"` + RoleType *int `json:"role_type" query:"role_type" validate:"omitempty,min=1,max=3" minimum:"1" maximum:"3" description:"角色类型"` + Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态"` } // RoleResponse 角色响应 type RoleResponse struct { - ID uint `json:"id"` - RoleName string `json:"role_name"` - RoleDesc string `json:"role_desc"` - RoleType int `json:"role_type"` - Status int `json:"status"` - Creator uint `json:"creator"` - Updater uint `json:"updater"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + ID uint `json:"id" description:"角色ID"` + RoleName string `json:"role_name" description:"角色名称"` + RoleDesc string `json:"role_desc" description:"角色描述"` + RoleType int `json:"role_type" description:"角色类型"` + Status int `json:"status" description:"状态"` + Creator uint `json:"creator" description:"创建人ID"` + Updater uint `json:"updater" description:"更新人ID"` + CreatedAt string `json:"created_at" description:"创建时间"` + UpdatedAt string `json:"updated_at" description:"更新时间"` +} + +// RolePageResult 角色分页响应 +type RolePageResult struct { + Items []RoleResponse `json:"items" description:"角色列表"` + Total int64 `json:"total" description:"总记录数"` + Page int `json:"page" description:"当前页码"` + Size int `json:"size" description:"每页数量"` } // AssignPermissionsRequest 分配权限请求 type AssignPermissionsRequest struct { - PermIDs []uint `json:"perm_ids" validate:"required,min=1"` + PermIDs []uint `json:"perm_ids" validate:"required,min=1" required:"true" minItems:"1" description:"权限ID列表"` } + +// AssignPermissionsParams 分配权限参数聚合 +type AssignPermissionsParams struct { + IDReq + AssignPermissionsRequest +} + +// RemovePermissionParams 移除权限参数聚合 +type RemovePermissionParams struct { + RoleID uint `path:"role_id" required:"true" description:"角色ID"` + PermID uint `path:"perm_id" required:"true" description:"权限ID"` +} \ No newline at end of file diff --git a/internal/routes/account.go b/internal/routes/account.go index eea4e5a..9a72264 100644 --- a/internal/routes/account.go +++ b/internal/routes/account.go @@ -3,22 +3,71 @@ package routes import ( "github.com/gofiber/fiber/v2" - "github.com/break/junhong_cmp_fiber/internal/handler" + "github.com/break/junhong_cmp_fiber/internal/handler/admin" + "github.com/break/junhong_cmp_fiber/internal/model" + "github.com/break/junhong_cmp_fiber/pkg/openapi" ) // registerAccountRoutes 注册账号相关路由 -func registerAccountRoutes(api fiber.Router, h *handler.AccountHandler) { +func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *openapi.Generator, basePath string) { accounts := api.Group("/accounts") + groupPath := basePath + "/accounts" // 账号 CRUD - accounts.Post("", h.Create) // POST /api/v1/accounts - accounts.Get("", h.List) // GET /api/v1/accounts - accounts.Get("/:id", h.Get) // GET /api/v1/accounts/:id - accounts.Put("/:id", h.Update) // PUT /api/v1/accounts/:id - accounts.Delete("/:id", h.Delete) // DELETE /api/v1/accounts/:id + Register(accounts, doc, groupPath, "POST", "", h.Create, RouteSpec{ + Summary: "创建账号", + Tags: []string{"Account"}, + Input: new(model.CreateAccountRequest), + Output: new(model.AccountResponse), + }) + + Register(accounts, doc, groupPath, "GET", "", h.List, RouteSpec{ + Summary: "账号列表", + Tags: []string{"Account"}, + Input: new(model.AccountListRequest), + Output: new(model.AccountPageResult), + }) + + Register(accounts, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{ + Summary: "获取账号详情", + Tags: []string{"Account"}, + Input: new(model.IDReq), + Output: new(model.AccountResponse), + }) + + Register(accounts, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{ + Summary: "更新账号", + Tags: []string{"Account"}, + Input: new(model.UpdateAccountParams), + Output: new(model.AccountResponse), + }) + + Register(accounts, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{ + Summary: "删除账号", + Tags: []string{"Account"}, + Input: new(model.IDReq), + Output: nil, + }) // 账号-角色关联 - accounts.Post("/:id/roles", h.AssignRoles) // POST /api/v1/accounts/:id/roles - accounts.Get("/:id/roles", h.GetRoles) // GET /api/v1/accounts/:id/roles - accounts.Delete("/:account_id/roles/:role_id", h.RemoveRole) // DELETE /api/v1/accounts/:account_id/roles/:role_id + Register(accounts, doc, groupPath, "POST", "/:id/roles", h.AssignRoles, RouteSpec{ + Summary: "分配角色", + Tags: []string{"Account"}, + Input: new(model.AssignRolesParams), + Output: nil, // TODO: Define AccountRole response DTO + }) + + Register(accounts, doc, groupPath, "GET", "/:id/roles", h.GetRoles, RouteSpec{ + Summary: "获取账号角色", + Tags: []string{"Account"}, + Input: new(model.IDReq), + Output: new([]model.Role), + }) + + Register(accounts, doc, groupPath, "DELETE", "/:account_id/roles/:role_id", h.RemoveRole, RouteSpec{ + Summary: "移除角色", + Tags: []string{"Account"}, + Input: new(model.RemoveRoleParams), + Output: nil, + }) }