feat: 客户端接口数据模型基础准备

- 新增资产状态、订单来源、操作人类型、实名链接类型常量
- 8个模型新增字段(asset_status/generation/source/retail_price等)
- 数据库迁移000082:7张表15+字段,含存量retail_price回填
- BUG-1修复:代理零售价渠道隔离,cost_price分配锁定
- BUG-2修复:一次性佣金仅客户端订单触发
- BUG-4修复:充值回调Store操作纳入事务
- 新增资产手动停用接口(PATCH /iot-cards/:id/deactivate、/devices/:id/deactivate)
- Carrier管理新增实名链接配置
- 后台订单generation写时快照
- BatchUpdatePricing支持retail_price调价目标
- 清理全部H5旧接口和个人客户旧登录方法
This commit is contained in:
2026-03-19 10:56:50 +08:00
parent 817d0d6e04
commit ec86dbf463
70 changed files with 1438 additions and 1188 deletions

View File

@@ -0,0 +1,59 @@
package admin
import (
"context"
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// AssetLifecycleService 资产生命周期服务接口
type AssetLifecycleService interface {
// DeactivateIotCard 停用 IoT 卡
DeactivateIotCard(ctx context.Context, id uint) error
// DeactivateDevice 停用设备
DeactivateDevice(ctx context.Context, id uint) error
}
// AssetLifecycleHandler 资产生命周期处理器
type AssetLifecycleHandler struct {
service AssetLifecycleService
}
// NewAssetLifecycleHandler 创建资产生命周期处理器
func NewAssetLifecycleHandler(service AssetLifecycleService) *AssetLifecycleHandler {
return &AssetLifecycleHandler{service: service}
}
// DeactivateIotCard 手动停用 IoT 卡
// PATCH /api/admin/iot-cards/:id/deactivate
func (h *AssetLifecycleHandler) DeactivateIotCard(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的卡ID")
}
if err := h.service.DeactivateIotCard(c.UserContext(), uint(id)); err != nil {
return err
}
return response.Success(c, nil)
}
// DeactivateDevice 手动停用设备
// PATCH /api/admin/devices/:id/deactivate
func (h *AssetLifecycleHandler) DeactivateDevice(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的设备ID")
}
if err := h.service.DeactivateDevice(c.UserContext(), uint(id)); err != nil {
return err
}
return response.Success(c, nil)
}

View File

