f
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ComponentReportDownload 组件报告下载记录
|
||||
type ComponentReportDownload struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"下载记录ID"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||
ProductCode string `gorm:"type:varchar(50);not null;index" comment:"产品编号"`
|
||||
ProductName string `gorm:"type:varchar(200);not null" comment:"产品名称"`
|
||||
// 直接关联购买订单
|
||||
OrderID *string `gorm:"type:varchar(36);index" comment:"关联的购买订单ID"`
|
||||
OrderNumber *string `gorm:"type:varchar(64);index" comment:"关联的购买订单号"`
|
||||
|
||||
// 组合包相关字段(从购买记录复制)
|
||||
SubProductIDs string `gorm:"type:text" comment:"子产品ID列表(JSON数组,组合包使用)"`
|
||||
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表(JSON数组)"`
|
||||
|
||||
// 价格相关字段
|
||||
OriginalPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0.00" comment:"原始价格(组合包使用UIComponentPrice,单品使用Price)"`
|
||||
DownloadPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0.00" comment:"实际支付价格"`
|
||||
|
||||
// 下载相关信息
|
||||
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径(用于二次下载)"`
|
||||
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"`
|
||||
DownloadCount int `gorm:"default:0" comment:"下载次数"`
|
||||
LastDownloadAt *time.Time `comment:"最后下载时间"`
|
||||
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期(从创建日起30天)"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (ComponentReportDownload) TableName() string {
|
||||
return "component_report_downloads"
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (c *ComponentReportDownload) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExpired 检查是否已过期
|
||||
func (c *ComponentReportDownload) IsExpired() bool {
|
||||
if c.ExpiresAt == nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().After(*c.ExpiresAt)
|
||||
}
|
||||
|
||||
// CanDownload 检查是否可以下载
|
||||
func (c *ComponentReportDownload) CanDownload() bool {
|
||||
// 下载记录存在即表示用户有下载权限,只需检查是否过期
|
||||
return !c.IsExpired()
|
||||
}
|
||||
153
internal/domains/product/entities/product.go
Normal file
153
internal/domains/product/entities/product.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Product 产品实体
|
||||
type Product struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"产品ID"`
|
||||
OldID *string `gorm:"type:varchar(36);index" comment:"旧产品ID,用于兼容"`
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
|
||||
Description string `gorm:"type:text" comment:"产品简介"`
|
||||
Content string `gorm:"type:text" comment:"产品内容"`
|
||||
CategoryID string `gorm:"type:varchar(36);not null" comment:"一级分类ID"`
|
||||
SubCategoryID *string `gorm:"type:varchar(36);index" comment:"二级分类ID"`
|
||||
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
||||
CostPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"成本价"`
|
||||
Remark string `gorm:"type:text" comment:"备注"`
|
||||
IsEnabled bool `gorm:"default:false" comment:"是否启用"`
|
||||
IsVisible bool `gorm:"default:false" comment:"是否展示"`
|
||||
IsPackage bool `gorm:"default:false" comment:"是否组合包"`
|
||||
// 组合包相关关联
|
||||
PackageItems []*ProductPackageItem `gorm:"foreignKey:PackageID" comment:"组合包项目列表"`
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `gorm:"default:false" comment:"是否出售UI组件"`
|
||||
UIComponentPrice decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"UI组件销售价格(组合包使用)"`
|
||||
// SEO信息
|
||||
SEOTitle string `gorm:"type:varchar(200)" comment:"SEO标题"`
|
||||
SEODescription string `gorm:"type:text" comment:"SEO描述"`
|
||||
SEOKeywords string `gorm:"type:text" comment:"SEO关键词"`
|
||||
|
||||
// 关联关系
|
||||
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"一级分类"`
|
||||
SubCategory *ProductSubCategory `gorm:"foreignKey:SubCategoryID" comment:"二级分类"`
|
||||
Documentation *ProductDocumentation `gorm:"foreignKey:ProductID" comment:"产品文档"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (p *Product) BeforeCreate(tx *gorm.DB) error {
|
||||
if p.ID == "" {
|
||||
p.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查产品是否有效
|
||||
func (p *Product) IsValid() bool {
|
||||
return p.DeletedAt.Time.IsZero() && p.IsEnabled
|
||||
}
|
||||
|
||||
// IsVisibleToUser 检查产品是否对用户可见
|
||||
func (p *Product) IsVisibleToUser() bool {
|
||||
return p.IsValid() && p.IsVisible
|
||||
}
|
||||
|
||||
// CanBeSubscribed 检查产品是否可以订阅
|
||||
func (p *Product) CanBeSubscribed() bool {
|
||||
return p.IsValid()
|
||||
}
|
||||
|
||||
// UpdateSEO 更新SEO信息
|
||||
func (p *Product) UpdateSEO(title, description, keywords string) {
|
||||
p.SEOTitle = title
|
||||
p.SEODescription = description
|
||||
p.SEOKeywords = keywords
|
||||
}
|
||||
|
||||
// Enable 启用产品
|
||||
func (p *Product) Enable() {
|
||||
p.IsEnabled = true
|
||||
}
|
||||
|
||||
// Disable 禁用产品
|
||||
func (p *Product) Disable() {
|
||||
p.IsEnabled = false
|
||||
}
|
||||
|
||||
// Show 显示产品
|
||||
func (p *Product) Show() {
|
||||
p.IsVisible = true
|
||||
}
|
||||
|
||||
// Hide 隐藏产品
|
||||
func (p *Product) Hide() {
|
||||
p.IsVisible = false
|
||||
}
|
||||
|
||||
// SetAsPackage 设置为组合包
|
||||
func (p *Product) SetAsPackage() {
|
||||
p.IsPackage = true
|
||||
}
|
||||
|
||||
func (p *Product) IsCombo() bool {
|
||||
return p.IsPackage
|
||||
}
|
||||
|
||||
// SetOldID 设置旧ID
|
||||
func (p *Product) SetOldID(oldID string) {
|
||||
p.OldID = &oldID
|
||||
}
|
||||
|
||||
// GetOldID 获取旧ID
|
||||
func (p *Product) GetOldID() string {
|
||||
if p.OldID != nil {
|
||||
return *p.OldID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HasOldID 检查是否有旧ID
|
||||
func (p *Product) HasOldID() bool {
|
||||
return p.OldID != nil && *p.OldID != ""
|
||||
}
|
||||
|
||||
// HasSubCategory 检查是否有二级分类
|
||||
func (p *Product) HasSubCategory() bool {
|
||||
return p.SubCategoryID != nil && *p.SubCategoryID != ""
|
||||
}
|
||||
|
||||
// GetFullCategoryPath 获取完整分类路径(一级分类/二级分类)
|
||||
func (p *Product) GetFullCategoryPath() string {
|
||||
if p.Category == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if p.SubCategory != nil {
|
||||
return p.Category.Name + " / " + p.SubCategory.Name
|
||||
}
|
||||
|
||||
return p.Category.Name
|
||||
}
|
||||
|
||||
// GetFullCategoryCode 获取完整分类编号(一级分类编号.二级分类编号)
|
||||
func (p *Product) GetFullCategoryCode() string {
|
||||
if p.Category == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if p.SubCategory != nil {
|
||||
return p.Category.Code + "." + p.SubCategory.Code
|
||||
}
|
||||
|
||||
return p.Category.Code
|
||||
}
|
||||
159
internal/domains/product/entities/product_api_config.go
Normal file
159
internal/domains/product/entities/product_api_config.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductApiConfig 产品API配置实体
|
||||
type ProductApiConfig struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"配置ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"产品ID"`
|
||||
|
||||
// 请求参数配置
|
||||
RequestParams string `gorm:"type:json;not null" comment:"请求参数配置JSON"`
|
||||
|
||||
// 响应字段配置
|
||||
ResponseFields string `gorm:"type:json;not null" comment:"响应字段配置JSON"`
|
||||
|
||||
// 响应示例
|
||||
ResponseExample string `gorm:"type:json;not null" comment:"响应示例JSON"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// RequestParam 请求参数结构
|
||||
type RequestParam struct {
|
||||
Name string `json:"name" comment:"参数名称"`
|
||||
Field string `json:"field" comment:"参数字段名"`
|
||||
Type string `json:"type" comment:"参数类型"`
|
||||
Required bool `json:"required" comment:"是否必填"`
|
||||
Description string `json:"description" comment:"参数描述"`
|
||||
Example string `json:"example" comment:"参数示例"`
|
||||
Validation string `json:"validation" comment:"验证规则"`
|
||||
}
|
||||
|
||||
// ResponseField 响应字段结构
|
||||
type ResponseField struct {
|
||||
Name string `json:"name" comment:"字段名称"`
|
||||
Path string `json:"path" comment:"字段路径"`
|
||||
Type string `json:"type" comment:"字段类型"`
|
||||
Description string `json:"description" comment:"字段描述"`
|
||||
Required bool `json:"required" comment:"是否必填"`
|
||||
Example string `json:"example" comment:"字段示例"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (pac *ProductApiConfig) BeforeCreate(tx *gorm.DB) error {
|
||||
if pac.ID == "" {
|
||||
pac.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Validate 验证产品API配置
|
||||
func (pac *ProductApiConfig) Validate() error {
|
||||
if pac.ProductID == "" {
|
||||
return NewValidationError("产品ID不能为空")
|
||||
}
|
||||
if pac.RequestParams == "" {
|
||||
return NewValidationError("请求参数配置不能为空")
|
||||
}
|
||||
if pac.ResponseFields == "" {
|
||||
return NewValidationError("响应字段配置不能为空")
|
||||
}
|
||||
if pac.ResponseExample == "" {
|
||||
return NewValidationError("响应示例不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewValidationError 创建验证错误
|
||||
func NewValidationError(message string) error {
|
||||
return &ValidationError{Message: message}
|
||||
}
|
||||
|
||||
// ValidationError 验证错误
|
||||
type ValidationError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// GetRequestParams 获取请求参数列表
|
||||
func (pac *ProductApiConfig) GetRequestParams() ([]RequestParam, error) {
|
||||
var params []RequestParam
|
||||
if pac.RequestParams != "" {
|
||||
err := json.Unmarshal([]byte(pac.RequestParams), ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// SetRequestParams 设置请求参数列表
|
||||
func (pac *ProductApiConfig) SetRequestParams(params []RequestParam) error {
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pac.RequestParams = string(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetResponseFields 获取响应字段列表
|
||||
func (pac *ProductApiConfig) GetResponseFields() ([]ResponseField, error) {
|
||||
var fields []ResponseField
|
||||
if pac.ResponseFields != "" {
|
||||
err := json.Unmarshal([]byte(pac.ResponseFields), &fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// SetResponseFields 设置响应字段列表
|
||||
func (pac *ProductApiConfig) SetResponseFields(fields []ResponseField) error {
|
||||
data, err := json.Marshal(fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pac.ResponseFields = string(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetResponseExample 获取响应示例
|
||||
func (pac *ProductApiConfig) GetResponseExample() (map[string]interface{}, error) {
|
||||
var example map[string]interface{}
|
||||
if pac.ResponseExample != "" {
|
||||
err := json.Unmarshal([]byte(pac.ResponseExample), &example)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return example, nil
|
||||
}
|
||||
|
||||
// SetResponseExample 设置响应示例
|
||||
func (pac *ProductApiConfig) SetResponseExample(example map[string]interface{}) error {
|
||||
data, err := json.Marshal(example)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pac.ResponseExample = string(data)
|
||||
return nil
|
||||
}
|
||||
65
internal/domains/product/entities/product_category.go
Normal file
65
internal/domains/product/entities/product_category.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductCategory 产品分类实体
|
||||
type ProductCategory struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"分类ID"`
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"分类名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"分类编号"`
|
||||
Description string `gorm:"type:text" comment:"分类描述"`
|
||||
Sort int `gorm:"default:0" comment:"排序"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||
|
||||
// 关联关系
|
||||
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
|
||||
SubCategories []ProductSubCategory `gorm:"foreignKey:CategoryID" comment:"二级分类列表"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (pc *ProductCategory) BeforeCreate(tx *gorm.DB) error {
|
||||
if pc.ID == "" {
|
||||
pc.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查分类是否有效
|
||||
func (pc *ProductCategory) IsValid() bool {
|
||||
return pc.DeletedAt.Time.IsZero() && pc.IsEnabled
|
||||
}
|
||||
|
||||
// IsVisibleToUser 检查分类是否对用户可见
|
||||
func (pc *ProductCategory) IsVisibleToUser() bool {
|
||||
return pc.IsValid() && pc.IsVisible
|
||||
}
|
||||
|
||||
// Enable 启用分类
|
||||
func (pc *ProductCategory) Enable() {
|
||||
pc.IsEnabled = true
|
||||
}
|
||||
|
||||
// Disable 禁用分类
|
||||
func (pc *ProductCategory) Disable() {
|
||||
pc.IsEnabled = false
|
||||
}
|
||||
|
||||
// Show 显示分类
|
||||
func (pc *ProductCategory) Show() {
|
||||
pc.IsVisible = true
|
||||
}
|
||||
|
||||
// Hide 隐藏分类
|
||||
func (pc *ProductCategory) Hide() {
|
||||
pc.IsVisible = false
|
||||
}
|
||||
249
internal/domains/product/entities/product_documentation.go
Normal file
249
internal/domains/product/entities/product_documentation.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductDocumentation 产品文档实体
|
||||
type ProductDocumentation struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"文档ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"产品ID"`
|
||||
RequestURL string `gorm:"type:varchar(500);not null" comment:"请求链接"`
|
||||
RequestMethod string `gorm:"type:varchar(20);not null" comment:"请求方法"`
|
||||
BasicInfo string `gorm:"type:text" comment:"基础说明(请求头配置、参数加密等)"`
|
||||
RequestParams string `gorm:"type:text" comment:"请求参数"`
|
||||
ResponseFields string `gorm:"type:text" comment:"返回字段说明"`
|
||||
ResponseExample string `gorm:"type:text" comment:"响应示例"`
|
||||
ErrorCodes string `gorm:"type:text" comment:"错误代码"`
|
||||
Version string `gorm:"type:varchar(20);default:'1.0'" comment:"文档版本"`
|
||||
PDFFilePath string `gorm:"type:varchar(500)" comment:"PDF文档文件路径或URL"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (pd *ProductDocumentation) BeforeCreate(tx *gorm.DB) error {
|
||||
if pd.ID == "" {
|
||||
pd.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查文档是否有效
|
||||
func (pd *ProductDocumentation) IsValid() bool {
|
||||
return pd.DeletedAt.Time.IsZero()
|
||||
}
|
||||
|
||||
// UpdateContent 更新文档内容
|
||||
func (pd *ProductDocumentation) UpdateContent(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) {
|
||||
pd.RequestURL = requestURL
|
||||
pd.RequestMethod = requestMethod
|
||||
pd.BasicInfo = basicInfo
|
||||
pd.RequestParams = requestParams
|
||||
pd.ResponseFields = responseFields
|
||||
pd.ResponseExample = responseExample
|
||||
pd.ErrorCodes = errorCodes
|
||||
}
|
||||
|
||||
// IncrementVersion 增加版本号
|
||||
func (pd *ProductDocumentation) IncrementVersion() {
|
||||
if pd.Version == "" {
|
||||
pd.Version = "1.0"
|
||||
return
|
||||
}
|
||||
|
||||
// 解析版本号 major.minor
|
||||
parts := strings.Split(pd.Version, ".")
|
||||
if len(parts) < 2 {
|
||||
// 如果格式不正确,重置为 1.0
|
||||
pd.Version = "1.0"
|
||||
return
|
||||
}
|
||||
|
||||
// 解析 major 和 minor
|
||||
var major, minor int
|
||||
_, err := fmt.Sscanf(parts[0], "%d", &major)
|
||||
if err != nil {
|
||||
pd.Version = "1.0"
|
||||
return
|
||||
}
|
||||
_, err = fmt.Sscanf(parts[1], "%d", &minor)
|
||||
if err != nil {
|
||||
pd.Version = "1.0"
|
||||
return
|
||||
}
|
||||
|
||||
// 递增 minor
|
||||
minor++
|
||||
// 如果 minor 达到 10,则 major +1,minor 重置为 0
|
||||
if minor >= 10 {
|
||||
major++
|
||||
minor = 0
|
||||
}
|
||||
|
||||
// 更新版本号
|
||||
pd.Version = fmt.Sprintf("%d.%d", major, minor)
|
||||
}
|
||||
|
||||
// Validate 验证文档完整性
|
||||
func (pd *ProductDocumentation) Validate() error {
|
||||
if pd.RequestURL == "" {
|
||||
return errors.New("请求链接不能为空")
|
||||
}
|
||||
if pd.RequestMethod == "" {
|
||||
return errors.New("请求方法不能为空")
|
||||
}
|
||||
if pd.ProductID == "" {
|
||||
return errors.New("产品ID不能为空")
|
||||
}
|
||||
|
||||
// 验证请求方法
|
||||
validMethods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}
|
||||
methodValid := false
|
||||
for _, method := range validMethods {
|
||||
if strings.ToUpper(pd.RequestMethod) == method {
|
||||
methodValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !methodValid {
|
||||
return fmt.Errorf("无效的请求方法: %s", pd.RequestMethod)
|
||||
}
|
||||
|
||||
// 验证URL格式(简单验证)
|
||||
if !strings.HasPrefix(pd.RequestURL, "http://") && !strings.HasPrefix(pd.RequestURL, "https://") {
|
||||
return errors.New("请求链接必须以http://或https://开头")
|
||||
}
|
||||
|
||||
// 验证版本号格式
|
||||
if pd.Version != "" {
|
||||
if !isValidVersion(pd.Version) {
|
||||
return fmt.Errorf("无效的版本号格式: %s", pd.Version)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanPublish 检查是否可以发布
|
||||
func (pd *ProductDocumentation) CanPublish() error {
|
||||
if err := pd.Validate(); err != nil {
|
||||
return fmt.Errorf("文档验证失败: %w", err)
|
||||
}
|
||||
if pd.BasicInfo == "" {
|
||||
return errors.New("基础说明不能为空")
|
||||
}
|
||||
if pd.RequestParams == "" {
|
||||
return errors.New("请求参数不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDocumentation 更新文档内容并自动递增版本
|
||||
func (pd *ProductDocumentation) UpdateDocumentation(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) error {
|
||||
// 验证必填字段
|
||||
if requestURL == "" || requestMethod == "" {
|
||||
return errors.New("请求链接和请求方法不能为空")
|
||||
}
|
||||
|
||||
// 更新内容
|
||||
pd.UpdateContent(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes)
|
||||
|
||||
// 自动递增版本
|
||||
pd.IncrementVersion()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDocumentationSummary 获取文档摘要
|
||||
func (pd *ProductDocumentation) GetDocumentationSummary() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": pd.ID,
|
||||
"product_id": pd.ProductID,
|
||||
"request_url": pd.RequestURL,
|
||||
"method": pd.RequestMethod,
|
||||
"version": pd.Version,
|
||||
"created_at": pd.CreatedAt,
|
||||
"updated_at": pd.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// HasRequiredFields 检查是否包含必需字段
|
||||
func (pd *ProductDocumentation) HasRequiredFields() bool {
|
||||
return pd.RequestURL != "" &&
|
||||
pd.RequestMethod != "" &&
|
||||
pd.ProductID != "" &&
|
||||
pd.BasicInfo != "" &&
|
||||
pd.RequestParams != ""
|
||||
}
|
||||
|
||||
// IsComplete 检查文档是否完整
|
||||
func (pd *ProductDocumentation) IsComplete() bool {
|
||||
return pd.HasRequiredFields() &&
|
||||
pd.ResponseFields != "" &&
|
||||
pd.ResponseExample != "" &&
|
||||
pd.ErrorCodes != ""
|
||||
}
|
||||
|
||||
// GetCompletionPercentage 获取文档完成度百分比
|
||||
func (pd *ProductDocumentation) GetCompletionPercentage() int {
|
||||
totalFields := 8 // 总字段数
|
||||
completedFields := 0
|
||||
|
||||
if pd.RequestURL != "" {
|
||||
completedFields++
|
||||
}
|
||||
if pd.RequestMethod != "" {
|
||||
completedFields++
|
||||
}
|
||||
if pd.BasicInfo != "" {
|
||||
completedFields++
|
||||
}
|
||||
if pd.RequestParams != "" {
|
||||
completedFields++
|
||||
}
|
||||
if pd.ResponseFields != "" {
|
||||
completedFields++
|
||||
}
|
||||
if pd.ResponseExample != "" {
|
||||
completedFields++
|
||||
}
|
||||
if pd.ErrorCodes != "" {
|
||||
completedFields++
|
||||
}
|
||||
return (completedFields * 100) / totalFields
|
||||
}
|
||||
|
||||
// isValidVersion 验证版本号格式
|
||||
func isValidVersion(version string) bool {
|
||||
// 简单的版本号验证:x.y.z 格式
|
||||
parts := strings.Split(version, ".")
|
||||
if len(parts) < 1 || len(parts) > 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
return false
|
||||
}
|
||||
// 检查是否为数字
|
||||
for _, char := range part {
|
||||
if char < '0' || char > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
32
internal/domains/product/entities/product_package_item.go
Normal file
32
internal/domains/product/entities/product_package_item.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductPackageItem 产品组合包项目
|
||||
type ProductPackageItem struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)"`
|
||||
PackageID string `gorm:"type:varchar(36);not null;index" comment:"组合包产品ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"子产品ID"`
|
||||
SortOrder int `gorm:"default:0" comment:"排序"`
|
||||
|
||||
// 关联关系
|
||||
Package *Product `gorm:"foreignKey:PackageID" comment:"组合包产品"`
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"子产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (ppi *ProductPackageItem) BeforeCreate(tx *gorm.DB) error {
|
||||
if ppi.ID == "" {
|
||||
ppi.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
53
internal/domains/product/entities/product_parameter.go
Normal file
53
internal/domains/product/entities/product_parameter.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductParameter 产品参数配置实体
|
||||
type ProductParameter struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"参数配置ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"参数名称"`
|
||||
Field string `gorm:"type:varchar(50);not null" comment:"参数字段名"`
|
||||
Type string `gorm:"type:varchar(20);not null;default:'string'" comment:"参数类型"`
|
||||
Required bool `gorm:"default:true" comment:"是否必填"`
|
||||
Description string `gorm:"type:text" comment:"参数描述"`
|
||||
Example string `gorm:"type:varchar(200)" comment:"参数示例"`
|
||||
Validation string `gorm:"type:text" comment:"验证规则"`
|
||||
SortOrder int `gorm:"default:0" comment:"排序"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (pp *ProductParameter) BeforeCreate(tx *gorm.DB) error {
|
||||
if pp.ID == "" {
|
||||
pp.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查参数配置是否有效
|
||||
func (pp *ProductParameter) IsValid() bool {
|
||||
return pp.DeletedAt.Time.IsZero()
|
||||
}
|
||||
|
||||
// GetValidationRules 获取验证规则
|
||||
func (pp *ProductParameter) GetValidationRules() map[string]interface{} {
|
||||
if pp.Validation == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 这里可以解析JSON格式的验证规则
|
||||
// 暂时返回空map,后续可以扩展
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
82
internal/domains/product/entities/product_sub_category.go
Normal file
82
internal/domains/product/entities/product_sub_category.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductSubCategory 产品二级分类实体
|
||||
type ProductSubCategory struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"二级分类ID"`
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"二级分类名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"二级分类编号"`
|
||||
Description string `gorm:"type:text" comment:"二级分类描述"`
|
||||
CategoryID string `gorm:"type:varchar(36);not null;index" comment:"一级分类ID"`
|
||||
Sort int `gorm:"default:0" comment:"排序"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||
|
||||
// 关联关系
|
||||
Category *ProductCategory `gorm:"foreignKey:CategoryID" comment:"一级分类"`
|
||||
Products []Product `gorm:"foreignKey:SubCategoryID" comment:"产品列表"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (psc *ProductSubCategory) BeforeCreate(tx *gorm.DB) error {
|
||||
if psc.ID == "" {
|
||||
psc.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查二级分类是否有效
|
||||
func (psc *ProductSubCategory) IsValid() bool {
|
||||
return psc.DeletedAt.Time.IsZero() && psc.IsEnabled
|
||||
}
|
||||
|
||||
// IsVisibleToUser 检查二级分类是否对用户可见
|
||||
func (psc *ProductSubCategory) IsVisibleToUser() bool {
|
||||
return psc.IsValid() && psc.IsVisible
|
||||
}
|
||||
|
||||
// Enable 启用二级分类
|
||||
func (psc *ProductSubCategory) Enable() {
|
||||
psc.IsEnabled = true
|
||||
}
|
||||
|
||||
// Disable 禁用二级分类
|
||||
func (psc *ProductSubCategory) Disable() {
|
||||
psc.IsEnabled = false
|
||||
}
|
||||
|
||||
// Show 显示二级分类
|
||||
func (psc *ProductSubCategory) Show() {
|
||||
psc.IsVisible = true
|
||||
}
|
||||
|
||||
// Hide 隐藏二级分类
|
||||
func (psc *ProductSubCategory) Hide() {
|
||||
psc.IsVisible = false
|
||||
}
|
||||
|
||||
// GetFullPath 获取完整路径(一级分类/二级分类)
|
||||
func (psc *ProductSubCategory) GetFullPath() string {
|
||||
if psc.Category != nil {
|
||||
return psc.Category.Name + " / " + psc.Name
|
||||
}
|
||||
return psc.Name
|
||||
}
|
||||
|
||||
// GetFullCode 获取完整编号(一级分类编号.二级分类编号)
|
||||
func (psc *ProductSubCategory) GetFullCode() string {
|
||||
if psc.Category != nil {
|
||||
return psc.Category.Code + "." + psc.Code
|
||||
}
|
||||
return psc.Code
|
||||
}
|
||||
36
internal/domains/product/entities/product_ui_component.go
Normal file
36
internal/domains/product/entities/product_ui_component.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ProductUIComponent 产品UI组件关联实体
|
||||
type ProductUIComponent struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"关联ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||
UIComponentID string `gorm:"type:varchar(36);not null;index" comment:"UI组件ID"`
|
||||
Price decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"销售价格"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用销售"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
UIComponent *UIComponent `gorm:"foreignKey:UIComponentID" comment:"UI组件"`
|
||||
}
|
||||
|
||||
func (ProductUIComponent) TableName() string {
|
||||
return "product_ui_components"
|
||||
}
|
||||
|
||||
func (p *ProductUIComponent) BeforeCreate(tx *gorm.DB) error {
|
||||
if p.ID == "" {
|
||||
p.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
52
internal/domains/product/entities/subscription.go
Normal file
52
internal/domains/product/entities/subscription.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Subscription 订阅实体
|
||||
type Subscription struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||
Price decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
|
||||
UIComponentPrice decimal.Decimal `gorm:"type:decimal(10,2);not null;default:0" comment:"UI组件价格(组合包使用)"`
|
||||
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
|
||||
Version int64 `gorm:"default:1" comment:"乐观锁版本号"`
|
||||
|
||||
// 关联关系
|
||||
Product *Product `gorm:"foreignKey:ProductID" comment:"产品"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (s *Subscription) BeforeCreate(tx *gorm.DB) error {
|
||||
if s.ID == "" {
|
||||
s.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查订阅是否有效
|
||||
func (s *Subscription) IsValid() bool {
|
||||
return s.DeletedAt.Time.IsZero()
|
||||
}
|
||||
|
||||
// IncrementAPIUsage 增加API使用次数
|
||||
func (s *Subscription) IncrementAPIUsage(count int64) {
|
||||
s.APIUsed += count
|
||||
s.Version++ // 增加版本号
|
||||
}
|
||||
|
||||
// ResetAPIUsage 重置API使用次数
|
||||
func (s *Subscription) ResetAPIUsage() {
|
||||
s.APIUsed = 0
|
||||
s.Version++ // 增加版本号
|
||||
}
|
||||
40
internal/domains/product/entities/ui_component.go
Normal file
40
internal/domains/product/entities/ui_component.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UIComponent UI组件实体
|
||||
type UIComponent struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"组件ID"`
|
||||
ComponentCode string `gorm:"type:varchar(50);not null;uniqueIndex" json:"component_code" comment:"组件编码"`
|
||||
ComponentName string `gorm:"type:varchar(100);not null" json:"component_name" comment:"组件名称"`
|
||||
Description string `gorm:"type:text" json:"description" comment:"组件描述"`
|
||||
FilePath *string `gorm:"type:varchar(500)" json:"file_path" comment:"组件文件路径"`
|
||||
FileHash *string `gorm:"type:varchar(64)" json:"file_hash" comment:"文件哈希值"`
|
||||
FileSize *int64 `gorm:"type:bigint" json:"file_size" comment:"文件大小"`
|
||||
FileType *string `gorm:"type:varchar(50)" json:"file_type" comment:"文件类型"`
|
||||
FolderPath *string `gorm:"type:varchar(500)" json:"folder_path" comment:"组件文件夹路径"`
|
||||
IsExtracted bool `gorm:"default:false" json:"is_extracted" comment:"是否已解压"`
|
||||
FileUploadTime *time.Time `gorm:"type:timestamp" json:"file_upload_time" comment:"文件上传时间"`
|
||||
Version string `gorm:"type:varchar(20)" json:"version" comment:"组件版本"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active" comment:"是否启用"`
|
||||
SortOrder int `gorm:"default:0" json:"sort_order" comment:"排序"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
func (UIComponent) TableName() string {
|
||||
return "ui_components"
|
||||
}
|
||||
|
||||
func (u *UIComponent) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.ID == "" {
|
||||
u.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
)
|
||||
|
||||
// ComponentReportRepository 组件报告仓储接口
|
||||
type ComponentReportRepository interface {
|
||||
// 创建下载记录
|
||||
Create(ctx context.Context, download *entities.ComponentReportDownload) error
|
||||
|
||||
// 更新下载记录
|
||||
UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error
|
||||
|
||||
// 根据ID获取下载记录
|
||||
GetDownloadByID(ctx context.Context, id string) (*entities.ComponentReportDownload, error)
|
||||
|
||||
// 获取用户的下载记录列表
|
||||
GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error)
|
||||
|
||||
// 获取用户有效的下载记录
|
||||
GetActiveDownload(ctx context.Context, userID, productID string) (*entities.ComponentReportDownload, error)
|
||||
|
||||
// 更新下载记录文件路径
|
||||
UpdateFilePath(ctx context.Context, downloadID, filePath string) error
|
||||
|
||||
// 增加下载次数
|
||||
IncrementDownloadCount(ctx context.Context, downloadID string) error
|
||||
|
||||
// 检查用户是否已下载过指定产品编号的组件
|
||||
HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error)
|
||||
|
||||
// 获取用户已下载的产品编号列表
|
||||
GetUserDownloadedProductCodes(ctx context.Context, userID string) ([]string, error)
|
||||
|
||||
// 根据支付订单号获取下载记录
|
||||
GetDownloadByPaymentOrderID(ctx context.Context, orderID string) (*entities.ComponentReportDownload, error)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
)
|
||||
|
||||
// ProductApiConfigRepository 产品API配置仓库接口
|
||||
type ProductApiConfigRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, config entities.ProductApiConfig) error
|
||||
Update(ctx context.Context, config entities.ProductApiConfig) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
GetByID(ctx context.Context, id string) (*entities.ProductApiConfig, error)
|
||||
|
||||
// 根据产品ID查找配置
|
||||
FindByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error)
|
||||
|
||||
// 根据产品代码查找配置
|
||||
FindByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error)
|
||||
|
||||
// 批量获取配置
|
||||
FindByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error)
|
||||
|
||||
// 检查配置是否存在
|
||||
ExistsByProductID(ctx context.Context, productID string) (bool, error)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductCategoryRepository 产品分类仓储接口
|
||||
type ProductCategoryRepository interface {
|
||||
interfaces.Repository[entities.ProductCategory]
|
||||
|
||||
// 基础查询方法
|
||||
FindByCode(ctx context.Context, code string) (*entities.ProductCategory, error)
|
||||
FindVisible(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
FindEnabled(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) ([]*entities.ProductCategory, int64, error)
|
||||
|
||||
// 统计方法
|
||||
CountEnabled(ctx context.Context) (int64, error)
|
||||
CountVisible(ctx context.Context) (int64, error)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
)
|
||||
|
||||
// ProductDocumentationRepository 产品文档仓储接口
|
||||
type ProductDocumentationRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, documentation *entities.ProductDocumentation) error
|
||||
Update(ctx context.Context, documentation *entities.ProductDocumentation) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
FindByID(ctx context.Context, id string) (*entities.ProductDocumentation, error)
|
||||
|
||||
// 业务查询操作
|
||||
FindByProductID(ctx context.Context, productID string) (*entities.ProductDocumentation, error)
|
||||
|
||||
// 批量操作
|
||||
FindByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductDocumentation, error)
|
||||
UpdateBatch(ctx context.Context, documentations []*entities.ProductDocumentation) error
|
||||
|
||||
// 统计操作
|
||||
CountByProductID(ctx context.Context, productID string) (int64, error)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductRepository 产品仓储接口
|
||||
type ProductRepository interface {
|
||||
interfaces.Repository[entities.Product]
|
||||
|
||||
// 基础查询方法
|
||||
FindByCode(ctx context.Context, code string) (*entities.Product, error)
|
||||
FindByOldID(ctx context.Context, oldID string) (*entities.Product, error)
|
||||
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.Product, error)
|
||||
FindVisible(ctx context.Context) ([]*entities.Product, error)
|
||||
FindEnabled(ctx context.Context) ([]*entities.Product, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListProducts(ctx context.Context, query *queries.ListProductsQuery) ([]*entities.Product, int64, error)
|
||||
ListProductsWithSubscriptionStatus(ctx context.Context, query *queries.ListProductsQuery) ([]*entities.Product, map[string]bool, int64, error)
|
||||
|
||||
// 业务查询方法
|
||||
FindSubscribableProducts(ctx context.Context, userID string) ([]*entities.Product, error)
|
||||
FindProductsByIDs(ctx context.Context, ids []string) ([]*entities.Product, error)
|
||||
|
||||
// 统计方法
|
||||
CountByCategory(ctx context.Context, categoryID string) (int64, error)
|
||||
CountEnabled(ctx context.Context) (int64, error)
|
||||
CountVisible(ctx context.Context) (int64, error)
|
||||
|
||||
// 组合包相关方法
|
||||
GetPackageItems(ctx context.Context, packageID string) ([]*entities.ProductPackageItem, error)
|
||||
CreatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error
|
||||
GetPackageItemByID(ctx context.Context, itemID string) (*entities.ProductPackageItem, error)
|
||||
UpdatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error
|
||||
DeletePackageItem(ctx context.Context, itemID string) error
|
||||
DeletePackageItemsByPackageID(ctx context.Context, packageID string) error
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
)
|
||||
|
||||
// ProductSubCategoryRepository 产品二级分类仓储接口
|
||||
type ProductSubCategoryRepository interface {
|
||||
// 基础CRUD方法
|
||||
GetByID(ctx context.Context, id string) (*entities.ProductSubCategory, error)
|
||||
Create(ctx context.Context, category entities.ProductSubCategory) (*entities.ProductSubCategory, error)
|
||||
Update(ctx context.Context, category entities.ProductSubCategory) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
List(ctx context.Context) ([]*entities.ProductSubCategory, error)
|
||||
|
||||
// 查询方法
|
||||
FindByCode(ctx context.Context, code string) (*entities.ProductSubCategory, error)
|
||||
FindByCategoryID(ctx context.Context, categoryID string) ([]*entities.ProductSubCategory, error)
|
||||
FindVisible(ctx context.Context) ([]*entities.ProductSubCategory, error)
|
||||
FindEnabled(ctx context.Context) ([]*entities.ProductSubCategory, error)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
)
|
||||
|
||||
// ProductUIComponentRepository 产品UI组件关联仓储接口
|
||||
type ProductUIComponentRepository interface {
|
||||
Create(ctx context.Context, relation entities.ProductUIComponent) (entities.ProductUIComponent, error)
|
||||
GetByProductID(ctx context.Context, productID string) ([]entities.ProductUIComponent, error)
|
||||
GetByUIComponentID(ctx context.Context, componentID string) ([]entities.ProductUIComponent, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
DeleteByProductID(ctx context.Context, productID string) error
|
||||
BatchCreate(ctx context.Context, relations []entities.ProductUIComponent) error
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
// ListCategoriesQuery 分类列表查询
|
||||
type ListCategoriesQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// GetCategoryQuery 获取分类详情查询
|
||||
type GetCategoryQuery struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GetCategoryTreeQuery 获取分类树查询
|
||||
type GetCategoryTreeQuery struct {
|
||||
IncludeDisabled bool `json:"include_disabled"`
|
||||
IncludeHidden bool `json:"include_hidden"`
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package queries
|
||||
|
||||
// ListProductsQuery 产品列表查询
|
||||
type ListProductsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Keyword string `json:"keyword"`
|
||||
CategoryID string `json:"category_id"`
|
||||
MinPrice *float64 `json:"min_price"`
|
||||
MaxPrice *float64 `json:"max_price"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
IsPackage *bool `json:"is_package"`
|
||||
UserID string `json:"user_id"` // 用户ID,用于查询订阅状态
|
||||
IsSubscribed *bool `json:"is_subscribed"` // 是否已订阅筛选
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// SearchProductsQuery 产品搜索查询
|
||||
type SearchProductsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Keyword string `json:"keyword"`
|
||||
CategoryID string `json:"category_id"`
|
||||
MinPrice *float64 `json:"min_price"`
|
||||
MaxPrice *float64 `json:"max_price"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
IsPackage *bool `json:"is_package"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
|
||||
// GetProductQuery 获取产品详情查询
|
||||
type GetProductQuery struct {
|
||||
ID string `json:"id"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// GetProductsByIDsQuery 根据ID列表获取产品查询
|
||||
type GetProductsByIDsQuery struct {
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package queries
|
||||
|
||||
// ListSubscriptionsQuery 订阅列表查询
|
||||
type ListSubscriptionsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
Keyword string `json:"keyword"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
|
||||
// 新增筛选字段
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
ProductName string `json:"product_name"` // 产品名称
|
||||
StartTime string `json:"start_time"` // 订阅开始时间
|
||||
EndTime string `json:"end_time"` // 订阅结束时间
|
||||
}
|
||||
|
||||
// GetSubscriptionQuery 获取订阅详情查询
|
||||
type GetSubscriptionQuery struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// GetUserSubscriptionsQuery 获取用户订阅查询
|
||||
type GetUserSubscriptionsQuery struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptionsQuery 获取产品订阅查询
|
||||
type GetProductSubscriptionsQuery struct {
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SubscriptionRepository 订阅仓储接口
|
||||
type SubscriptionRepository interface {
|
||||
interfaces.Repository[entities.Subscription]
|
||||
|
||||
// 基础查询方法
|
||||
FindByUserID(ctx context.Context, userID string) ([]*entities.Subscription, error)
|
||||
FindByProductID(ctx context.Context, productID string) ([]*entities.Subscription, error)
|
||||
FindByUserAndProduct(ctx context.Context, userID, productID string) (*entities.Subscription, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error)
|
||||
|
||||
// 统计方法
|
||||
CountByUser(ctx context.Context, userID string) (int64, error)
|
||||
CountByProduct(ctx context.Context, productID string) (int64, error)
|
||||
GetTotalRevenue(ctx context.Context) (float64, error)
|
||||
|
||||
// 乐观锁更新方法
|
||||
IncrementAPIUsageWithOptimisticLock(ctx context.Context, subscriptionID string, increment int64) error
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
)
|
||||
|
||||
// UIComponentRepository UI组件仓储接口
|
||||
type UIComponentRepository interface {
|
||||
Create(ctx context.Context, component entities.UIComponent) (entities.UIComponent, error)
|
||||
GetByID(ctx context.Context, id string) (*entities.UIComponent, error)
|
||||
GetByCode(ctx context.Context, code string) (*entities.UIComponent, error)
|
||||
List(ctx context.Context, filters map[string]interface{}) ([]entities.UIComponent, int64, error)
|
||||
Update(ctx context.Context, component entities.UIComponent) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
GetByCodes(ctx context.Context, codes []string) ([]entities.UIComponent, error)
|
||||
}
|
||||
161
internal/domains/product/services/product_api_config_service.go
Normal file
161
internal/domains/product/services/product_api_config_service.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProductApiConfigService 产品API配置领域服务接口
|
||||
type ProductApiConfigService interface {
|
||||
// 根据产品ID获取API配置
|
||||
GetApiConfigByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error)
|
||||
|
||||
// 根据产品代码获取API配置
|
||||
GetApiConfigByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error)
|
||||
|
||||
// 批量获取产品API配置
|
||||
GetApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error)
|
||||
|
||||
// 创建产品API配置
|
||||
CreateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error
|
||||
|
||||
// 更新产品API配置
|
||||
UpdateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error
|
||||
|
||||
// 删除产品API配置
|
||||
DeleteApiConfig(ctx context.Context, configID string) error
|
||||
|
||||
// 检查产品API配置是否存在
|
||||
ExistsByProductID(ctx context.Context, productID string) (bool, error)
|
||||
}
|
||||
|
||||
// ProductApiConfigServiceImpl 产品API配置领域服务实现
|
||||
type ProductApiConfigServiceImpl struct {
|
||||
apiConfigRepo repositories.ProductApiConfigRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductApiConfigService 创建产品API配置领域服务
|
||||
func NewProductApiConfigService(
|
||||
apiConfigRepo repositories.ProductApiConfigRepository,
|
||||
logger *zap.Logger,
|
||||
) ProductApiConfigService {
|
||||
return &ProductApiConfigServiceImpl{
|
||||
apiConfigRepo: apiConfigRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetApiConfigByProductID 根据产品ID获取API配置
|
||||
func (s *ProductApiConfigServiceImpl) GetApiConfigByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error) {
|
||||
s.logger.Debug("获取产品API配置", zap.String("product_id", productID))
|
||||
|
||||
config, err := s.apiConfigRepo.FindByProductID(ctx, productID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品API配置失败", zap.Error(err), zap.String("product_id", productID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetApiConfigByProductCode 根据产品代码获取API配置
|
||||
func (s *ProductApiConfigServiceImpl) GetApiConfigByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error) {
|
||||
s.logger.Debug("根据产品代码获取API配置", zap.String("product_code", productCode))
|
||||
|
||||
config, err := s.apiConfigRepo.FindByProductCode(ctx, productCode)
|
||||
if err != nil {
|
||||
s.logger.Error("根据产品代码获取API配置失败", zap.Error(err), zap.String("product_code", productCode))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetApiConfigsByProductIDs 批量获取产品API配置
|
||||
func (s *ProductApiConfigServiceImpl) GetApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error) {
|
||||
s.logger.Debug("批量获取产品API配置", zap.Strings("product_ids", productIDs))
|
||||
|
||||
configs, err := s.apiConfigRepo.FindByProductIDs(ctx, productIDs)
|
||||
if err != nil {
|
||||
s.logger.Error("批量获取产品API配置失败", zap.Error(err), zap.Strings("product_ids", productIDs))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// CreateApiConfig 创建产品API配置
|
||||
func (s *ProductApiConfigServiceImpl) CreateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error {
|
||||
s.logger.Debug("创建产品API配置", zap.String("product_id", config.ProductID))
|
||||
|
||||
// 检查是否已存在配置
|
||||
exists, err := s.apiConfigRepo.ExistsByProductID(ctx, config.ProductID)
|
||||
if err != nil {
|
||||
s.logger.Error("检查产品API配置是否存在失败", zap.Error(err), zap.String("product_id", config.ProductID))
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return entities.NewValidationError("产品API配置已存在")
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
if err := config.Validate(); err != nil {
|
||||
s.logger.Error("产品API配置验证失败", zap.Error(err), zap.String("product_id", config.ProductID))
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
err = s.apiConfigRepo.Create(ctx, *config)
|
||||
if err != nil {
|
||||
s.logger.Error("创建产品API配置失败", zap.Error(err), zap.String("product_id", config.ProductID))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("产品API配置创建成功", zap.String("product_id", config.ProductID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateApiConfig 更新产品API配置
|
||||
func (s *ProductApiConfigServiceImpl) UpdateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error {
|
||||
s.logger.Debug("更新产品API配置", zap.String("config_id", config.ID))
|
||||
|
||||
// 验证配置
|
||||
if err := config.Validate(); err != nil {
|
||||
s.logger.Error("产品API配置验证失败", zap.Error(err), zap.String("config_id", config.ID))
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
err := s.apiConfigRepo.Update(ctx, *config)
|
||||
if err != nil {
|
||||
s.logger.Error("更新产品API配置失败", zap.Error(err), zap.String("config_id", config.ID))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("产品API配置更新成功", zap.String("config_id", config.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteApiConfig 删除产品API配置
|
||||
func (s *ProductApiConfigServiceImpl) DeleteApiConfig(ctx context.Context, configID string) error {
|
||||
s.logger.Debug("删除产品API配置", zap.String("config_id", configID))
|
||||
|
||||
err := s.apiConfigRepo.Delete(ctx, configID)
|
||||
if err != nil {
|
||||
s.logger.Error("删除产品API配置失败", zap.Error(err), zap.String("config_id", configID))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("产品API配置删除成功", zap.String("config_id", configID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExistsByProductID 检查产品API配置是否存在
|
||||
func (s *ProductApiConfigServiceImpl) ExistsByProductID(ctx context.Context, productID string) (bool, error) {
|
||||
return s.apiConfigRepo.ExistsByProductID(ctx, productID)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories"
|
||||
)
|
||||
|
||||
// ProductDocumentationService 产品文档服务
|
||||
type ProductDocumentationService struct {
|
||||
docRepo repositories.ProductDocumentationRepository
|
||||
productRepo repositories.ProductRepository
|
||||
}
|
||||
|
||||
// NewProductDocumentationService 创建文档服务实例
|
||||
func NewProductDocumentationService(
|
||||
docRepo repositories.ProductDocumentationRepository,
|
||||
productRepo repositories.ProductRepository,
|
||||
) *ProductDocumentationService {
|
||||
return &ProductDocumentationService{
|
||||
docRepo: docRepo,
|
||||
productRepo: productRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDocumentation 创建文档
|
||||
func (s *ProductDocumentationService) CreateDocumentation(ctx context.Context, productID string, doc *entities.ProductDocumentation) error {
|
||||
// 验证产品是否存在
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
if !product.IsValid() {
|
||||
return errors.New("产品已禁用或删除")
|
||||
}
|
||||
|
||||
// 检查是否已存在文档
|
||||
existingDoc, err := s.docRepo.FindByProductID(ctx, productID)
|
||||
if err == nil && existingDoc != nil {
|
||||
return errors.New("该产品已存在文档")
|
||||
}
|
||||
|
||||
// 设置产品ID
|
||||
doc.ProductID = productID
|
||||
|
||||
// 验证文档完整性
|
||||
if err := doc.Validate(); err != nil {
|
||||
return fmt.Errorf("文档验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建文档
|
||||
return s.docRepo.Create(ctx, doc)
|
||||
}
|
||||
|
||||
// UpdateDocumentation 更新文档
|
||||
func (s *ProductDocumentationService) UpdateDocumentation(ctx context.Context, id string, requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) error {
|
||||
// 查找现有文档
|
||||
doc, err := s.docRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("文档不存在: %w", err)
|
||||
}
|
||||
|
||||
// 使用实体的更新方法
|
||||
err = doc.UpdateDocumentation(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("文档更新失败: %w", err)
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
return s.docRepo.Update(ctx, doc)
|
||||
}
|
||||
|
||||
// GetDocumentation 获取文档
|
||||
func (s *ProductDocumentationService) GetDocumentation(ctx context.Context, id string) (*entities.ProductDocumentation, error) {
|
||||
return s.docRepo.FindByID(ctx, id)
|
||||
}
|
||||
|
||||
// GetDocumentationByProductID 通过产品ID获取文档
|
||||
func (s *ProductDocumentationService) GetDocumentationByProductID(ctx context.Context, productID string) (*entities.ProductDocumentation, error) {
|
||||
return s.docRepo.FindByProductID(ctx, productID)
|
||||
}
|
||||
|
||||
// DeleteDocumentation 删除文档
|
||||
func (s *ProductDocumentationService) DeleteDocumentation(ctx context.Context, id string) error {
|
||||
_, err := s.docRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("文档不存在: %w", err)
|
||||
}
|
||||
|
||||
return s.docRepo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// GetDocumentationWithProduct 获取文档及其关联的产品信息
|
||||
func (s *ProductDocumentationService) GetDocumentationWithProduct(ctx context.Context, id string) (*entities.ProductDocumentation, error) {
|
||||
doc, err := s.docRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加载产品信息
|
||||
product, err := s.productRepo.GetByID(ctx, doc.ProductID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取产品信息失败: %w", err)
|
||||
}
|
||||
|
||||
doc.Product = &product
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// GetDocumentationsByProductIDs 批量获取文档
|
||||
func (s *ProductDocumentationService) GetDocumentationsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductDocumentation, error) {
|
||||
return s.docRepo.FindByProductIDs(ctx, productIDs)
|
||||
}
|
||||
|
||||
// UpdateDocumentationEntity 更新文档实体(用于更新PDFFilePath等字段)
|
||||
func (s *ProductDocumentationService) UpdateDocumentationEntity(ctx context.Context, doc *entities.ProductDocumentation) error {
|
||||
// 验证文档是否存在
|
||||
_, err := s.docRepo.FindByID(ctx, doc.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("文档不存在: %w", err)
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
return s.docRepo.Update(ctx, doc)
|
||||
}
|
||||
437
internal/domains/product/services/product_management_service.go
Normal file
437
internal/domains/product/services/product_management_service.go
Normal file
@@ -0,0 +1,437 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/application/product/dto/commands"
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories"
|
||||
"hyapi-server/internal/domains/product/repositories/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductManagementService 产品管理领域服务
|
||||
// 负责产品的基本管理操作,包括创建、查询、更新等
|
||||
type ProductManagementService struct {
|
||||
productRepo repositories.ProductRepository
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
subCategoryRepo repositories.ProductSubCategoryRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductManagementService 创建产品管理领域服务
|
||||
func NewProductManagementService(
|
||||
productRepo repositories.ProductRepository,
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
subCategoryRepo repositories.ProductSubCategoryRepository,
|
||||
logger *zap.Logger,
|
||||
) *ProductManagementService {
|
||||
return &ProductManagementService{
|
||||
productRepo: productRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
subCategoryRepo: subCategoryRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProduct 创建产品
|
||||
func (s *ProductManagementService) CreateProduct(ctx context.Context, product *entities.Product) (*entities.Product, error) {
|
||||
// 验证产品信息
|
||||
if err := s.ValidateProduct(product); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证产品编号唯一性
|
||||
if err := s.ValidateProductCode(product.Code, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建产品
|
||||
createdProduct, err := s.productRepo.Create(ctx, *product)
|
||||
if err != nil {
|
||||
s.logger.Error("创建产品失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品创建成功",
|
||||
zap.String("product_id", createdProduct.ID),
|
||||
zap.String("product_name", createdProduct.Name),
|
||||
)
|
||||
|
||||
return &createdProduct, nil
|
||||
}
|
||||
|
||||
// GetProductByID 根据ID获取产品
|
||||
func (s *ProductManagementService) GetProductByID(ctx context.Context, productID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
func (s *ProductManagementService) GetProductByCode(ctx context.Context, productCode string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.FindByCode(ctx, productCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
return product, nil
|
||||
}
|
||||
|
||||
// GetProductWithCategory 获取产品及其分类信息
|
||||
func (s *ProductManagementService) GetProductWithCategory(ctx context.Context, productID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, product.CategoryID)
|
||||
if err == nil {
|
||||
product.Category = &category
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是组合包,加载子产品信息
|
||||
if product.IsPackage {
|
||||
packageItems, err := s.productRepo.GetPackageItems(ctx, productID)
|
||||
if err == nil {
|
||||
product.PackageItems = packageItems
|
||||
}
|
||||
}
|
||||
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// GetProductByOldIDWithCategory 根据旧ID获取产品及其分类信息
|
||||
func (s *ProductManagementService) GetProductByOldIDWithCategory(ctx context.Context, oldID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.FindByOldID(ctx, oldID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, product.CategoryID)
|
||||
if err == nil {
|
||||
product.Category = &category
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是组合包,加载子产品信息
|
||||
if product.IsPackage {
|
||||
packageItems, err := s.productRepo.GetPackageItems(ctx, product.ID)
|
||||
if err == nil {
|
||||
product.PackageItems = packageItems
|
||||
}
|
||||
}
|
||||
|
||||
return product, nil
|
||||
}
|
||||
|
||||
// GetPackageItems 获取组合包项目列表
|
||||
func (s *ProductManagementService) GetPackageItems(ctx context.Context, packageID string) ([]*entities.ProductPackageItem, error) {
|
||||
packageItems, err := s.productRepo.GetPackageItems(ctx, packageID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取组合包项目失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取组合包项目失败: %w", err)
|
||||
}
|
||||
return packageItems, nil
|
||||
}
|
||||
|
||||
// CreatePackageItem 创建组合包项目
|
||||
func (s *ProductManagementService) CreatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error {
|
||||
if err := s.productRepo.CreatePackageItem(ctx, packageItem); err != nil {
|
||||
s.logger.Error("创建组合包项目失败", zap.Error(err))
|
||||
return fmt.Errorf("创建组合包项目失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("组合包项目创建成功",
|
||||
zap.String("package_id", packageItem.PackageID),
|
||||
zap.String("product_id", packageItem.ProductID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPackageItemByID 根据ID获取组合包项目
|
||||
func (s *ProductManagementService) GetPackageItemByID(ctx context.Context, itemID string) (*entities.ProductPackageItem, error) {
|
||||
packageItem, err := s.productRepo.GetPackageItemByID(ctx, itemID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("组合包项目不存在: %w", err)
|
||||
}
|
||||
return packageItem, nil
|
||||
}
|
||||
|
||||
// UpdatePackageItem 更新组合包项目
|
||||
func (s *ProductManagementService) UpdatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error {
|
||||
if err := s.productRepo.UpdatePackageItem(ctx, packageItem); err != nil {
|
||||
s.logger.Error("更新组合包项目失败", zap.Error(err))
|
||||
return fmt.Errorf("更新组合包项目失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("组合包项目更新成功",
|
||||
zap.String("item_id", packageItem.ID),
|
||||
zap.String("package_id", packageItem.PackageID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePackageItem 删除组合包项目
|
||||
func (s *ProductManagementService) DeletePackageItem(ctx context.Context, itemID string) error {
|
||||
if err := s.productRepo.DeletePackageItem(ctx, itemID); err != nil {
|
||||
s.logger.Error("删除组合包项目失败", zap.Error(err))
|
||||
return fmt.Errorf("删除组合包项目失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("组合包项目删除成功", zap.String("item_id", itemID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePackageItemsBatch 批量更新组合包子产品
|
||||
func (s *ProductManagementService) UpdatePackageItemsBatch(ctx context.Context, packageID string, items []commands.PackageItemData) error {
|
||||
// 删除现有的所有子产品
|
||||
if err := s.productRepo.DeletePackageItemsByPackageID(ctx, packageID); err != nil {
|
||||
s.logger.Error("删除现有组合包子产品失败", zap.Error(err))
|
||||
return fmt.Errorf("删除现有组合包子产品失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建新的子产品项目
|
||||
for _, item := range items {
|
||||
packageItem := &entities.ProductPackageItem{
|
||||
PackageID: packageID,
|
||||
ProductID: item.ProductID,
|
||||
SortOrder: item.SortOrder,
|
||||
}
|
||||
|
||||
if err := s.productRepo.CreatePackageItem(ctx, packageItem); err != nil {
|
||||
s.logger.Error("创建组合包子产品失败", zap.Error(err))
|
||||
return fmt.Errorf("创建组合包子产品失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("批量更新组合包子产品成功",
|
||||
zap.String("package_id", packageID),
|
||||
zap.Int("item_count", len(items)),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProduct 更新产品
|
||||
func (s *ProductManagementService) UpdateProduct(ctx context.Context, product *entities.Product) error {
|
||||
// 验证产品信息
|
||||
if err := s.ValidateProduct(product); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证产品编号唯一性(排除自己)
|
||||
if err := s.ValidateProductCode(product.Code, product.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.productRepo.Update(ctx, *product); err != nil {
|
||||
s.logger.Error("更新产品失败", zap.Error(err))
|
||||
return fmt.Errorf("更新产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品更新成功",
|
||||
zap.String("product_id", product.ID),
|
||||
zap.String("product_name", product.Name),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProduct 删除产品
|
||||
func (s *ProductManagementService) DeleteProduct(ctx context.Context, productID string) error {
|
||||
if err := s.productRepo.Delete(ctx, productID); err != nil {
|
||||
s.logger.Error("删除产品失败", zap.Error(err))
|
||||
return fmt.Errorf("删除产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品删除成功", zap.String("product_id", productID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVisibleProducts 获取可见产品列表
|
||||
func (s *ProductManagementService) GetVisibleProducts(ctx context.Context) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindVisible(ctx)
|
||||
}
|
||||
|
||||
// GetEnabledProducts 获取启用产品列表
|
||||
func (s *ProductManagementService) GetEnabledProducts(ctx context.Context) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindEnabled(ctx)
|
||||
}
|
||||
|
||||
// GetProductsByCategory 根据分类获取产品
|
||||
func (s *ProductManagementService) GetProductsByCategory(ctx context.Context, categoryID string) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindByCategoryID(ctx, categoryID)
|
||||
}
|
||||
|
||||
// ValidateProduct 验证产品
|
||||
func (s *ProductManagementService) ValidateProduct(product *entities.Product) error {
|
||||
if product == nil {
|
||||
return errors.New("产品不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Name) == "" {
|
||||
return errors.New("产品名称不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
if product.Price.IsNegative() {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
|
||||
if product.CostPrice.IsNegative() {
|
||||
return errors.New("成本价不能为负数")
|
||||
}
|
||||
|
||||
// 验证分类是否存在
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(context.Background(), product.CategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品分类不存在: %w", err)
|
||||
}
|
||||
if !category.IsValid() {
|
||||
return errors.New("产品分类已禁用或删除")
|
||||
}
|
||||
}
|
||||
|
||||
// 验证二级分类是否存在(如果设置了二级分类)
|
||||
if product.SubCategoryID != nil && *product.SubCategoryID != "" {
|
||||
subCategory, err := s.subCategoryRepo.GetByID(context.Background(), *product.SubCategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品二级分类不存在: %w", err)
|
||||
}
|
||||
if !subCategory.IsValid() {
|
||||
return errors.New("产品二级分类已禁用或删除")
|
||||
}
|
||||
// 验证二级分类是否属于指定的一级分类
|
||||
if subCategory.CategoryID != product.CategoryID {
|
||||
return errors.New("二级分类不属于指定的一级分类")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateProductCode 验证产品编号唯一性
|
||||
func (s *ProductManagementService) ValidateProductCode(code string, excludeID string) error {
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
existingProduct, err := s.productRepo.FindByCode(context.Background(), code)
|
||||
if err == nil && existingProduct != nil && existingProduct.ID != excludeID {
|
||||
return errors.New("产品编号已存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListProducts 获取产品列表(支持筛选和分页)
|
||||
func (s *ProductManagementService) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.Product, int64, error) {
|
||||
// 构建查询条件
|
||||
query := &queries.ListProductsQuery{
|
||||
Page: options.Page,
|
||||
PageSize: options.PageSize,
|
||||
SortBy: options.Sort,
|
||||
SortOrder: options.Order,
|
||||
}
|
||||
|
||||
// 应用筛选条件
|
||||
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
|
||||
query.Keyword = keyword
|
||||
}
|
||||
if categoryID, ok := filters["category_id"].(string); ok && categoryID != "" {
|
||||
query.CategoryID = categoryID
|
||||
}
|
||||
if isEnabled, ok := filters["is_enabled"].(bool); ok {
|
||||
query.IsEnabled = &isEnabled
|
||||
}
|
||||
if isVisible, ok := filters["is_visible"].(bool); ok {
|
||||
query.IsVisible = &isVisible
|
||||
}
|
||||
if isPackage, ok := filters["is_package"].(bool); ok {
|
||||
query.IsPackage = &isPackage
|
||||
}
|
||||
|
||||
// 调用仓储层获取产品列表
|
||||
products, total, err := s.productRepo.ListProducts(ctx, query)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取产品列表失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品列表查询成功",
|
||||
zap.Int("count", len(products)),
|
||||
zap.Int64("total", total),
|
||||
zap.Int("page", options.Page),
|
||||
zap.Int("page_size", options.PageSize),
|
||||
)
|
||||
|
||||
return products, total, nil
|
||||
}
|
||||
|
||||
// ListProductsWithSubscriptionStatus 获取产品列表(包含订阅状态)
|
||||
func (s *ProductManagementService) ListProductsWithSubscriptionStatus(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.Product, map[string]bool, int64, error) {
|
||||
// 构建查询条件
|
||||
query := &queries.ListProductsQuery{
|
||||
Page: options.Page,
|
||||
PageSize: options.PageSize,
|
||||
SortBy: options.Sort,
|
||||
SortOrder: options.Order,
|
||||
}
|
||||
|
||||
// 应用筛选条件
|
||||
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
|
||||
query.Keyword = keyword
|
||||
}
|
||||
if categoryID, ok := filters["category_id"].(string); ok && categoryID != "" {
|
||||
query.CategoryID = categoryID
|
||||
}
|
||||
if isEnabled, ok := filters["is_enabled"].(bool); ok {
|
||||
query.IsEnabled = &isEnabled
|
||||
}
|
||||
if isVisible, ok := filters["is_visible"].(bool); ok {
|
||||
query.IsVisible = &isVisible
|
||||
}
|
||||
if isPackage, ok := filters["is_package"].(bool); ok {
|
||||
query.IsPackage = &isPackage
|
||||
}
|
||||
if userID, ok := filters["user_id"].(string); ok && userID != "" {
|
||||
query.UserID = userID
|
||||
}
|
||||
if isSubscribed, ok := filters["is_subscribed"].(bool); ok {
|
||||
query.IsSubscribed = &isSubscribed
|
||||
}
|
||||
|
||||
// 调用仓储层获取产品列表(包含订阅状态)
|
||||
products, subscriptionStatusMap, total, err := s.productRepo.ListProductsWithSubscriptionStatus(ctx, query)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
return nil, nil, 0, fmt.Errorf("获取产品列表失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品列表查询成功",
|
||||
zap.Int("count", len(products)),
|
||||
zap.Int64("total", total),
|
||||
zap.Int("page", options.Page),
|
||||
zap.Int("page_size", options.PageSize),
|
||||
zap.String("user_id", query.UserID),
|
||||
)
|
||||
|
||||
return products, subscriptionStatusMap, total, nil
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/domains/product/entities"
|
||||
"hyapi-server/internal/domains/product/repositories"
|
||||
"hyapi-server/internal/domains/product/repositories/queries"
|
||||
"hyapi-server/internal/shared/interfaces"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// ProductSubscriptionService 产品订阅领域服务
|
||||
// 负责产品订阅相关的业务逻辑,包括订阅验证、订阅管理等
|
||||
type ProductSubscriptionService struct {
|
||||
productRepo repositories.ProductRepository
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductSubscriptionService 创建产品订阅领域服务
|
||||
func NewProductSubscriptionService(
|
||||
productRepo repositories.ProductRepository,
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
logger *zap.Logger,
|
||||
) *ProductSubscriptionService {
|
||||
return &ProductSubscriptionService{
|
||||
productRepo: productRepo,
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// UserSubscribedProductByCode 查找用户已订阅的产品
|
||||
func (s *ProductSubscriptionService) UserSubscribedProductByCode(ctx context.Context, userID string, productCode string) (*entities.Subscription, error) {
|
||||
product, err := s.productRepo.FindByCode(ctx, productCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, product.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// GetUserSubscribedProduct 查找用户已订阅的产品
|
||||
func (s *ProductSubscriptionService) GetUserSubscribedProduct(ctx context.Context, userID string, productID string) (*entities.Subscription, error) {
|
||||
subscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, productID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// CanUserSubscribeProduct 检查用户是否可以订阅产品
|
||||
func (s *ProductSubscriptionService) CanUserSubscribeProduct(ctx context.Context, userID string, productID string) (bool, error) {
|
||||
// 检查产品是否存在且可订阅
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
if !product.CanBeSubscribed() {
|
||||
return false, errors.New("产品不可订阅")
|
||||
}
|
||||
|
||||
// 检查用户是否已有该产品的订阅
|
||||
existingSubscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, productID)
|
||||
if err == nil && existingSubscription != nil {
|
||||
return false, errors.New("用户已有该产品的订阅")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CreateSubscription 创建订阅
|
||||
func (s *ProductSubscriptionService) CreateSubscription(ctx context.Context, userID, productID string) (*entities.Subscription, error) {
|
||||
// 检查是否可以订阅
|
||||
canSubscribe, err := s.CanUserSubscribeProduct(ctx, userID, productID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !canSubscribe {
|
||||
return nil, errors.New("无法订阅该产品")
|
||||
}
|
||||
|
||||
// 获取产品信息以获取价格
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 创建订阅
|
||||
subscription := &entities.Subscription{
|
||||
UserID: userID,
|
||||
ProductID: productID,
|
||||
Price: product.Price,
|
||||
UIComponentPrice: product.UIComponentPrice,
|
||||
}
|
||||
|
||||
createdSubscription, err := s.subscriptionRepo.Create(ctx, *subscription)
|
||||
if err != nil {
|
||||
s.logger.Error("创建订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("订阅创建成功",
|
||||
zap.String("subscription_id", createdSubscription.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("product_id", productID),
|
||||
)
|
||||
|
||||
return &createdSubscription, nil
|
||||
}
|
||||
|
||||
// ListSubscriptions 获取订阅列表
|
||||
func (s *ProductSubscriptionService) ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error) {
|
||||
return s.subscriptionRepo.ListSubscriptions(ctx, query)
|
||||
}
|
||||
|
||||
// GetUserSubscriptions 获取用户订阅列表
|
||||
func (s *ProductSubscriptionService) GetUserSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error) {
|
||||
return s.subscriptionRepo.FindByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// GetSubscriptionByID 根据ID获取订阅
|
||||
func (s *ProductSubscriptionService) GetSubscriptionByID(ctx context.Context, subscriptionID string) (*entities.Subscription, error) {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
return &subscription, nil
|
||||
}
|
||||
|
||||
// CancelSubscription 取消订阅
|
||||
func (s *ProductSubscriptionService) CancelSubscription(ctx context.Context, subscriptionID string) error {
|
||||
// 由于订阅实体没有状态字段,这里直接删除订阅
|
||||
if err := s.subscriptionRepo.Delete(ctx, subscriptionID); err != nil {
|
||||
s.logger.Error("取消订阅失败", zap.Error(err))
|
||||
return fmt.Errorf("取消订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("订阅取消成功",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计信息
|
||||
func (s *ProductSubscriptionService) GetProductStats(ctx context.Context) (map[string]int64, error) {
|
||||
stats := make(map[string]int64)
|
||||
|
||||
total, err := s.productRepo.CountByCategory(ctx, "")
|
||||
if err == nil {
|
||||
stats["total"] = total
|
||||
}
|
||||
|
||||
enabled, err := s.productRepo.CountEnabled(ctx)
|
||||
if err == nil {
|
||||
stats["enabled"] = enabled
|
||||
}
|
||||
|
||||
visible, err := s.productRepo.CountVisible(ctx)
|
||||
if err == nil {
|
||||
stats["visible"] = visible
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (s *ProductSubscriptionService) SaveSubscription(ctx context.Context, subscription *entities.Subscription) error {
|
||||
exists, err := s.subscriptionRepo.Exists(ctx, subscription.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查订阅是否存在失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return s.subscriptionRepo.Update(ctx, *subscription)
|
||||
} else {
|
||||
_, err := s.subscriptionRepo.Create(ctx, *subscription)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建订阅失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// IncrementSubscriptionAPIUsage 增加订阅API使用次数(使用乐观锁,带重试机制)
|
||||
func (s *ProductSubscriptionService) IncrementSubscriptionAPIUsage(ctx context.Context, subscriptionID string, increment int64) error {
|
||||
const maxRetries = 3
|
||||
const baseDelay = 10 * time.Millisecond
|
||||
|
||||
for attempt := 0; attempt < maxRetries; attempt++ {
|
||||
// 使用乐观锁直接更新数据库
|
||||
err := s.subscriptionRepo.IncrementAPIUsageWithOptimisticLock(ctx, subscriptionID, increment)
|
||||
if err == nil {
|
||||
// 更新成功
|
||||
if attempt > 0 {
|
||||
s.logger.Info("订阅API使用次数更新成功(重试后)",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Int64("increment", increment),
|
||||
zap.Int("retry_count", attempt))
|
||||
} else {
|
||||
s.logger.Info("订阅API使用次数更新成功",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Int64("increment", increment))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否是版本冲突错误
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 版本冲突,等待后重试
|
||||
if attempt < maxRetries-1 {
|
||||
delay := time.Duration(attempt+1) * baseDelay
|
||||
s.logger.Debug("订阅版本冲突,准备重试",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Int("attempt", attempt+1),
|
||||
zap.Duration("delay", delay))
|
||||
time.Sleep(delay)
|
||||
continue
|
||||
}
|
||||
// 最后一次重试失败
|
||||
s.logger.Error("订阅不存在或版本冲突,重试次数已用完",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Int("max_retries", maxRetries),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("订阅不存在或已被其他操作修改(重试%d次后失败): %w", maxRetries, err)
|
||||
}
|
||||
|
||||
// 其他错误直接返回,不重试
|
||||
s.logger.Error("更新订阅API使用次数失败",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Int64("increment", increment),
|
||||
zap.Error(err))
|
||||
return fmt.Errorf("更新订阅API使用次数失败: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("更新失败,已重试%d次", maxRetries)
|
||||
}
|
||||
|
||||
// GetSubscriptionStats 获取订阅统计信息
|
||||
func (s *ProductSubscriptionService) GetSubscriptionStats(ctx context.Context) (map[string]interface{}, error) {
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
// 获取总订阅数
|
||||
totalSubscriptions, err := s.subscriptionRepo.Count(ctx, interfaces.CountOptions{})
|
||||
if err != nil {
|
||||
s.logger.Error("获取订阅总数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取订阅总数失败: %w", err)
|
||||
}
|
||||
stats["total_subscriptions"] = totalSubscriptions
|
||||
|
||||
// 获取总收入
|
||||
totalRevenue, err := s.subscriptionRepo.GetTotalRevenue(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取总收入失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取总收入失败: %w", err)
|
||||
}
|
||||
stats["total_revenue"] = totalRevenue
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetUserSubscriptionStats 获取用户订阅统计信息
|
||||
func (s *ProductSubscriptionService) GetUserSubscriptionStats(ctx context.Context, userID string) (map[string]interface{}, error) {
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
// 获取用户订阅数
|
||||
userSubscriptions, err := s.subscriptionRepo.FindByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
|
||||
}
|
||||
|
||||
// 计算用户总收入
|
||||
var totalRevenue float64
|
||||
for _, subscription := range userSubscriptions {
|
||||
totalRevenue += subscription.Price.InexactFloat64()
|
||||
}
|
||||
|
||||
stats["total_subscriptions"] = int64(len(userSubscriptions))
|
||||
stats["total_revenue"] = totalRevenue
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// UpdateSubscriptionPrice 更新订阅价格
|
||||
func (s *ProductSubscriptionService) UpdateSubscriptionPrice(ctx context.Context, subscriptionID string, newPrice float64) error {
|
||||
// 获取订阅
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新价格
|
||||
subscription.Price = decimal.NewFromFloat(newPrice)
|
||||
subscription.Version++ // 增加版本号
|
||||
|
||||
// 保存更新
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("更新订阅价格失败", zap.Error(err))
|
||||
return fmt.Errorf("更新订阅价格失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("订阅价格更新成功",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Float64("new_price", newPrice))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSubscriptionPriceWithUIComponent 更新订阅价格和UI组件价格
|
||||
func (s *ProductSubscriptionService) UpdateSubscriptionPriceWithUIComponent(ctx context.Context, subscriptionID string, newPrice float64, newUIComponentPrice float64) error {
|
||||
// 获取订阅
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新价格
|
||||
subscription.Price = decimal.NewFromFloat(newPrice)
|
||||
subscription.UIComponentPrice = decimal.NewFromFloat(newUIComponentPrice)
|
||||
subscription.Version++ // 增加版本号
|
||||
|
||||
// 保存更新
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("更新订阅价格失败", zap.Error(err))
|
||||
return fmt.Errorf("更新订阅价格失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("订阅价格更新成功",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
zap.Float64("new_price", newPrice),
|
||||
zap.Float64("new_ui_component_price", newUIComponentPrice))
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user