topfans/backend/gateway/controller/peripheral_controller.go
2026-07-17 16:53:57 +08:00

158 lines
4.6 KiB
Go

package controller
import (
"errors"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"github.com/topfans/backend/gateway/dto"
"github.com/topfans/backend/gateway/pkg/response"
"github.com/topfans/backend/pkg/logger"
"github.com/topfans/backend/services/assetService/service"
)
// GetVerificationByHash GET /api/v1/peripheral-info/by-hash/:hash?sign=xxx
//
// stage 1 唯一验真接口(详见 §5.1.3/§5.1.5):按加密 code(code_hash)验真
// - path 参数:encrypted_code(32 hex)
// - query 参数:sign(32 hex,HMAC 签名,第二道防线)
// - 验签失败返 50013,前端引导用户重新扫码
func (ctrl *PeripheralController) GetVerificationByHash(c *gin.Context) {
codeHash := c.Param("hash")
sign := c.Query("sign")
if codeHash == "" {
response.Error(c, http.StatusBadRequest, "code_hash 必填")
return
}
if sign == "" {
response.Error(c, http.StatusBadRequest, "sign 必填")
return
}
result, err := ctrl.svc.GetVerificationByHash(c.Request.Context(), codeHash, sign)
if err != nil {
ctrl.handleServiceError(c, err)
return
}
response.Success(c, ctrl.toVerificationDTO(result))
}
// toVerificationDTO *VerificationResult → *dto.VerificationData 转换 helper
func (ctrl *PeripheralController) toVerificationDTO(result *service.VerificationResult) *dto.VerificationData {
return &dto.VerificationData{
AssetID: result.AssetID,
Code: result.Code,
Company: result.Company,
Hash: result.Hash,
VerifyCount: result.VerifyCount,
Brand: result.Brand,
Image: result.Image,
Verifier: result.Verifier,
VerifiedAt: result.VerifiedAt,
MaterialType: result.MaterialType,
}
}
// PeripheralController 周边验真 + 加入藏品 controller
type PeripheralController struct {
svc *service.PeripheralService
}
// NewPeripheralController 构造 PeripheralController
func NewPeripheralController(svc *service.PeripheralService) *PeripheralController {
return &PeripheralController{svc: svc}
}
// GetVerification GET /api/v1/assets/:asset_id/verification
//
// H5 调用不强制 JWT(spec §4.3);app 内自带 JWT 但本端点不强校验,
// 故 handler 不读取 c.Get("user_id")。
func (ctrl *PeripheralController) GetVerification(c *gin.Context) {
assetID, err := strconv.ParseInt(c.Param("asset_id"), 10, 64)
if err != nil || assetID <= 0 {
response.Error(c, http.StatusBadRequest, "asset_id 格式错误")
return
}
result, err := ctrl.svc.GetVerification(c.Request.Context(), assetID)
if err != nil {
ctrl.handleServiceError(c, err)
return
}
response.Success(c, &dto.VerificationData{
AssetID: result.AssetID,
Code: result.Code,
Company: result.Company,
Hash: result.Hash,
VerifyCount: result.VerifyCount,
Brand: result.Brand,
Image: result.Image,
Verifier: result.Verifier,
VerifiedAt: result.VerifiedAt,
MaterialType: result.MaterialType,
})
}
// MintFromPeripheralByHash POST /api/v1/peripheral-info/mint-by-hash/:hash?sign=xxx
//
// stage 1 唯一加入藏品接口(详见 §5.1.5):按加密 code(code_hash)加入藏品
// - path 参数:encrypted_code(32 hex)
// - query 参数:sign(32 hex,HMAC 签名)
// - 强制 JWT
func (ctrl *PeripheralController) MintFromPeripheralByHash(c *gin.Context) {
codeHash := c.Param("hash")
sign := c.Query("sign")
if codeHash == "" {
response.Error(c, http.StatusBadRequest, "code_hash 必填")
return
}
if sign == "" {
response.Error(c, http.StatusBadRequest, "sign 必填")
return
}
userID, exists := c.Get("user_id")
if !exists {
response.Error(c, http.StatusUnauthorized, "未登录")
return
}
ownerUID, ok := userID.(int64)
if !ok {
response.Error(c, http.StatusInternalServerError, "user_id 类型错误")
return
}
result, err := ctrl.svc.MintFromPeripheralByHash(c.Request.Context(), ownerUID, codeHash, sign)
if err != nil {
ctrl.handleServiceError(c, err)
return
}
response.Success(c, &dto.MintData{
InstanceID: result.InstanceID,
AssetID: result.AssetID,
MintedAt: result.MintedAt,
CoverImage: result.CoverImage,
})
}
// handleServiceError 统一错误处理
// - BizError → response.ErrorWithCode(code, message) 透传业务码
// - 其他 → 500 服务繁忙,记录 ERROR 日志
func (ctrl *PeripheralController) handleServiceError(c *gin.Context, err error) {
var bizErr *service.BizError
if errors.As(err, &bizErr) {
response.ErrorWithCode(c, bizErr.Code, bizErr.Message)
return
}
logger.Logger.Error("peripheral service error", zap.Error(err))
response.Error(c, http.StatusInternalServerError, "服务繁忙")
}