add购买记录功能

This commit is contained in:
2025-12-22 18:32:34 +08:00
parent 65a61d0336
commit 7f8554fa12
314 changed files with 4029 additions and 83496 deletions

View File

@@ -0,0 +1,752 @@
package product
import (
"context"
"fmt"
"time"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/domains/finance/entities"
financeRepositories "tyapi-server/internal/domains/finance/repositories"
productEntities "tyapi-server/internal/domains/product/entities"
productRepositories "tyapi-server/internal/domains/product/repositories"
"tyapi-server/internal/shared/component_report"
"tyapi-server/internal/shared/payment"
)
// ComponentReportOrderService 组件报告订单服务
type ComponentReportOrderService struct {
productRepo productRepositories.ProductRepository
docRepo productRepositories.ProductDocumentationRepository
apiConfigRepo productRepositories.ProductApiConfigRepository
purchaseOrderRepo financeRepositories.PurchaseOrderRepository
componentReportRepo productRepositories.ComponentReportRepository
rechargeRecordRepo financeRepositories.RechargeRecordRepository
alipayOrderRepo financeRepositories.AlipayOrderRepository
wechatOrderRepo financeRepositories.WechatOrderRepository
aliPayService *payment.AliPayService
wechatPayService *payment.WechatPayService
exampleJSONGenerator *component_report.ExampleJSONGenerator
zipGenerator *component_report.ZipGenerator
logger *zap.Logger
}
// NewComponentReportOrderService 创建组件报告订单服务
func NewComponentReportOrderService(
productRepo productRepositories.ProductRepository,
docRepo productRepositories.ProductDocumentationRepository,
apiConfigRepo productRepositories.ProductApiConfigRepository,
purchaseOrderRepo financeRepositories.PurchaseOrderRepository,
componentReportRepo productRepositories.ComponentReportRepository,
rechargeRecordRepo financeRepositories.RechargeRecordRepository,
alipayOrderRepo financeRepositories.AlipayOrderRepository,
wechatOrderRepo financeRepositories.WechatOrderRepository,
aliPayService *payment.AliPayService,
wechatPayService *payment.WechatPayService,
logger *zap.Logger,
) *ComponentReportOrderService {
exampleJSONGenerator := component_report.NewExampleJSONGenerator(productRepo, docRepo, apiConfigRepo, logger)
zipGenerator := component_report.NewZipGenerator(logger)
return &ComponentReportOrderService{
productRepo: productRepo,
docRepo: docRepo,
apiConfigRepo: apiConfigRepo,
purchaseOrderRepo: purchaseOrderRepo,
componentReportRepo: componentReportRepo,
rechargeRecordRepo: rechargeRecordRepo,
alipayOrderRepo: alipayOrderRepo,
wechatOrderRepo: wechatOrderRepo,
aliPayService: aliPayService,
wechatPayService: wechatPayService,
exampleJSONGenerator: exampleJSONGenerator,
zipGenerator: zipGenerator,
logger: logger,
}
}
// CreateOrderInfo 获取订单信息
func (s *ComponentReportOrderService) GetOrderInfo(ctx context.Context, userID, productID string) (*OrderInfo, error) {
s.logger.Info("开始获取订单信息", zap.String("user_id", userID), zap.String("product_id", productID))
// 获取产品信息
product, err := s.productRepo.GetByID(ctx, productID)
if err != nil {
s.logger.Error("获取产品信息失败", zap.Error(err), zap.String("product_id", productID))
return nil, fmt.Errorf("获取产品信息失败: %w", err)
}
s.logger.Info("获取产品信息成功",
zap.String("product_id", product.ID),
zap.String("product_code", product.Code),
zap.String("product_name", product.Name),
zap.Bool("is_package", product.IsPackage),
zap.String("price", product.Price.String()),
)
// 检查是否为组合包
if !product.IsPackage {
s.logger.Error("产品不是组合包", zap.String("product_id", productID), zap.String("product_code", product.Code))
return nil, fmt.Errorf("只有组合包产品才能下载示例报告")
}
// 获取组合包子产品
packageItems, err := s.productRepo.GetPackageItems(ctx, productID)
if err != nil {
s.logger.Error("获取组合包子产品失败", zap.Error(err), zap.String("product_id", productID))
return nil, fmt.Errorf("获取组合包子产品失败: %w", err)
}
s.logger.Info("获取组合包子产品成功",
zap.String("product_id", productID),
zap.Int("package_items_count", len(packageItems)),
)
// 获取用户已购买的产品编号列表
purchasedCodes, err := s.purchaseOrderRepo.GetUserPurchasedProductCodes(ctx, userID)
if err != nil {
s.logger.Warn("获取用户已购买产品编号失败", zap.Error(err), zap.String("user_id", userID))
purchasedCodes = []string{}
}
s.logger.Info("获取用户已购买产品编号列表",
zap.String("user_id", userID),
zap.Strings("purchased_codes", purchasedCodes),
zap.Int("purchased_count", len(purchasedCodes)),
)
// 创建已购买编号的map用于快速查找
purchasedMap := make(map[string]bool)
for _, code := range purchasedCodes {
purchasedMap[code] = true
}
// 使用产品的UIComponentPrice作为最终价格
finalPrice := product.UIComponentPrice
s.logger.Info("使用UI组件价格",
zap.String("product_id", productID),
zap.String("product_ui_component_price", finalPrice.String()),
)
// 准备子产品信息列表(仅用于展示,不参与价格计算)
var subProducts []SubProductPriceInfo
for _, item := range packageItems {
var subProduct productEntities.Product
var productCode string
var productName string
var price decimal.Decimal
if item.Product != nil {
subProduct = *item.Product
productCode = subProduct.Code
productName = subProduct.Name
price = subProduct.Price
} else {
subProduct, err = s.productRepo.GetByID(ctx, item.ProductID)
if err != nil {
s.logger.Warn("获取子产品信息失败", zap.Error(err), zap.String("product_id", item.ProductID))
continue
}
productCode = subProduct.Code
productName = subProduct.Name
price = subProduct.Price
}
if productCode == "" {
continue
}
// 检查是否已购买
isPurchased := purchasedMap[productCode]
subProducts = append(subProducts, SubProductPriceInfo{
ProductID: subProduct.ID,
ProductCode: productCode,
ProductName: productName,
Price: price.String(),
IsPurchased: isPurchased,
})
}
// 检查用户是否有已支付的下载记录(针对当前产品)
hasPaidDownload := false
orders, _, err := s.purchaseOrderRepo.GetByUserID(ctx, userID, 100, 0)
if err == nil {
s.logger.Info("检查用户已支付的下载记录",
zap.String("user_id", userID),
zap.String("product_id", productID),
zap.Int("orders_count", len(orders)),
)
for _, order := range orders {
if order.ProductID == productID && order.Status == entities.PurchaseOrderStatusPaid {
hasPaidDownload = true
s.logger.Info("找到有效的已支付下载记录",
zap.String("order_id", order.ID),
zap.String("order_no", order.OrderNo),
zap.String("product_id", order.ProductID),
zap.String("purchase_status", string(order.Status)),
)
break
}
}
} else {
s.logger.Warn("获取用户订单失败", zap.Error(err), zap.String("user_id", userID))
}
// 如果可以下载价格为0免费或者用户已支付
canDownload := finalPrice.IsZero() || hasPaidDownload
s.logger.Info("最终订单信息",
zap.String("product_id", productID),
zap.String("product_code", product.Code),
zap.String("product_name", product.Name),
zap.Int("sub_products_count", len(subProducts)),
zap.String("price", finalPrice.String()),
zap.Strings("purchased_product_codes", purchasedCodes),
zap.Bool("has_paid_download", hasPaidDownload),
zap.Bool("can_download", canDownload),
)
// 记录每个子产品的信息
for i, subProduct := range subProducts {
s.logger.Info("子产品详情",
zap.Int("index", i),
zap.String("sub_product_id", subProduct.ProductID),
zap.String("sub_product_code", subProduct.ProductCode),
zap.String("sub_product_name", subProduct.ProductName),
zap.String("price", subProduct.Price),
zap.Bool("is_purchased", subProduct.IsPurchased),
)
}
return &OrderInfo{
ProductID: productID,
ProductCode: product.Code,
ProductName: product.Name,
IsPackage: true,
SubProducts: subProducts,
Price: finalPrice.String(),
PurchasedProductCodes: purchasedCodes,
CanDownload: canDownload,
}, nil
}
// CreatePaymentOrder 创建支付订单
func (s *ComponentReportOrderService) CreatePaymentOrder(ctx context.Context, req *CreatePaymentOrderRequest) (*CreatePaymentOrderResponse, error) {
// 获取产品信息
product, err := s.productRepo.GetByID(ctx, req.ProductID)
if err != nil {
return nil, fmt.Errorf("获取产品信息失败: %w", err)
}
// 检查是否为组合包
if !product.IsPackage {
return nil, fmt.Errorf("只有组合包产品才能下载示例报告")
}
// 获取组合包子产品
packageItems, err := s.productRepo.GetPackageItems(ctx, req.ProductID)
if err != nil {
return nil, fmt.Errorf("获取组合包子产品失败: %w", err)
}
// 使用产品的UIComponentPrice作为价格
finalPrice := product.UIComponentPrice
s.logger.Info("使用UI组件价格创建支付订单",
zap.String("product_id", req.ProductID),
zap.String("product_ui_component_price", finalPrice.String()),
)
// 检查价格是否为0
if finalPrice.IsZero() {
return s.createFreeOrder(ctx, req, &product, nil, nil, finalPrice)
}
// 准备子产品信息列表(仅用于展示)
var subProductCodes []string
var subProductIDs []string
for _, item := range packageItems {
var subProduct productEntities.Product
var productCode string
if item.Product != nil {
subProduct = *item.Product
productCode = subProduct.Code
} else {
subProduct, err = s.productRepo.GetByID(ctx, item.ProductID)
if err != nil {
continue
}
productCode = subProduct.Code
}
if productCode == "" {
continue
}
// 收集所有子产品信息
subProductCodes = append(subProductCodes, productCode)
subProductIDs = append(subProductIDs, subProduct.ID)
}
// 生成商户订单号
var outTradeNo string
if req.PaymentType == "alipay" {
outTradeNo = s.aliPayService.GenerateOutTradeNo()
} else {
outTradeNo = s.wechatPayService.GenerateOutTradeNo()
}
// 创建购买订单 - 设置为待支付状态
purchaseOrder := entities.NewPurchaseOrder(
req.UserID,
req.ProductID,
product.Code,
product.Name,
fmt.Sprintf("组件报告下载-%s", product.Name),
finalPrice,
req.Platform, // 使用传入的平台参数
req.PaymentType,
req.PaymentType,
)
// 设置为待支付状态
purchaseOrder.Status = entities.PurchaseOrderStatusCreated
createdPurchaseOrder, err := s.purchaseOrderRepo.Create(ctx, purchaseOrder)
if err != nil {
return nil, fmt.Errorf("创建购买订单失败: %w", err)
}
// 创建相应的支付记录(未支付状态)
if req.PaymentType == "alipay" {
// 使用工厂方法创建支付宝订单
alipayOrder := entities.NewAlipayOrder(createdPurchaseOrder.ID, outTradeNo,
fmt.Sprintf("组件报告下载-%s", product.Name), finalPrice, req.Platform)
// 设置为待支付状态
alipayOrder.Status = entities.AlipayOrderStatusPending
_, err = s.alipayOrderRepo.Create(ctx, *alipayOrder)
if err != nil {
s.logger.Error("创建支付宝订单记录失败", zap.Error(err))
}
} else {
// 使用工厂方法创建微信订单
wechatOrder := entities.NewWechatOrder(createdPurchaseOrder.ID, outTradeNo,
fmt.Sprintf("组件报告下载-%s", product.Name), finalPrice, req.Platform)
// 设置为待支付状态
wechatOrder.Status = entities.WechatOrderStatusPending
_, err = s.wechatOrderRepo.Create(ctx, *wechatOrder)
if err != nil {
s.logger.Error("创建微信订单记录失败", zap.Error(err))
}
}
// 调用真实支付接口创建支付订单
var payURL string
var codeURL string
if req.PaymentType == "alipay" {
// 调用支付宝支付接口
payURL, err = s.aliPayService.CreateAlipayOrder(ctx, req.Platform, finalPrice,
fmt.Sprintf("组件报告下载-%s", product.Name), outTradeNo)
if err != nil {
return nil, fmt.Errorf("创建支付宝支付订单失败: %w", err)
}
} else if req.PaymentType == "wechat" {
// 调用微信支付接口
floatValue, _ := finalPrice.Float64() // 忽略第二个返回值
result, err := s.wechatPayService.CreateWechatOrder(ctx, floatValue,
fmt.Sprintf("组件报告下载-%s", product.Name), outTradeNo)
if err != nil {
return nil, fmt.Errorf("创建微信支付订单失败: %w", err)
}
// 微信支付返回的是map格式提取code_url
if resultMap, ok := result.(map[string]string); ok {
codeURL = resultMap["code_url"]
}
}
// 创建一个临时下载记录,用于跟踪支付状态,但不生成报告文件
download := &productEntities.ComponentReportDownload{
UserID: req.UserID,
ProductID: req.ProductID,
ProductCode: product.Code,
ProductName: product.Name,
OrderID: &createdPurchaseOrder.ID, // 关联购买订单ID
OrderNumber: &outTradeNo, // 外部订单号
ExpiresAt: calculateExpiryTime(), // 30天后过期
// 注意这里不设置FilePath因为文件将在支付成功后生成
}
err = s.componentReportRepo.Create(ctx, download)
if err != nil {
s.logger.Error("创建下载记录失败", zap.Error(err))
// 不中断流程,即使创建下载记录失败也继续返回订单信息
}
// 返回支付响应包含支付URL
response := &CreatePaymentOrderResponse{
OrderID: download.ID,
OrderNo: createdPurchaseOrder.OrderNo,
PaymentType: req.PaymentType,
Amount: finalPrice.String(),
PayURL: payURL,
CodeURL: codeURL,
}
s.logger.Info("支付订单创建成功",
zap.String("order_id", download.ID),
zap.String("purchase_order_id", createdPurchaseOrder.ID),
zap.String("user_id", req.UserID),
zap.String("product_id", req.ProductID),
zap.String("payment_type", req.PaymentType),
zap.String("out_trade_no", outTradeNo),
)
return response, nil
}
// createFreeOrder 创建免费订单
func (s *ComponentReportOrderService) createFreeOrder(
ctx context.Context,
req *CreatePaymentOrderRequest,
product *productEntities.Product,
subProductCodes []string,
subProductIDs []string,
finalPrice decimal.Decimal,
) (*CreatePaymentOrderResponse, error) {
// 序列化子产品列表
// 简化后的实体不再需要序列化子产品列表
// 创建免费订单
purchaseOrder := entities.NewPurchaseOrder(
req.UserID,
req.ProductID,
product.Code,
product.Name,
fmt.Sprintf("组件报告下载-%s", product.Name),
finalPrice,
"app",
"free",
"free",
)
// 设置为已支付状态
purchaseOrder.Status = entities.PurchaseOrderStatusPaid
now := time.Now()
purchaseOrder.PayTime = &now
createdPurchaseOrder, err := s.purchaseOrderRepo.Create(ctx, purchaseOrder)
if err != nil {
return nil, fmt.Errorf("创建免费订单失败: %w", err)
}
// 创建下载记录
download := &productEntities.ComponentReportDownload{
UserID: req.UserID,
ProductID: req.ProductID,
ProductCode: product.Code,
ProductName: product.Name,
OrderID: &createdPurchaseOrder.ID, // 关联购买订单ID
OrderNumber: &createdPurchaseOrder.OrderNo, // 外部订单号
ExpiresAt: calculateExpiryTime(), // 30天后过期
}
err = s.componentReportRepo.Create(ctx, download)
if err != nil {
s.logger.Error("创建下载记录失败", zap.Error(err))
// 不中断流程,即使创建下载记录失败也继续返回订单信息
}
return &CreatePaymentOrderResponse{
OrderID: download.ID,
OrderNo: createdPurchaseOrder.OrderNo,
PaymentType: "free",
Amount: "0.00",
}, nil
}
// generateReportFile 生成报告文件
func (s *ComponentReportOrderService) generateReportFile(ctx context.Context, download *productEntities.ComponentReportDownload) (string, error) {
// 解析子产品编号列表
// 简化后的实体只使用主产品编号
subProductCodes := []string{download.ProductCode}
// 生成筛选后的组件ZIP文件
zipPath, err := s.zipGenerator.GenerateZipFile(
ctx,
download.ProductID,
subProductCodes,
s.exampleJSONGenerator,
"", // 使用默认路径
)
if err != nil {
return "", fmt.Errorf("生成筛选后的组件ZIP文件失败: %w", err)
}
// 更新下载记录的文件路径
download.FilePath = &zipPath
err = s.componentReportRepo.UpdateDownload(ctx, download)
if err != nil {
s.logger.Error("更新下载记录文件信息失败", zap.Error(err), zap.String("download_id", download.ID))
// 即使更新失败,也返回文件路径,因为文件已经生成
}
s.logger.Info("报告文件生成成功",
zap.String("download_id", download.ID),
zap.String("file_path", zipPath),
zap.String("product_id", download.ProductID),
zap.String("product_code", download.ProductCode))
return zipPath, nil
}
// CheckPaymentStatus 检查支付状态
func (s *ComponentReportOrderService) CheckPaymentStatus(ctx context.Context, orderID string) (*CheckPaymentStatusResponse, error) {
// 获取下载记录信息
download, err := s.componentReportRepo.GetDownloadByID(ctx, orderID)
if err != nil {
return nil, fmt.Errorf("获取下载记录信息失败: %w", err)
}
// 使用OrderID查询购买订单状态来判断支付状态
var paymentStatus string
var canDownload bool
if download.OrderID != nil {
// 查询购买订单状态
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, *download.OrderID)
if err != nil {
s.logger.Error("查询购买订单失败", zap.Error(err), zap.String("order_id", *download.OrderID))
paymentStatus = "unknown"
} else {
// 根据购买订单状态设置支付状态
switch purchaseOrder.Status {
case entities.PurchaseOrderStatusPaid:
paymentStatus = "success"
canDownload = true
case entities.PurchaseOrderStatusCreated:
paymentStatus = "pending"
canDownload = false
case entities.PurchaseOrderStatusCancelled:
paymentStatus = "cancelled"
canDownload = false
case entities.PurchaseOrderStatusFailed:
paymentStatus = "failed"
canDownload = false
default:
paymentStatus = "unknown"
canDownload = false
}
}
} else if download.OrderNumber != nil {
// 兼容旧的支付订单逻辑
paymentStatus = "success" // 简化处理,有支付订单号就认为已支付
canDownload = true
} else {
paymentStatus = "pending"
canDownload = false
}
// 检查是否过期
if download.IsExpired() {
canDownload = false
}
// 返回支付状态
return &CheckPaymentStatusResponse{
OrderID: download.ID,
PaymentStatus: paymentStatus,
CanDownload: canDownload,
}, nil
}
// DownloadFile 下载文件
func (s *ComponentReportOrderService) DownloadFile(ctx context.Context, orderID string) (string, error) {
// 获取下载记录信息
download, err := s.componentReportRepo.GetDownloadByID(ctx, orderID)
if err != nil {
return "", fmt.Errorf("获取下载记录信息失败: %w", err)
}
// 使用OrderID查询购买订单状态来判断支付状态
var canDownload bool
if download.OrderID != nil {
// 查询购买订单状态
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, *download.OrderID)
if err != nil {
s.logger.Error("查询购买订单失败", zap.Error(err), zap.String("order_id", *download.OrderID))
canDownload = false
} else {
// 检查购买订单状态
canDownload = purchaseOrder.Status == entities.PurchaseOrderStatusPaid
}
} else if download.OrderNumber != nil {
// 兼容旧的支付订单逻辑
canDownload = true // 简化处理,有支付订单号就认为已支付
} else {
canDownload = false
}
// 检查是否过期
if download.IsExpired() {
canDownload = false
}
if !canDownload {
return "", fmt.Errorf("订单未支付或已过期,无法下载文件")
}
// 检查文件是否已存在
if download.FilePath != nil && *download.FilePath != "" {
// 文件已存在,直接返回文件路径
s.logger.Info("返回已存在的文件路径",
zap.String("order_id", orderID),
zap.String("file_path", *download.FilePath))
return *download.FilePath, nil
}
// 文件不存在,生成文件
filePath, err := s.generateReportFile(ctx, download)
if err != nil {
return "", fmt.Errorf("生成报告文件失败: %w", err)
}
s.logger.Info("成功生成报告文件",
zap.String("order_id", orderID),
zap.String("file_path", filePath))
return filePath, nil
}
// GetUserOrders 获取用户订单列表
func (s *ComponentReportOrderService) GetUserOrders(ctx context.Context, userID string, limit, offset int) ([]*UserOrderResponse, int64, error) {
// 获取用户的下载记录
downloads, err := s.componentReportRepo.GetUserDownloads(ctx, userID, nil)
if err != nil {
return nil, 0, err
}
// 转换为响应格式
result := make([]*UserOrderResponse, 0, len(downloads))
for _, download := range downloads {
// 使用OrderID查询购买订单状态来判断支付状态
var purchaseStatus string = "pending"
var paymentType string = "unknown"
var paymentTime *time.Time = nil
if download.OrderID != nil {
// 查询购买订单状态
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, *download.OrderID)
if err == nil {
switch purchaseOrder.Status {
case entities.PurchaseOrderStatusPaid:
purchaseStatus = "paid"
paymentTime = purchaseOrder.PayTime
case entities.PurchaseOrderStatusCreated:
purchaseStatus = "created"
case entities.PurchaseOrderStatusCancelled:
purchaseStatus = "cancelled"
case entities.PurchaseOrderStatusFailed:
purchaseStatus = "failed"
}
paymentType = purchaseOrder.PayChannel
}
} else if download.OrderNumber != nil {
// 兼容旧的支付订单逻辑
purchaseStatus = "paid" // 简化处理,有支付订单号就认为已支付
paymentType = "unknown"
}
result = append(result, &UserOrderResponse{
ID: download.ID,
OrderNo: "",
ProductID: download.ProductID,
ProductCode: download.ProductCode,
PaymentType: paymentType,
PurchaseStatus: purchaseStatus,
Price: "0.00", // 下载记录不存储价格信息
CreatedAt: download.CreatedAt,
PaymentTime: paymentTime,
})
}
return result, int64(len(result)), nil
}
// calculateExpiryTime 计算下载有效期从创建日起30天
func calculateExpiryTime() *time.Time {
now := time.Now()
expiry := now.AddDate(0, 0, 30) // 30天后过期
return &expiry
}
// 数据结构定义
// OrderInfo 订单信息
type OrderInfo struct {
ProductID string `json:"product_id"`
ProductCode string `json:"product_code"`
ProductName string `json:"product_name"`
IsPackage bool `json:"is_package"`
SubProducts []SubProductPriceInfo `json:"sub_products"`
Price string `json:"price"` // UI组件价格
PurchasedProductCodes []string `json:"purchased_product_codes"`
CanDownload bool `json:"can_download"`
}
// SubProductPriceInfo 子产品价格信息
type SubProductPriceInfo struct {
ProductID string `json:"product_id"`
ProductCode string `json:"product_code"`
ProductName string `json:"product_name"`
Price string `json:"price"`
IsPurchased bool `json:"is_purchased"`
}
// CreatePaymentOrderRequest 创建支付订单请求
type CreatePaymentOrderRequest struct {
UserID string `json:"user_id"`
ProductID string `json:"product_id"`
PaymentType string `json:"payment_type"` // wechat 或 alipay
Platform string `json:"platform"` // 支付平台app, h5, pc可选默认根据User-Agent判断
SubProductCodes []string `json:"sub_product_codes,omitempty"`
}
// CreatePaymentOrderResponse 创建支付订单响应
type CreatePaymentOrderResponse struct {
OrderID string `json:"order_id"`
OrderNo string `json:"order_no"`
CodeURL string `json:"code_url"` // 支付二维码URL微信
PayURL string `json:"pay_url"` // 支付链接(支付宝)
PaymentType string `json:"payment_type"`
Amount string `json:"amount"`
}
// CheckPaymentStatusResponse 检查支付状态响应
type CheckPaymentStatusResponse struct {
OrderID string `json:"order_id"` // 订单ID
PaymentStatus string `json:"payment_status"` // 支付状态pending, success, failed
CanDownload bool `json:"can_download"` // 是否可以下载
}
// UserOrderResponse 用户订单响应
type UserOrderResponse struct {
ID string `json:"id"`
OrderNo string `json:"order_no"`
ProductID string `json:"product_id"`
ProductCode string `json:"product_code"`
PaymentType string `json:"payment_type"`
PurchaseStatus string `json:"purchase_status"`
Price string `json:"price"` // UI组件价格
CreatedAt time.Time `json:"created_at"`
PaymentTime *time.Time `json:"payment_time"`
}

