This commit is contained in:
2025-12-19 17:05:09 +08:00
parent cc3472ff40
commit 39c46937ea
307 changed files with 87686 additions and 129 deletions

View File

@@ -0,0 +1,130 @@
package repositories
import (
"context"
"encoding/json"
"errors"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"tyapi-server/internal/shared/database"
"go.uber.org/zap"
"gorm.io/gorm"
)
const (
ComponentReportDownloadsTable = "component_report_downloads"
)
type GormComponentReportRepository struct {
*database.CachedBaseRepositoryImpl
}
var _ repositories.ComponentReportRepository = (*GormComponentReportRepository)(nil)
func NewGormComponentReportRepository(db *gorm.DB, logger *zap.Logger) repositories.ComponentReportRepository {
return &GormComponentReportRepository{
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, ComponentReportDownloadsTable),
}
}
func (r *GormComponentReportRepository) CreateDownload(ctx context.Context, download *entities.ComponentReportDownload) (*entities.ComponentReportDownload, error) {
err := r.CreateEntity(ctx, download)
if err != nil {
return nil, err
}
return download, nil
}
func (r *GormComponentReportRepository) UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error {
return r.UpdateEntity(ctx, download)
}
func (r *GormComponentReportRepository) GetDownloadByID(ctx context.Context, id string) (*entities.ComponentReportDownload, error) {
var download entities.ComponentReportDownload
err := r.SmartGetByID(ctx, id, &download)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, gorm.ErrRecordNotFound
}
return nil, err
}
return &download, nil
}
func (r *GormComponentReportRepository) GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error) {
var downloads []entities.ComponentReportDownload
query := r.GetDB(ctx).Where("user_id = ? AND payment_status = ?", userID, "success")
if productID != nil && *productID != "" {
query = query.Where("product_id = ?", *productID)
}
err := query.Order("created_at DESC").Find(&downloads).Error
if err != nil {
return nil, err
}
result := make([]*entities.ComponentReportDownload, len(downloads))
for i := range downloads {
result[i] = &downloads[i]
}
return result, nil
}
func (r *GormComponentReportRepository) HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error) {
var count int64
err := r.GetDB(ctx).Model(&entities.ComponentReportDownload{}).
Where("user_id = ? AND product_code = ? AND payment_status = ?", userID, productCode, "success").
Count(&count).Error
if err != nil {
return false, err
}
return count > 0, nil
}
func (r *GormComponentReportRepository) GetUserDownloadedProductCodes(ctx context.Context, userID string) ([]string, error) {
var downloads []entities.ComponentReportDownload
err := r.GetDB(ctx).
Select("DISTINCT sub_product_codes").
Where("user_id = ? AND payment_status = ?", userID, "success").
Find(&downloads).Error
if err != nil {
return nil, err
}
codesMap := make(map[string]bool)
for _, download := range downloads {
if download.SubProductCodes != "" {
var codes []string
if err := json.Unmarshal([]byte(download.SubProductCodes), &codes); err == nil {
for _, code := range codes {
codesMap[code] = true
}
}
}
// 也添加主产品编号
if download.ProductCode != "" {
codesMap[download.ProductCode] = true
}
}
codes := make([]string, 0, len(codesMap))
for code := range codesMap {
codes = append(codes, code)
}
return codes, nil
}
func (r *GormComponentReportRepository) GetDownloadByPaymentOrderID(ctx context.Context, orderID string) (*entities.ComponentReportDownload, error) {
var download entities.ComponentReportDownload
err := r.GetDB(ctx).Where("payment_order_id = ?", orderID).First(&download).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, gorm.ErrRecordNotFound
}
return nil, err
}
return &download, nil
}

View File

