urbanu 1 mesiac pred
rodič
commit
53fb952c2f

+ 49 - 0
apis/daytask/file.go

@@ -0,0 +1,49 @@
+package daytask
+
+import (
+	"app/commons/config"
+	"github.com/gin-gonic/gin"
+)
+
+// UploadFile 上传文件到MinIO
+func (s *Server) UploadFile(c *gin.Context) {
+	ctx := s.FromContext(c)
+
+	// 获取上传的文件
+	file, err := c.FormFile("file")
+	if err != nil {
+		ctx.Fail("file_required")
+		return
+	}
+
+	// 检查文件大小 (最大10MB)
+	if file.Size > 10*1024*1024 {
+		ctx.Fail("file_too_large")
+		return
+	}
+
+	// 检查文件类型
+	contentType := file.Header.Get("Content-Type")
+	allowedTypes := map[string]bool{
+		"image/jpeg": true,
+		"image/png":  true,
+		"image/gif":  true,
+		"image/webp": true,
+		"image/bmp":  true,
+	}
+	if !allowedTypes[contentType] {
+		ctx.Fail("invalid_file_type")
+		return
+	}
+
+	// 上传到存储(MinIO/S3)
+	url, err := config.UploadFileToStorage(file)
+	if err != nil {
+		ctx.Fail("upload_failed")
+		return
+	}
+
+	ctx.OK(gin.H{
+		"url": url,
+	})
+}

+ 21 - 1
apis/daytask/home.go

@@ -214,13 +214,33 @@ func (s *Server) TaskDetail(c *gin.Context) {
 	steps := make([]*entity.DtTaskStep, 0)
 	db.Model(&entity.DtTaskStep{}).
 		Where("task_id = ?", taskId).
-		Order("step_no ASC, sort ASC").
+		Order("step_no ASC, id ASC").
 		Find(&steps)
 
+	// 获取审核样例
+	examples := make([]*entity.DtTaskExample, 0)
+	db.Model(&entity.DtTaskExample{}).
+		Where("task_id = ?", taskId).
+		Order("id ASC").
+		Find(&examples)
+
+	// 获取用户领取状态
+	var apply *entity.DtUserTask
+	userId := ctx.UserId()
+	if userId > 0 {
+		userTask := &entity.DtUserTask{}
+		if err := db.Where("user_id = ? AND task_id = ?", userId, taskId).
+			Order("id DESC").First(userTask).Error; err == nil {
+			apply = userTask
+		}
+	}
+
 	ctx.OK(gin.H{
 		"task":     task,
 		"category": category,
 		"steps":    steps,
+		"examples": examples,
+		"apply":    apply,
 	})
 }
 

+ 41 - 68
apis/daytask/rank.go

