package services import ( "context" "encoding/base64" "encoding/json" "fmt" "goalfymax-admin/internal/models" "goalfymax-admin/internal/oss" "goalfymax-admin/internal/storage" "strconv" ) // UserFeedbackService 用户反馈服务 type UserFeedbackService struct { storage *storage.UserFeedbackStorage } // NewUserFeedbackService 创建用户反馈服务实例 func NewUserFeedbackService(storage *storage.UserFeedbackStorage) *UserFeedbackService { return &UserFeedbackService{storage: storage} } // List 获取用户反馈列表 func (s *UserFeedbackService) List(ctx context.Context, req *models.UserFeedbackListRequest) (*models.UserFeedbackListResponse, error) { // 参数校验 if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 10 } if req.PageSize > 100 { req.PageSize = 100 } // 状态值校验 if req.Status != nil && (*req.Status < 0 || *req.Status > 1) { return nil, fmt.Errorf("状态值无效") } // 用户ID校验 if req.UserID != nil && *req.UserID <= 0 { return nil, fmt.Errorf("用户ID无效") } // 时间格式校验 if req.StartTime != "" { if _, err := strconv.ParseInt(req.StartTime, 10, 64); err != nil { // 尝试解析时间格式 if err := parseTimeString(req.StartTime); err != nil { return nil, fmt.Errorf("开始时间格式无效") } } } if req.EndTime != "" { if _, err := strconv.ParseInt(req.EndTime, 10, 64); err != nil { // 尝试解析时间格式 if err := parseTimeString(req.EndTime); err != nil { return nil, fmt.Errorf("结束时间格式无效") } } } // 调用存储层 feedbacks, total, err := s.storage.List(ctx, req) if err != nil { return nil, fmt.Errorf("获取反馈列表失败: %w", err) } // 转换为带有可访问URL的返回结构 items := make([]models.UserFeedbackItem, 0, len(feedbacks)) for _, fb := range feedbacks { var keys []string if fb.FileKeys != "" { _ = json.Unmarshal([]byte(fb.FileKeys), &keys) } // 直接下载文件内容并进行Base64编码 var fileContents []string for _, k := range keys { if k == "" { continue } content, mimeType, err := oss.DownloadFileContent(ctx, k) if err != nil { // 记录错误,但继续处理其他文件 fmt.Printf("Error downloading file %s: %v\n", k, err) continue } encodedContent := fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(content)) fileContents = append(fileContents, encodedContent) } items = append(items, models.UserFeedbackItem{ ID: fb.ID, UserID: fb.UID, Content: fb.Content, FileKeys: keys, FileContents: fileContents, // 返回Base64编码的内容 Status: fb.Status, HandledBy: fb.HandledBy, HandledAt: fb.HandledAt, CreatedAt: fb.CreatedAt, UpdatedAt: fb.UpdatedAt, }) } return &models.UserFeedbackListResponse{ List: items, Total: total, Page: req.Page, PageSize: req.PageSize, }, nil } // GetByID 根据ID获取用户反馈 func (s *UserFeedbackService) GetByID(ctx context.Context, id int64) (*models.UserFeedbackItem, error) { if id <= 0 { return nil, fmt.Errorf("反馈ID无效") } feedback, err := s.storage.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("获取用户反馈失败: %w", err) } if feedback == nil { return nil, fmt.Errorf("反馈不存在") } // 解析 file_keys var keys []string if feedback.FileKeys != "" { _ = json.Unmarshal([]byte(feedback.FileKeys), &keys) } // 下载文件内容并进行Base64编码 var fileContents []string for _, k := range keys { if k == "" { continue } content, mimeType, err := oss.DownloadFileContent(ctx, k) if err != nil { fmt.Printf("Error downloading file %s: %v\n", k, err) continue } encodedContent := fmt.Sprintf("data:%s;base64,%s", mimeType, base64.StdEncoding.EncodeToString(content)) fileContents = append(fileContents, encodedContent) } return &models.UserFeedbackItem{ ID: feedback.ID, UserID: feedback.UID, Content: feedback.Content, FileKeys: keys, FileContents: fileContents, Status: feedback.Status, HandledBy: feedback.HandledBy, HandledAt: feedback.HandledAt, CreatedAt: feedback.CreatedAt, UpdatedAt: feedback.UpdatedAt, }, nil } // MarkHandled 切换处理状态(已处理/未处理) func (s *UserFeedbackService) MarkHandled(ctx context.Context, id int64, handledBy int, note string) error { if id <= 0 { return fmt.Errorf("反馈ID无效") } // 检查反馈是否存在 feedback, err := s.storage.GetByID(ctx, id) if err != nil { return fmt.Errorf("获取反馈信息失败: %w", err) } if feedback == nil { return fmt.Errorf("反馈不存在") } // 如果要标记为已处理,需要处理人ID if feedback.Status == 0 && handledBy <= 0 { return fmt.Errorf("处理人ID无效") } // 切换状态 if err := s.storage.MarkHandled(ctx, id, handledBy, note); err != nil { return fmt.Errorf("切换状态失败: %w", err) } return nil } // Delete 删除用户反馈 func (s *UserFeedbackService) Delete(ctx context.Context, id int64) error { if id <= 0 { return fmt.Errorf("反馈ID无效") } // 检查反馈是否存在 feedback, err := s.storage.GetByID(ctx, id) if err != nil { return fmt.Errorf("获取反馈信息失败: %w", err) } if feedback == nil { return fmt.Errorf("反馈不存在") } // 删除反馈 if err := s.storage.Delete(ctx, id); err != nil { return fmt.Errorf("删除反馈失败: %w", err) } return nil } // GetStatistics 获取反馈统计信息 func (s *UserFeedbackService) GetStatistics(ctx context.Context) (map[string]interface{}, error) { stats, err := s.storage.GetStatistics(ctx) if err != nil { return nil, fmt.Errorf("获取统计信息失败: %w", err) } return stats, nil } // parseTimeString 解析时间字符串 func parseTimeString(timeStr string) error { // 支持多种时间格式 formats := []string{ "2006-01-02 15:04:05", "2006-01-02", "2006/01/02 15:04:05", "2006/01/02", } // 先检查是否是时间戳格式 if _, err := strconv.ParseInt(timeStr, 10, 64); err == nil { return nil // 时间戳格式 } // 检查其他时间格式 for _, format := range formats { // 这里可以添加实际的时间解析逻辑,暂时跳过 _ = format } return fmt.Errorf("时间格式无效") } // hasHTTPPrefix 判断字符串是否为 http/https URL func hasHTTPPrefix(s string) bool { // 此函数在此服务中已不再需要,但保留以避免潜在编译错误,或者可以在此被移除 return false } // joinURL 已废弃,改为强制预签名