@@ -0,0 +1,80 @@
package repositories
import (
"context"
"fmt"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"gorm.io/gorm"
)
// GormProductUIComponentRepository 产品UI组件关联仓储实现
type GormProductUIComponentRepository struct {
db *gorm.DB
}
// NewGormProductUIComponentRepository 创建产品UI组件关联仓储实例
func NewGormProductUIComponentRepository(db *gorm.DB) repositories.ProductUIComponentRepository {
return &GormProductUIComponentRepository{db: db}
}
// Create 创建产品UI组件关联
func (r *GormProductUIComponentRepository) Create(ctx context.Context, relation entities.ProductUIComponent) (entities.ProductUIComponent, error) {
if err := r.db.WithContext(ctx).Create(&relation).Error; err != nil {
return entities.ProductUIComponent{}, fmt.Errorf("创建产品UI组件关联失败: %w", err)
}
return relation, nil
}
// GetByProductID 根据产品ID获取UI组件关联列表
func (r *GormProductUIComponentRepository) GetByProductID(ctx context.Context, productID string) ([]entities.ProductUIComponent, error) {
var relations []entities.ProductUIComponent
if err := r.db.WithContext(ctx).
Preload("UIComponent").
Where("product_id = ?", productID).
Find(&relations).Error; err != nil {
return nil, fmt.Errorf("获取产品UI组件关联列表失败: %w", err)
}
return relations, nil
}
// GetByUIComponentID 根据UI组件ID获取产品关联列表
func (r *GormProductUIComponentRepository) GetByUIComponentID(ctx context.Context, componentID string) ([]entities.ProductUIComponent, error) {
var relations []entities.ProductUIComponent
if err := r.db.WithContext(ctx).
Preload("Product").
Where("ui_component_id = ?", componentID).
Find(&relations).Error; err != nil {
return nil, fmt.Errorf("获取UI组件产品关联列表失败: %w", err)
}
return relations, nil
}
// Delete 删除产品UI组件关联
func (r *GormProductUIComponentRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.ProductUIComponent{}, id).Error; err != nil {
return fmt.Errorf("删除产品UI组件关联失败: %w", err)
}
return nil
}
// DeleteByProductID 根据产品ID删除所有关联
func (r *GormProductUIComponentRepository) DeleteByProductID(ctx context.Context, productID string) error {
if err := r.db.WithContext(ctx).Where("product_id = ?", productID).Delete(&entities.ProductUIComponent{}).Error; err != nil {
return fmt.Errorf("根据产品ID删除UI组件关联失败: %w", err)
}
return nil
}
// BatchCreate 批量创建产品UI组件关联
func (r *GormProductUIComponentRepository) BatchCreate(ctx context.Context, relations []entities.ProductUIComponent) error {
if len(relations) == 0 {
return nil
}
if err := r.db.WithContext(ctx).CreateInBatches(relations, 100).Error; err != nil {
return fmt.Errorf("批量创建产品UI组件关联失败: %w", err)
}
return nil
}

View File

@@ -0,0 +1,129 @@
package repositories
import (
"context"
"fmt"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
"gorm.io/gorm"
)
// GormUIComponentRepository UI组件仓储实现
type GormUIComponentRepository struct {
db *gorm.DB
}
// NewGormUIComponentRepository 创建UI组件仓储实例
func NewGormUIComponentRepository(db *gorm.DB) repositories.UIComponentRepository {
return &GormUIComponentRepository{db: db}
}
// Create 创建UI组件
func (r *GormUIComponentRepository) Create(ctx context.Context, component entities.UIComponent) (entities.UIComponent, error) {
if err := r.db.WithContext(ctx).Create(&component).Error; err != nil {
return entities.UIComponent{}, fmt.Errorf("创建UI组件失败: %w", err)
}
return component, nil
}
// GetByID 根据ID获取UI组件
func (r *GormUIComponentRepository) GetByID(ctx context.Context, id string) (*entities.UIComponent, error) {
var component entities.UIComponent
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&component).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, fmt.Errorf("获取UI组件失败: %w", err)
}
return &component, nil
}
// GetByCode 根据编码获取UI组件
func (r *GormUIComponentRepository) GetByCode(ctx context.Context, code string) (*entities.UIComponent, error) {
var component entities.UIComponent
if err := r.db.WithContext(ctx).Where("component_code = ?", code).First(&component).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, fmt.Errorf("获取UI组件失败: %w", err)
}
return &component, nil
}
// List 获取UI组件列表
func (r *GormUIComponentRepository) List(ctx context.Context, filters map[string]interface{}) ([]entities.UIComponent, int64, error) {
var components []entities.UIComponent
var total int64
query := r.db.WithContext(ctx).Model(&entities.UIComponent{})
// 应用过滤条件
if isActive, ok := filters["is_active"]; ok {
query = query.Where("is_active = ?", isActive)
}
if keyword, ok := filters["keyword"]; ok && keyword != "" {
query = query.Where("component_name LIKE ? OR component_code LIKE ? OR description LIKE ?",
"%"+keyword.(string)+"%", "%"+keyword.(string)+"%", "%"+keyword.(string)+"%")
}
// 获取总数
if err := query.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("获取UI组件总数失败: %w", err)
}
// 分页
if page, ok := filters["page"]; ok {
if pageSize, ok := filters["page_size"]; ok {
offset := (page.(int) - 1) * pageSize.(int)
query = query.Offset(offset).Limit(pageSize.(int))
}
}
// 排序
if sortBy, ok := filters["sort_by"]; ok {
if sortOrder, ok := filters["sort_order"]; ok {
query = query.Order(fmt.Sprintf("%s %s", sortBy, sortOrder))
}
} else {
query = query.Order("sort_order ASC, created_at DESC")
}
// 获取数据
if err := query.Find(&components).Error; err != nil {
return nil, 0, fmt.Errorf("获取UI组件列表失败: %w", err)
}
return components, total, nil
}
// Update 更新UI组件
func (r *GormUIComponentRepository) Update(ctx context.Context, component entities.UIComponent) error {
if err := r.db.WithContext(ctx).Save(&component).Error; err != nil {
return fmt.Errorf("更新UI组件失败: %w", err)
}
return nil
}
// Delete 删除UI组件
func (r *GormUIComponentRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.UIComponent{}, id).Error; err != nil {
return fmt.Errorf("删除UI组件失败: %w", err)
}
return nil
}
// GetByCodes 根据编码列表获取UI组件
func (r *GormUIComponentRepository) GetByCodes(ctx context.Context, codes []string) ([]entities.UIComponent, error) {
var components []entities.UIComponent
if len(codes) == 0 {
return components, nil
}
if err := r.db.WithContext(ctx).Where("component_code IN ?", codes).Find(&components).Error; err != nil {
return nil, fmt.Errorf("根据编码列表获取UI组件失败: %w", err)
}
return components, nil
}