@@ -3,7 +3,6 @@
package app
import (
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/service/personal_customer"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
@@ -25,45 +24,6 @@ func NewPersonalCustomerHandler(service *personal_customer.Service, logger *zap.
}
}
// SendCodeRequest 发送验证码请求
type SendCodeRequest struct {
Phone string `json:"phone" validate:"required,len=11"` // 手机号11位
}
// SendCode 发送验证码
// POST /api/c/v1/login/send-code
func (h *PersonalCustomerHandler) SendCode(c *fiber.Ctx) error {
var req SendCodeRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
// 发送验证码
if err := h.service.SendVerificationCode(c.Context(), req.Phone); err != nil {
h.logger.Error("发送验证码失败",
zap.String("phone", req.Phone),
zap.Error(err),
)
return errors.Wrap(errors.CodeInternalError, err, "发送验证码失败")
}
return response.Success(c, fiber.Map{
"message": "验证码已发送",
})
}
// LoginRequest 登录请求
type LoginRequest struct {
Phone string `json:"phone" validate:"required,len=11"` // 手机号11位
Code string `json:"code" validate:"required,len=6"` // 验证码6位
}
// LoginResponse 登录响应
type LoginResponse struct {
Token string `json:"token"` // 访问令牌
Customer *PersonalCustomerDTO `json:"customer"` // 客户信息
}
// PersonalCustomerDTO 个人客户 DTO
type PersonalCustomerDTO struct {
ID uint `json:"id"`
@@ -74,87 +34,6 @@ type PersonalCustomerDTO struct {
Status int `json:"status"`
}
// Login 登录(手机号 + 验证码)
// POST /api/c/v1/login
func (h *PersonalCustomerHandler) Login(c *fiber.Ctx) error {
var req LoginRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
// 登录
token, customer, err := h.service.LoginByPhone(c.Context(), req.Phone, req.Code)
if err != nil {
h.logger.Error("登录失败",
zap.String("phone", req.Phone),
zap.Error(err),
)
return errors.Wrap(errors.CodeInternalError, err, "登录失败")
}
// 构造响应
// 注意Phone 字段已从 PersonalCustomer 模型移除,需要从 PersonalCustomerPhone 表查询
resp := &LoginResponse{
Token: token,
Customer: &PersonalCustomerDTO{
ID: customer.ID,
Phone: req.Phone, // 使用请求中的手机号(临时方案)
Nickname: customer.Nickname,
AvatarURL: customer.AvatarURL,
WxOpenID: customer.WxOpenID,
Status: customer.Status,
},
}
return response.Success(c, resp)
}
// WechatOAuthLogin 微信 OAuth 登录
// POST /api/c/v1/wechat/auth
func (h *PersonalCustomerHandler) WechatOAuthLogin(c *fiber.Ctx) error {
var req dto.WechatOAuthRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
result, err := h.service.WechatOAuthLogin(c.Context(), req.Code)
if err != nil {
h.logger.Error("微信 OAuth 登录失败",
zap.String("code", req.Code),
zap.Error(err),
)
return err
}
return response.Success(c, result)
}
// BindWechat 绑定微信
// POST /api/c/v1/bind-wechat
func (h *PersonalCustomerHandler) BindWechat(c *fiber.Ctx) error {
var req dto.WechatOAuthRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
customerID, ok := c.Locals("customer_id").(uint)
if !ok {
return errors.New(errors.CodeUnauthorized, "未找到客户信息")
}
if err := h.service.BindWechatWithCode(c.Context(), customerID, req.Code); err != nil {
h.logger.Error("绑定微信失败",
zap.Uint("customer_id", customerID),
zap.Error(err),
)
return err
}
return response.Success(c, fiber.Map{
"message": "绑定成功",
})
}
// UpdateProfileRequest 更新个人资料请求
type UpdateProfileRequest struct {
Nickname string `json:"nickname"` // 昵称

View File

@@ -1,160 +0,0 @@
package h5
import (
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/service/auth"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
// AuthHandler H5认证处理器
type AuthHandler struct {
authService *auth.Service
validator *validator.Validate
}
// NewAuthHandler 创建H5认证处理器
func NewAuthHandler(authService *auth.Service, validator *validator.Validate) *AuthHandler {
return &AuthHandler{
authService: authService,
validator: validator,
}
}
// Login H5登录
func (h *AuthHandler) Login(c *fiber.Ctx) error {
var req dto.LoginRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.validator.Struct(&req); err != nil {
logger.GetAppLogger().Warn("参数验证失败",
zap.String("path", c.Path()),
zap.String("method", c.Method()),
zap.Error(err),
)
return errors.New(errors.CodeInvalidParam)
}
clientIP := c.IP()
ctx := c.UserContext()
resp, err := h.authService.Login(ctx, &req, clientIP)
if err != nil {
return err
}
return response.Success(c, resp)
}
// Logout H5登出
func (h *AuthHandler) Logout(c *fiber.Ctx) error {
auth := c.Get("Authorization")
accessToken := ""
if len(auth) > 7 && auth[:7] == "Bearer " {
accessToken = auth[7:]
}
refreshToken := ""
var req dto.RefreshTokenRequest
if err := c.BodyParser(&req); err == nil {
refreshToken = req.RefreshToken
}
ctx := c.UserContext()
if err := h.authService.Logout(ctx, accessToken, refreshToken); err != nil {
return err
}
return response.Success(c, nil)
}
// RefreshToken 刷新访问令牌
func (h *AuthHandler) RefreshToken(c *fiber.Ctx) error {
var req dto.RefreshTokenRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.validator.Struct(&req); err != nil {
logger.GetAppLogger().Warn("参数验证失败",
zap.String("path", c.Path()),
zap.String("method", c.Method()),
zap.Error(err),
)
return errors.New(errors.CodeInvalidParam)
}
ctx := c.UserContext()
newAccessToken, err := h.authService.RefreshToken(ctx, req.RefreshToken)
if err != nil {
return err
}
resp := &dto.RefreshTokenResponse{
AccessToken: newAccessToken,
ExpiresIn: 86400,
}
return response.Success(c, resp)
}
// GetMe 获取当前用户信息
func (h *AuthHandler) GetMe(c *fiber.Ctx) error {
userID := middleware.GetUserIDFromContext(c.UserContext())
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
ctx := c.UserContext()
userInfo, permissions, err := h.authService.GetCurrentUser(ctx, userID)
if err != nil {
return err
}
data := map[string]interface{}{
"user": userInfo,
"permissions": permissions,
}
return response.Success(c, data)
}
// ChangePassword 修改密码
func (h *AuthHandler) ChangePassword(c *fiber.Ctx) error {
userID := middleware.GetUserIDFromContext(c.UserContext())
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
var req dto.ChangePasswordRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.validator.Struct(&req); err != nil {
logger.GetAppLogger().Warn("参数验证失败",
zap.String("path", c.Path()),
zap.String("method", c.Method()),
zap.Error(err),
)
return errors.New(errors.CodeInvalidParam)
}
ctx := c.UserContext()
if err := h.authService.ChangePassword(ctx, userID, req.OldPassword, req.NewPassword); err != nil {
return err
}
return response.Success(c, nil)
}

View File

@@ -1,55 +0,0 @@
package h5
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
enterpriseDeviceService "github.com/break/junhong_cmp_fiber/internal/service/enterprise_device"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type EnterpriseDeviceHandler struct {
service *enterpriseDeviceService.Service
}
func NewEnterpriseDeviceHandler(service *enterpriseDeviceService.Service) *EnterpriseDeviceHandler {
return &EnterpriseDeviceHandler{service: service}
}
func (h *EnterpriseDeviceHandler) ListDevices(c *fiber.Ctx) error {
var req dto.H5EnterpriseDeviceListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
serviceReq := &dto.EnterpriseDeviceListReq{
Page: req.Page,
PageSize: req.PageSize,
VirtualNo: req.VirtualNo,
}
result, err := h.service.ListDevicesForEnterprise(c.UserContext(), serviceReq)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.List, result.Total, req.Page, req.PageSize)
}
func (h *EnterpriseDeviceHandler) GetDeviceDetail(c *fiber.Ctx) error {
deviceIDStr := c.Params("device_id")
deviceID, err := strconv.ParseUint(deviceIDStr, 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "设备ID格式错误")
}
result, err := h.service.GetDeviceDetail(c.UserContext(), uint(deviceID))
if err != nil {
return err
}
return response.Success(c, result)
}

View File

@@ -1,211 +0,0 @@
package h5
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
orderService "github.com/break/junhong_cmp_fiber/internal/service/order"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
type OrderHandler struct {
service *orderService.Service
}
func NewOrderHandler(service *orderService.Service) *OrderHandler {
return &OrderHandler{service: service}
}
func (h *OrderHandler) Create(c *fiber.Ctx) error {
var req dto.CreateOrderRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if req.PaymentMethod != model.PaymentMethodWallet {
return errors.New(errors.CodeInvalidParam, "H5端只支持钱包支付")
}
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
var buyerType string
var buyerID uint
switch userType {
case constants.UserTypeAgent:
buyerType = model.BuyerTypeAgent
buyerID = middleware.GetShopIDFromContext(ctx)
case constants.UserTypeEnterprise:
return errors.New(errors.CodeForbidden, "企业账号不支持在线购买")
case constants.UserTypePersonalCustomer:
buyerType = model.BuyerTypePersonal
buyerID = middleware.GetCustomerIDFromContext(ctx)
default:
return errors.New(errors.CodeForbidden, "不支持的用户类型")
}
order, err := h.service.CreateH5Order(ctx, &req, buyerType, buyerID)
if err != nil {
return err
}
return response.Success(c, order)
}
func (h *OrderHandler) Get(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的订单ID")
}
order, err := h.service.Get(c.UserContext(), uint(id))
if err != nil {
return err
}
return response.Success(c, order)
}
func (h *OrderHandler) List(c *fiber.Ctx) error {
var req dto.OrderListRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
var buyerType string
var buyerID uint
switch userType {
case constants.UserTypeAgent:
buyerType = model.BuyerTypeAgent
buyerID = middleware.GetShopIDFromContext(ctx)
case constants.UserTypePersonalCustomer:
buyerType = model.BuyerTypePersonal
buyerID = middleware.GetCustomerIDFromContext(ctx)
default:
return errors.New(errors.CodeForbidden, "不支持的用户类型")
}
orders, err := h.service.List(ctx, &req, buyerType, buyerID)
if err != nil {
return err
}
return response.Success(c, orders)
}
func (h *OrderHandler) WalletPay(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的订单ID")
}
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
var buyerType string
var buyerID uint
switch userType {
case constants.UserTypeAgent:
buyerType = model.BuyerTypeAgent
buyerID = middleware.GetShopIDFromContext(ctx)
case constants.UserTypePersonalCustomer:
buyerType = model.BuyerTypePersonal
buyerID = middleware.GetCustomerIDFromContext(ctx)
default:
return errors.New(errors.CodeForbidden, "不支持的用户类型")
}
if err := h.service.WalletPay(ctx, uint(id), buyerType, buyerID); err != nil {
return err
}
return response.Success(c, nil)
}
// WechatPayJSAPI 微信 JSAPI 支付
// POST /api/h5/orders/:id/wechat-pay/jsapi
func (h *OrderHandler) WechatPayJSAPI(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的订单ID")
}
var req dto.WechatPayJSAPIRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
var buyerType string
var buyerID uint
switch userType {
case constants.UserTypeAgent:
buyerType = model.BuyerTypeAgent
buyerID = middleware.GetShopIDFromContext(ctx)
case constants.UserTypePersonalCustomer:
buyerType = model.BuyerTypePersonal
buyerID = middleware.GetCustomerIDFromContext(ctx)
default:
return errors.New(errors.CodeForbidden, "不支持的用户类型")
}
result, err := h.service.WechatPayJSAPI(ctx, uint(id), req.OpenID, buyerType, buyerID)
if err != nil {
return err
}
return response.Success(c, result)
}
// WechatPayH5 微信 H5 支付
// POST /api/h5/orders/:id/wechat-pay/h5
func (h *OrderHandler) WechatPayH5(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的订单ID")
}
var req dto.WechatPayH5Request
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
var buyerType string
var buyerID uint
switch userType {
case constants.UserTypeAgent:
buyerType = model.BuyerTypeAgent
buyerID = middleware.GetShopIDFromContext(ctx)
case constants.UserTypePersonalCustomer:
buyerType = model.BuyerTypePersonal
buyerID = middleware.GetCustomerIDFromContext(ctx)
default:
return errors.New(errors.CodeForbidden, "不支持的用户类型")
}
result, err := h.service.WechatPayH5(ctx, uint(id), &req.SceneInfo, buyerType, buyerID)
if err != nil {
return err
}
return response.Success(c, result)
}