@@ -12,7 +12,8 @@ func (s *Server) RankList(c *gin.Context) {
 	db := s.DB()
 	userId := ctx.UserId()
 
-	rankType := ctx.QueryString("type", "daily") // daily/weekly/monthly/total
+	rankType := ctx.QueryString("type", "task")      // task任务排行/invite邀请排行
+	timeType := ctx.QueryString("timeType", "daily") // daily/weekly/monthly
 	limit := ctx.QueryInt64("limit", 50)
 
 	if limit > 100 {
@@ -20,101 +21,73 @@ func (s *Server) RankList(c *gin.Context) {
 	}
 
 	type RankInfo struct {
-		Rank     int     `json:"rank"`
-		UserId   int64   `json:"userId"`
-		Uid      string  `json:"uid"`
-		Nickname string  `json:"nickname"`
-		Avatar   string  `json:"avatar"`
-		Amount   float64 `json:"amount"`
+		Rank     int    `json:"rank"`
+		UserId   int64  `json:"userId"`
+		Uid      string `json:"uid"`
+		Nickname string `json:"nickname"`
+		Avatar   string `json:"avatar"`
+		Count    int    `json:"count"`
 	}
 
 	ranks := make([]*RankInfo, 0)
 
-	// 根据类型查询不同的排行榜
-	switch rankType {
+	// 时间条件
+	var timeCondition string
+	switch timeType {
 	case "daily":
-		// 今日收益排行
-		today := time.Now().Format("2006-01-02")
-		db.Raw(`
-			SELECT
-				@rank := @rank + 1 as rank,
-				u.id as user_id,
-				u.uid,
-				u.nickname,
-				u.avatar,
-				COALESCE(SUM(bl.amount), 0) as amount
-			FROM dt_user u
-			LEFT JOIN dt_balance_log bl ON u.id = bl.user_id
-				AND bl.type IN (?, ?)
-				AND DATE(FROM_UNIXTIME(bl.created_at)) = ?
-			CROSS JOIN (SELECT @rank := 0) r
-			WHERE u.status = 1
-			GROUP BY u.id
-			HAVING amount > 0
-			ORDER BY amount DESC
-			LIMIT ?
-		`, entity.BalanceLogTypeTaskIncome, entity.BalanceLogTypeCommission, today, limit).Scan(&ranks)
-
+		timeCondition = "DATE(FROM_UNIXTIME(t.created_at)) = '" + time.Now().Format("2006-01-02") + "'"
 	case "weekly":
-		// 本周收益排行
 		weekStart := time.Now().AddDate(0, 0, -int(time.Now().Weekday())).Format("2006-01-02")
+		timeCondition = "DATE(FROM_UNIXTIME(t.created_at)) >= '" + weekStart + "'"
+	case "monthly":
+		month := time.Now().Format("2006-01")
+		timeCondition = "DATE_FORMAT(FROM_UNIXTIME(t.created_at), '%Y-%m') = '" + month + "'"
+	default:
+		timeCondition = "DATE(FROM_UNIXTIME(t.created_at)) = '" + time.Now().Format("2006-01-02") + "'"
+	}
+
+	// 根据类型查询不同的排行榜
+	switch rankType {
+	case "task":
+		// 任务完成数量排行
 		db.Raw(`
 			SELECT
-				@rank := @rank + 1 as rank,
+				@rank := @rank + 1 as `+"`rank`"+`,
 				u.id as user_id,
 				u.uid,
 				u.nickname,
 				u.avatar,
-				COALESCE(SUM(bl.amount), 0) as amount
+				COUNT(t.id) as count
 			FROM dt_user u
-			LEFT JOIN dt_balance_log bl ON u.id = bl.user_id
-				AND bl.type IN (?, ?)
-				AND DATE(FROM_UNIXTIME(bl.created_at)) >= ?
+			LEFT JOIN dt_user_task t ON u.id = t.user_id
+				AND t.status = ?
+				AND `+timeCondition+`
 			CROSS JOIN (SELECT @rank := 0) r
 			WHERE u.status = 1
 			GROUP BY u.id
-			HAVING amount > 0
-			ORDER BY amount DESC
+			HAVING count > 0
+			ORDER BY count DESC
 			LIMIT ?
-		`, entity.BalanceLogTypeTaskIncome, entity.BalanceLogTypeCommission, weekStart, limit).Scan(&ranks)
+		`, entity.UserTaskStatusCompleted, limit).Scan(&ranks)
 
-	case "monthly":
-		// 本月收益排行
-		month := time.Now().Format("2006-01")
+	case "invite":
+		// 邀请人数排行
 		db.Raw(`
 			SELECT
-				@rank := @rank + 1 as rank,
+				@rank := @rank + 1 as `+"`rank`"+`,
 				u.id as user_id,
 				u.uid,
 				u.nickname,
 				u.avatar,
-				COALESCE(SUM(bl.amount), 0) as amount
+				COUNT(t.id) as count
 			FROM dt_user u
-			LEFT JOIN dt_balance_log bl ON u.id = bl.user_id
-				AND bl.type IN (?, ?)
-				AND DATE_FORMAT(FROM_UNIXTIME(bl.created_at), '%Y-%m') = ?
+			LEFT JOIN dt_user t ON u.id = t.invite_user_id
+				AND `+timeCondition+`
 			CROSS JOIN (SELECT @rank := 0) r
 			WHERE u.status = 1
 			GROUP BY u.id
-			HAVING amount > 0
-			ORDER BY amount DESC
-			LIMIT ?
-		`, entity.BalanceLogTypeTaskIncome, entity.BalanceLogTypeCommission, month, limit).Scan(&ranks)
-
-	case "total":
-		// 累计收益排行
-		db.Raw(`
-			SELECT
-				@rank := @rank + 1 as rank,
-				id as user_id,
-				uid,
-				nickname,
-				avatar,
-				total_income as amount
-			FROM dt_user
-			CROSS JOIN (SELECT @rank := 0) r
-			WHERE status = 1 AND total_income > 0
-			ORDER BY total_income DESC
+			HAVING count > 0
+			ORDER BY count DESC
 			LIMIT ?
 		`, limit).Scan(&ranks)
 
@@ -142,7 +115,7 @@ func (s *Server) RankList(c *gin.Context) {
 				Uid:      user.Uid,
 				Nickname: user.Nickname,
 				Avatar:   user.Avatar,
-				Amount:   user.TotalIncome,
+				Count:    0,
 			}
 		}
 	}

+ 8 - 4
apis/daytask/routers.go

@@ -70,6 +70,9 @@ func (h UserRouter) Register(group *gin.RouterGroup) {
 	group.POST("user/password", server().UserPassword)  // 修改密码
 	group.POST("user/avatar", server().UserAvatar)      // 修改头像
 
+	// 文件上传
+	group.POST("file/upload", server().UploadFile)      // 上传文件到MinIO
+
 	// 收款账户
 	group.GET("payment/list", server().PaymentList)       // 收款账户列表
 	group.POST("payment/add", server().PaymentAdd)        // 添加收款账户
@@ -88,10 +91,11 @@ func (h UserRouter) Register(group *gin.RouterGroup) {
 	group.GET("task/my/detail", server().TaskMyDetail) // 我的任务详情
 
 	// 财务相关
-	group.GET("wallet/info", server().WalletInfo)           // 钱包信息
-	group.GET("wallet/balance/log", server().BalanceLog)    // 余额流水
-	group.POST("wallet/withdraw", server().WalletWithdraw)  // 申请提现
-	group.GET("wallet/withdraw/log", server().WithdrawLog)  // 提现记录
+	group.GET("wallet/info", server().WalletInfo)              // 钱包信息
+	group.GET("wallet/balance/log", server().BalanceLog)       // 余额流水
+	group.GET("wallet/withdraw/config", server().WithdrawConfig) // 提现配置
+	group.POST("wallet/withdraw", server().WalletWithdraw)     // 申请提现
+	group.GET("wallet/withdraw/log", server().WithdrawLog)     // 提现记录
 
 	// 团队相关
 	group.GET("team/info", server().TeamInfo)       // 团队信息

+ 11 - 7
apis/daytask/task.go

@@ -4,8 +4,9 @@ import (
 	"app/commons/model/entity"
 	"app/commons/services"
 	"encoding/json"
-	"github.com/gin-gonic/gin"
 	"time"
+
+	"github.com/gin-gonic/gin"
 )
 
 // TaskApply 领取任务
@@ -75,10 +76,10 @@ func (s *Server) TaskApply(c *gin.Context) {
 		return
 	}
 
-	// 检查是否有进行中的任务
+	// 检查是否有进行中或待审核的任务
 	var pendingCount int64
 	db.Model(&entity.DtUserTask{}).
-		Where("user_id = ? AND task_id = ? AND status = ?", userId, req.TaskId, entity.UserTaskStatusPending).
+		Where("user_id = ? AND task_id = ? AND status IN (?, ?)", userId, req.TaskId, entity.UserTaskStatusPending, entity.UserTaskStatusSubmitted).
 		Count(&pendingCount)
 	if pendingCount > 0 {
 		ctx.Fail("task_in_progress")
@@ -93,12 +94,15 @@ func (s *Server) TaskApply(c *gin.Context) {
 		RewardAmount: task.RewardAmount,
 		Status:       entity.UserTaskStatusPending,
 	}
-	db.Create(userTask)
+	if err := db.Create(userTask).Error; err != nil {
+		ctx.Fail("claim_failed")
+		return
+	}
 
-	// 更新任务剩余数量
+	// 更新任务剩余数量(原子操作)
 	db.Model(&entity.DtTask{}).
 		Where("id = ? AND remain_count > 0", req.TaskId).
-		Update("remain_count", task.RemainCount-1)
+		UpdateColumn("remain_count", db.Raw("remain_count - 1"))
 
 	ctx.OK(userTask)
 }
@@ -279,7 +283,7 @@ func (s *Server) TaskMyDetail(c *gin.Context) {
 	steps := make([]*entity.DtTaskStep, 0)
 	db.Model(&entity.DtTaskStep{}).
 		Where("task_id = ?", userTask.TaskId).
-		Order("step_no ASC, sort ASC").
+		Order("step_no ASC").
 		Find(&steps)
 
 	ctx.OK(gin.H{

+ 34 - 0
apis/daytask/wallet.go

@@ -182,6 +182,40 @@ func (s *Server) WalletWithdraw(c *gin.Context) {
 	ctx.OK(order)
 }
 
+// WithdrawConfig 提现配置
+func (s *Server) WithdrawConfig(c *gin.Context) {
+	ctx := s.FromContext(c)
+	db := s.DB()
+
+	// 获取最低提现金额
+	minWithdraw := 100.0
+	minConfig := &entity.DtConfig{}
+	if err := db.Where("`key` = ?", "min_withdraw").First(minConfig).Error; err == nil {
+		fmt.Sscanf(minConfig.Value, "%f", &minWithdraw)
+	}
+
+	// 获取手续费率
+	feeRate := 0.02
+	feeConfig := &entity.DtConfig{}
+	if err := db.Where("`key` = ?", "withdraw_fee").First(feeConfig).Error; err == nil {
+		fmt.Sscanf(feeConfig.Value, "%f", &feeRate)
+	}
+
+	// 获取最大提现金额
+	maxWithdraw := 50000.0
+	maxConfig := &entity.DtConfig{}
+	if err := db.Where("`key` = ?", "max_withdraw").First(maxConfig).Error; err == nil {
+		fmt.Sscanf(maxConfig.Value, "%f", &maxWithdraw)
+	}
+
+	ctx.OK(gin.H{
+		"minAmount": minWithdraw,
+		"maxAmount": maxWithdraw,
+		"feeRate":   feeRate,
+		"multiple":  10, // 提现金额必须是10的倍数
+	})
+}
+
 // WithdrawLog 提现记录
 func (s *Server) WithdrawLog(c *gin.Context) {
 	ctx := s.FromContext(c)

+ 12 - 1
apis/middleware/user_logger.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"github.com/gin-gonic/gin"
 	"io"
+	"strings"
 	"time"
 )
 
@@ -21,11 +22,21 @@ func UserLogger() gin.HandlerFunc {
 		query := c.Request.URL.RawQuery
 		// 读取请求体
 		var requestBody []byte
+		var requestBodyStr string
+		contentType := c.Request.Header.Get("Content-Type")
+		// 检查是否为文件上传请求(multipart/form-data)
+		isFileUpload := strings.Contains(contentType, "multipart/form-data")
 		if c.Request.Body != nil {
 			requestBody, _ = io.ReadAll(c.Request.Body)
 			// 读取后重新赋值,因为读取后body会被清空
 			c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
 		}
+		// 文件上传请求不记录请求体(避免二进制数据存储问题)
+		if isFileUpload {
+			requestBodyStr = "[file upload]"
+		} else {
+			requestBodyStr = string(requestBody)
+		}
 		// 创建一个自定义的 ResponseWriter 来捕获响应
 		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
 		c.Writer = blw
@@ -47,7 +58,7 @@ func UserLogger() gin.HandlerFunc {
 			Path:      path,
 			Query:     query,
 			Status:    c.Writer.Status(),
-			Request:   string(requestBody),
+			Request:   requestBodyStr,
 			Response:  blw.body.String(),
 			Elapsed:   fmt.Sprintf("%s", elapsed.Round(time.Millisecond)),
 		}

+ 1 - 0
commons/config/app_env.go

@@ -11,6 +11,7 @@ type Server struct {
 	Zap      *Zap      `mapstructure:"zap" json:"zap" yaml:"zap"`
 	Redis    *Redis    `mapstructure:"redis" json:"redis" yaml:"redis"`
 	Exchange *Exchange `mapstructure:"exchange" json:"exchange" yaml:"exchange"`
+	File     *File     `mapstructure:"file" json:"file" yaml:"file"`
 }
 
 var envConfig *Server

+ 17 - 0
commons/config/config_default_init.go

@@ -116,4 +116,21 @@ func setDefaultEnvConfig(vp *viper.Viper) {
 		EnabledCluster: false,
 		Cluster:        []string{"127.0.0.1:6379"},
 	})
+	vp.SetDefault("file", &File{
+		OssType:   "aws",
+		Path:      "http://127.0.0.1:9000",
+		ProxyPath: "/daytask-media",
+		StorePath: "./static",
+		OriginConf: map[string]interface{}{
+			"bucket":              "daytask-media",
+			"region":              "us-east-1",
+			"endpoint":            "http://127.0.0.1:9000",
+			"secret-id":           "minioadmin",
+			"secret-key":          "minioadmin",
+			"base-url":            "http://127.0.0.1:9000/daytask-media",
+			"path-prefix":         "uploads",
+			"s3-force-path-style": true,
+			"disable-ssl":         true,
+		},
+	})
 }

+ 109 - 0
commons/config/minio.go

@@ -0,0 +1,109 @@
+package config
+
+import (
+	"encoding/json"
+	"fmt"
+	"mime/multipart"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/s3/s3manager"
+	"github.com/pkg/errors"
+)
+
+// File 文件存储配置
+type File struct {
+	OssType    string                 `mapstructure:"oss-type" json:"oss-type" yaml:"oss-type"`
+	Path       string                 `mapstructure:"path" json:"path" yaml:"path"`
+	ProxyPath  string                 `mapstructure:"proxy-path" json:"proxy-path" yaml:"proxy-path"`
+	StorePath  string                 `mapstructure:"store-path" json:"store-path" yaml:"store-path"`
+	OriginConf map[string]interface{} `mapstructure:"origin-conf" json:"origin-conf" yaml:"origin-conf"`
+}
+
+// AwsS3 AWS S3配置(兼容MinIO)
+type AwsS3 struct {
+	Bucket           string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
+	Region           string `mapstructure:"region" json:"region" yaml:"region"`
+	Endpoint         string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
+	SecretID         string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"`
+	SecretKey        string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
+	BaseURL          string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
+	PathPrefix       string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"`
+	S3ForcePathStyle bool   `mapstructure:"s3-force-path-style" json:"s3-force-path-style" yaml:"s3-force-path-style"`
+	DisableSSL       bool   `mapstructure:"disable-ssl" json:"disable-ssl" yaml:"disable-ssl"`
+}
+
+// newSession 创建S3 session
+func (a *AwsS3) newSession() *session.Session {
+	sess, _ := session.NewSession(&aws.Config{
+		Region:           aws.String(a.Region),
+		Endpoint:         aws.String(a.Endpoint),
+		S3ForcePathStyle: aws.Bool(a.S3ForcePathStyle),
+		DisableSSL:       aws.Bool(a.DisableSSL),
+		Credentials: credentials.NewStaticCredentials(
+			a.SecretID,
+			a.SecretKey,
+			"",
+		),
+	})
+	return sess
+}
+
+// UploadFile 上传文件
+func (a *AwsS3) UploadFile(file *multipart.FileHeader) (string, string, error) {
+	_session := a.newSession()
+	uploader := s3manager.NewUploader(_session)
+
+	// 生成文件名
+	fileKey := fmt.Sprintf("%d%s", time.Now().Unix(), file.Filename)
+	filename := a.PathPrefix + "/" + fileKey
+
+	f, openError := file.Open()
+	if openError != nil {
+		return "", "", errors.New("function file.Open() failed, err:" + openError.Error())
+	}
+	defer f.Close()
+
+	_, err := uploader.Upload(&s3manager.UploadInput{
+		Bucket: aws.String(a.Bucket),
+		Key:    aws.String(filename),
+		Body:   f,
+	})
+	if err != nil {
+		return "", "", err
+	}
+
+	return a.BaseURL + "/" + filename, fileKey, nil
+}
+
+// UploadFileToStorage 上传文件到存储
+func UploadFileToStorage(file *multipart.FileHeader) (string, error) {
+	conf := EnvConf().File
+	if conf == nil {
+		return "", fmt.Errorf("file config not found")
+	}
+
+	if conf.OssType != "aws" {
+		return "", fmt.Errorf("only aws/minio storage type is supported")
+	}
+
+	// 解析 OriginConf 到 AwsS3 结构
+	engine := new(AwsS3)
+	byteData, err := json.Marshal(conf.OriginConf)
+	if err != nil {
+		return "", err
+	}
+	err = json.Unmarshal(byteData, engine)
+	if err != nil {
+		return "", err
+	}
+
+	viewPath, _, err := engine.UploadFile(file)
+	if err != nil {
+		return "", err
+	}
+
+	return viewPath, nil
+}

+ 5 - 3
commons/model/entity/dt_task.go

@@ -3,12 +3,10 @@ package entity
 // DtTask 任务表
 type DtTask struct {
 	MysqlFullModel
-	TaskNo            string  `json:"taskNo" gorm:"type:varchar(32);uniqueIndex:uk_task_no;comment:任务编号"`
+	TaskNo            string  `json:"taskNo" gorm:"type:varchar(32);default:'';comment:任务编号"`
 	CategoryId        int64   `json:"categoryId" gorm:"index:idx_category_id;comment:分类ID"`
 	Title             string  `json:"title" gorm:"type:varchar(128);comment:任务标题"`
-	TitleVi           string  `json:"titleVi" gorm:"type:varchar(128);comment:任务标题(越南语)"`
 	Description       string  `json:"description" gorm:"type:text;comment:任务描述"`
-	DescriptionVi     string  `json:"descriptionVi" gorm:"type:text;comment:任务描述(越南语)"`
 	Cover             string  `json:"cover" gorm:"type:varchar(255);comment:封面图"`
 	TargetUrl         string  `json:"targetUrl" gorm:"type:varchar(512);comment:目标链接"`
 	TaskType          int8    `json:"taskType" gorm:"default:1;comment:任务类型: 1=关注 2=点赞 3=评论 4=分享 5=观看"`
@@ -26,6 +24,10 @@ type DtTask struct {
 	AuditTimeout      int     `json:"auditTimeout" gorm:"default:24;comment:审核超时(小时)"`
 	IsTop             int8    `json:"isTop" gorm:"default:0;comment:是否置顶: 0=否 1=是"`
 	IsRecommend       int8    `json:"isRecommend" gorm:"default:0;comment:是否推荐: 0=否 1=是"`
+	// 认证要求标签
+	RequirePhone    int8   `json:"requirePhone" gorm:"default:0;comment:需要手机认证: 0=否 1=是"`
+	RequireRealname int8   `json:"requireRealname" gorm:"default:0;comment:需要实名认证: 0=否 1=是"`
+	RequireIdcard   int8   `json:"requireIdcard" gorm:"default:0;comment:需要身份认证: 0=否 1=是"`
 	Status            int8    `json:"status" gorm:"default:0;index:idx_status;comment:状态: 0=草稿 1=上架 2=下架 3=已结束"`
 	Remark            string  `json:"remark" gorm:"type:varchar(255);comment:备注"`
 }

+ 0 - 1
commons/model/entity/dt_task_category.go

@@ -4,7 +4,6 @@ package entity
 type DtTaskCategory struct {
 	MysqlBaseModel
 	Name        string `json:"name" gorm:"type:varchar(64);comment:分类名称"`
-	NameVi      string `json:"nameVi" gorm:"type:varchar(64);comment:分类名称(越南语)"`
 	Icon        string `json:"icon" gorm:"type:varchar(255);comment:分类图标"`
 	Platform    string `json:"platform" gorm:"type:varchar(32);comment:关联平台: tiktok/youtube/instagram/facebook"`
 	Description string `json:"description" gorm:"type:varchar(255);comment:分类描述"`

+ 22 - 0
commons/model/entity/dt_task_example.go

@@ -0,0 +1,22 @@
+package entity
+
+// DtTaskExample 审核样例表
+type DtTaskExample struct {
+	MysqlBaseModel
+	TaskId      int64  `json:"taskId" gorm:"index:idx_task_id;comment:任务ID"`
+	Image       string `json:"image" gorm:"type:varchar(255);not null;comment:样例图片"`
+	Description string `json:"description" gorm:"type:varchar(255);comment:样例描述"`
+	Sort        int    `json:"sort" gorm:"default:0;comment:排序"`
+}
+
+func (*DtTaskExample) TableName() string {
+	return "dt_task_example"
+}
+
+func (*DtTaskExample) Comment() string {
+	return "审核样例表"
+}
+
+func NewDtTaskExample() *DtTaskExample {
+	return &DtTaskExample{}
+}

+ 0 - 2
commons/model/entity/dt_task_step.go

@@ -6,9 +6,7 @@ type DtTaskStep struct {
 	TaskId        int64  `json:"taskId" gorm:"index:idx_task_id;comment:任务ID"`
 	StepNo        int    `json:"stepNo" gorm:"default:1;comment:步骤序号"`
 	Title         string `json:"title" gorm:"type:varchar(128);comment:步骤标题"`
-	TitleVi       string `json:"titleVi" gorm:"type:varchar(128);comment:步骤标题(越南语)"`
 	Description   string `json:"description" gorm:"type:text;comment:步骤说明"`
-	DescriptionVi string `json:"descriptionVi" gorm:"type:text;comment:步骤说明(越南语)"`
 	Image         string `json:"image" gorm:"type:varchar(255);comment:示例图片"`
 	RequireUpload int8   `json:"requireUpload" gorm:"default:0;comment:是否需要上传凭证: 0=否 1=是"`
 	Sort          int    `json:"sort" gorm:"default:0;comment:排序"`

+ 2 - 1
commons/model/entity/dt_user_task.go

@@ -5,7 +5,8 @@ type DtUserTask struct {
 	MysqlBaseModel
 	UserId       int64   `json:"userId" gorm:"index:idx_user_id;comment:用户ID"`
 	TaskId       int64   `json:"taskId" gorm:"index:idx_task_id;comment:任务ID"`
-	TaskNo       string  `json:"taskNo" gorm:"type:varchar(32);comment:任务编号"`
+	TaskNo       string  `json:"taskNo" gorm:"type:varchar(32);default:'';comment:任务编号"`
+	OrderNo      string  `json:"orderNo" gorm:"type:varchar(32);default:'';comment:订单编号"`
 	RewardAmount float64 `json:"rewardAmount" gorm:"type:decimal(18,2);default:0.00;comment:任务奖励"`
 	Screenshots  string  `json:"screenshots" gorm:"type:text;comment:提交截图(JSON数组)"`
 	Remark       string  `json:"remark" gorm:"type:varchar(512);comment:用户备注"`

+ 3 - 1
go.mod

@@ -3,6 +3,7 @@ module app
 go 1.24.4
 
 require (
+	github.com/aws/aws-sdk-go v1.55.8
 	github.com/btcsuite/btcd v0.24.2
 	github.com/btcsuite/btcd/btcutil v1.1.6
 	github.com/demdxx/gocast v1.2.0
@@ -12,6 +13,7 @@ require (
 	github.com/go-sql-driver/mysql v1.9.3
 	github.com/golang-jwt/jwt/v5 v5.2.2
 	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/pkg/errors v0.9.1
 	github.com/redis/go-redis/v9 v9.11.0
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/samber/lo v1.51.0
@@ -63,6 +65,7 @@ require (
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/jonboulle/clockwork v0.5.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
@@ -73,7 +76,6 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
-	github.com/pkg/errors v0.9.1 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.4 // indirect
 	github.com/sagikazarmark/locafero v0.7.0 // indirect