View File

@@ -0,0 +1,115 @@
package storage
import (
"context"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"go.uber.org/zap"
)
// LocalFileStorageService 本地文件存储服务
type LocalFileStorageService struct {
basePath string
logger *zap.Logger
}
// LocalFileStorageConfig 本地文件存储配置
type LocalFileStorageConfig struct {
BasePath string `yaml:"base_path"`
}
// NewLocalFileStorageService 创建本地文件存储服务
func NewLocalFileStorageService(basePath string, logger *zap.Logger) *LocalFileStorageService {
// 确保基础路径存在
if err := os.MkdirAll(basePath, 0755); err != nil {
logger.Error("创建基础存储目录失败", zap.Error(err), zap.String("path", basePath))
}
return &LocalFileStorageService{
basePath: basePath,
logger: logger,
}
}
// StoreFile 存储文件
func (s *LocalFileStorageService) StoreFile(ctx context.Context, file io.Reader, filename string) (string, error) {
// 构建完整文件路径
fullPath := filepath.Join(s.basePath, filename)
// 确保目录存在
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0755); err != nil {
s.logger.Error("创建目录失败", zap.Error(err), zap.String("dir", dir))
return "", fmt.Errorf("创建目录失败: %w", err)
}
// 创建文件
dst, err := os.Create(fullPath)
if err != nil {
s.logger.Error("创建文件失败", zap.Error(err), zap.String("path", fullPath))
return "", fmt.Errorf("创建文件失败: %w", err)
}
defer dst.Close()
// 复制文件内容
if _, err := io.Copy(dst, file); err != nil {
s.logger.Error("写入文件失败", zap.Error(err), zap.String("path", fullPath))
// 删除部分写入的文件
_ = os.Remove(fullPath)
return "", fmt.Errorf("写入文件失败: %w", err)
}
s.logger.Info("文件存储成功", zap.String("path", fullPath))
return fullPath, nil
}
// StoreMultipartFile 存储multipart文件
func (s *LocalFileStorageService) StoreMultipartFile(ctx context.Context, file *multipart.FileHeader, filename string) (string, error) {
src, err := file.Open()
if err != nil {
return "", fmt.Errorf("打开上传文件失败: %w", err)
}
defer src.Close()
return s.StoreFile(ctx, src, filename)
}
// GetFileURL 获取文件URL
func (s *LocalFileStorageService) GetFileURL(ctx context.Context, filePath string) (string, error) {
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return "", fmt.Errorf("文件不存在: %s", filePath)
}
// 返回文件路径在实际应用中这里应该返回可访问的URL
return filePath, nil
}
// DeleteFile 删除文件
func (s *LocalFileStorageService) DeleteFile(ctx context.Context, filePath string) error {
if err := os.Remove(filePath); err != nil {
if os.IsNotExist(err) {
// 文件不存在,不视为错误
return nil
}
s.logger.Error("删除文件失败", zap.Error(err), zap.String("path", filePath))
return fmt.Errorf("删除文件失败: %w", err)
}
s.logger.Info("文件删除成功", zap.String("path", filePath))
return nil
}
// GetFileReader 获取文件读取器
func (s *LocalFileStorageService) GetFileReader(ctx context.Context, filePath string) (io.ReadCloser, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %w", err)
}
return file, nil
}

View File

