feat():learning后台管理项目初始化
This commit is contained in:
260
internal/services/user_feedback_service.go
Normal file
260
internal/services/user_feedback_service.go
Normal file
@@ -0,0 +1,260 @@
|
||||
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 已废弃,改为强制预签名
|
||||
Reference in New Issue
Block a user