topfans/backend/gateway/controller/castlove_config_controller.go
2026-06-09 12:38:12 +08:00

108 lines
3.3 KiB
Go

package controller
import (
"context"
"net/http"
"time"
"dubbo.apache.org/dubbo-go/v3/client"
"github.com/gin-gonic/gin"
"github.com/topfans/backend/gateway/pkg/response"
pbCastlove "github.com/topfans/backend/pkg/proto/castlove"
"github.com/topfans/backend/pkg/logger"
"go.uber.org/zap"
)
// CastloveConfigController 铸爱工艺配置 控制器
// 端点:GET /api/v1/castlove/config
// 流程:JWT 鉴权 → 透传 Dubbo RPC → 透传 DB → sanitize → 5s 进程内缓存 → JSON 响应
type CastloveConfigController struct {
castloveConfigClient pbCastlove.CastloveConfigService
}
// NewCastloveConfigController 创建铸爱工艺配置 Controller 实例
// dubboClient:指向 assetService(端口 20003)的 Dubbo 客户端
func NewCastloveConfigController(dubboClient *client.Client) (*CastloveConfigController, error) {
cli, err := pbCastlove.NewCastloveConfigService(dubboClient)
if err != nil {
return nil, err
}
return &CastloveConfigController{
castloveConfigClient: cli,
}, nil
}
// GetCastloveConfig 获取铸爱工艺配置(全量分类 + 卡片)
// @Summary 获取铸爱工艺配置
// @Description 强制鉴权;返回全量分类(星卡/吧唧/海报 等)及其下卡片(含 route_path / route_params)
// @Tags castlove
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} response.Response
// @Router /api/v1/castlove/config [get]
func (ctrl *CastloveConfigController) GetCastloveConfig(c *gin.Context) {
logger.Logger.Info("GetCastloveConfig request")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := ctrl.castloveConfigClient.GetCastloveConfig(ctx, &pbCastlove.GetCastloveConfigRequest{})
if err != nil {
logger.Logger.Error("GetCastloveConfig RPC failed", zap.Error(err))
response.Error(c, http.StatusInternalServerError, "配置加载失败")
return
}
if resp.Base == nil || resp.Base.GetCode() != 200 {
// proto Base 为 nil 视为异常,但仍尝试把 categories 透传出去,避免空响应
logger.Logger.Warn("GetCastloveConfig: unexpected base",
zap.Any("base", resp.Base),
)
}
// proto 数组转 []map 以便 gin 序列化为 JSON 数组而非 proto 字段名
categories := make([]map[string]interface{}, 0, len(resp.Categories))
for _, cat := range resp.Categories {
crafts := make([]map[string]interface{}, 0, len(cat.Crafts))
for _, cr := range cat.Crafts {
crafts = append(crafts, map[string]interface{}{
"id": cr.Id,
"name": cr.Name,
"image_url": cr.ImageUrl,
"route_path": nullableString(cr.RoutePath), // "" → nil,与 DB NULL 行为一致
"route_params": nullableJSONString(cr.RouteParams),
"sort_order": cr.SortOrder,
})
}
categories = append(categories, map[string]interface{}{
"id": cat.Id,
"name": cat.Name,
"type_key": nullableString(cat.TypeKey),
"sort_order": cat.SortOrder,
"crafts": crafts,
})
}
response.Success(c, map[string]interface{}{
"categories": categories,
"version": resp.Version,
})
}
// nullableString 空串转 nil(JSON 序列化时为 null,与 DB NULL 行为一致)
func nullableString(s string) interface{} {
if s == "" {
return nil
}
return s
}
// nullableJSONString 空串转 nil(route_params 同上)
func nullableJSONString(s string) interface{} {
if s == "" {
return nil
}
return s
}