Files
junhong_cmp_fiber/docs/package_structure_summary.md
huang 8f66ca7bcb
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m14s
文档提交
2026-04-11 11:27:43 +08:00

12 KiB
Raw Permalink Blame History

后台管理套餐相关接口代码结构探索

📋 概览

套餐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.go
  • PackageUsageStore - internal/store/postgres/package_usage_store.go
  • PackageUsageDailyRecordStore - internal/store/postgres/package_usage_daily_record_store.go
  • ShopPackageAllocationStore - 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 PackagePackageSeriesPackageUsage
DTO internal/model/dto/package_dto.go PackageResponseCreatePackageRequest
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 和数据库中都有定义,用于按天计算套餐有效期。