feat():learning后台管理项目初始化

This commit is contained in:
yuj
2025-12-04 16:23:46 +08:00
parent 39886d50d2
commit 88e048f4d1
154 changed files with 28966 additions and 6 deletions

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