View File

@@ -14,6 +14,10 @@ type CreateProductCommand struct {
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
@@ -35,6 +39,10 @@ type UpdateProductCommand struct {
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`

View File

@@ -15,17 +15,20 @@ type PackageItemResponse struct {
// ProductInfoResponse 产品详情响应
type ProductInfoResponse 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"`
Price float64 `json:"price" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
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"`
Price float64 `json:"price" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
@@ -60,15 +63,15 @@ type ProductSearchResponse struct {
// ProductSimpleResponse 产品简单信息响应
type ProductSimpleResponse 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:"产品简介"`
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
Price float64 `json:"price" comment:"产品价格"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
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:"产品简介"`
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
Price float64 `json:"price" comment:"产品价格"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
}
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
@@ -101,6 +104,10 @@ type ProductAdminInfoResponse struct {
IsVisible bool `json:"is_visible" comment:"是否可见"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// UI组件相关字段
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格组合包使用"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`

View File

@@ -54,20 +54,22 @@ func NewProductApplicationService(
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
// 1. 构建产品实体
product := &entities.Product{
Name: cmd.Name,
Code: cmd.Code,
Description: cmd.Description,
Content: cmd.Content,
CategoryID: cmd.CategoryID,
Price: decimal.NewFromFloat(cmd.Price),
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
Remark: cmd.Remark,
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
IsPackage: cmd.IsPackage,
SEOTitle: cmd.SEOTitle,
SEODescription: cmd.SEODescription,
SEOKeywords: cmd.SEOKeywords,
Name: cmd.Name,
Code: cmd.Code,
Description: cmd.Description,
Content: cmd.Content,
CategoryID: cmd.CategoryID,
Price: decimal.NewFromFloat(cmd.Price),
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
Remark: cmd.Remark,
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
IsPackage: cmd.IsPackage,
SellUIComponent: cmd.SellUIComponent,
UIComponentPrice: decimal.NewFromFloat(cmd.UIComponentPrice),
SEOTitle: cmd.SEOTitle,
SEODescription: cmd.SEODescription,
SEOKeywords: cmd.SEOKeywords,
}
// 2. 创建产品
@@ -101,6 +103,8 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
existingProduct.IsEnabled = cmd.IsEnabled
existingProduct.IsVisible = cmd.IsVisible
existingProduct.IsPackage = cmd.IsPackage
existingProduct.SellUIComponent = cmd.SellUIComponent
existingProduct.UIComponentPrice = decimal.NewFromFloat(cmd.UIComponentPrice)
existingProduct.SEOTitle = cmd.SEOTitle
existingProduct.SEODescription = cmd.SEODescription
existingProduct.SEOKeywords = cmd.SEOKeywords
@@ -486,21 +490,23 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
// convertToProductInfoResponse 转换为产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
response := &responses.ProductInfoResponse{
ID: product.ID,
OldID: product.OldID,
Name: product.Name,
Code: product.Code,
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
Price: product.Price.InexactFloat64(),
IsEnabled: product.IsEnabled,
IsPackage: product.IsPackage,
SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords,
CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt,
ID: product.ID,
OldID: product.OldID,
Name: product.Name,
Code: product.Code,
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
Price: product.Price.InexactFloat64(),
IsEnabled: product.IsEnabled,
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,
}
// 添加分类信息
@@ -530,24 +536,26 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
response := &responses.ProductAdminInfoResponse{
ID: product.ID,
OldID: product.OldID,
Name: product.Name,
Code: product.Code,
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
Price: product.Price.InexactFloat64(),
CostPrice: product.CostPrice.InexactFloat64(),
Remark: product.Remark,
IsEnabled: product.IsEnabled,
IsVisible: product.IsVisible, // 管理员可以看到可见状态
IsPackage: product.IsPackage,
SEOTitle: product.SEOTitle,
SEODescription: product.SEODescription,
SEOKeywords: product.SEOKeywords,
CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt,
ID: product.ID,
OldID: product.OldID,
Name: product.Name,
Code: product.Code,
Description: product.Description,
Content: product.Content,
CategoryID: product.CategoryID,
Price: product.Price.InexactFloat64(),
CostPrice: product.CostPrice.InexactFloat64(),
Remark: product.Remark,
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,
}
// 添加分类信息

View File

@@ -182,7 +182,7 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFile(ctx contex
fileType := strings.ToLower(filepath.Ext(file.Filename))
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
createdComponent.FolderPath = &folderPath
createdComponent.FileType = &fileType
@@ -255,7 +255,7 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFiles(ctx conte
}
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
createdComponent.FolderPath = &folderPath
// 检查是否有ZIP文件
@@ -363,7 +363,7 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFilesAndPaths(c
}
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
createdComponent.FolderPath = &folderPath
// 检查是否有ZIP文件
@@ -634,7 +634,7 @@ func (s *UIComponentApplicationServiceImpl) UploadAndExtractUIComponentFile(ctx
fileType := strings.ToLower(filepath.Ext(file.Filename))
// 更新组件信息
folderPath := "resources/Pure Component/src/ui"
folderPath := "resources/Pure_Component/src/ui"
component.FolderPath = &folderPath
component.FileType = &fileType