This commit is contained in:
2026-06-28 20:55:56 +08:00
parent 30a24aab68
commit 2eec7e5bbe
8 changed files with 508 additions and 0 deletions

View File

@@ -144,3 +144,52 @@ type ProductAdminListResponse struct {
Size int `json:"size" comment:"每页数量"`
Items []ProductAdminInfoResponse `json:"items" comment:"产品列表"`
}
// PackageItemAIResponse AI组合包项目响应不含成本价
type PackageItemAIResponse struct {
ID string `json:"id" comment:"项目ID"`
ProductID string `json:"product_id" comment:"子产品ID"`
ProductCode string `json:"product_code" comment:"子产品编号"`
ProductName string `json:"product_name" comment:"子产品名称"`
SortOrder int `json:"sort_order" comment:"排序"`
Price float64 `json:"price" comment:"子产品价格"`
}
// ProductAIInfoResponse AI产品详情响应含启用/可见状态,不含用户状态、成本价、备注)
type ProductAIInfoResponse struct {
ID string `json:"id" comment:"产品ID"`
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
Name string `json:"name" comment:"产品名称"`
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" comment:"一级分类ID"`
SubCategoryID *string `json:"sub_category_id,omitempty" comment:"二级分类ID"`
Price float64 `json:"price" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否可见"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格组合包使用"`
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
Category *CategoryInfoResponse `json:"category,omitempty" comment:"一级分类信息"`
SubCategory *SubCategoryInfoResponse `json:"sub_category,omitempty" comment:"二级分类信息"`
PackageItems []*PackageItemAIResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}
// ProductAIListResponse AI产品列表响应
type ProductAIListResponse struct {
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Items []ProductAIInfoResponse `json:"items" comment:"产品列表"`
}

View File

