1. 需求确认

2. 结构的一些变更
This commit is contained in:
2026-01-06 11:07:04 +08:00
parent e907315afa
commit 2d566a9820
18 changed files with 2210 additions and 111 deletions

46
cmd/gendocs/main.go Normal file
View File

@@ -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)
}

1019
docs/admin-openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
## 2025-12-23T17:39:31 沟通
1. 一次性佣金(组合佣金)冲突问题
2. 阻断不合理的问题(以下都是以同为一个套餐系列下的大前提)
- 一次性佣金套餐与长期佣金不能共存(分配时阻断)
- 组合佣金与另外两种佣金不能共存(分配时应当阻断)
- 次月生效的其他套餐系列怎么处理(疑问?)
3. 聚水潭,可能存在入库是20位/19位 出库时变成19位/20位

View File

@@ -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": {}
}

View File

@@ -265,3 +265,34 @@ flowchart TD
1. 确认上面的东西是不是对的 1. 确认上面的东西是不是对的
2. 确认一下是否还有别的业务 2. 确认一下是否还有别的业务
3. 跟我讲一下我们的实际业务 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. 阶梯分佣满足 激活(实名 + 达到累计/首次充值金额 + 在网状态(必须是正常的)(能不能拿到在网状态 存疑) ) = 激活

