Files
goalfylearning-admin/internal/services/user_feedback_service.go

261 lines
6.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 已废弃,改为强制预签名