View File

@@ -1,93 +0,0 @@
package h5
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model"
packageService "github.com/break/junhong_cmp_fiber/internal/service/package"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
"gorm.io/gorm"
)
// PackageUsageHandler H5 端套餐使用情况 Handler
type PackageUsageHandler struct {
db *gorm.DB
customerViewService *packageService.CustomerViewService
}
// NewPackageUsageHandler 创建 H5 端套餐使用情况 Handler
func NewPackageUsageHandler(db *gorm.DB, customerViewService *packageService.CustomerViewService) *PackageUsageHandler {
return &PackageUsageHandler{
db: db,
customerViewService: customerViewService,
}
}
// GetMyUsage 任务 15.2-15.5: 获取我的套餐使用情况
// GET /api/h5/packages/my-usage
func (h *PackageUsageHandler) GetMyUsage(c *fiber.Ctx) error {
ctx := c.UserContext()
// 任务 15.3: 从 JWT 上下文中提取用户信息
userType := middleware.GetUserTypeFromContext(ctx)
var carrierType string
var carrierID uint
// 根据用户类型获取载体信息
switch userType {
case constants.UserTypePersonalCustomer:
// 个人客户:查询其订单关联的 IoT 卡或设备
customerID := middleware.GetCustomerIDFromContext(ctx)
if customerID == 0 {
return errors.New(errors.CodeInvalidParam, "未找到客户信息")
}
// 查询该客户的套餐使用记录,获取载体信息
var usage model.PackageUsage
err := h.db.WithContext(ctx).
Joins("JOIN tb_order ON tb_order.id = tb_package_usage.order_id").
Where("tb_order.buyer_type = ? AND tb_order.buyer_id = ?", model.BuyerTypePersonal, customerID).
Where("tb_package_usage.status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}).
Order("tb_package_usage.activated_at DESC").
First(&usage).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "未找到套餐使用记录")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询套餐使用记录失败")
}
// 确定载体类型和 ID
if usage.IotCardID > 0 {
carrierType = "iot_card"
carrierID = usage.IotCardID
} else if usage.DeviceID > 0 {
carrierType = "device"
carrierID = usage.DeviceID
} else {
return errors.New(errors.CodeInvalidParam, "套餐使用记录未关联卡或设备")
}
case constants.UserTypeAgent, constants.UserTypeEnterprise:
// 代理和企业用户暂不支持通过此接口查询
// 他们应该使用后台管理接口查询指定卡/设备的套餐情况
return errors.New(errors.CodeForbidden, "此接口仅供个人客户使用")
default:
return errors.New(errors.CodeForbidden, "不支持的用户类型")
}
// 任务 15.4: 调用 CustomerViewService.GetMyUsage 获取流量数据
usageData, err := h.customerViewService.GetMyUsage(ctx, carrierType, carrierID)
if err != nil {
return err
}
// 任务 15.5: 返回 PackageUsageCustomerViewResponse 响应
return response.Success(c, usageData)
}

