All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m14s
12 KiB
12 KiB
后台管理套餐相关接口代码结构探索
📋 概览
套餐(Package/PackageSeries)相关功能采用标准的分层架构:
Handler → Service → Store → Model
1️⃣ 核心文件位置
Model 层(数据模型)
| 文件 | 路径 | 说明 |
|---|---|---|
| Package | internal/model/package.go:28-56 |
套餐主模型,包含 duration_days 字段 |
| PackageSeries | internal/model/package.go:10-26 |
套餐系列模型 |
| PackageUsage | internal/model/package.go:58-90 |
套餐使用情况模型 |
| PackageUsageDailyRecord | internal/model/package.go:92-107 |
套餐流量日记录模型 |
DTO 层(数据传输对象)
| 文件 | 路径 | 说明 |
|---|---|---|
| PackageResponse | internal/model/dto/package_dto.go:71-99 |
套餐响应结构,包含 duration_days 字段 |
| CreatePackageRequest | internal/model/dto/package_dto.go:3-19 |
创建套餐请求 |
| UpdatePackageRequest | internal/model/dto/package_dto.go:21-36 |
更新套餐请求 |
| PackageListRequest | internal/model/dto/package_dto.go:38-47 |
套餐列表请求 |
| PackagePageResult | internal/model/dto/package_dto.go:125-131 |
套餐分页结果 |
Handler 层(HTTP 处理)
| 文件 | 路径 | 说明 |
|---|---|---|
| PackageHandler | internal/handler/admin/package.go |
套餐管理处理器 |
| 方法 | 行号 | 功能 |
List() |
22-34 | 获取套餐列表 |
Create() |
36-48 | 创建套餐 |
Get() |
50-62 | 获取套餐详情 |
Update() |
64-81 | 更新套餐 |
Delete() |
83-94 | 删除套餐 |
UpdateStatus() |
96-112 | 更新套餐状态 |
UpdateShelfStatus() |
114-130 | 更新上架状态 |
UpdateRetailPrice() |
132-148 | 更新零售价 |
Service 层(业务逻辑)
| 文件 | 路径 | 说明 |
|---|---|---|
| Service | internal/service/package/service.go:19-38 |
套餐服务主类 |
| 依赖 | 说明 | |
packageStore |
套餐数据访问 | |
packageSeriesStore |
套餐系列数据访问 | |
packageAllocationStore |
套餐分配数据访问 | |
shopSeriesAllocationStore |
店铺系列分配数据访问 |
关键方法(service.go 第 40-80 行):
Create()- 创建套餐,包含虚流量和周期类型校验List()- 列表查询,支持代理用户过滤Get()- 获取详情Update()- 更新套餐Delete()- 删除套餐UpdateStatus()- 更新状态UpdateShelfStatus()- 更新上架状态UpdateRetailPrice()- 更新零售价
Store 层(数据访问)
| 文件 | 路径 | 说明 |
|---|---|---|
| PackageStore | internal/store/postgres/package_store.go:15-21 |
套餐数据访问 |
| 方法 | 行号 | 功能 |
Create() |
23-25 | 创建套餐 |
GetByID() |
27-33 | 按 ID 查询 |
GetByCode() |
35-41 | 按编码查询 |
Update() |
43-45 | 更新套餐 |
Delete() |
47-49 | 删除套餐 |
List() |
51-100+ | 列表查询(支持代理用户过滤) |
其他相关 Store:
PackageSeriesStore-internal/store/postgres/package_series_store.goPackageUsageStore-internal/store/postgres/package_usage_store.goPackageUsageDailyRecordStore-internal/store/postgres/package_usage_daily_record_store.goShopPackageAllocationStore-internal/store/postgres/shop_package_allocation_store.go
2️⃣ API 路由注册
主路由注册入口
文件:internal/routes/admin.go:71-78
if handlers.PackageSeries != nil {
registerPackageSeriesRoutes(authGroup, handlers.PackageSeries, doc, basePath)
}
if handlers.Package != nil {
registerPackageRoutes(authGroup, handlers.Package, doc, basePath)
}
if handlers.PackageUsage != nil {
registerPackageUsageRoutes(authGroup, handlers.PackageUsage, doc, basePath)
}
套餐路由详情
文件:internal/routes/package.go:11-78
| 方法 | 路径 | 处理器 | 说明 |
|---|---|---|---|
| GET | /packages |
List() |
套餐列表 |
| POST | /packages |
Create() |
创建套餐 |
| GET | /packages/:id |
Get() |
获取详情 |
| PUT | /packages/:id |
Update() |
更新套餐 |
| DELETE | /packages/:id |
Delete() |
删除套餐 |
| PATCH | /packages/:id/status |
UpdateStatus() |
更新状态 |
| PATCH | /packages/:id/shelf |
UpdateShelfStatus() |
更新上架状态 |
| PATCH | /packages/:id/retail-price |
UpdateRetailPrice() |
更新零售价 |
套餐系列路由
文件:internal/routes/package_series.go:11-62
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /package-series |
列表 |
| POST | /package-series |
创建 |
| GET | /package-series/:id |
详情 |
| PUT | /package-series/:id |
更新 |
| DELETE | /package-series/:id |
删除 |
| PATCH | /package-series/:id/status |
更新状态 |
套餐使用记录路由
文件:internal/routes/package_usage.go:12-23
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /package-usage/:id/daily-records |
获取流量详单 |
3️⃣ 关键字段详解
Package 模型中的 duration_days 字段
位置:internal/model/package.go:46
DurationDays int `gorm:"column:duration_days;type:int;comment:套餐天数(calendar_type=by_day时必填)" json:"duration_days"`
说明:
- 当
calendar_type = "by_day"时必填 - 当
calendar_type = "natural_month"时不需要 - 用于按天计算套餐有效期
PackageResponse DTO 中的 duration_days
位置:internal/model/dto/package_dto.go:96
DurationDays *int `json:"duration_days,omitempty" description:"套餐天数(calendar_type=by_day时有值)"`
说明:
- 可选字段(指针类型)
- 仅当
calendar_type = "by_day"时返回值
创建请求中的 duration_days
位置:internal/model/dto/package_dto.go:16
DurationDays *int `json:"duration_days" validate:"omitempty,min=1,max=3650" minimum:"1" maximum:"3650" description:"套餐天数(calendar_type=by_day时必填)"`
验证规则:
- 可选字段
- 当提供时,必须在 1-3650 之间
- 当
calendar_type = "by_day"时必须提供
4️⃣ 业务逻辑关键点
套餐周期类型校验(Service 层)
位置:internal/service/package/service.go:65-80
// 校验套餐周期类型和时长配置
calendarType := constants.PackageCalendarTypeByDay // 默认按天
if req.CalendarType != nil {
calendarType = *req.CalendarType
}
if calendarType == constants.PackageCalendarTypeNaturalMonth {
// 自然月套餐:必须提供 duration_months
if req.DurationMonths <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "自然月套餐必须提供有效的duration_months")
}
} else if calendarType == constants.PackageCalendarTypeByDay {
// 按天套餐:必须提供 duration_days
if req.DurationDays == nil || *req.DurationDays <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "按天套餐必须提供有效的duration_days")
}
}
虚流量配置校验
位置:internal/service/package/service.go:51-63
if req.EnableVirtualData {
if req.VirtualDataMB == nil || *req.VirtualDataMB <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "启用虚流量时,虚流量额度必须大于0")
}
realDataMB := int64(0)
if req.RealDataMB != nil {
realDataMB = *req.RealDataMB
}
if *req.VirtualDataMB > realDataMB {
return nil, errors.New(errors.CodeInvalidParam, "虚流量额度不能大于真流量额度")
}
}
代理用户套餐过滤
位置:internal/store/postgres/package_store.go:57-66
// 代理用户额外过滤:只能看到已分配(allocation.status=启用)的套餐
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
isAgent := userType == constants.UserTypeAgent && shopID > 0
if isAgent {
query = query.Joins("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("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
shopID, constants.StatusEnabled)
}
5️⃣ 相关常量
位置:pkg/constants/ 目录
常用常量:
PackageCalendarTypeNaturalMonth- 自然月PackageCalendarTypeByDay- 按天UserTypeAgent- 代理用户类型StatusEnabled- 启用状态
6️⃣ 数据库表结构
tb_package(套餐表)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| package_code | varchar(100) | 套餐编码(唯一) |
| package_name | varchar(255) | 套餐名称 |
| series_id | bigint | 套餐系列 ID |
| package_type | varchar(50) | 套餐类型(formal/addon) |
| duration_months | int | 套餐时长(月数) |
| real_data_mb | bigint | 真流量额度(MB) |
| virtual_data_mb | bigint | 虚流量额度(MB) |
| enable_virtual_data | boolean | 是否启用虚流量 |
| duration_days | int | 套餐天数(按天时必填) |
| calendar_type | varchar(20) | 套餐周期类型(natural_month/by_day) |
| data_reset_cycle | varchar(20) | 流量重置周期(daily/monthly/yearly/none) |
| expiry_base | varchar(30) | 到期时间基准(from_activation/from_purchase) |
| cost_price | bigint | 成本价(分) |
| suggested_retail_price | bigint | 建议售价(分) |
| shelf_status | int | 上架状态(1-上架/2-下架) |
| status | int | 状态(1-启用/2-禁用) |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
| deleted_at | timestamp | 删除时间 |
tb_package_series(套餐系列表)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| series_code | varchar(100) | 系列编码(唯一) |
| series_name | varchar(255) | 系列名称 |
| description | text | 描述 |
| enable_one_time_commission | boolean | 是否启用一次性佣金 |
| one_time_commission_config | jsonb | 一次性佣金规则配置 |
| status | int | 状态(1-启用/2-禁用) |
7️⃣ 完整请求/响应示例
创建套餐(按天)
请求:
POST /api/admin/packages
{
"package_code": "PKG_DAY_30",
"package_name": "30天套餐",
"package_type": "formal",
"duration_months": 1,
"real_data_mb": 10240,
"enable_virtual_data": false,
"cost_price": 9900,
"suggested_retail_price": 19900,
"calendar_type": "by_day",
"duration_days": 30,
"data_reset_cycle": "monthly",
"expiry_base": "from_activation"
}
响应:
{
"code": 0,
"data": {
"id": 1,
"package_code": "PKG_DAY_30",
"package_name": "30天套餐",
"package_type": "formal",
"duration_months": 1,
"real_data_mb": 10240,
"enable_virtual_data": false,
"cost_price": 9900,
"suggested_retail_price": 19900,
"calendar_type": "by_day",
"duration_days": 30,
"data_reset_cycle": "monthly",
"expiry_base": "from_activation",
"status": 1,
"shelf_status": 2,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"msg": "success"
}
📌 总结
| 层级 | 关键文件 | 核心类/函数 |
|---|---|---|
| Model | internal/model/package.go |
Package、PackageSeries、PackageUsage |
| DTO | internal/model/dto/package_dto.go |
PackageResponse、CreatePackageRequest |
| Handler | internal/handler/admin/package.go |
PackageHandler |
| Service | internal/service/package/service.go |
Service |
| Store | internal/store/postgres/package_store.go |
PackageStore |
| Routes | internal/routes/package.go |
registerPackageRoutes() |
| Route Registry | internal/routes/admin.go:71-78 |
路由注册入口 |
关键字段:duration_days 在 Model、DTO 和数据库中都有定义,用于按天计算套餐有效期。