6
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/redis/go-redis/v9 v9.16.0 github.com/redis/go-redis/v9 v9.16.0
github.com/spf13/viper v1.21.0 github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1 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 v0.40.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
github.com/testcontainers/testcontainers-go/modules/redis v0.38.0 github.com/testcontainers/testcontainers-go/modules/redis v0.38.0
@@ -21,6 +22,7 @@ require (
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.44.0 golang.org/x/crypto v0.44.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 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/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1 gorm.io/gorm v1.31.1
@@ -102,6 +104,8 @@ require (
github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // 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/tinylib/msgp v1.2.5 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
@@ -123,5 +127,5 @@ require (
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.14.0 // indirect
google.golang.org/grpc v1.75.1 // indirect google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.10 // 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
) )

22
go.sum
View File

@@ -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/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 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 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 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10 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/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 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg= 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 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= 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/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 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= 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 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 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/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 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 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 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= 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= 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/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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 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/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 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,15 +1,15 @@
package bootstrap package bootstrap
import ( import (
"github.com/break/junhong_cmp_fiber/internal/handler" "github.com/break/junhong_cmp_fiber/internal/handler/admin"
) )
// initHandlers 初始化所有 Handler 实例 // initHandlers 初始化所有 Handler 实例
func initHandlers(svc *services) *Handlers { func initHandlers(svc *services) *Handlers {
return &Handlers{ return &Handlers{
Account: handler.NewAccountHandler(svc.Account), Account: admin.NewAccountHandler(svc.Account),
Role: handler.NewRoleHandler(svc.Role), Role: admin.NewRoleHandler(svc.Role),
Permission: handler.NewPermissionHandler(svc.Permission), Permission: admin.NewPermissionHandler(svc.Permission),
// TODO: 新增 Handler 在此初始化 // TODO: 新增 Handler 在此初始化
} }
} }

View File

@@ -1,14 +1,14 @@
package bootstrap package bootstrap
import ( import (
"github.com/break/junhong_cmp_fiber/internal/handler" "github.com/break/junhong_cmp_fiber/internal/handler/admin"
) )
// Handlers 封装所有 HTTP 处理器 // Handlers 封装所有 HTTP 处理器
// 用于路由注册 // 用于路由注册
type Handlers struct { type Handlers struct {
Account *handler.AccountHandler Account *admin.AccountHandler
Role *handler.RoleHandler Role *admin.RoleHandler
Permission *handler.PermissionHandler Permission *admin.PermissionHandler
// TODO: 新增 Handler 在此添加字段 // TODO: 新增 Handler 在此添加字段
} }

View File

@@ -1,4 +1,4 @@
package handler package admin
import ( import (
"strconv" "strconv"

View File

@@ -1,4 +1,4 @@
package handler package admin
import ( import (
"strconv" "strconv"

View File

@@ -1,4 +1,4 @@
package handler package admin
import ( import (
"strconv" "strconv"

View File

@@ -1,4 +1,4 @@
package handler package admin
import ( import (
"fmt" "fmt"

View File

@@ -2,48 +2,56 @@ package model
// CreateAccountRequest 创建账号请求 // CreateAccountRequest 创建账号请求
type CreateAccountRequest struct { type CreateAccountRequest struct {
Username string `json:"username" validate:"required,min=3,max=50"` 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"` 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"` 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"` 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"` ShopID *uint `json:"shop_id" description:"关联店铺ID"`
ParentID *uint `json:"parent_id"` ParentID *uint `json:"parent_id" description:"父账号ID"`
} }
// UpdateAccountRequest 更新账号请求 // UpdateAccountRequest 更新账号请求
type UpdateAccountRequest struct { type UpdateAccountRequest struct {
Username *string `json:"username" validate:"omitempty,min=3,max=50"` Username *string `json:"username" validate:"omitempty,min=3,max=50" minLength:"3" maxLength:"50" description:"用户名"`
Phone *string `json:"phone" validate:"omitempty,len=11"` Phone *string `json:"phone" validate:"omitempty,len=11" minLength:"11" maxLength:"11" description:"手机号"`
Password *string `json:"password" validate:"omitempty,min=8,max=32"` 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"` Status *int `json:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态 (0:禁用, 1:启用)"`
} }
// AccountListRequest 账号列表查询请求 // AccountListRequest 账号列表查询请求
type AccountListRequest struct { type AccountListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=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"` 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"` Username string `json:"username" query:"username" validate:"omitempty,max=50" maxLength:"50" description:"用户名模糊查询"`
Phone string `json:"phone" query:"phone" validate:"omitempty,max=20"` 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"` 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"` Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态"`
} }
// AccountResponse 账号响应 // AccountResponse 账号响应
type AccountResponse struct { type AccountResponse struct {
ID uint `json:"id"` ID uint `json:"id" description:"账号ID"`
Username string `json:"username"` Username string `json:"username" description:"用户名"`
Phone string `json:"phone"` Phone string `json:"phone" description:"手机号"`
UserType int `json:"user_type"` UserType int `json:"user_type" description:"用户类型"`
ShopID *uint `json:"shop_id,omitempty"` ShopID *uint `json:"shop_id,omitempty" description:"关联店铺ID"`
ParentID *uint `json:"parent_id,omitempty"` ParentID *uint `json:"parent_id,omitempty" description:"父账号ID"`
Status int `json:"status"` Status int `json:"status" description:"状态"`
Creator uint `json:"creator"` Creator uint `json:"creator" description:"创建人ID"`
Updater uint `json:"updater"` Updater uint `json:"updater" description:"更新人ID"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at" description:"更新时间"`
} }
// AssignRolesRequest 分配角色请求 // AssignRolesRequest 分配角色请求
type AssignRolesRequest struct { 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:"每页数量"`
} }

24
internal/model/common.go Normal file
View File

@@ -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"`
}

View File

@@ -2,58 +2,72 @@ package model
// CreatePermissionRequest 创建权限请求 // CreatePermissionRequest 创建权限请求
type CreatePermissionRequest struct { type CreatePermissionRequest struct {
PermName string `json:"perm_name" validate:"required,min=1,max=50"` 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"` 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"` 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"` URL string `json:"url" validate:"omitempty,max=255" maxLength:"255" description:"请求路径"`
ParentID *uint `json:"parent_id"` ParentID *uint `json:"parent_id" description:"父权限ID"`
Sort int `json:"sort" validate:"omitempty,min=0"` Sort int `json:"sort" validate:"omitempty,min=0" minimum:"0" description:"排序值"`
} }
// UpdatePermissionRequest 更新权限请求 // UpdatePermissionRequest 更新权限请求
type UpdatePermissionRequest struct { type UpdatePermissionRequest struct {
PermName *string `json:"perm_name" validate:"omitempty,min=1,max=50"` 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"` PermCode *string `json:"perm_code" validate:"omitempty,min=1,max=100" minLength:"1" maxLength:"100" description:"权限编码"`
URL *string `json:"url" validate:"omitempty,max=255"` URL *string `json:"url" validate:"omitempty,max=255" maxLength:"255" description:"请求路径"`
ParentID *uint `json:"parent_id"` ParentID *uint `json:"parent_id" description:"父权限ID"`
Sort *int `json:"sort" validate:"omitempty,min=0"` Sort *int `json:"sort" validate:"omitempty,min=0" minimum:"0" description:"排序值"`
Status *int `json:"status" validate:"omitempty,min=0,max=1"` Status *int `json:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态 (0:禁用, 1:启用)"`
}
// UpdatePermissionParams 更新权限参数聚合
type UpdatePermissionParams struct {
IDReq
UpdatePermissionRequest
} }
// PermissionListRequest 权限列表查询请求 // PermissionListRequest 权限列表查询请求
type PermissionListRequest struct { type PermissionListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=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"` 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"` 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"` 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"` 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"` ParentID *uint `json:"parent_id" query:"parent_id" description:"父权限ID"`
Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"` Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态"`
} }
// PermissionResponse 权限响应 // PermissionResponse 权限响应
type PermissionResponse struct { type PermissionResponse struct {
ID uint `json:"id"` ID uint `json:"id" description:"权限ID"`
PermName string `json:"perm_name"` PermName string `json:"perm_name" description:"权限名称"`
PermCode string `json:"perm_code"` PermCode string `json:"perm_code" description:"权限编码"`
PermType int `json:"perm_type"` PermType int `json:"perm_type" description:"权限类型"`
URL string `json:"url,omitempty"` URL string `json:"url,omitempty" description:"请求路径"`
ParentID *uint `json:"parent_id,omitempty"` ParentID *uint `json:"parent_id,omitempty" description:"父权限ID"`
Sort int `json:"sort"` Sort int `json:"sort" description:"排序值"`
Status int `json:"status"` Status int `json:"status" description:"状态"`
Creator uint `json:"creator"` Creator uint `json:"creator" description:"创建人ID"`
Updater uint `json:"updater"` Updater uint `json:"updater" description:"更新人ID"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at"` 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 权限树节点(用于层级展示) // PermissionTreeNode 权限树节点(用于层级展示)
type PermissionTreeNode struct { type PermissionTreeNode struct {
ID uint `json:"id"` ID uint `json:"id" description:"权限ID"`
PermName string `json:"perm_name"` PermName string `json:"perm_name" description:"权限名称"`
PermCode string `json:"perm_code"` PermCode string `json:"perm_code" description:"权限编码"`
PermType int `json:"perm_type"` PermType int `json:"perm_type" description:"权限类型"`
URL string `json:"url,omitempty"` URL string `json:"url,omitempty" description:"请求路径"`
Sort int `json:"sort"` Sort int `json:"sort" description:"排序值"`
Children []*PermissionTreeNode `json:"children,omitempty"` Children []*PermissionTreeNode `json:"children,omitempty" description:"子权限列表"`
} }

View File

@@ -2,41 +2,67 @@ package model
// CreateRoleRequest 创建角色请求 // CreateRoleRequest 创建角色请求
type CreateRoleRequest struct { type CreateRoleRequest struct {
RoleName string `json:"role_name" validate:"required,min=1,max=50"` 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"` RoleDesc string `json:"role_desc" validate:"omitempty,max=255" maxLength:"255" description:"角色描述"`
RoleType int `json:"role_type" validate:"required,min=1,max=3"` RoleType int `json:"role_type" validate:"required,min=1,max=3" required:"true" minimum:"1" maximum:"3" description:"角色类型 (1:超级管理员, 2:普通管理员, 3:操作员)"`
} }
// UpdateRoleRequest 更新角色请求 // UpdateRoleRequest 更新角色请求
type UpdateRoleRequest struct { type UpdateRoleRequest struct {
RoleName *string `json:"role_name" validate:"omitempty,min=1,max=50"` 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"` RoleDesc *string `json:"role_desc" validate:"omitempty,max=255" maxLength:"255" description:"角色描述"`
Status *int `json:"status" validate:"omitempty,min=0,max=1"` Status *int `json:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态 (0:禁用, 1:启用)"`
}
// UpdateRoleParams 更新角色参数聚合
type UpdateRoleParams struct {
IDReq
UpdateRoleRequest
} }
// RoleListRequest 角色列表查询请求 // RoleListRequest 角色列表查询请求
type RoleListRequest struct { type RoleListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=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"` 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"` 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"` 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"` Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1" minimum:"0" maximum:"1" description:"状态"`
} }
// RoleResponse 角色响应 // RoleResponse 角色响应
type RoleResponse struct { type RoleResponse struct {
ID uint `json:"id"` ID uint `json:"id" description:"角色ID"`
RoleName string `json:"role_name"` RoleName string `json:"role_name" description:"角色名称"`
RoleDesc string `json:"role_desc"` RoleDesc string `json:"role_desc" description:"角色描述"`
RoleType int `json:"role_type"` RoleType int `json:"role_type" description:"角色类型"`
Status int `json:"status"` Status int `json:"status" description:"状态"`
Creator uint `json:"creator"` Creator uint `json:"creator" description:"创建人ID"`
Updater uint `json:"updater"` Updater uint `json:"updater" description:"更新人ID"`
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at"` 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 分配权限请求 // AssignPermissionsRequest 分配权限请求
type AssignPermissionsRequest struct { 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"`
}

View File

@@ -3,22 +3,71 @@ package routes
import ( import (
"github.com/gofiber/fiber/v2" "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 注册账号相关路由 // 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") accounts := api.Group("/accounts")
groupPath := basePath + "/accounts"
// 账号 CRUD // 账号 CRUD
accounts.Post("", h.Create) // POST /api/v1/accounts Register(accounts, doc, groupPath, "POST", "", h.Create, RouteSpec{
accounts.Get("", h.List) // GET /api/v1/accounts Summary: "创建账号",
accounts.Get("/:id", h.Get) // GET /api/v1/accounts/:id Tags: []string{"Account"},
accounts.Put("/:id", h.Update) // PUT /api/v1/accounts/:id Input: new(model.CreateAccountRequest),
accounts.Delete("/:id", h.Delete) // DELETE /api/v1/accounts/:id 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 Register(accounts, doc, groupPath, "POST", "/:id/roles", h.AssignRoles, RouteSpec{
accounts.Get("/:id/roles", h.GetRoles) // GET /api/v1/accounts/:id/roles Summary: "分配角色",
accounts.Delete("/:account_id/roles/:role_id", h.RemoveRole) // DELETE /api/v1/accounts/:account_id/roles/:role_id 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,
})
} }