View File

@@ -1,169 +0,0 @@
package h5
import (
"strconv"
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
rechargeService "github.com/break/junhong_cmp_fiber/internal/service/recharge"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// RechargeHandler 充值订单处理器
// 提供充值订单的创建、预检、查询等接口
type RechargeHandler struct {
service *rechargeService.Service
}
// NewRechargeHandler 创建充值订单处理器实例
// 参数:
// - service: 充值服务
//
// 返回:
// - *RechargeHandler: 充值订单处理器实例
func NewRechargeHandler(service *rechargeService.Service) *RechargeHandler {
return &RechargeHandler{service: service}
}
// Create 创建充值订单
// POST /api/h5/wallets/recharge
// 请求参数:
// - resource_type: 资源类型iot_card/device
// - resource_id: 资源ID卡ID或设备ID
// - amount: 充值金额(分)
// - payment_method: 支付方式wechat/alipay
//
// 响应:
// - 成功: 返回充值订单信息订单ID、订单号、金额、状态等
// - 失败: 返回错误信息
func (h *RechargeHandler) Create(c *fiber.Ctx) error {
var req dto.CreateRechargeRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
// 获取个人客户ID作为用户ID
userID := middleware.GetCustomerIDFromContext(ctx)
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "用户未登录")
}
result, err := h.service.Create(ctx, &req, userID)
if err != nil {
return err
}
return response.Success(c, result)
}
// RechargeCheck 充值预检
// GET /api/h5/wallets/recharge-check
// 请求参数:
// - resource_type: 资源类型iot_card/device
// - resource_id: 资源ID卡ID或设备ID
//
// 响应:
// - 成功: 返回预检信息(是否需要强充、强充金额、最小/最大充值金额等)
// - 失败: 返回错误信息
func (h *RechargeHandler) RechargeCheck(c *fiber.Ctx) error {
var req dto.RechargeCheckRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
// 验证必填参数
if req.ResourceType == "" {
return errors.New(errors.CodeInvalidParam, "资源类型不能为空")
}
if req.ResourceID == 0 {
return errors.New(errors.CodeInvalidParam, "资源ID不能为空")
}
ctx := c.UserContext()
result, err := h.service.GetRechargeCheck(ctx, req.ResourceType, req.ResourceID)
if err != nil {
return err
}
// 转换为 DTO 响应
resp := &dto.RechargeCheckResponse{
NeedForceRecharge: result.NeedForceRecharge,
ForceRechargeAmount: result.ForceRechargeAmount,
TriggerType: result.TriggerType,
MinAmount: result.MinAmount,
MaxAmount: result.MaxAmount,
CurrentAccumulated: result.CurrentAccumulated,
Threshold: result.Threshold,
Message: result.Message,
FirstCommissionPaid: result.FirstCommissionPaid,
}
return response.Success(c, resp)
}
// List 查询充值订单列表
// GET /api/h5/wallets/recharges
// 请求参数:
// - page: 页码从1开始默认1
// - page_size: 每页数量默认20最大100
// - wallet_id: 钱包ID筛选可选
// - status: 状态筛选可选1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款)
// - start_time: 开始时间筛选(可选)
// - end_time: 结束时间筛选(可选)
//
// 响应:
// - 成功: 返回充值订单列表(分页数据、总记录数、总页数)
// - 失败: 返回错误信息
func (h *RechargeHandler) List(c *fiber.Ctx) error {
var req dto.RechargeListRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
ctx := c.UserContext()
// 获取个人客户ID作为用户ID
userID := middleware.GetCustomerIDFromContext(ctx)
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "用户未登录")
}
result, err := h.service.List(ctx, &req, userID)
if err != nil {
return err
}
return response.Success(c, result)
}
// Get 查询充值订单详情
// GET /api/h5/wallets/recharges/:id
// 路径参数:
// - id: 充值订单ID
//
// 响应:
// - 成功: 返回充值订单详情订单ID、订单号、金额、状态、支付信息等
// - 失败: 返回错误信息
func (h *RechargeHandler) Get(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的充值订单ID")
}
ctx := c.UserContext()
// 获取个人客户ID作为用户ID
userID := middleware.GetCustomerIDFromContext(ctx)
if userID == 0 {
return errors.New(errors.CodeUnauthorized, "用户未登录")
}
result, err := h.service.GetByID(ctx, uint(id), userID)
if err != nil {
return err
}
return response.Success(c, result)
}