@@ -0,0 +1,110 @@
package storage
import (
"context"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"go.uber.org/zap"
)
// LocalFileStorageServiceImpl 本地文件存储服务实现
type LocalFileStorageServiceImpl struct {
basePath string
logger *zap.Logger
}
// NewLocalFileStorageServiceImpl 创建本地文件存储服务实现
func NewLocalFileStorageServiceImpl(basePath string, logger *zap.Logger) *LocalFileStorageServiceImpl {
// 确保基础路径存在
if err := os.MkdirAll(basePath, 0755); err != nil {
logger.Error("创建基础存储目录失败", zap.Error(err), zap.String("path", basePath))
}
return &LocalFileStorageServiceImpl{
basePath: basePath,
logger: logger,
}
}
// StoreFile 存储文件
func (s *LocalFileStorageServiceImpl) StoreFile(ctx context.Context, file io.Reader, filename string) (string, error) {
// 构建完整文件路径
fullPath := filepath.Join(s.basePath, filename)
// 确保目录存在
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0755); err != nil {
s.logger.Error("创建目录失败", zap.Error(err), zap.String("dir", dir))
return "", fmt.Errorf("创建目录失败: %w", err)
}
// 创建文件
dst, err := os.Create(fullPath)
if err != nil {
s.logger.Error("创建文件失败", zap.Error(err), zap.String("path", fullPath))
return "", fmt.Errorf("创建文件失败: %w", err)
}
defer dst.Close()
// 复制文件内容
if _, err := io.Copy(dst, file); err != nil {
s.logger.Error("写入文件失败", zap.Error(err), zap.String("path", fullPath))
// 删除部分写入的文件
_ = os.Remove(fullPath)
return "", fmt.Errorf("写入文件失败: %w", err)
}
s.logger.Info("文件存储成功", zap.String("path", fullPath))
return fullPath, nil
}
// StoreMultipartFile 存储multipart文件
func (s *LocalFileStorageServiceImpl) StoreMultipartFile(ctx context.Context, file *multipart.FileHeader, filename string) (string, error) {
src, err := file.Open()
if err != nil {
return "", fmt.Errorf("打开上传文件失败: %w", err)
}
defer src.Close()
return s.StoreFile(ctx, src, filename)
}
// GetFileURL 获取文件URL
func (s *LocalFileStorageServiceImpl) GetFileURL(ctx context.Context, filePath string) (string, error) {
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return "", fmt.Errorf("文件不存在: %s", filePath)
}
// 返回文件路径在实际应用中这里应该返回可访问的URL
return filePath, nil
}
// DeleteFile 删除文件
func (s *LocalFileStorageServiceImpl) DeleteFile(ctx context.Context, filePath string) error {
if err := os.Remove(filePath); err != nil {
if os.IsNotExist(err) {
// 文件不存在,不视为错误
return nil
}
s.logger.Error("删除文件失败", zap.Error(err), zap.String("path", filePath))
return fmt.Errorf("删除文件失败: %w", err)
}
s.logger.Info("文件删除成功", zap.String("path", filePath))
return nil
}
// GetFileReader 获取文件读取器
func (s *LocalFileStorageServiceImpl) GetFileReader(ctx context.Context, filePath string) (io.ReadCloser, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %w", err)
}
return file, nil
}

View File

@@ -0,0 +1,92 @@
package handlers
import (
"strings"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/application/product"
"tyapi-server/internal/shared/interfaces"
)
// FileDownloadHandler 文件下载处理器
type FileDownloadHandler struct {
uiComponentAppService product.UIComponentApplicationService
responseBuilder interfaces.ResponseBuilder
logger *zap.Logger
}
// NewFileDownloadHandler 创建文件下载处理器
func NewFileDownloadHandler(
uiComponentAppService product.UIComponentApplicationService,
responseBuilder interfaces.ResponseBuilder,
logger *zap.Logger,
) *FileDownloadHandler {
return &FileDownloadHandler{
uiComponentAppService: uiComponentAppService,
responseBuilder: responseBuilder,
logger: logger,
}
}
// DownloadUIComponentFile 下载UI组件文件
// @Summary 下载UI组件文件
// @Description 下载UI组件文件
// @Tags 文件下载
// @Accept json
// @Produce application/octet-stream
// @Param id path string true "UI组件ID"
// @Success 200 {file} file "文件内容"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在或文件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/ui-components/{id}/download [get]
func (h *FileDownloadHandler) DownloadUIComponentFile(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
// 获取UI组件信息
component, err := h.uiComponentAppService.GetUIComponentByID(c.Request.Context(), id)
if err != nil {
h.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
h.responseBuilder.InternalError(c, "获取UI组件失败")
return
}
if component == nil {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
if component.FilePath == nil {
h.responseBuilder.NotFound(c, "UI组件文件不存在")
return
}
// 获取文件路径
filePath, err := h.uiComponentAppService.DownloadUIComponentFile(c.Request.Context(), id)
if err != nil {
h.logger.Error("获取UI组件文件路径失败", zap.Error(err), zap.String("id", id))
h.responseBuilder.InternalError(c, "获取UI组件文件路径失败")
return
}
// 设置下载文件名
fileName := component.ComponentName
if !strings.HasSuffix(strings.ToLower(fileName), ".zip") {
fileName += ".zip"
}
// 设置响应头
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+fileName)
c.Header("Content-Type", "application/octet-stream")
// 发送文件
c.File(filePath)
}

View File

