Compare commits
3 Commits
5d9be1d7e4
...
7e4c61e6e9
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e4c61e6e9 | |||
| 2d0b4e9bc0 | |||
| 520b126ecf |
25
CLAUDE.md
25
CLAUDE.md
@@ -113,6 +113,24 @@ Handler → Service → Store → Model
|
|||||||
- 禁止硬编码字符串和 magic numbers
|
- 禁止硬编码字符串和 magic numbers
|
||||||
- **必须为所有常量添加中文注释**
|
- **必须为所有常量添加中文注释**
|
||||||
|
|
||||||
|
### 枚举与状态字段(必须遵守)
|
||||||
|
|
||||||
|
**两个强制规则**:
|
||||||
|
|
||||||
|
1. **int vs string**:状态类(生命周期)用 `int`,类型/方式类用 `string`
|
||||||
|
2. **DTO description 必须从 constants 原文抄写**,禁止凭记忆填写枚举值(历史上已有 description 与 constants 不一致导致前端显示错误的案例)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ✅ description 从 constants 原文抄,格式统一用冒号+逗号
|
||||||
|
Status int `json:"status" description:"状态 (1:待支付, 2:已支付, 3:已完成, 4:已关闭, 5:已退款)"`
|
||||||
|
StatusName string `json:"status_name" description:"状态名称(中文)"` // Response DTO 必须加
|
||||||
|
|
||||||
|
// ❌ 禁止:启用/禁用用 1=启用 2=禁用(全局约定是 0=禁用, 1=启用)
|
||||||
|
// ❌ 禁止:description 枚举值与 constants 不一致
|
||||||
|
```
|
||||||
|
|
||||||
|
**完整规范**: 参见 [`docs/enum-status-standards.md`](docs/enum-status-standards.md)
|
||||||
|
|
||||||
### 注释规范
|
### 注释规范
|
||||||
|
|
||||||
- **所有注释使用中文**,导出符号必须有文档注释(包、函数、类型、接口、常量)
|
- **所有注释使用中文**,导出符号必须有文档注释(包、函数、类型、接口、常量)
|
||||||
@@ -220,6 +238,13 @@ Handler → Service → Store → Model
|
|||||||
- [ ] 常量定义在 `pkg/constants/`
|
- [ ] 常量定义在 `pkg/constants/`
|
||||||
- [ ] 使用 Go 惯用法(非 Java 风格)
|
- [ ] 使用 Go 惯用法(非 Java 风格)
|
||||||
|
|
||||||
|
### 枚举与状态
|
||||||
|
|
||||||
|
- [ ] 状态类字段用 `int`,类型/方式类字段用 `string`
|
||||||
|
- [ ] 禁用/启用使用 `0=禁用, 1=启用`(禁止 `1=启用, 2=禁用`)
|
||||||
|
- [ ] DTO description 枚举列表已从 `pkg/constants/` 原文抄写,无遗漏、无错误
|
||||||
|
- [ ] Response DTO 的 int 状态字段有对应的 `_name` 文字字段
|
||||||
|
|
||||||
### 文档和注释
|
### 文档和注释
|
||||||
|
|
||||||
- [ ] 所有注释使用中文
|
- [ ] 所有注释使用中文
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewClientRechargeOrderHandler(
|
|||||||
return &ClientRechargeOrderHandler{
|
return &ClientRechargeOrderHandler{
|
||||||
rechargeOrderStore: rechargeOrderStore,
|
rechargeOrderStore: rechargeOrderStore,
|
||||||
paymentStore: paymentStore,
|
paymentStore: paymentStore,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +64,10 @@ func (h *ClientRechargeOrderHandler) ListRechargeOrders(c *fiber.Ctx) error {
|
|||||||
PageSize: req.PageSize,
|
PageSize: req.PageSize,
|
||||||
UserID: &customerID,
|
UserID: &customerID,
|
||||||
}
|
}
|
||||||
|
if assetType, assetID, ok := middleware.GetCurrentAsset(c); ok {
|
||||||
|
params.ResourceType = &assetType
|
||||||
|
params.ResourceID = &assetID
|
||||||
|
}
|
||||||
if req.Status > 0 {
|
if req.Status > 0 {
|
||||||
params.Status = &req.Status
|
params.Status = &req.Status
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ func (m *PersonalAuthMiddleware) Authenticate() fiber.Handler {
|
|||||||
|
|
||||||
c.Locals("customer_id", claims.CustomerID)
|
c.Locals("customer_id", claims.CustomerID)
|
||||||
c.Locals("customer_phone", claims.Phone)
|
c.Locals("customer_phone", claims.Phone)
|
||||||
|
c.Locals("customer_asset_type", claims.AssetType)
|
||||||
|
c.Locals("customer_asset_id", claims.AssetID)
|
||||||
c.Locals("skip_owner_filter", true)
|
c.Locals("skip_owner_filter", true)
|
||||||
|
|
||||||
m.logger.Debug("个人客户认证成功",
|
m.logger.Debug("个人客户认证成功",
|
||||||
@@ -113,3 +115,11 @@ func GetCustomerPhone(c *fiber.Ctx) (string, bool) {
|
|||||||
phone, ok := c.Locals("customer_phone").(string)
|
phone, ok := c.Locals("customer_phone").(string)
|
||||||
return phone, ok
|
return phone, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCurrentAsset 从 context 中获取当前登录所用的资产类型和 ID
|
||||||
|
func GetCurrentAsset(c *fiber.Ctx) (assetType string, assetID uint, ok bool) {
|
||||||
|
assetType, _ = c.Locals("customer_asset_type").(string)
|
||||||
|
assetID, _ = c.Locals("customer_asset_id").(uint)
|
||||||
|
ok = assetType != "" && assetID != 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ func (s *Service) WechatLogin(ctx context.Context, req *dto.WechatLoginRequest,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, needBindPhone, err := s.issueLoginToken(ctx, customerID)
|
token, needBindPhone, err := s.issueLoginToken(ctx, customerID, assetClaims.AssetType, assetClaims.AssetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ func (s *Service) MiniappLogin(ctx context.Context, req *dto.MiniappLoginRequest
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, needBindPhone, err := s.issueLoginToken(ctx, customerID)
|
token, needBindPhone, err := s.issueLoginToken(ctx, customerID, assetClaims.AssetType, assetClaims.AssetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -691,7 +691,7 @@ func (s *Service) markAssetAsSold(ctx context.Context, tx *gorm.DB, assetType st
|
|||||||
return errors.New(errors.CodeInvalidParam)
|
return errors.New(errors.CodeInvalidParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) issueLoginToken(ctx context.Context, customerID uint) (string, bool, error) {
|
func (s *Service) issueLoginToken(ctx context.Context, customerID uint, assetType string, assetID uint) (string, bool, error) {
|
||||||
// 查询用户已绑定的主手机号,写入 JWT,供后续接口直接从 context 取用
|
// 查询用户已绑定的主手机号,写入 JWT,供后续接口直接从 context 取用
|
||||||
var boundPhone string
|
var boundPhone string
|
||||||
needBindPhone := false
|
needBindPhone := false
|
||||||
@@ -704,7 +704,7 @@ func (s *Service) issueLoginToken(ctx context.Context, customerID uint) (string,
|
|||||||
needBindPhone = true
|
needBindPhone = true
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := s.jwtManager.GeneratePersonalCustomerToken(customerID, boundPhone)
|
token, err := s.jwtManager.GeneratePersonalCustomerToken(customerID, boundPhone, assetType, assetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, errors.Wrap(errors.CodeInternalError, err, "生成登录令牌失败")
|
return "", false, errors.Wrap(errors.CodeInternalError, err, "生成登录令牌失败")
|
||||||
}
|
}
|
||||||
@@ -762,7 +762,7 @@ func (s *Service) DevLogin(ctx context.Context, identifier string) (string, uint
|
|||||||
return "", 0, false, err
|
return "", 0, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, _, err := s.issueLoginToken(ctx, customerID)
|
token, _, err := s.issueLoginToken(ctx, customerID, assetType, assetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, false, err
|
return "", 0, false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,14 +131,16 @@ func (s *RechargeOrderStore) ListByUserID(ctx context.Context, userID uint, offs
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListRechargeOrderParams struct {
|
type ListRechargeOrderParams struct {
|
||||||
Page int
|
Page int
|
||||||
PageSize int
|
PageSize int
|
||||||
UserID *uint
|
UserID *uint
|
||||||
IotCardID *uint
|
IotCardID *uint
|
||||||
DeviceID *uint
|
DeviceID *uint
|
||||||
Status *int
|
Status *int
|
||||||
StartTime interface{}
|
StartTime interface{}
|
||||||
EndTime interface{}
|
EndTime interface{}
|
||||||
|
ResourceType *string
|
||||||
|
ResourceID *uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RechargeOrderStore) List(ctx context.Context, params *ListRechargeOrderParams) ([]*model.RechargeOrder, int64, error) {
|
func (s *RechargeOrderStore) List(ctx context.Context, params *ListRechargeOrderParams) ([]*model.RechargeOrder, int64, error) {
|
||||||
@@ -159,6 +161,9 @@ func (s *RechargeOrderStore) List(ctx context.Context, params *ListRechargeOrder
|
|||||||
if params.Status != nil {
|
if params.Status != nil {
|
||||||
query = query.Where("status = ?", *params.Status)
|
query = query.Where("status = ?", *params.Status)
|
||||||
}
|
}
|
||||||
|
if params.ResourceType != nil && params.ResourceID != nil {
|
||||||
|
query = query.Where("resource_type = ? AND resource_id = ?", *params.ResourceType, *params.ResourceID)
|
||||||
|
}
|
||||||
if params.StartTime != nil {
|
if params.StartTime != nil {
|
||||||
query = query.Where("created_at >= ?", params.StartTime)
|
query = query.Where("created_at >= ?", params.StartTime)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
type PersonalCustomerClaims struct {
|
type PersonalCustomerClaims struct {
|
||||||
CustomerID uint `json:"customer_id"` // 个人客户 ID
|
CustomerID uint `json:"customer_id"` // 个人客户 ID
|
||||||
Phone string `json:"phone"` // 手机号
|
Phone string `json:"phone"` // 手机号
|
||||||
|
AssetType string `json:"asset_type"` // 登录所用资产类型(iot_card / device)
|
||||||
|
AssetID uint `json:"asset_id"` // 登录所用资产 ID
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,10 +31,12 @@ func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GeneratePersonalCustomerToken 生成个人客户 Token
|
// GeneratePersonalCustomerToken 生成个人客户 Token
|
||||||
func (m *JWTManager) GeneratePersonalCustomerToken(customerID uint, phone string) (string, error) {
|
func (m *JWTManager) GeneratePersonalCustomerToken(customerID uint, phone string, assetType string, assetID uint) (string, error) {
|
||||||
claims := &PersonalCustomerClaims{
|
claims := &PersonalCustomerClaims{
|
||||||
CustomerID: customerID,
|
CustomerID: customerID,
|
||||||
Phone: phone,
|
Phone: phone,
|
||||||
|
AssetType: assetType,
|
||||||
|
AssetID: assetID,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.tokenDuration)),
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.tokenDuration)),
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
|||||||
Reference in New Issue
Block a user