Files
junhong_cmp_fiber/internal/routes/personal.go
break 333ba4b647
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m23s
feat(H5弹窗): AUG26-007 风险换卡与运营弹窗投放通知
新增 000225 迁移:运营弹窗配置表 tb_h5_popup_configuration(页面/范围/优先级/频率/受控动作/启停/有效期/版本)
与 tb_notification 可空 JSONB 列 popup_snapshot。

新增通知直建窄接口 DirectWriter.CreateOrGetPersonal:与 Outbox 消费共用 prepareDelivery 的渲染、
展示期与 CreateIdempotent 规则,冲突时回查返回既有行;同步扩展个人通知查询与已读两处类型白名单,
并按个人客户入口补齐投递审计来源。

新增 H5 候选与风险换卡:GET /api/c/v1/popup-candidates 先判风险资格(广电卡 + 风险停机 +
无活动物流换货单),命中只返回风险候选;未命中再按时间/启停/页面/店铺/设备类型/卡类型范围/频率
匹配运营配置。POST /api/c/v1/risk-exchanges/:asset_id/address 锁资产行后幂等创建待发货物流换货单,
首次地址锁定,不沿用资产级群发通知。

新增后台运营弹窗配置 CRUD 与启停(仅超级管理员与平台账号),更新递增版本并刷新最近更新时间,
标题与正文统一拒绝 URL 与前端路由,全部写操作记录操作者、前后值、版本与时间。

同步 OpenAPI(cmd/gendocs、cmd/api/docs.go、pkg/openapi/handlers.go)与参数校验中文提示共用实现。
2026-09-15 15:23:52 +08:00