@@ -0,0 +1,551 @@
package handlers
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/application/product"
"tyapi-server/internal/shared/interfaces"
)
// UIComponentHandler UI组件HTTP处理器
type UIComponentHandler struct {
uiComponentAppService product.UIComponentApplicationService
responseBuilder interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
}
// NewUIComponentHandler 创建UI组件HTTP处理器
func NewUIComponentHandler(
uiComponentAppService product.UIComponentApplicationService,
responseBuilder interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
logger *zap.Logger,
) *UIComponentHandler {
return &UIComponentHandler{
uiComponentAppService: uiComponentAppService,
responseBuilder: responseBuilder,
validator: validator,
logger: logger,
}
}
// CreateUIComponent 创建UI组件
// @Summary 创建UI组件
// @Description 管理员创建新的UI组件
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param request body product.CreateUIComponentRequest true "创建UI组件请求"
// @Success 200 {object} interfaces.Response{data=entities.UIComponent} "创建成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components [post]
func (h *UIComponentHandler) CreateUIComponent(c *gin.Context) {
var req product.CreateUIComponentRequest
// 一次性读取请求体并绑定到结构体
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("验证创建UI组件请求失败", zap.Error(err))
h.responseBuilder.BadRequest(c, fmt.Sprintf("请求参数错误: %v", err))
return
}
// 使用结构体数据记录日志
h.logger.Info("创建UI组件请求数据",
zap.String("component_code", req.ComponentCode),
zap.String("component_name", req.ComponentName),
zap.String("description", req.Description),
zap.String("version", req.Version),
zap.Bool("is_active", req.IsActive),
zap.Int("sort_order", req.SortOrder))
component, err := h.uiComponentAppService.CreateUIComponent(c.Request.Context(), req)
if err != nil {
h.logger.Error("创建UI组件失败", zap.Error(err), zap.String("component_code", req.ComponentCode))
if err == product.ErrComponentCodeAlreadyExists {
h.responseBuilder.BadRequest(c, "UI组件编码已存在")
return
}
h.responseBuilder.InternalError(c, fmt.Sprintf("创建UI组件失败: %v", err))
return
}
h.responseBuilder.Success(c, component)
}
// CreateUIComponentWithFile 创建UI组件并上传文件
// @Summary 创建UI组件并上传文件
// @Description 管理员创建新的UI组件并同时上传文件
// @Tags UI组件管理
// @Accept multipart/form-data
// @Produce json
// @Param component_code formData string true "组件编码"
// @Param component_name formData string true "组件名称"
// @Param description formData string false "组件描述"
// @Param version formData string false "组件版本"
// @Param is_active formData bool false "是否启用"
// @Param sort_order formData int false "排序"
// @Param file formData file true "组件文件"
// @Success 200 {object} interfaces.Response{data=entities.UIComponent} "创建成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/create-with-file [post]
func (h *UIComponentHandler) CreateUIComponentWithFile(c *gin.Context) {
// 创建请求结构体
var req product.CreateUIComponentRequest
// 从表单数据中获取组件信息
req.ComponentCode = c.PostForm("component_code")
req.ComponentName = c.PostForm("component_name")
req.Description = c.PostForm("description")
req.Version = c.PostForm("version")
req.IsActive = c.PostForm("is_active") == "true"
if sortOrderStr := c.PostForm("sort_order"); sortOrderStr != "" {
if sortOrder, err := strconv.Atoi(sortOrderStr); err == nil {
req.SortOrder = sortOrder
}
}
// 验证必需字段
if req.ComponentCode == "" {
h.responseBuilder.BadRequest(c, "组件编码不能为空")
return
}
if req.ComponentName == "" {
h.responseBuilder.BadRequest(c, "组件名称不能为空")
return
}
// 获取上传的文件
form, err := c.MultipartForm()
if err != nil {
h.logger.Error("获取表单数据失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取表单数据失败")
return
}
files := form.File["files"]
if len(files) == 0 {
h.responseBuilder.BadRequest(c, "请上传组件文件")
return
}
// 检查文件大小100MB
for _, fileHeader := range files {
if fileHeader.Size > 100*1024*1024 {
h.responseBuilder.BadRequest(c, fmt.Sprintf("文件 %s 大小不能超过100MB", fileHeader.Filename))
return
}
}
// 获取路径信息
paths := c.PostFormArray("paths")
// 记录请求日志
h.logger.Info("创建UI组件并上传文件请求",
zap.String("component_code", req.ComponentCode),
zap.String("component_name", req.ComponentName),
zap.String("description", req.Description),
zap.String("version", req.Version),
zap.Bool("is_active", req.IsActive),
zap.Int("sort_order", req.SortOrder),
zap.Int("files_count", len(files)),
zap.Strings("paths", paths))
// 调用应用服务创建组件并上传文件
component, err := h.uiComponentAppService.CreateUIComponentWithFilesAndPaths(c.Request.Context(), req, files, paths)
if err != nil {
h.logger.Error("创建UI组件并上传文件失败", zap.Error(err), zap.String("component_code", req.ComponentCode))
if err == product.ErrComponentCodeAlreadyExists {
h.responseBuilder.BadRequest(c, "UI组件编码已存在")
return
}
h.responseBuilder.InternalError(c, fmt.Sprintf("创建UI组件并上传文件失败: %v", err))
return
}
h.responseBuilder.Success(c, component)
}
// GetUIComponent 获取UI组件详情
// @Summary 获取UI组件详情
// @Description 根据ID获取UI组件详情
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param id path string true "UI组件ID"
// @Success 200 {object} interfaces.Response{data=entities.UIComponent} "获取成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id} [get]
func (h *UIComponentHandler) GetUIComponent(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
component, err := h.uiComponentAppService.GetUIComponentByID(c.Request.Context(), id)
if err != nil {
h.logger.Error("获取UI组件失败", zap.Error(err), zap.String("id", id))
h.responseBuilder.InternalError(c, "获取UI组件失败")
return
}
if component == nil {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
h.responseBuilder.Success(c, component)
}
// UpdateUIComponent 更新UI组件
// @Summary 更新UI组件
// @Description 更新UI组件信息
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param id path string true "UI组件ID"
// @Param request body product.UpdateUIComponentRequest true "更新UI组件请求"
// @Success 200 {object} interfaces.Response "更新成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id} [put]
func (h *UIComponentHandler) UpdateUIComponent(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
var req product.UpdateUIComponentRequest
// 设置ID
req.ID = id
// 验证请求
if err := h.validator.Validate(c, &req); err != nil {
h.logger.Error("验证更新UI组件请求失败", zap.Error(err))
return
}
err := h.uiComponentAppService.UpdateUIComponent(c.Request.Context(), req)
if err != nil {
h.logger.Error("更新UI组件失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
if err == product.ErrComponentCodeAlreadyExists {
h.responseBuilder.BadRequest(c, "UI组件编码已存在")
return
}
h.responseBuilder.InternalError(c, "更新UI组件失败")
return
}
h.responseBuilder.Success(c, nil)
}
// DeleteUIComponent 删除UI组件
// @Summary 删除UI组件
// @Description 删除UI组件
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param id path string true "UI组件ID"
// @Success 200 {object} interfaces.Response "删除成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id} [delete]
func (h *UIComponentHandler) DeleteUIComponent(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
err := h.uiComponentAppService.DeleteUIComponent(c.Request.Context(), id)
if err != nil {
h.logger.Error("删除UI组件失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
h.responseBuilder.InternalError(c, "删除UI组件失败")
return
}
h.responseBuilder.Success(c, nil)
}
// ListUIComponents 获取UI组件列表
// @Summary 获取UI组件列表
// @Description 获取UI组件列表支持分页和筛选
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param keyword query string false "关键词搜索"
// @Param is_active query bool false "是否启用"
// @Param sort_by query string false "排序字段" default(sort_order)
// @Param sort_order query string false "排序方向" default(asc)
// @Success 200 {object} interfaces.Response{data=product.ListUIComponentsResponse} "获取成功"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components [get]
func (h *UIComponentHandler) ListUIComponents(c *gin.Context) {
// 解析查询参数
req := product.ListUIComponentsRequest{}
if pageStr := c.Query("page"); pageStr != "" {
if page, err := strconv.Atoi(pageStr); err == nil {
req.Page = page
}
}
if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
if pageSize, err := strconv.Atoi(pageSizeStr); err == nil {
req.PageSize = pageSize
}
}
req.Keyword = c.Query("keyword")
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
req.IsActive = &isActive
}
}
req.SortBy = c.DefaultQuery("sort_by", "sort_order")
req.SortOrder = c.DefaultQuery("sort_order", "asc")
response, err := h.uiComponentAppService.ListUIComponents(c.Request.Context(), req)
if err != nil {
h.logger.Error("获取UI组件列表失败", zap.Error(err))
h.responseBuilder.InternalError(c, "获取UI组件列表失败")
return
}
h.responseBuilder.Success(c, response)
}
// UploadUIComponentFile 上传UI组件文件
// @Summary 上传UI组件文件
// @Description 上传UI组件文件
// @Tags UI组件管理
// @Accept multipart/form-data
// @Produce json
// @Param id path string true "UI组件ID"
// @Param file formData file true "UI组件文件(ZIP格式)"
// @Success 200 {object} interfaces.Response{data=string} "上传成功,返回文件路径"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id}/upload [post]
func (h *UIComponentHandler) UploadUIComponentFile(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
h.logger.Error("获取上传文件失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取上传文件失败")
return
}
// 检查文件大小100MB
if file.Size > 100*1024*1024 {
h.responseBuilder.BadRequest(c, "文件大小不能超过100MB")
return
}
filePath, err := h.uiComponentAppService.UploadUIComponentFile(c.Request.Context(), id, file)
if err != nil {
h.logger.Error("上传UI组件文件失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
if err == product.ErrInvalidFileType {
h.responseBuilder.BadRequest(c, "文件类型错误")
return
}
h.responseBuilder.InternalError(c, "上传UI组件文件失败")
return
}
h.responseBuilder.Success(c, filePath)
}
// UploadAndExtractUIComponentFile 上传并解压UI组件文件
// @Summary 上传并解压UI组件文件
// @Description 上传文件并自动解压到组件文件夹仅ZIP文件支持解压
// @Tags UI组件管理
// @Accept multipart/form-data
// @Produce json
// @Param id path string true "UI组件ID"
// @Param file formData file true "UI组件文件(任意格式ZIP格式支持自动解压)"
// @Success 200 {object} interfaces.Response "上传成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id}/upload-extract [post]
func (h *UIComponentHandler) UploadAndExtractUIComponentFile(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
h.logger.Error("获取上传文件失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取上传文件失败")
return
}
// 检查文件大小100MB
if file.Size > 100*1024*1024 {
h.responseBuilder.BadRequest(c, "文件大小不能超过100MB")
return
}
err = h.uiComponentAppService.UploadAndExtractUIComponentFile(c.Request.Context(), id, file)
if err != nil {
h.logger.Error("上传并解压UI组件文件失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
if err == product.ErrInvalidFileType {
h.responseBuilder.BadRequest(c, "文件类型错误")
return
}
h.responseBuilder.InternalError(c, "上传并解压UI组件文件失败")
return
}
h.responseBuilder.Success(c, nil)
}
// GetUIComponentFolderContent 获取UI组件文件夹内容
// @Summary 获取UI组件文件夹内容
// @Description 获取UI组件文件夹内容
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param id path string true "UI组件ID"
// @Success 200 {object} interfaces.Response{data=[]FileInfo} "获取成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id}/folder-content [get]
func (h *UIComponentHandler) GetUIComponentFolderContent(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
files, err := h.uiComponentAppService.GetUIComponentFolderContent(c.Request.Context(), id)
if err != nil {
h.logger.Error("获取UI组件文件夹内容失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
h.responseBuilder.InternalError(c, "获取UI组件文件夹内容失败")
return
}
h.responseBuilder.Success(c, files)
}
// DeleteUIComponentFolder 删除UI组件文件夹
// @Summary 删除UI组件文件夹
// @Description 删除UI组件文件夹
// @Tags UI组件管理
// @Accept json
// @Produce json
// @Param id path string true "UI组件ID"
// @Success 200 {object} interfaces.Response "删除成功"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id}/folder [delete]
func (h *UIComponentHandler) DeleteUIComponentFolder(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
err := h.uiComponentAppService.DeleteUIComponentFolder(c.Request.Context(), id)
if err != nil {
h.logger.Error("删除UI组件文件夹失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
h.responseBuilder.InternalError(c, "删除UI组件文件夹失败")
return
}
h.responseBuilder.Success(c, nil)
}
// DownloadUIComponentFile 下载UI组件文件
// @Summary 下载UI组件文件
// @Description 下载UI组件文件
// @Tags UI组件管理
// @Accept json
// @Produce application/octet-stream
// @Param id path string true "UI组件ID"
// @Success 200 {file} file "文件内容"
// @Failure 400 {object} interfaces.Response "请求参数错误"
// @Failure 404 {object} interfaces.Response "UI组件不存在或文件不存在"
// @Failure 500 {object} interfaces.Response "服务器内部错误"
// @Router /api/v1/admin/ui-components/{id}/download [get]
func (h *UIComponentHandler) DownloadUIComponentFile(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.responseBuilder.BadRequest(c, "UI组件ID不能为空")
return
}
filePath, err := h.uiComponentAppService.DownloadUIComponentFile(c.Request.Context(), id)
if err != nil {
h.logger.Error("下载UI组件文件失败", zap.Error(err), zap.String("id", id))
if err == product.ErrComponentNotFound {
h.responseBuilder.NotFound(c, "UI组件不存在")
return
}
if err == product.ErrComponentFileNotFound {
h.responseBuilder.NotFound(c, "UI组件文件不存在")
return
}
h.responseBuilder.InternalError(c, "下载UI组件文件失败")
return
}
// 这里应该实现文件下载逻辑,返回文件内容
// 由于我们使用的是本地文件存储,可以直接返回文件
c.File(filePath)
}

View File

@@ -2,6 +2,7 @@ package routes
import (
"tyapi-server/internal/infrastructure/http/handlers"
component_report "tyapi-server/internal/shared/component_report"
sharedhttp "tyapi-server/internal/shared/http"
"tyapi-server/internal/shared/middleware"
@@ -11,7 +12,7 @@ import (
// ProductRoutes 产品路由
type ProductRoutes struct {
productHandler *handlers.ProductHandler
componentReportHandler *handlers.ComponentReportHandler
componentReportHandler *component_report.ComponentReportHandler
auth *middleware.JWTAuthMiddleware
optionalAuth *middleware.OptionalAuthMiddleware
logger *zap.Logger
@@ -20,7 +21,7 @@ type ProductRoutes struct {
// NewProductRoutes 创建产品路由
func NewProductRoutes(
productHandler *handlers.ProductHandler,
componentReportHandler *handlers.ComponentReportHandler,
componentReportHandler *component_report.ComponentReportHandler,
auth *middleware.JWTAuthMiddleware,
optionalAuth *middleware.OptionalAuthMiddleware,
logger *zap.Logger,
@@ -58,19 +59,24 @@ func (r *ProductRoutes) Register(router *sharedhttp.GinRouter) {
// 订阅产品(需要认证)
products.POST("/:id/subscribe", r.auth.Handle(), r.productHandler.SubscribeProduct)
}
// 组件报告相关路由(需要认证
componentReport := products.Group("/:id/component-report", r.auth.Handle())
{
// 获取报告下载信息
componentReport.GET("/info", r.componentReportHandler.GetReportDownloadInfo)
// 创建支付订单(暂时注释,后续实现)
// componentReport.POST("/create-order", r.componentReportHandler.CreateReportPaymentOrder)
// 下载报告文件
componentReport.GET("/download/:downloadId", r.componentReportHandler.DownloadReport)
}
// 组件报告 - 需要认证
componentReport := engine.Group("/api/v1/component-report", r.auth.Handle())
{
// 生成并下载 example.json 文件
componentReport.POST("/download-example-json", r.componentReportHandler.DownloadExampleJSON)
// 生成并下载示例报告ZIP文件
componentReport.POST("/generate-and-download", r.componentReportHandler.GenerateAndDownloadZip)
}
// 产品组件报告相关接口 - 需要认证
componentReportGroup := products.Group("/:id/component-report", r.auth.Handle())
{
componentReportGroup.GET("/check", r.componentReportHandler.CheckDownloadAvailability)
componentReportGroup.GET("/info", r.componentReportHandler.GetDownloadInfo)
componentReportGroup.POST("/create-order", r.componentReportHandler.CreatePaymentOrder)
componentReportGroup.GET("/check-payment/:orderId", r.componentReportHandler.CheckPaymentStatus)
}
// 分类 - 公开接口
@@ -103,9 +109,6 @@ func (r *ProductRoutes) Register(router *sharedhttp.GinRouter) {
// 取消订阅
subscriptions.POST("/:id/cancel", r.productHandler.CancelMySubscription)
}
// 我的组件报告下载历史
my.GET("/component-reports", r.componentReportHandler.GetUserDownloadHistory)
}
r.logger.Info("产品路由注册完成")

View File

@@ -0,0 +1,48 @@
package routes
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/infrastructure/http/handlers"
"tyapi-server/internal/shared/interfaces"
)
// UIComponentRoutes UI组件路由
type UIComponentRoutes struct {
uiComponentHandler *handlers.UIComponentHandler
logger *zap.Logger
}
// NewUIComponentRoutes 创建UI组件路由
func NewUIComponentRoutes(
uiComponentHandler *handlers.UIComponentHandler,
logger *zap.Logger,
) *UIComponentRoutes {
return &UIComponentRoutes{
uiComponentHandler: uiComponentHandler,
logger: logger,
}
}
// RegisterRoutes 注册UI组件路由
func (r *UIComponentRoutes) RegisterRoutes(router *gin.RouterGroup, authMiddleware interfaces.Middleware) {
uiComponentGroup := router.Group("/ui-components")
uiComponentGroup.Use(authMiddleware.Handle())
{
// UI组件管理
uiComponentGroup.POST("", r.uiComponentHandler.CreateUIComponent) // 创建UI组件
uiComponentGroup.POST("/create-with-file", r.uiComponentHandler.CreateUIComponentWithFile) // 创建UI组件并上传文件
uiComponentGroup.GET("", r.uiComponentHandler.ListUIComponents) // 获取UI组件列表
uiComponentGroup.GET("/:id", r.uiComponentHandler.GetUIComponent) // 获取UI组件详情
uiComponentGroup.PUT("/:id", r.uiComponentHandler.UpdateUIComponent) // 更新UI组件
uiComponentGroup.DELETE("/:id", r.uiComponentHandler.DeleteUIComponent) // 删除UI组件
// 文件操作
uiComponentGroup.POST("/:id/upload", r.uiComponentHandler.UploadUIComponentFile) // 上传UI组件文件
uiComponentGroup.POST("/:id/upload-extract", r.uiComponentHandler.UploadAndExtractUIComponentFile) // 上传并解压UI组件文件
uiComponentGroup.GET("/:id/folder-content", r.uiComponentHandler.GetUIComponentFolderContent) // 获取UI组件文件夹内容
uiComponentGroup.DELETE("/:id/folder", r.uiComponentHandler.DeleteUIComponentFolder) // 删除UI组件文件夹
uiComponentGroup.GET("/:id/download", r.uiComponentHandler.DownloadUIComponentFile) // 下载UI组件文件
}
}