All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m14s
367 lines
17 KiB
Markdown
367 lines
17 KiB
Markdown
# 套餐接口架构图
|
|
|
|
## 1. 整体架构
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ HTTP 请求 │
|
|
│ POST /api/admin/packages │
|
|
└────────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
┌────────▼────────┐
|
|
│ 认证中间件 │
|
|
│ (AdminAuth) │
|
|
└────────┬────────┘
|
|
│
|
|
┌────────────────────▼────────────────────┐
|
|
│ 路由注册 (routes/admin.go) │
|
|
│ registerPackageRoutes() │
|
|
│ ↓ │
|
|
│ internal/routes/package.go │
|
|
└────────────────────┬────────────────────┘
|
|
│
|
|
┌────────────────────▼────────────────────┐
|
|
│ PackageHandler │
|
|
│ (internal/handler/admin/package.go) │
|
|
│ │
|
|
│ - List() │
|
|
│ - Create() │
|
|
│ - Get() │
|
|
│ - Update() │
|
|
│ - Delete() │
|
|
│ - UpdateStatus() │
|
|
│ - UpdateShelfStatus() │
|
|
│ - UpdateRetailPrice() │
|
|
└────────────────────┬────────────────────┘
|
|
│
|
|
┌────────────────────▼────────────────────┐
|
|
│ PackageService │
|
|
│ (internal/service/package/service.go) │
|
|
│ │
|
|
│ 业务逻辑: │
|
|
│ - 虚流量校验 │
|
|
│ - 周期类型校验 │
|
|
│ - 代理权限检查 │
|
|
│ - DTO 转换 │
|
|
└────────────────────┬────────────────────┘
|
|
│
|
|
┌────────────────────▼────────────────────┐
|
|
│ PackageStore │
|
|
│ (internal/store/postgres/...) │
|
|
│ │
|
|
│ - PackageStore │
|
|
│ - PackageSeriesStore │
|
|
│ - ShopPackageAllocationStore │
|
|
│ - ShopSeriesAllocationStore │
|
|
└────────────────────┬────────────────────┘
|
|
│
|
|
┌────────────────────▼────────────────────┐
|
|
│ PostgreSQL 数据库 │
|
|
│ │
|
|
│ - tb_package │
|
|
│ - tb_package_series │
|
|
│ - tb_shop_package_allocation │
|
|
│ - tb_shop_series_allocation │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 分层详细结构
|
|
|
|
### Handler 层
|
|
```
|
|
PackageHandler
|
|
├── List(c *fiber.Ctx)
|
|
│ └── 解析查询参数 → 调用 Service.List() → 返回分页结果
|
|
├── Create(c *fiber.Ctx)
|
|
│ └── 解析请求体 → 调用 Service.Create() → 返回创建结果
|
|
├── Get(c *fiber.Ctx)
|
|
│ └── 解析 ID 参数 → 调用 Service.Get() → 返回详情
|
|
├── Update(c *fiber.Ctx)
|
|
│ └── 解析 ID 和请求体 → 调用 Service.Update() → 返回更新结果
|
|
├── Delete(c *fiber.Ctx)
|
|
│ └── 解析 ID 参数 → 调用 Service.Delete() → 返回删除结果
|
|
├── UpdateStatus(c *fiber.Ctx)
|
|
│ └── 解析 ID 和状态 → 调用 Service.UpdateStatus() → 返回结果
|
|
├── UpdateShelfStatus(c *fiber.Ctx)
|
|
│ └── 解析 ID 和上架状态 → 调用 Service.UpdateShelfStatus() → 返回结果
|
|
└── UpdateRetailPrice(c *fiber.Ctx)
|
|
└── 解析 ID 和零售价 → 调用 Service.UpdateRetailPrice() → 返回结果
|
|
```
|
|
|
|
### Service 层
|
|
```
|
|
PackageService
|
|
├── Create(ctx, req)
|
|
│ ├── 校验套餐编码唯一性
|
|
│ ├── 校验虚流量配置
|
|
│ │ ├── 启用时虚流量 > 0
|
|
│ │ └── 虚流量 ≤ 真流量
|
|
│ ├── 校验周期类型和时长
|
|
│ │ ├── natural_month: 需要 duration_months
|
|
│ │ └── by_day: 需要 duration_days
|
|
│ ├── 查询套餐系列信息
|
|
│ ├── 创建 Package 模型
|
|
│ └── 调用 Store.Create()
|
|
├── List(ctx, req)
|
|
│ ├── 获取用户类型和店铺 ID
|
|
│ ├── 代理用户额外过滤
|
|
│ │ └── INNER JOIN tb_shop_package_allocation
|
|
│ ├── 应用其他过滤条件
|
|
│ └── 调用 Store.List()
|
|
├── Get(ctx, id)
|
|
│ └── 调用 Store.GetByID()
|
|
├── Update(ctx, id, req)
|
|
│ ├── 获取现有套餐
|
|
│ ├── 更新字段
|
|
│ └── 调用 Store.Update()
|
|
├── Delete(ctx, id)
|
|
│ └── 调用 Store.Delete()
|
|
├── UpdateStatus(ctx, id, status)
|
|
│ └── 更新状态字段
|
|
├── UpdateShelfStatus(ctx, id, shelfStatus)
|
|
│ └── 更新上架状态字段
|
|
└── UpdateRetailPrice(ctx, id, price)
|
|
└── 更新零售价字段
|
|
```
|
|
|
|
### Store 层
|
|
```
|
|
PackageStore
|
|
├── Create(ctx, pkg)
|
|
│ └── db.Create(pkg)
|
|
├── GetByID(ctx, id)
|
|
│ └── db.First(&pkg, id)
|
|
├── GetByCode(ctx, code)
|
|
│ └── db.Where("package_code = ?", code).First(&pkg)
|
|
├── Update(ctx, pkg)
|
|
│ └── db.Save(pkg)
|
|
├── Delete(ctx, id)
|
|
│ └── db.Delete(&Package{}, id)
|
|
└── List(ctx, opts, filters)
|
|
├── 构建基础查询
|
|
├── 代理用户 JOIN 分配表
|
|
├── 应用过滤条件
|
|
│ ├── package_name (模糊搜索)
|
|
│ ├── series_id
|
|
│ ├── status
|
|
│ ├── shelf_status
|
|
│ └── package_type
|
|
├── 计算总数
|
|
├── 应用分页
|
|
└── 执行查询
|
|
```
|
|
|
|
---
|
|
|
|
## 3. 数据模型关系
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ tb_package (套餐表) │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ package_code (UNIQUE) │
|
|
│ package_name │
|
|
│ series_id (FK → tb_package_series) │
|
|
│ package_type (formal/addon) │
|
|
│ duration_months │
|
|
│ duration_days ← 关键字段 │
|
|
│ calendar_type (natural_month/by_day) │
|
|
│ real_data_mb │
|
|
│ virtual_data_mb │
|
|
│ enable_virtual_data │
|
|
│ data_reset_cycle │
|
|
│ expiry_base │
|
|
│ cost_price │
|
|
│ suggested_retail_price │
|
|
│ shelf_status │
|
|
│ status │
|
|
│ created_at, updated_at, deleted_at │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ 1:N
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ tb_package_usage (套餐使用表) │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ order_id (FK) │
|
|
│ package_id (FK → tb_package) │
|
|
│ usage_type (single_card/device) │
|
|
│ iot_card_id / device_id │
|
|
│ data_limit_mb │
|
|
│ data_usage_mb │
|
|
│ activated_at, expires_at │
|
|
│ status │
|
|
│ priority │
|
|
│ master_usage_id (加油包关联主套餐) │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ 1:N
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ tb_package_usage_daily_record (日记录表) │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ package_usage_id (FK) │
|
|
│ date │
|
|
│ daily_usage_mb │
|
|
│ cumulative_usage_mb │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ tb_package_series (套餐系列表) │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ series_code (UNIQUE) │
|
|
│ series_name │
|
|
│ description │
|
|
│ enable_one_time_commission │
|
|
│ one_time_commission_config (JSONB) │
|
|
│ status │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
│
|
|
│ 1:N
|
|
│
|
|
▼
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ tb_shop_package_allocation (分配表) │
|
|
├──────────────────────────────────────────────────────────────┤
|
|
│ id (PK) │
|
|
│ shop_id (FK → tb_shop) │
|
|
│ package_id (FK → tb_package) │
|
|
│ retail_price │
|
|
│ shelf_status │
|
|
│ status │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 请求流程示例
|
|
|
|
### 创建套餐(按天)
|
|
|
|
```
|
|
1. HTTP 请求
|
|
POST /api/admin/packages
|
|
{
|
|
"package_code": "PKG_DAY_30",
|
|
"package_name": "30天套餐",
|
|
"calendar_type": "by_day",
|
|
"duration_days": 30,
|
|
...
|
|
}
|
|
|
|
2. Handler.Create()
|
|
├─ 解析请求体 → CreatePackageRequest
|
|
└─ 调用 Service.Create(ctx, req)
|
|
|
|
3. Service.Create()
|
|
├─ 校验 package_code 唯一性
|
|
│ └─ Store.GetByCode(ctx, "PKG_DAY_30") → nil ✓
|
|
├─ 校验虚流量配置 ✓
|
|
├─ 校验周期类型
|
|
│ ├─ calendar_type = "by_day" ✓
|
|
│ ├─ duration_days = 30 ✓
|
|
│ └─ 校验通过 ✓
|
|
├─ 查询套餐系列 (可选)
|
|
├─ 创建 Package 模型
|
|
└─ Store.Create(ctx, pkg)
|
|
|
|
4. Store.Create()
|
|
└─ db.Create(pkg)
|
|
└─ INSERT INTO tb_package (...)
|
|
|
|
5. Service 返回 PackageResponse
|
|
└─ Handler 返回 HTTP 200
|
|
|
|
6. HTTP 响应
|
|
{
|
|
"code": 0,
|
|
"data": {
|
|
"id": 1,
|
|
"package_code": "PKG_DAY_30",
|
|
"duration_days": 30,
|
|
...
|
|
},
|
|
"msg": "success"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 代理用户套餐查询流程
|
|
|
|
```
|
|
1. 代理用户请求
|
|
GET /api/admin/packages?status=1
|
|
|
|
2. Handler.List()
|
|
└─ Service.List(ctx, req)
|
|
|
|
3. Service.List()
|
|
├─ 获取用户类型: UserTypeAgent ✓
|
|
├─ 获取店铺 ID: 10 ✓
|
|
└─ Store.List(ctx, opts, filters)
|
|
|
|
4. Store.List()
|
|
├─ 构建基础查询
|
|
│ └─ SELECT * FROM tb_package
|
|
├─ 检测代理用户
|
|
│ └─ isAgent = true ✓
|
|
├─ 添加 JOIN 条件
|
|
│ └─ INNER JOIN tb_shop_package_allocation
|
|
│ ON tb_shop_package_allocation.package_id = tb_package.id
|
|
│ AND tb_shop_package_allocation.deleted_at IS NULL
|
|
├─ 添加 WHERE 条件
|
|
│ └─ WHERE tb_shop_package_allocation.shop_id = 10
|
|
│ AND tb_shop_package_allocation.status = 1
|
|
├─ 应用其他过滤
|
|
│ └─ AND tb_package.status = 1
|
|
├─ 计算总数
|
|
├─ 应用分页
|
|
└─ 执行查询
|
|
|
|
5. 返回结果
|
|
└─ 只返回已分配给店铺 10 的套餐
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 字段验证流程
|
|
|
|
```
|
|
CreatePackageRequest
|
|
├─ package_code
|
|
│ └─ validate: required, min=1, max=100
|
|
│ └─ Service: 检查唯一性
|
|
├─ package_name
|
|
│ └─ validate: required, min=1, max=255
|
|
├─ calendar_type
|
|
│ └─ validate: oneof=natural_month by_day
|
|
│ └─ Service: 根据类型校验时长字段
|
|
├─ duration_months
|
|
│ └─ validate: required, min=1, max=120
|
|
│ └─ Service: 当 calendar_type=natural_month 时必填
|
|
├─ duration_days
|
|
│ └─ validate: omitempty, min=1, max=3650
|
|
│ └─ Service: 当 calendar_type=by_day 时必填
|
|
├─ real_data_mb
|
|
│ └─ validate: omitempty, min=0
|
|
├─ virtual_data_mb
|
|
│ └─ validate: omitempty, min=0
|
|
│ └─ Service: 启用虚流量时必须 > 0 且 ≤ real_data_mb
|
|
├─ enable_virtual_data
|
|
│ └─ Service: 虚流量配置校验
|
|
├─ cost_price
|
|
│ └─ validate: required, min=0
|
|
└─ suggested_retail_price
|
|
└─ validate: omitempty, min=0
|
|
```
|
|
|