333 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
apphandler "github.com/break/junhong_cmp_fiber/internal/handler/app"
"github.com/break/junhong_cmp_fiber/internal/middleware"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/config"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// RegisterPersonalCustomerRoutes 注册个人客户路由
// 路由挂载在 /api/c/v1 下
//
// 重要Fiber 的 Group.Use() 会在路由表中注册全局 USE 处理器,
// 匹配该前缀下的所有请求(不区分 Group 对象)。
// 因此公开路由必须在任何 Use() 调用之前注册,利用 Fiber 按注册顺序匹配的机制,
// 确保公开路由优先命中并直接返回,不会被后续的认证中间件拦截。
func RegisterPersonalCustomerRoutes(router fiber.Router, doc *openapi.Generator, basePath string, handlers *bootstrap.Handlers, personalAuthMiddleware *middleware.PersonalAuthMiddleware) {
authBasePath := "/auth"
// === 公开路由(无需认证)===
// 代理扫码注册必须在任何 Use() 调用之前注册,否则会被后续认证中间件拦截。
registerAgentDistributionPublicRoutes(router, handlers.AgentDistribution, doc, basePath)
Register(router, doc, basePath, "GET", "/wechat/appid", handlers.ClientWechat.GetAppID, RouteSpec{
Summary: "获取当前生效的公众号 AppID",
Description: "用于 C 端免登录场景获取当前生效微信配置中的公众号 AppID响应仅返回 app_id 字段",
Tags: []string{"个人客户 - 微信"},
Auth: false,
Input: nil,
Output: &dto.WechatAppIDResponse{},
})
Register(router, doc, basePath, "GET", "/wechat/jssdk-config", handlers.ClientWechat.GetJSSDKConfig, RouteSpec{
Summary: "获取微信 JSSDK 签名配置",
Description: "前端调用 wx.config() 初始化微信 JS-SDK 时所需的签名参数,需传入当前页面完整 URL",
Tags: []string{"个人客户 - 微信"},
Auth: false,
Input: &dto.JSSDKConfigRequest{},
Output: &dto.JSSDKConfigResponse{},
})
Register(router, doc, basePath, "POST", authBasePath+"/verify-asset", handlers.ClientAuth.VerifyAsset, RouteSpec{
Summary: "资产验证",
Tags: []string{"个人客户 - 认证"},
Auth: false,
Input: &dto.VerifyAssetRequest{},
Output: &dto.VerifyAssetResponse{},
})
Register(router, doc, basePath, "POST", authBasePath+"/wechat-login", handlers.ClientAuth.WechatLogin, RouteSpec{
Summary: "公众号登录",
Tags: []string{"个人客户 - 认证"},
Auth: false,
Input: &dto.WechatLoginRequest{},
Output: &dto.WechatLoginResponse{},
})
Register(router, doc, basePath, "POST", authBasePath+"/miniapp-login", handlers.ClientAuth.MiniappLogin, RouteSpec{
Summary: "小程序登录",
Tags: []string{"个人客户 - 认证"},
Auth: false,
Input: &dto.MiniappLoginRequest{},
Output: &dto.WechatLoginResponse{},
})
Register(router, doc, basePath, "POST", authBasePath+"/send-code", handlers.ClientAuth.SendCode, RouteSpec{
Summary: "发送验证码",
Tags: []string{"个人客户 - 认证"},
Auth: false,
Input: &dto.ClientSendCodeRequest{},
Output: &dto.ClientSendCodeResponse{},
})
// 开发模式测试登录接口(仅 logging.development=true 时注册)
if cfg := config.Get(); cfg != nil && cfg.Logging.Development {
Register(router, doc, basePath, "POST", authBasePath+"/dev-login", handlers.ClientAuth.DevLogin, RouteSpec{
Summary: "开发环境测试登录",
Description: "⚠️ 仅开发模式可用。传入资产标识符ICCID等自动绑定测试客户并签发 JWT用于接口测试。",
Tags: []string{"个人客户 - 认证"},
Auth: false,
Input: &dto.DevLoginRequest{},
Output: &dto.DevLoginResponse{},
})
}
// === 需要认证的 auth 路由 ===
authProtectedGroup := router.Group(authBasePath)
authProtectedGroup.Use(personalAuthMiddleware.Authenticate())
Register(authProtectedGroup, doc, basePath+authBasePath, "POST", "/bind-phone", handlers.ClientAuth.BindPhone, RouteSpec{
Summary: "绑定手机号",
Tags: []string{"个人客户 - 认证"},
Auth: true,
Input: &dto.BindPhoneRequest{},
Output: &dto.BindPhoneResponse{},
})
Register(authProtectedGroup, doc, basePath+authBasePath, "POST", "/change-phone", handlers.ClientAuth.ChangePhone, RouteSpec{
Summary: "更换手机号",
Tags: []string{"个人客户 - 认证"},
Auth: true,
Input: &dto.ChangePhoneRequest{},
Output: &dto.ChangePhoneResponse{},
})
Register(authProtectedGroup, doc, basePath+authBasePath, "POST", "/logout", handlers.ClientAuth.Logout, RouteSpec{
Summary: "退出登录",
Tags: []string{"个人客户 - 认证"},
Auth: true,
Input: nil,
Output: &dto.LogoutResponse{},
})
// 需要认证的路由
authGroup := router.Group("")
authGroup.Use(personalAuthMiddleware.Authenticate())
if handlers.ClientNotification != nil {
registerPersonalNotificationRoutes(authGroup, handlers.ClientNotification, doc, basePath)
}
if handlers.ClientPopup != nil {
registerPersonalPopupRoutes(authGroup, handlers.ClientPopup, doc, basePath)
}
// 获取个人资料
Register(authGroup, doc, basePath, "GET", "/profile", handlers.PersonalCustomer.GetProfile, RouteSpec{
Summary: "获取个人资料",
Description: "获取当前登录客户的个人资料",
Tags: []string{"个人客户 - 账户"},
Auth: true,
Input: nil,
Output: &apphandler.PersonalCustomerDTO{},
})
// 更新个人资料
Register(authGroup, doc, basePath, "PUT", "/profile", handlers.PersonalCustomer.UpdateProfile, RouteSpec{
Summary: "更新个人资料",
Description: "更新当前登录客户的昵称和头像",
Tags: []string{"个人客户 - 账户"},
Auth: true,
Input: &apphandler.UpdateProfileRequest{},
Output: nil,
})
Register(authGroup, doc, basePath, "GET", "/asset/info", handlers.ClientAsset.GetAssetInfo, RouteSpec{
Summary: "资产信息",
Tags: []string{"个人客户 - 资产"},
Auth: true,
Input: &dto.AssetInfoRequest{},
Output: &dto.AssetInfoResponse{},
})
Register(authGroup, doc, basePath, "GET", "/asset/packages", handlers.ClientAsset.GetAvailablePackages, RouteSpec{
Summary: "资产可购套餐列表",
Tags: []string{"个人客户 - 资产"},
Auth: true,
Input: &dto.AssetPackageListRequest{},
Output: &dto.AssetPackageListResponse{},
})
Register(authGroup, doc, basePath, "GET", "/asset/package-history", handlers.ClientAsset.GetPackageHistory, RouteSpec{
Summary: "资产套餐历史",
Description: "查询客户已绑定资产当前世代的套餐历史层级。每个主套餐及其关联加油包占一个分页名额total 为筛选后的顶层项数量status 与 package_type 必须由同一使用记录联合命中,命中任一成员即返回完整关系组。关联主套餐物理缺失返回异常独立项,存在但不可展示的关联主套餐返回统一读取错误。",
Tags: []string{"个人客户 - 资产"},
Auth: true,
Input: &dto.AssetPackageHistoryRequest{},
Output: &dto.AssetPackageHistoryResponse{},
})
Register(authGroup, doc, basePath, "POST", "/asset/refresh", handlers.ClientAsset.RefreshAsset, RouteSpec{
Summary: "资产刷新",
Tags: []string{"个人客户 - 资产"},
Auth: true,
Input: &dto.AssetRefreshRequest{},
Output: &dto.AssetRefreshResponse{},
})
Register(authGroup, doc, basePath, "GET", "/wallet/detail", handlers.ClientWallet.GetWalletDetail, RouteSpec{
Summary: "钱包详情",
Tags: []string{"个人客户 - 钱包"},
Auth: true,
Input: &dto.WalletDetailRequest{},
Output: &dto.WalletDetailResponse{},
})
Register(authGroup, doc, basePath, "GET", "/wallet/transactions", handlers.ClientWallet.GetWalletTransactions, RouteSpec{
Summary: "钱包流水列表",
Tags: []string{"个人客户 - 钱包"},
Auth: true,
Input: &dto.WalletTransactionListRequest{},
Output: &dto.WalletTransactionListResponse{},
})
Register(authGroup, doc, basePath, "GET", "/wallet/recharge-check", handlers.ClientWallet.GetRechargeCheck, RouteSpec{
Summary: "充值前校验",
Tags: []string{"个人客户 - 钱包"},
Auth: true,
Input: &dto.ClientRechargeCheckRequest{},
Output: &dto.ClientRechargeCheckResponse{},
})
Register(authGroup, doc, basePath, "POST", "/wallet/recharge", handlers.ClientWallet.CreateRecharge, RouteSpec{
Summary: "创建充值订单",
Tags: []string{"个人客户 - 钱包"},
Auth: true,
Input: &dto.ClientCreateRechargeRequest{},
Output: &dto.ClientRechargeResponse{},
})
Register(authGroup, doc, basePath, "GET", "/wallet/recharges", handlers.ClientWallet.GetRechargeList, RouteSpec{
Summary: "充值记录列表",
Tags: []string{"个人客户 - 钱包"},
Auth: true,
Input: &dto.ClientRechargeListRequest{},
Output: &dto.ClientRechargeListResponse{},
})
Register(authGroup, doc, basePath, "GET", "/recharge-orders", handlers.ClientRechargeOrder.ListRechargeOrders, RouteSpec{
Summary: "充值订单列表",
Tags: []string{"个人客户 - 充值订单"},
Auth: true,
Input: &dto.ClientRechargeOrderListRequest{},
Output: nil,
})
Register(authGroup, doc, basePath, "GET", "/recharge-orders/:id", handlers.ClientRechargeOrder.GetRechargeOrderDetail, RouteSpec{
Summary: "充值订单详情",
Tags: []string{"个人客户 - 充值订单"},
Auth: true,
Input: &dto.IDReq{},
Output: nil,
})
Register(authGroup, doc, basePath, "POST", "/orders/create", handlers.ClientOrder.CreateOrder, RouteSpec{
Summary: "创建订单",
Tags: []string{"个人客户 - 订单"},
Auth: true,
Input: &dto.ClientCreateOrderRequest{},
Output: &dto.ClientCreateOrderResponse{},
})
Register(authGroup, doc, basePath, "GET", "/orders", handlers.ClientOrder.ListOrders, RouteSpec{
Summary: "订单列表",
Tags: []string{"个人客户 - 订单"},
Auth: true,
Input: &dto.ClientOrderListRequest{},
Output: &dto.ClientOrderListResponse{},
})
Register(authGroup, doc, basePath, "GET", "/orders/:id", handlers.ClientOrder.GetOrderDetail, RouteSpec{
Summary: "订单详情",
Tags: []string{"个人客户 - 订单"},
Auth: true,
Input: &dto.IDReq{},
Output: &dto.ClientOrderDetailResponse{},
})
Register(authGroup, doc, basePath, "POST", "/orders/:id/pay", handlers.ClientOrder.PayOrder, RouteSpec{
Summary: "订单支付",
Tags: []string{"个人客户 - 订单"},
Auth: true,
Input: &dto.ClientPayOrderParams{},
Output: &dto.ClientPayOrderResponse{},
})
Register(authGroup, doc, basePath, "GET", "/exchange/pending", handlers.ClientExchange.GetPending, RouteSpec{
Summary: "查询待处理换货单",
Tags: []string{"个人客户 - 换货"},
Auth: true,
Input: &dto.ClientExchangePendingRequest{},
Output: &dto.ClientExchangePendingResponse{},
})
Register(authGroup, doc, basePath, "POST", "/exchange/:id/shipping-info", handlers.ClientExchange.SubmitShippingInfo, RouteSpec{
Summary: "提交收货信息",
Tags: []string{"个人客户 - 换货"},
Auth: true,
Input: &dto.ClientShippingInfoParams{},
Output: nil,
})
Register(authGroup, doc, basePath, "GET", "/realname/link", handlers.ClientRealname.GetRealnameLink, RouteSpec{
Summary: "获取实名认证链接",
Tags: []string{"个人客户 - 实名"},
Auth: true,
Input: &dto.RealnimeLinkRequest{},
Output: &dto.RealnimeLinkResponse{},
})
Register(authGroup, doc, basePath, "GET", "/device/cards", handlers.ClientDevice.GetDeviceCards, RouteSpec{
Summary: "获取设备卡列表",
Tags: []string{"个人客户 - 设备"},
Auth: true,
Input: &dto.DeviceCardListRequest{},
Output: &dto.DeviceCardListResponse{},
})
Register(authGroup, doc, basePath, "POST", "/device/reboot", handlers.ClientDevice.RebootDevice, RouteSpec{
Summary: "设备重启",
Tags: []string{"个人客户 - 设备"},
Auth: true,
Input: &dto.DeviceRebootRequest{},
Output: &dto.DeviceOperationResponse{},
})
Register(authGroup, doc, basePath, "POST", "/device/factory-reset", handlers.ClientDevice.FactoryResetDevice, RouteSpec{
Summary: "恢复出厂设置",
Tags: []string{"个人客户 - 设备"},
Auth: true,
Input: &dto.DeviceFactoryResetRequest{},
Output: &dto.DeviceOperationResponse{},
})
Register(authGroup, doc, basePath, "POST", "/device/wifi", handlers.ClientDevice.SetWiFi, RouteSpec{
Summary: "设备WiFi配置",
Tags: []string{"个人客户 - 设备"},
Auth: true,
Input: &dto.DeviceWifiRequest{},
Output: &dto.DeviceOperationResponse{},
})
Register(authGroup, doc, basePath, "POST", "/device/switch-card", handlers.ClientDevice.SwitchCard, RouteSpec{
Summary: "设备切卡",
Tags: []string{"个人客户 - 设备"},
Auth: true,
Input: &dto.DeviceSwitchCardRequest{},
Output: &dto.DeviceSwitchCardResponse{},
})
}