@@ -25,6 +25,9 @@ type ProductApplicationService interface {
ListProductsForAdmin(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductAdminListResponse, error)
GetProductByIDForAdmin(ctx context.Context, query *queries.GetProductDetailQuery) (*responses.ProductAdminInfoResponse, error)
// AI专用方法
ListProductsForAI(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductAIListResponse, error)
// 用户端专用方法
GetProductByIDForUser(ctx context.Context, query *queries.GetProductDetailQuery) (*responses.ProductInfoWithDocumentResponse, error)

View File

@@ -415,6 +415,27 @@ func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context
}, nil
}
// ListProductsForAI 获取产品列表AI专用
// 业务流程1. 获取全部产品(含禁用/隐藏) 2. 构建AI响应数据无用户状态、成本价、备注
func (s *ProductApplicationServiceImpl) ListProductsForAI(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductAIListResponse, error) {
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
if err != nil {
return nil, err
}
items := make([]responses.ProductAIInfoResponse, len(products))
for i := range products {
items[i] = *s.convertToProductAIInfoResponse(products[i])
}
return &responses.ProductAIListResponse{
Total: total,
Page: options.Page,
Size: options.PageSize,
Items: items,
}, nil
}
// ListProductsForAdmin 获取产品列表(管理员专用)
// 业务流程1. 获取所有产品列表(包括隐藏的) 2. 构建管理员响应数据
func (s *ProductApplicationServiceImpl) ListProductsForAdmin(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductAdminListResponse, error) {
@@ -546,6 +567,55 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
return response
}
// convertToProductAIInfoResponse 转换为AI产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductAIInfoResponse(product *entities.Product) *responses.ProductAIInfoResponse {
response := &responses.ProductAIInfoResponse{
ID: product.ID,
OldID: product.OldID,
Name: product.Name,
Code: product.Code,
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
SubCategoryID: product.SubCategoryID,
Price: product.Price.InexactFloat64(),
IsEnabled: product.IsEnabled,
IsVisible: product.IsVisible,
IsPackage: product.IsPackage,
SellUIComponent: product.SellUIComponent,
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords,
CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt,
}
if product.Category != nil {
response.Category = s.convertToCategoryInfoResponse(product.Category)
}
if product.SubCategory != nil {
response.SubCategory = s.convertToSubCategoryInfoResponse(product.SubCategory)
}
if product.IsPackage && len(product.PackageItems) > 0 {
response.PackageItems = make([]*responses.PackageItemAIResponse, len(product.PackageItems))
for i, item := range product.PackageItems {
response.PackageItems[i] = &responses.PackageItemAIResponse{
ID: item.ID,
ProductID: item.ProductID,
ProductCode: item.Product.Code,
ProductName: item.Product.Name,
SortOrder: item.SortOrder,
Price: item.Product.Price.InexactFloat64(),
}
}
}
return response
}
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
response := &responses.ProductAdminInfoResponse{

View File

@@ -1430,6 +1430,8 @@ func NewContainer() *Container {
routes.NewPDFGRoutes,
// 企业报告页面路由
routes.NewQYGLReportRoutes,
// AI产品路由
routes.NewAIProductRoutes,
),
// 应用生命周期
@@ -1550,6 +1552,7 @@ func RegisterRoutes(
publicQueryWhitelistRoutes *routes.PublicQueryWhitelistRoutes,
pdfgRoutes *routes.PDFGRoutes,
qyglReportRoutes *routes.QYGLReportRoutes,
aiProductRoutes *routes.AIProductRoutes,
jwtAuth *middleware.JWTAuthMiddleware,
adminAuth *middleware.AdminAuthMiddleware,
cfg *config.Config,
@@ -1580,6 +1583,7 @@ func RegisterRoutes(
publicQueryWhitelistRoutes.Register(router)
pdfgRoutes.Register(router)
qyglReportRoutes.Register(router)
aiProductRoutes.Register(router)
// 打印注册的路由信息
router.PrintRoutes()

View File

@@ -166,6 +166,80 @@ func (h *ProductHandler) ListProducts(c *gin.Context) {
h.responseBuilder.Success(c, result, "获取产品列表成功")
}
// ListProductsForAI 获取产品列表AI专用
// @Summary 获取产品列表AI专用
// @Description AI调用专用产品列表返回全部产品含禁用/隐藏),包含 is_enabled/is_visible不含用户订阅状态、成本价和备注
// @Tags AI接口
// @Accept json
// @Produce json
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param keyword query string false "搜索关键词"
// @Param category_id query string false "分类ID"
// @Param is_enabled query bool false "是否启用"
// @Param is_visible query bool false "是否可见"
// @Param is_package query bool false "是否组合包"
// @Param sort_by query string false "排序字段"
// @Param sort_order query string false "排序方向" Enums(asc, desc)
// @Success 200 {object} responses.ProductAIListResponse "获取产品列表成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/ai/products [get]
func (h *ProductHandler) ListProductsForAI(c *gin.Context) {
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
filters := make(map[string]interface{})
if keyword := c.Query("keyword"); keyword != "" {
filters["keyword"] = keyword
}
if categoryID := c.Query("category_id"); categoryID != "" {
filters["category_id"] = categoryID
}
if isEnabled := c.Query("is_enabled"); isEnabled != "" {
if enabled, err := strconv.ParseBool(isEnabled); err == nil {
filters["is_enabled"] = enabled
}
}
if isVisible := c.Query("is_visible"); isVisible != "" {
if visible, err := strconv.ParseBool(isVisible); err == nil {
filters["is_visible"] = visible
}
}
if isPackage := c.Query("is_package"); isPackage != "" {
if pkg, err := strconv.ParseBool(isPackage); err == nil {
filters["is_package"] = pkg
}
}
sortBy := c.Query("sort_by")
if sortBy == "" {
sortBy = "created_at"
}
sortOrder := c.Query("sort_order")
if sortOrder == "" {
sortOrder = "desc"
}
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: sortBy,
Order: sortOrder,
}
result, err := h.appService.ListProductsForAI(c.Request.Context(), filters, options)
if err != nil {
h.logger.Error("获取AI产品列表失败", zap.Error(err))
h.responseBuilder.InternalError(c, "获取产品列表失败")
return
}
h.responseBuilder.Success(c, result, "获取产品列表成功")
}
// getIntQuery 获取整数查询参数
func (h *ProductHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
if value := c.Query(key); value != "" {

View File

@@ -0,0 +1,37 @@
package routes
import (
"tyapi-server/internal/infrastructure/http/handlers"
sharedhttp "tyapi-server/internal/shared/http"
"go.uber.org/zap"
)
// AIProductRoutes AI产品接口路由
type AIProductRoutes struct {
productHandler *handlers.ProductHandler
logger *zap.Logger
}
// NewAIProductRoutes 创建AI产品路由
func NewAIProductRoutes(
productHandler *handlers.ProductHandler,
logger *zap.Logger,
) *AIProductRoutes {
return &AIProductRoutes{
productHandler: productHandler,
logger: logger,
}
}
// Register 注册AI产品相关路由
func (r *AIProductRoutes) Register(router *sharedhttp.GinRouter) {
engine := router.GetEngine()
aiProducts := engine.Group("/api/v1/ai/products")
{
aiProducts.GET("", r.productHandler.ListProductsForAI)
}
r.logger.Info("AI产品路由注册完成")
}